From 4e01a613a8a801b412072e4332aea624e35c7aff Mon Sep 17 00:00:00 2001 From: Gabriel Andretta Date: Fri, 17 Jun 2011 22:34:18 -0300 Subject: [PATCH] extension reloading --- sinatra-contrib/lib/sinatra/reloader.rb | 65 +++++++++++++--- sinatra-contrib/spec/reloader/app.rb.erb | 4 + sinatra-contrib/spec/reloader_spec.rb | 95 ++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 10 deletions(-) diff --git a/sinatra-contrib/lib/sinatra/reloader.rb b/sinatra-contrib/lib/sinatra/reloader.rb index 442f65fc..822adadc 100644 --- a/sinatra-contrib/lib/sinatra/reloader.rb +++ b/sinatra-contrib/lib/sinatra/reloader.rb @@ -183,9 +183,9 @@ module Sinatra source_location = block.respond_to?(:source_location) ? block.source_location.first : caller_files[1] signature = super - Watcher::List.for(self).watch(source_location, Watcher::Element.new( - :route, { :verb => verb, :signature => signature } - )) + watch_element( + source_location, :route, { :verb => verb, :signature => signature } + ) signature end @@ -196,7 +196,7 @@ module Sinatra def inline_templates=(file=nil) file = (file.nil? || file == true) ? (caller_files[1] || File.expand_path($0)) : file - Watcher::List.for(self).watch_inline_templates(file) + watch_element(file, :inline_templates) super end @@ -205,9 +205,7 @@ module Sinatra # middleware beign used. def use(middleware, *args, &block) path = caller_files[1] || File.expand_path($0) - Watcher::List.for(self).watch(path, Watcher::Element.new( - :middleware, [middleware, args, block] - )) + watch_element(path, :middleware, [middleware, args, block]) super end @@ -215,9 +213,18 @@ module Sinatra source_location = block.respond_to?(:source_location) ? block.source_location.first : caller_files[1] result = super - Watcher::List.for(self).watch(source_location, Watcher::Element.new( - :"#{type}_filter", filters[type].last - )) + watch_element(source_location, :"#{type}_filter", filters[type].last) + result + end + + # Does everything Sinatra::Base#register does, but it also lets + # the reloader know that an extension is beign registered, because + # the elements defined in its +registered+ method need a special + # treatment. + def register(*extensions, &block) + start_registering_extension + result = super + stop_registering_extension result end end @@ -253,6 +260,44 @@ module Sinatra def dont_reload(glob) Dir[glob].each { |path| Watcher::List.for(self).ignore(path) } end + + private + + attr_reader :register_path + + # Indicates an extesion is beign registered. + def start_registering_extension + @register_path = caller_files[2] + end + + # Indicates the extesion has been registered. + def stop_registering_extension + @register_path = nil + end + + # Indicates whether or not an extension is being registered. + def registering_extension? + !register_path.nil? + end + + # Builds a Watcher::Element from +type+ and +representation+ and + # tells the Watcher::List for the current application to watch it + # in the file located at +path+. + # + # If an extension is beign registered, it also tells the list to + # watch it in the file where the extesion has been registered. + # This prevents the duplication of the elements added by the + # extension in its +registered+ method with every reload. + def watch_element(path, type, representation=nil) + list = Watcher::List.for(self) + if type == :inline_templates + list.watch_inline_templates(path) + else + element = Watcher::Element.new(type, representation) + list.watch(path, element) + list.watch(register_path, element) if registering_extension? + end + end end end diff --git a/sinatra-contrib/spec/reloader/app.rb.erb b/sinatra-contrib/spec/reloader/app.rb.erb index b08277e7..eba1b707 100644 --- a/sinatra-contrib/spec/reloader/app.rb.erb +++ b/sinatra-contrib/spec/reloader/app.rb.erb @@ -5,6 +5,10 @@ class <%= name %> < Sinatra::Base enable :inline_templates <% end %> +<% extensions.each do |extension| %> + register <%= extension %> +<% end %> + <% middlewares.each do |middleware| %> use <%= middleware %> <% end %> diff --git a/sinatra-contrib/spec/reloader_spec.rb b/sinatra-contrib/spec/reloader_spec.rb index f5423071..5de8fc54 100644 --- a/sinatra-contrib/spec/reloader_spec.rb +++ b/sinatra-contrib/spec/reloader_spec.rb @@ -40,6 +40,7 @@ describe Sinatra::Reloader do def write_app_file(options={}) options[:routes] ||= ['get("/foo") { erb :foo }'] options[:inline_templates] ||= nil + options[:extensions] ||= [] options[:middlewares] ||= [] options[:filters] ||= [] options[:name] ||= app_name @@ -218,6 +219,100 @@ describe Sinatra::Reloader do end end + describe "extension reloading" do + it "doesn't duplicate routes with every reload" do + module ::RouteExtension + def self.registered(klass) + klass.get('/bar') { 'bar' } + end + end + + setup_example_app( + :routes => ['get("/foo") { "foo" }'], + :extensions => ['RouteExtension'] + ) + + expect { + 3.times do + update_app_file( + :routes => ['get("/foo") { "foo" }'], + :extensions => ['RouteExtension'] + ) + get('/foo') # ...to perform the reload + end + }.to_not change { app_const.routes['GET'].size } + end + + it "doesn't duplicate middleware with every reload" do + module ::MiddlewareExtension + def self.registered(klass) + klass.use Rack::Head + end + end + + setup_example_app( + :routes => ['get("/foo") { "foo" }'], + :extensions => ['MiddlewareExtension'] + ) + + expect { + 3.times do + update_app_file( + :routes => ['get("/foo") { "foo" }'], + :extensions => ['MiddlewareExtension'] + ) + get('/foo') # ...to perform the reload + end + }.to_not change { app_const.middleware.size } + end + + it "doesn't duplicate before filters with every reload" do + module ::BeforeFilterExtension + def self.registered(klass) + klass.before { @hi = 'hi' } + end + end + + setup_example_app( + :routes => ['get("/foo") { "foo" }'], + :extensions => ['BeforeFilterExtension'] + ) + + expect { + 3.times do + update_app_file( + :routes => ['get("/foo") { "foo" }'], + :extensions => ['BeforeFilterExtension'] + ) + get('/foo') # ...to perform the reload + end + }.to_not change { app_const.filters[:before].size } + end + + it "doesn't duplicate after filters with every reload" do + module ::AfterFilterExtension + def self.registered(klass) + klass.after { @bye = 'bye' } + end + end + + setup_example_app( + :routes => ['get("/foo") { "foo" }'], + :extensions => ['AfterFilterExtension'] + ) + + expect { + 3.times do + update_app_file( + :routes => ['get("/foo") { "foo" }'], + :extensions => ['AfterFilterExtension'] + ) + get('/foo') # ...to perform the reload + end + }.to_not change { app_const.filters[:after].size } + end + end + describe ".dont_reload" do before(:each) do setup_example_app(