From f7af75976a9117aa1cb294114af4f99a1d28f1cd Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 23 Jun 2010 02:38:44 +0200 Subject: [PATCH 01/87] require 'active_support/dependencies' in action_dispatch/middleware/stack --- actionpack/lib/action_dispatch/middleware/stack.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 5a029a60d1..6f243574e4 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -1,4 +1,5 @@ require "active_support/inflector/methods" +require "active_support/dependencies" module ActionDispatch class MiddlewareStack < Array From ad6be0876271f86e76c89645a0106b85c9d77ad7 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 22 Jun 2010 23:51:28 +0200 Subject: [PATCH 02/87] Made Engine valid rack app with its own middleware stack --- .../lib/rails/application/configuration.rb | 2 +- railties/lib/rails/application/railties.rb | 4 +-- railties/lib/rails/engine.rb | 13 +++++++ railties/lib/rails/engine/configurable.rb | 13 ++++++- railties/lib/rails/engine/configuration.rb | 2 ++ railties/test/railties/engine_test.rb | 35 +++++++++++++++++++ 6 files changed, 65 insertions(+), 4 deletions(-) diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index c3418e0d80..a28fc62ccd 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -9,7 +9,7 @@ module Rails attr_accessor :allow_concurrency, :cache_classes, :cache_store, :encoding, :consider_all_requests_local, :dependency_loading, - :filter_parameters, :log_level, :logger, :middleware, + :filter_parameters, :log_level, :logger, :plugins, :preload_frameworks, :reload_plugins, :secret_token, :serve_static_assets, :session_options, :time_zone, :whiny_nils diff --git a/railties/lib/rails/application/railties.rb b/railties/lib/rails/application/railties.rb index b3e6693f89..14ba0afc7c 100644 --- a/railties/lib/rails/application/railties.rb +++ b/railties/lib/rails/application/railties.rb @@ -17,7 +17,7 @@ module Rails end def engines - @engines ||= ::Rails::Engine.subclasses.map(&:new) + @engines ||= ::Rails::Engine.subclasses.map(&:instance) end def plugins @@ -28,4 +28,4 @@ module Rails end end end -end \ No newline at end of file +end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 555bc9dbc8..f8a33ffe24 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -140,6 +140,19 @@ module Rails end end + def app + raise "You can't use Engine as rack application without providing valid rack endpoint" unless endpoint + @app ||= config.middleware.build(endpoint) + end + + def endpoint + self.class.endpoint + end + + def call(env) + app.call(env) + end + # Add configured load paths to ruby load paths and remove duplicates. initializer :set_load_path, :before => :bootstrap_hook do _all_load_paths.reverse_each do |path| diff --git a/railties/lib/rails/engine/configurable.rb b/railties/lib/rails/engine/configurable.rb index 9a370f0abb..0af01cace2 100644 --- a/railties/lib/rails/engine/configurable.rb +++ b/railties/lib/rails/engine/configurable.rb @@ -3,10 +3,12 @@ module Rails module Configurable def self.included(base) base.extend ClassMethods + base.private_class_method :new end module ClassMethods delegate :middleware, :root, :paths, :to => :config + delegate :call, :to => :instance def config @config ||= Engine::Configuration.new(find_root_with_flag("lib")) @@ -15,6 +17,15 @@ module Rails def inherited(base) raise "You cannot inherit from a Rails::Engine child" end + + def instance + @instance ||= new + end + + def endpoint(endpoint = nil) + @endpoint = endpoint if endpoint + @endpoint + end end def config @@ -22,4 +33,4 @@ module Rails end end end -end \ No newline at end of file +end diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 521ed95447..9a9ec8b3ad 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -5,10 +5,12 @@ module Rails class Configuration < ::Rails::Railtie::Configuration attr_reader :root attr_writer :eager_load_paths, :autoload_once_paths, :autoload_paths + attr_accessor :middleware def initialize(root=nil) super() @root = root + @middleware = ActionDispatch::MiddlewareStack.new end def paths diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 7410a10712..08ba308bc4 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -50,5 +50,40 @@ module RailtiesTest assert index < initializers.index { |i| i.name == :build_middleware_stack } end + + class Upcaser + def initialize(app) + @app = app + end + + def call(env) + response = @app.call(env) + response[2].upcase! + response + end + end + + test "engine is a rack app and can have his own middleware stack" do + @plugin.write "lib/bukkits.rb", <<-RUBY + class Bukkits + class Engine < ::Rails::Engine + endpoint lambda { |env| [200, {'Content-Type' => 'text/html'}, 'Hello World'] } + + config.middleware.use ::RailtiesTest::EngineTest::Upcaser + end + end + RUBY + + boot_rails + + Rails::Application.routes.draw do |map| + mount(Bukkits::Engine => "/bukkits") + end + + env = Rack::MockRequest.env_for("/bukkits") + response = Rails::Application.call(env) + + assert_equal "HELLO WORLD", response[2] + end end end From c787bfdf932450c5fd4c0df805454aa40a388117 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 23 Jun 2010 16:10:26 +0200 Subject: [PATCH 03/87] Engine can now load its own plugins --- .../lib/rails/application/configuration.rb | 4 +--- railties/lib/rails/application/railties.rb | 24 +++++++++---------- railties/lib/rails/engine.rb | 5 ++++ railties/lib/rails/engine/configuration.rb | 4 +++- railties/lib/rails/engine/railties.rb | 23 ++++++++++++++++++ railties/test/railties/engine_test.rb | 18 ++++++++++++++ 6 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 railties/lib/rails/engine/railties.rb diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index a28fc62ccd..a20208f8d5 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -10,7 +10,7 @@ module Rails attr_accessor :allow_concurrency, :cache_classes, :cache_store, :encoding, :consider_all_requests_local, :dependency_loading, :filter_parameters, :log_level, :logger, - :plugins, :preload_frameworks, :reload_plugins, + :preload_frameworks, :reload_plugins, :secret_token, :serve_static_assets, :session_options, :time_zone, :whiny_nils @@ -53,8 +53,6 @@ module Rails paths.log "log/#{Rails.env}.log" paths.tmp "tmp" paths.tmp.cache "tmp/cache" - paths.vendor "vendor", :load_path => true - paths.vendor.plugins "vendor/plugins" if File.exists?("#{root}/test/mocks/#{Rails.env}") ActiveSupport::Deprecation.warn "\"Rails.root/test/mocks/#{Rails.env}\" won't be added " << diff --git a/railties/lib/rails/application/railties.rb b/railties/lib/rails/application/railties.rb index 14ba0afc7c..2b3783e998 100644 --- a/railties/lib/rails/application/railties.rb +++ b/railties/lib/rails/application/railties.rb @@ -1,13 +1,10 @@ -module Rails - class Application - class Railties - # TODO Write tests for this behavior extracted from Application - def initialize(config) - @config = config - end +require 'rails/engine/railties' +module Rails + class Application < Engine + class Railties < Rails::Engine::Railties def all(&block) - @all ||= railties + engines + plugins + @all ||= railties + engines + super @all.each(&block) if block @all end @@ -21,10 +18,13 @@ module Rails end def plugins - @plugins ||= begin - plugin_names = (@config.plugins || [:all]).map { |p| p.to_sym } - Plugin.all(plugin_names, @config.paths.vendor.plugins) - end + @plugins ||= super + plugins_for_engines + end + + def plugins_for_engines + engines.map { |e| + e.railties.plugins + }.flatten end end end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index f8a33ffe24..385e3439f1 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -2,6 +2,7 @@ require 'rails/railtie' require 'active_support/core_ext/module/delegation' require 'pathname' require 'rbconfig' +require 'rails/engine/railties' module Rails # Rails::Engine allows you to wrap a specific Rails application and share it accross @@ -140,6 +141,10 @@ module Rails end end + def railties + @railties ||= Railties.new(config) + end + def app raise "You can't use Engine as rack application without providing valid rack endpoint" unless endpoint @app ||= config.middleware.build(endpoint) diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 9a9ec8b3ad..0c91796fc9 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -5,7 +5,7 @@ module Rails class Configuration < ::Rails::Railtie::Configuration attr_reader :root attr_writer :eager_load_paths, :autoload_once_paths, :autoload_paths - attr_accessor :middleware + attr_accessor :middleware, :plugins def initialize(root=nil) super() @@ -31,6 +31,8 @@ module Rails paths.public "public" paths.public.javascripts "public/javascripts" paths.public.stylesheets "public/stylesheets" + paths.vendor "vendor", :load_path => true + paths.vendor.plugins "vendor/plugins" paths end end diff --git a/railties/lib/rails/engine/railties.rb b/railties/lib/rails/engine/railties.rb new file mode 100644 index 0000000000..389a7602c6 --- /dev/null +++ b/railties/lib/rails/engine/railties.rb @@ -0,0 +1,23 @@ +module Rails + class Engine < Railtie + class Railties + # TODO Write tests for this behavior extracted from Application + def initialize(config) + @config = config + end + + def all(&block) + @all ||= plugins + @all.each(&block) if block + @all + end + + def plugins + @plugins ||= begin + plugin_names = (@config.plugins || [:all]).map { |p| p.to_sym } + Plugin.all(plugin_names, @config.paths.vendor.plugins) + end + end + end + end +end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 08ba308bc4..a0028c1d70 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -85,5 +85,23 @@ module RailtiesTest assert_equal "HELLO WORLD", response[2] end + + test "engine can load its own plugins" do + @plugin.write "lib/bukkits.rb", <<-RUBY + class Bukkits + class Engine < ::Rails::Engine + config.paths.vendor.plugins = "#{File.join(@plugin.path, "lib/bukkits/plugins")}" + end + end + RUBY + + @plugin.write "lib/bukkits/plugins/yaffle/init.rb", <<-RUBY + Bukkits::Engine.config.yaffle_loaded = true + RUBY + + boot_rails + + assert Bukkits::Engine.config.yaffle_loaded + end end end From 675f3ead4193529de225cbb15dad3f9ed418f456 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 23 Jun 2010 23:02:02 +0200 Subject: [PATCH 04/87] Gather initializers from railties in engines to get rid of additional looping through initializers --- railties/lib/rails/application/railties.rb | 10 ---------- railties/lib/rails/engine.rb | 7 +++++++ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/railties/lib/rails/application/railties.rb b/railties/lib/rails/application/railties.rb index 2b3783e998..67352f537e 100644 --- a/railties/lib/rails/application/railties.rb +++ b/railties/lib/rails/application/railties.rb @@ -16,16 +16,6 @@ module Rails def engines @engines ||= ::Rails::Engine.subclasses.map(&:instance) end - - def plugins - @plugins ||= super + plugins_for_engines - end - - def plugins_for_engines - engines.map { |e| - e.railties.plugins - }.flatten - end end end end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 385e3439f1..12ca553a24 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -158,6 +158,13 @@ module Rails app.call(env) end + def initializers + initializers = [] + railties.all { |r| initializers += r.initializers } + initializers += super + initializers + end + # Add configured load paths to ruby load paths and remove duplicates. initializer :set_load_path, :before => :bootstrap_hook do _all_load_paths.reverse_each do |path| From 7c8f73d1bdf80b2c2561d02623db90f3aaad904a Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 24 Jun 2010 10:46:33 +0200 Subject: [PATCH 05/87] Added TODO for evaling init.rb in context of Engine --- railties/lib/rails/plugin.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb index 8d5132a5ca..4dab1c0b53 100644 --- a/railties/lib/rails/plugin.rb +++ b/railties/lib/rails/plugin.rb @@ -78,6 +78,8 @@ module Rails ActiveSupport::Deprecation.warn "Use toplevel init.rb; rails/init.rb is deprecated: #{initrb}" end config = app.config + # TODO: think about evaling initrb in context of Engine (currently it's + # always evaled in context of Rails::Application) eval(File.read(initrb), binding, initrb) end end From b5975a4a30edb1b77a4d7edd6817a7445c079193 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 24 Jun 2010 14:30:22 +0200 Subject: [PATCH 06/87] Delegate non existing class methods to instance for Engine --- railties/lib/rails/application.rb | 5 ----- railties/lib/rails/engine.rb | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 5b26333486..a4cdc7306f 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -71,11 +71,6 @@ module Rails super || instance.respond_to?(*args) end - protected - - def method_missing(*args, &block) - instance.send(*args, &block) - end end delegate :middleware, :to => :config diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 12ca553a24..9a8dd8e8d4 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -123,6 +123,12 @@ module Rails RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? Pathname.new(root).expand_path : Pathname.new(root).realpath end + + protected + + def method_missing(*args, &block) + instance.send(*args, &block) + end end delegate :paths, :root, :to => :config From c989d1a87d4ed0d3f4fe425ce2207eec5a8d3154 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 24 Jun 2010 14:32:11 +0200 Subject: [PATCH 07/87] Engine sets routes as default rack endpoint if no endpoint was given --- railties/lib/rails/application.rb | 4 ---- railties/lib/rails/engine.rb | 7 +++++-- railties/test/railties/engine_test.rb | 26 +++++++++++++++++++++++++- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index a4cdc7306f..a21b560084 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -103,10 +103,6 @@ module Rails super end - def routes - @routes ||= ActionDispatch::Routing::RouteSet.new - end - def railties @railties ||= Railties.new(config) end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 9a8dd8e8d4..c0607950b5 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -152,18 +152,21 @@ module Rails end def app - raise "You can't use Engine as rack application without providing valid rack endpoint" unless endpoint @app ||= config.middleware.build(endpoint) end def endpoint - self.class.endpoint + self.class.endpoint || routes end def call(env) app.call(env) end + def routes + @routes ||= ActionDispatch::Routing::RouteSet.new + end + def initializers initializers = [] railties.all { |r| initializers += r.initializers } diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index a0028c1d70..a2d46f02e7 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -86,11 +86,35 @@ module RailtiesTest assert_equal "HELLO WORLD", response[2] end + test "it provides routes as default endpoint" do + @plugin.write "lib/bukkits.rb", <<-RUBY + class Bukkits + class Engine < ::Rails::Engine + end + end + RUBY + + boot_rails + + Bukkits::Engine.routes.draw do |map| + match "/foo" => lambda { |env| [200, {'Content-Type' => 'text/html'}, 'foo'] } + end + + Rails::Application.routes.draw do |map| + mount(Bukkits::Engine => "/bukkits") + end + + env = Rack::MockRequest.env_for("/bukkits/foo") + response = Rails::Application.call(env) + + assert_equal "foo", response[2] + end + test "engine can load its own plugins" do @plugin.write "lib/bukkits.rb", <<-RUBY class Bukkits class Engine < ::Rails::Engine - config.paths.vendor.plugins = "#{File.join(@plugin.path, "lib/bukkits/plugins")}" + paths.vendor.plugins = "#{File.join(@plugin.path, "lib/bukkits/plugins")}" end end RUBY From 092b148b21899173028675ef727f876fff5db1bc Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 5 Jul 2010 17:40:35 +0200 Subject: [PATCH 08/87] Engine can now serve files with ActionDispatch::Static --- .../lib/rails/application/configuration.rb | 2 +- railties/lib/rails/engine.rb | 11 +++++++- railties/lib/rails/engine/configuration.rb | 5 ++-- railties/lib/rails/railtie/configuration.rb | 2 +- railties/test/railties/engine_test.rb | 26 +++++++++++++++++++ 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index a20208f8d5..14669f47b7 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -11,7 +11,7 @@ module Rails :encoding, :consider_all_requests_local, :dependency_loading, :filter_parameters, :log_level, :logger, :preload_frameworks, :reload_plugins, - :secret_token, :serve_static_assets, :session_options, + :secret_token, :session_options, :time_zone, :whiny_nils def initialize(*) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index c0607950b5..409502cb08 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -152,13 +152,22 @@ module Rails end def app - @app ||= config.middleware.build(endpoint) + @app ||= begin + config.middleware = config.middleware.merge_into(default_middleware_stack) + config.middleware.build(endpoint) + end end def endpoint self.class.endpoint || routes end + def default_middleware_stack + ActionDispatch::MiddlewareStack.new.tap do |middleware| + middleware.use ::ActionDispatch::Static, paths.public.to_a.first if config.serve_static_assets + end + end + def call(env) app.call(env) end diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 0c91796fc9..b1adbe1f22 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -5,12 +5,13 @@ module Rails class Configuration < ::Rails::Railtie::Configuration attr_reader :root attr_writer :eager_load_paths, :autoload_once_paths, :autoload_paths - attr_accessor :middleware, :plugins + attr_accessor :middleware, :plugins, :serve_static_assets def initialize(root=nil) super() @root = root - @middleware = ActionDispatch::MiddlewareStack.new + @serve_static_assets = true + @middleware = Rails::Configuration::MiddlewareStackProxy.new end def paths diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index 4e6f94c534..96d9bd0da2 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -78,4 +78,4 @@ module Rails end end end -end \ No newline at end of file +end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index a2d46f02e7..b2e7be9749 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -127,5 +127,31 @@ module RailtiesTest assert Bukkits::Engine.config.yaffle_loaded end + + test "engine can serve files" do + @plugin.write "lib/bukkits.rb", <<-RUBY + class Bukkits + class Engine < ::Rails::Engine + paths.public = "#{File.join(@plugin.path, "lib/bukkits/public")}" + config.serve_static_assets = true + end + end + RUBY + + @plugin.write "lib/bukkits/public/omg.txt", <<-RUBY + OMG + RUBY + + boot_rails + + Rails::Application.routes.draw do |map| + mount(Bukkits::Engine => "/bukkits") + end + + env = Rack::MockRequest.env_for("/bukkits/omg.txt") + response = Rails::Application.call(env) + + assert_equal response[2].path, File.join(@plugin.path, "lib/bukkits/public/omg.txt") + end end end From e4bd7ed2fa60a0dbb03bbdea6fa532c6ea0ee704 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 7 Jul 2010 21:23:01 +0200 Subject: [PATCH 09/87] Ensure that init.rb is evaled in context of Engine --- railties/test/railties/engine_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index b2e7be9749..ad2f769f34 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -120,7 +120,7 @@ module RailtiesTest RUBY @plugin.write "lib/bukkits/plugins/yaffle/init.rb", <<-RUBY - Bukkits::Engine.config.yaffle_loaded = true + config.yaffle_loaded = true RUBY boot_rails From 7d7263bf9dbf814f10803f5ec553ac2b0190b6a9 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 7 Jul 2010 21:33:27 +0200 Subject: [PATCH 10/87] We don't need to overwrite default paths in tests --- railties/test/railties/engine_test.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index ad2f769f34..eeebbcc01f 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -114,12 +114,11 @@ module RailtiesTest @plugin.write "lib/bukkits.rb", <<-RUBY class Bukkits class Engine < ::Rails::Engine - paths.vendor.plugins = "#{File.join(@plugin.path, "lib/bukkits/plugins")}" end end RUBY - @plugin.write "lib/bukkits/plugins/yaffle/init.rb", <<-RUBY + @plugin.write "vendor/plugins/yaffle/init.rb", <<-RUBY config.yaffle_loaded = true RUBY @@ -132,13 +131,12 @@ module RailtiesTest @plugin.write "lib/bukkits.rb", <<-RUBY class Bukkits class Engine < ::Rails::Engine - paths.public = "#{File.join(@plugin.path, "lib/bukkits/public")}" config.serve_static_assets = true end end RUBY - @plugin.write "lib/bukkits/public/omg.txt", <<-RUBY + @plugin.write "public/omg.txt", <<-RUBY OMG RUBY @@ -151,7 +149,7 @@ module RailtiesTest env = Rack::MockRequest.env_for("/bukkits/omg.txt") response = Rails::Application.call(env) - assert_equal response[2].path, File.join(@plugin.path, "lib/bukkits/public/omg.txt") + assert_equal response[2].path, File.join(@plugin.path, "public/omg.txt") end end end From 3939d6bb51fc36ac863f0e4766ed8d87fad98297 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 8 Jul 2010 12:07:25 +0200 Subject: [PATCH 11/87] Removed ActionDispatch::Static, but left empty MiddlewareStack to unify app method between Engine and Application --- railties/lib/rails/application.rb | 6 ----- .../lib/rails/application/configuration.rb | 2 +- railties/lib/rails/engine.rb | 14 ++++++----- railties/lib/rails/engine/configurable.rb | 5 ---- railties/lib/rails/engine/configuration.rb | 3 +-- railties/test/railties/engine_test.rb | 25 ------------------- 6 files changed, 10 insertions(+), 45 deletions(-) diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index a21b560084..05a26b64a8 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -147,12 +147,6 @@ module Rails self end - def app - @app ||= begin - config.middleware = config.middleware.merge_into(default_middleware_stack) - config.middleware.build(routes) - end - end alias :build_middleware_stack :app def call(env) diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 14669f47b7..a20208f8d5 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -11,7 +11,7 @@ module Rails :encoding, :consider_all_requests_local, :dependency_loading, :filter_parameters, :log_level, :logger, :preload_frameworks, :reload_plugins, - :secret_token, :session_options, + :secret_token, :serve_static_assets, :session_options, :time_zone, :whiny_nils def initialize(*) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 409502cb08..a602dac5ff 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -124,6 +124,11 @@ module Rails Pathname.new(root).expand_path : Pathname.new(root).realpath end + def endpoint(endpoint = nil) + @endpoint = endpoint if endpoint + @endpoint + end + protected def method_missing(*args, &block) @@ -162,12 +167,6 @@ module Rails self.class.endpoint || routes end - def default_middleware_stack - ActionDispatch::MiddlewareStack.new.tap do |middleware| - middleware.use ::ActionDispatch::Static, paths.public.to_a.first if config.serve_static_assets - end - end - def call(env) app.call(env) end @@ -251,6 +250,9 @@ module Rails end protected + def default_middleware_stack + ActionDispatch::MiddlewareStack.new + end def _all_autoload_paths @_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq diff --git a/railties/lib/rails/engine/configurable.rb b/railties/lib/rails/engine/configurable.rb index 0af01cace2..e668911913 100644 --- a/railties/lib/rails/engine/configurable.rb +++ b/railties/lib/rails/engine/configurable.rb @@ -21,11 +21,6 @@ module Rails def instance @instance ||= new end - - def endpoint(endpoint = nil) - @endpoint = endpoint if endpoint - @endpoint - end end def config diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index b1adbe1f22..06e7222753 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -5,12 +5,11 @@ module Rails class Configuration < ::Rails::Railtie::Configuration attr_reader :root attr_writer :eager_load_paths, :autoload_once_paths, :autoload_paths - attr_accessor :middleware, :plugins, :serve_static_assets + attr_accessor :middleware, :plugins def initialize(root=nil) super() @root = root - @serve_static_assets = true @middleware = Rails::Configuration::MiddlewareStackProxy.new end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index eeebbcc01f..39189edaa3 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -126,30 +126,5 @@ module RailtiesTest assert Bukkits::Engine.config.yaffle_loaded end - - test "engine can serve files" do - @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits - class Engine < ::Rails::Engine - config.serve_static_assets = true - end - end - RUBY - - @plugin.write "public/omg.txt", <<-RUBY - OMG - RUBY - - boot_rails - - Rails::Application.routes.draw do |map| - mount(Bukkits::Engine => "/bukkits") - end - - env = Rack::MockRequest.env_for("/bukkits/omg.txt") - response = Rails::Application.call(env) - - assert_equal response[2].path, File.join(@plugin.path, "public/omg.txt") - end end end From efe9555cd77b409c6965bf803a47b1230ee7dec7 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 8 Jul 2010 18:13:56 +0200 Subject: [PATCH 12/87] We don't need to add railties initlaizers in Application as there is already done in Engine and it's called with super --- railties/lib/rails/application.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 05a26b64a8..72e5be2b9a 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -162,7 +162,6 @@ module Rails def initializers initializers = Bootstrap.initializers_for(self) - railties.all { |r| initializers += r.initializers } initializers += super initializers += Finisher.initializers_for(self) initializers From 15f95b98ac048407a1ce6873bb02fc5fbb6c991c Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 14 Jul 2010 13:38:36 +0200 Subject: [PATCH 13/87] Ensure that plugins are not loaded twice --- railties/lib/rails/plugin.rb | 9 +++++++ railties/test/railties/engine_test.rb | 38 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb index 4dab1c0b53..22a0eb10a8 100644 --- a/railties/lib/rails/plugin.rb +++ b/railties/lib/rails/plugin.rb @@ -18,6 +18,10 @@ module Rails # root during the boot process. # class Plugin < Engine + def self.global_plugins + @global_plugins ||= [] + end + def self.inherited(base) raise "You cannot inherit from Rails::Plugin" end @@ -28,6 +32,11 @@ module Rails Dir["#{path}/*"].each do |plugin_path| plugin = new(plugin_path) next unless list.include?(plugin.name) || list.include?(:all) + if global_plugins.include?(plugin.name) + warn "WARNING: plugin #{plugin.name} from #{path} was not loaded. Plugin with the same name has been already loaded." + next + end + global_plugins << plugin.name plugins << plugin end end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 39189edaa3..980265d969 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -1,8 +1,23 @@ require "isolation/abstract_unit" require "railties/shared_tests" +require 'stringio' module RailtiesTest class EngineTest < Test::Unit::TestCase + # TODO: it's copied from generators/test_case, maybe make a module with such helpers? + def capture(stream) + begin + stream = stream.to_s + eval "$#{stream} = StringIO.new" + yield + result = eval("$#{stream}").string + ensure + eval("$#{stream} = #{stream.upcase}") + end + + result + end + include ActiveSupport::Testing::Isolation include SharedTests @@ -126,5 +141,28 @@ module RailtiesTest assert Bukkits::Engine.config.yaffle_loaded end + + test "engine does not load plugins that already exists in application" do + @plugin.write "lib/bukkits.rb", <<-RUBY + class Bukkits + class Engine < ::Rails::Engine + end + end + RUBY + + @plugin.write "vendor/plugins/yaffle/init.rb", <<-RUBY + config.engine_yaffle_loaded = true + RUBY + + app_file "vendor/plugins/yaffle/init.rb", <<-RUBY + config.app_yaffle_loaded = true + RUBY + + warnings = capture(:stderr) { boot_rails } + + assert !warnings.empty? + assert !Bukkits::Engine.config.respond_to?(:engine_yaffle_loaded) + assert Rails.application.config.app_yaffle_loaded + end end end From 7cccfed5943cddd6d72ed0df0ee7291cca9025ad Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Sun, 18 Jul 2010 23:23:54 +0200 Subject: [PATCH 14/87] Allow Engines loading its own environment file from config/environments --- railties/lib/rails/application.rb | 4 --- railties/lib/rails/application/bootstrap.rb | 7 ++--- .../lib/rails/application/configuration.rb | 1 - railties/lib/rails/engine.rb | 9 ++++++ railties/lib/rails/engine/configuration.rb | 1 + railties/test/application/loading_test.rb | 17 +++++++++++ railties/test/railties/engine_test.rb | 29 ++++++++++++++----- 7 files changed, 50 insertions(+), 18 deletions(-) diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 72e5be2b9a..cbb1aad68f 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -43,10 +43,6 @@ module Rails class << self private :new - def configure(&block) - class_eval(&block) - end - def instance if self == Rails::Application if Rails.application diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb index 44e26b5713..e39b3bc705 100644 --- a/railties/lib/rails/application/bootstrap.rb +++ b/railties/lib/rails/application/bootstrap.rb @@ -6,10 +6,7 @@ module Rails module Bootstrap include Initializable - initializer :load_environment_config do - environment = config.paths.config.environments.to_a.first - require environment if environment - end + initializer :load_environment_hook do end initializer :load_active_support do require 'active_support/dependencies' @@ -73,4 +70,4 @@ module Rails end end end -end \ No newline at end of file +end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index a20208f8d5..c248d41e59 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -48,7 +48,6 @@ module Rails paths.app.controllers << builtin_controller if builtin_controller paths.config.database "config/database.yml" paths.config.environment "config/environment.rb" - paths.config.environments "config/environments", :glob => "#{Rails.env}.rb" paths.lib.templates "lib/templates" paths.log "log/#{Rails.env}.log" paths.tmp "tmp" diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index a602dac5ff..efde82d1ed 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -129,6 +129,10 @@ module Rails @endpoint end + def configure(&block) + class_eval(&block) + end + protected def method_missing(*args, &block) @@ -249,6 +253,11 @@ module Rails # consistently executed after all the initializers above across all engines. end + initializer :load_environment_config, :before => :load_environment_hook do + environment = config.paths.config.environments.to_a.first + require environment if environment + end + protected def default_middleware_stack ActionDispatch::MiddlewareStack.new diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 06e7222753..bf3ad1f26b 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -28,6 +28,7 @@ module Rails paths.config.initializers "config/initializers", :glob => "**/*.rb" paths.config.locales "config/locales", :glob => "*.{rb,yml}" paths.config.routes "config/routes.rb" + paths.config.environments "config/environments", :glob => "#{Rails.env}.rb" paths.public "public" paths.public.javascripts "public/javascripts" paths.public.stylesheets "public/stylesheets" diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index ecf7904c39..7ea712d27a 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -42,6 +42,23 @@ class LoadingTest < Test::Unit::TestCase User end + test "load config/environments/environment before Bootstrap initializers" do + app_file "config/environments/development.rb", <<-RUBY + AppTemplate::Application.configure do + config.development_environment_loaded = true + end + RUBY + + add_to_config <<-RUBY + config.before_initialize do + config.loaded = config.development_environment_loaded + end + RUBY + + require "#{app_path}/config/environment" + assert ::AppTemplate::Application.config.loaded + end + def test_descendants_are_cleaned_on_each_request_without_cache_classes add_to_config <<-RUBY config.cache_classes = false diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 980265d969..09f2b5baa0 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -54,15 +54,9 @@ module RailtiesTest initializers = Rails.application.initializers.tsort index = initializers.index { |i| i.name == "dummy_initializer" } - selection = initializers[(index-3)..(index)].map(&:name).map(&:to_s) - - assert_equal %w( - load_config_initializers - load_config_initializers - engines_blank_point - dummy_initializer - ), selection + assert index > initializers.index { |i| i.name == :load_config_initializers } + assert index > initializers.index { |i| i.name == :engines_blank_point } assert index < initializers.index { |i| i.name == :build_middleware_stack } end @@ -164,5 +158,24 @@ module RailtiesTest assert !Bukkits::Engine.config.respond_to?(:engine_yaffle_loaded) assert Rails.application.config.app_yaffle_loaded end + + test "it loads its environment file" do + @plugin.write "lib/bukkits.rb", <<-RUBY + class Bukkits + class Engine < ::Rails::Engine + end + end + RUBY + + @plugin.write "config/environments/development.rb", <<-RUBY + Bukkits::Engine.configure do + config.environment_loaded = true + end + RUBY + + boot_rails + + assert Bukkits::Engine.config.environment_loaded + end end end From 939d4255e690bd0406179cad00836975273fd49e Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 19 Jul 2010 14:53:15 +0200 Subject: [PATCH 15/87] Removed most of deprecated stuff from Application and Engine --- railties/lib/rails.rb | 5 -- .../lib/rails/application/configuration.rb | 9 --- railties/lib/rails/configuration.rb | 81 ------------------- railties/lib/rails/engine.rb | 3 - railties/lib/rails/railtie.rb | 9 --- .../test/application/configuration_test.rb | 16 ---- 6 files changed, 123 deletions(-) diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index 7c41367a84..3663910281 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -94,10 +94,5 @@ module Rails def public_path application && application.paths.public.to_a.first end - - def public_path=(path) - ActiveSupport::Deprecation.warn "Setting Rails.public_path= is deprecated. " << - "Please set paths.public = in config/application.rb instead.", caller - end end end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index c248d41e59..7e34a16487 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -1,12 +1,9 @@ -require 'active_support/deprecation' require 'active_support/core_ext/string/encoding' require 'rails/engine/configuration' module Rails class Application class Configuration < ::Rails::Engine::Configuration - include ::Rails::Configuration::Deprecated - attr_accessor :allow_concurrency, :cache_classes, :cache_store, :encoding, :consider_all_requests_local, :dependency_loading, :filter_parameters, :log_level, :logger, @@ -53,12 +50,6 @@ module Rails paths.tmp "tmp" paths.tmp.cache "tmp/cache" - if File.exists?("#{root}/test/mocks/#{Rails.env}") - ActiveSupport::Deprecation.warn "\"Rails.root/test/mocks/#{Rails.env}\" won't be added " << - "automatically to load paths anymore in future releases" - paths.mocks_path "test/mocks", :autoload => true, :glob => Rails.env - end - paths end end diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index e5af12b901..8369795e71 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -71,86 +71,5 @@ module Rails end end end - - module Deprecated - def frameworks(*args) - raise "config.frameworks in no longer supported. See the generated " \ - "config/boot.rb for steps on how to limit the frameworks that " \ - "will be loaded" - end - alias :frameworks= :frameworks - - def view_path=(value) - ActiveSupport::Deprecation.warn "config.view_path= is deprecated, " << - "please do paths.app.views= instead", caller - paths.app.views = value - end - - def view_path - ActiveSupport::Deprecation.warn "config.view_path is deprecated, " << - "please do paths.app.views instead", caller - paths.app.views.to_a.first - end - - def routes_configuration_file=(value) - ActiveSupport::Deprecation.warn "config.routes_configuration_file= is deprecated, " << - "please do paths.config.routes= instead", caller - paths.config.routes = value - end - - def routes_configuration_file - ActiveSupport::Deprecation.warn "config.routes_configuration_file is deprecated, " << - "please do paths.config.routes instead", caller - paths.config.routes.to_a.first - end - - def database_configuration_file=(value) - ActiveSupport::Deprecation.warn "config.database_configuration_file= is deprecated, " << - "please do paths.config.database= instead", caller - paths.config.database = value - end - - def database_configuration_file - ActiveSupport::Deprecation.warn "config.database_configuration_file is deprecated, " << - "please do paths.config.database instead", caller - paths.config.database.to_a.first - end - - def log_path=(value) - ActiveSupport::Deprecation.warn "config.log_path= is deprecated, " << - "please do paths.log= instead", caller - paths.config.log = value - end - - def log_path - ActiveSupport::Deprecation.warn "config.log_path is deprecated, " << - "please do paths.log instead", caller - paths.config.log.to_a.first - end - - def controller_paths=(value) - ActiveSupport::Deprecation.warn "config.controller_paths= is deprecated, " << - "please do paths.app.controllers= instead", caller - paths.app.controllers = value - end - - def controller_paths - ActiveSupport::Deprecation.warn "config.controller_paths is deprecated, " << - "please do paths.app.controllers instead", caller - paths.app.controllers.to_a.uniq - end - - def cookie_secret=(value) - ActiveSupport::Deprecation.warn "config.cookie_secret= is deprecated, " << - "please use config.secret_token= instead", caller - self.secret_token = value - end - - def cookie_secret - ActiveSupport::Deprecation.warn "config.cookie_secret is deprecated, " << - "please use config.secret_token instead", caller - self.secret_token - end - end end end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index efde82d1ed..22e3e931b1 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -94,9 +94,6 @@ module Rails class << self attr_accessor :called_from - # TODO Remove this. It's deprecated. - alias :engine_name :railtie_name - def inherited(base) unless base.abstract_railtie? base.called_from = begin diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 58b0d851f7..7126ec1699 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -1,7 +1,6 @@ require 'rails/initializable' require 'rails/configuration' require 'active_support/inflector' -require 'active_support/deprecation' module Rails # Railtie is the core of the Rails Framework and provides several hooks to extend @@ -142,14 +141,6 @@ module Rails end end - def railtie_name(*) - ActiveSupport::Deprecation.warn "railtie_name is deprecated and has no effect", caller - end - - def log_subscriber(*) - ActiveSupport::Deprecation.warn "log_subscriber is deprecated and has no effect", caller - end - def rake_tasks(&blk) @rake_tasks ||= [] @rake_tasks << blk if blk diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index d63d25b42e..2da695b78e 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -125,22 +125,6 @@ module ApplicationTests assert !ActionController.autoload?(:RecordIdentifier) end - test "runtime error is raised if config.frameworks= is used" do - add_to_config "config.frameworks = []" - - assert_raises RuntimeError do - require "#{app_path}/config/environment" - end - end - - test "runtime error is raised if config.frameworks is used" do - add_to_config "config.frameworks -= []" - - assert_raises RuntimeError do - require "#{app_path}/config/environment" - end - end - test "filter_parameters should be able to set via config.filter_parameters" do add_to_config <<-RUBY config.filter_parameters += [ :foo, 'bar', lambda { |key, value| From 32a5b49911b88e8e410583d382e8253004abce50 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 19 Jul 2010 17:53:14 +0200 Subject: [PATCH 16/87] Move singleton pattern to Railtie and remove Engine::Configurable and Application::Configurable in favor of unified Railtie::Configurable --- railties/lib/rails/application.rb | 29 +++------------- .../lib/rails/application/configurable.rb | 19 ----------- railties/lib/rails/application/railties.rb | 2 +- railties/lib/rails/engine.rb | 33 ++++++++++++------- railties/lib/rails/engine/configurable.rb | 31 ----------------- railties/lib/rails/railtie.rb | 8 ++++- railties/lib/rails/railtie/configurable.rb | 26 +++++++++++---- .../test/application/configuration_test.rb | 13 ++++---- railties/test/railties/engine_test.rb | 8 ++--- 9 files changed, 62 insertions(+), 107 deletions(-) delete mode 100644 railties/lib/rails/application/configurable.rb delete mode 100644 railties/lib/rails/engine/configurable.rb diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index cbb1aad68f..fee99faf43 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -41,20 +41,6 @@ module Rails autoload :Railties, 'rails/application/railties' class << self - private :new - - def instance - if self == Rails::Application - if Rails.application - ActiveSupport::Deprecation.warn "Calling a method in Rails::Application is deprecated, " << - "please call it directly in your application constant #{Rails.application.class.name}.", caller - end - Rails.application - else - @@instance ||= new - end - end - def inherited(base) raise "You cannot have more than one Rails::Application" if Rails.application super @@ -62,15 +48,8 @@ module Rails Rails.application.add_lib_to_load_path! ActiveSupport.run_load_hooks(:before_configuration, base.instance) end - - def respond_to?(*args) - super || instance.respond_to?(*args) - end - end - delegate :middleware, :to => :config - # This method is called just after an application inherits from Rails::Application, # allowing the developer to load classes in lib and use them during application # configuration. @@ -99,10 +78,6 @@ module Rails super end - def railties - @railties ||= Railties.new(config) - end - def routes_reloader @routes_reloader ||= ActiveSupport::FileUpdateChecker.new([]){ reload_routes! } end @@ -163,6 +138,10 @@ module Rails initializers end + def config + @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd)) + end + protected def default_middleware_stack diff --git a/railties/lib/rails/application/configurable.rb b/railties/lib/rails/application/configurable.rb deleted file mode 100644 index f598e33965..0000000000 --- a/railties/lib/rails/application/configurable.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Rails - class Application - module Configurable - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - def inherited(base) - raise "You cannot inherit from a Rails::Application child" - end - end - - def config - @config ||= Application::Configuration.new(self.class.find_root_with_flag("config.ru", Dir.pwd)) - end - end - end -end \ No newline at end of file diff --git a/railties/lib/rails/application/railties.rb b/railties/lib/rails/application/railties.rb index 67352f537e..c1d2de571f 100644 --- a/railties/lib/rails/application/railties.rb +++ b/railties/lib/rails/application/railties.rb @@ -10,7 +10,7 @@ module Rails end def railties - @railties ||= ::Rails::Railtie.subclasses.map(&:new) + @railties ||= ::Rails::Railtie.subclasses.map(&:instance) end def engines diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 22e3e931b1..41b0a764e2 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -125,19 +125,9 @@ module Rails @endpoint = endpoint if endpoint @endpoint end - - def configure(&block) - class_eval(&block) - end - - protected - - def method_missing(*args, &block) - instance.send(*args, &block) - end end - delegate :paths, :root, :to => :config + delegate :middleware, :root, :paths, :to => :config def load_tasks super @@ -154,7 +144,7 @@ module Rails end def railties - @railties ||= Railties.new(config) + @railties ||= self.class::Railties.new(config) end def app @@ -183,6 +173,10 @@ module Rails initializers end + def config + @config ||= Engine::Configuration.new(find_root_with_flag("lib")) + end + # Add configured load paths to ruby load paths and remove duplicates. initializer :set_load_path, :before => :bootstrap_hook do _all_load_paths.reverse_each do |path| @@ -256,6 +250,21 @@ module Rails end protected + def find_root_with_flag(flag, default=nil) + root_path = self.class.called_from + + while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/#{flag}") + parent = File.dirname(root_path) + root_path = parent != root_path && parent + end + + root = File.exist?("#{root_path}/#{flag}") ? root_path : default + raise "Could not find root path for #{self}" unless root + + Config::CONFIG['host_os'] =~ /mswin|mingw/ ? + Pathname.new(root).expand_path : Pathname.new(root).realpath + end + def default_middleware_stack ActionDispatch::MiddlewareStack.new end diff --git a/railties/lib/rails/engine/configurable.rb b/railties/lib/rails/engine/configurable.rb deleted file mode 100644 index e668911913..0000000000 --- a/railties/lib/rails/engine/configurable.rb +++ /dev/null @@ -1,31 +0,0 @@ -module Rails - class Engine - module Configurable - def self.included(base) - base.extend ClassMethods - base.private_class_method :new - end - - module ClassMethods - delegate :middleware, :root, :paths, :to => :config - delegate :call, :to => :instance - - def config - @config ||= Engine::Configuration.new(find_root_with_flag("lib")) - end - - def inherited(base) - raise "You cannot inherit from a Rails::Engine child" - end - - def instance - @instance ||= new - end - end - - def config - self.class.config - end - end - end -end diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 7126ec1699..0514e425fd 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -130,13 +130,15 @@ module Rails ABSTRACT_RAILTIES = %w(Rails::Railtie Rails::Plugin Rails::Engine Rails::Application) class << self + private :new + def subclasses @subclasses ||= [] end def inherited(base) unless base.abstract_railtie? - base.send(:include, self::Configurable) + base.send(:include, Railtie::Configurable) subclasses << base end end @@ -164,6 +166,10 @@ module Rails end end + def config + @config ||= Railtie::Configuration.new + end + def eager_load! end diff --git a/railties/lib/rails/railtie/configurable.rb b/railties/lib/rails/railtie/configurable.rb index a2eb938c5a..b6d4ed2312 100644 --- a/railties/lib/rails/railtie/configurable.rb +++ b/railties/lib/rails/railtie/configurable.rb @@ -6,17 +6,29 @@ module Rails end module ClassMethods - def config - @config ||= Railtie::Configuration.new - end + delegate :config, :to => :instance def inherited(base) - raise "You cannot inherit from a Rails::Railtie child" + raise "You cannot inherit from a #{self.superclass.name} child" end - end - def config - self.class.config + def instance + @instance ||= new + end + + def respond_to?(*args) + super || instance.respond_to?(*args) + end + + def configure(&block) + class_eval(&block) + end + + protected + + def method_missing(*args, &block) + instance.send(*args, &block) + end end end end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 2da695b78e..63d53fff90 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -26,18 +26,17 @@ module ApplicationTests FileUtils.rm_rf(new_app) if File.directory?(new_app) end - test "Rails::Application.instance is nil until app is initialized" do + test "Rails.application is nil until app is initialized" do require 'rails' - assert_nil Rails::Application.instance + assert_nil Rails.application require "#{app_path}/config/environment" - assert_equal AppTemplate::Application.instance, Rails::Application.instance + assert_equal AppTemplate::Application.instance, Rails.application end - test "Rails::Application responds to all instance methods" do + test "Rails.application responds to all instance methods" do require "#{app_path}/config/environment" - assert_respond_to Rails::Application, :routes_reloader - assert_equal Rails::Application.routes_reloader, Rails.application.routes_reloader - assert_equal Rails::Application.routes_reloader, AppTemplate::Application.routes_reloader + assert_respond_to Rails.application, :routes_reloader + assert_equal Rails.application.routes_reloader, AppTemplate::Application.routes_reloader end test "Rails::Application responds to paths" do diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 09f2b5baa0..e8675daef0 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -85,12 +85,12 @@ module RailtiesTest boot_rails - Rails::Application.routes.draw do |map| + Rails.application.routes.draw do |map| mount(Bukkits::Engine => "/bukkits") end env = Rack::MockRequest.env_for("/bukkits") - response = Rails::Application.call(env) + response = Rails.application.call(env) assert_equal "HELLO WORLD", response[2] end @@ -109,12 +109,12 @@ module RailtiesTest match "/foo" => lambda { |env| [200, {'Content-Type' => 'text/html'}, 'foo'] } end - Rails::Application.routes.draw do |map| + Rails.application.routes.draw do |map| mount(Bukkits::Engine => "/bukkits") end env = Rack::MockRequest.env_for("/bukkits/foo") - response = Rails::Application.call(env) + response = Rails.application.call(env) assert_equal "foo", response[2] end From 28016d33b0f2e668ca59a912c7fb2d777e3cf0a3 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 1 Jul 2010 09:04:30 +0200 Subject: [PATCH 17/87] Use env['action_dispatch.routes'] to determine if we should generate prefix or not. This technique is here to allow using routes from Engine in Application and vice versa. When using Engine routes inside Application it should generate prefix based on mount point. When using Engine routes inside Engine it should use env['SCRIPT_NAME']. In any other case it should generate prefix as env should not be even available. --- .../lib/action_dispatch/routing/mapper.rb | 27 +++++ .../lib/action_dispatch/routing/route_set.rb | 20 ++-- .../lib/action_dispatch/routing/url_for.rb | 8 +- actionpack/test/controller/routing_test.rb | 2 +- .../test/dispatch/prefix_generation_test.rb | 102 ++++++++++++++++++ 5 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 actionpack/test/dispatch/prefix_generation_test.rb diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index a2570cb877..f3aa09f4a7 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -261,7 +261,11 @@ module ActionDispatch raise "A rack application must be specified" unless path + options[:as] ||= app_name(app) + match(path, options.merge(:to => app, :anchor => false, :format => false)) + + define_generate_prefix(app, options[:as]) self end @@ -269,6 +273,29 @@ module ActionDispatch @set.default_url_options = options end alias_method :default_url_options, :default_url_options= + + private + def app_name(app) + return unless app.respond_to?(:routes) + class_name = app.class.is_a?(Class) ? app.name : app.class.name + ActiveSupport::Inflector.underscore(class_name).gsub("/", "_") + end + + def define_generate_prefix(app, name) + return unless app.respond_to?(:routes) + + _route = @set.named_routes.routes[name.to_sym] + _router = @set + app.routes.class_eval do + define_method :_generate_prefix do |options| + keys = _route.segment_keys + ActionDispatch::Routing::RouteSet::RESERVED_OPTIONS + prefix_options = options.reject { |k, v| !(keys).include?(k) } + # we must actually delete prefix segment keys to avoid passing them to next url_for + _route.segment_keys.each { |k| options.delete(k) } + _router.url_helpers.send("#{name}_path", prefix_options) + end + end + end end module HttpHelpers diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index b531cc1a8e..cb0373951f 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -303,10 +303,9 @@ module ActionDispatch end class Generator #:nodoc: - attr_reader :options, :recall, :set, :script_name, :named_route + attr_reader :options, :recall, :set, :named_route def initialize(options, recall, set, extras = false) - @script_name = options.delete(:script_name) @named_route = options.delete(:use_route) @options = options.dup @recall = recall.dup @@ -401,7 +400,7 @@ module ActionDispatch return [path, params.keys] if @extras path << "?#{params.to_query}" if params.any? - "#{script_name}#{path}" + path rescue Rack::Mount::RoutingError raise_routing_error end @@ -453,7 +452,11 @@ module ActionDispatch Generator.new(options, recall, self, extras).generate end - RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash] + RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :script_name, :skip_prefix, :routes] + + def _generate_prefix(options = {}) + nil + end def url_for(options) finalize! @@ -464,7 +467,6 @@ module ActionDispatch rewritten_url = "" path_segments = options.delete(:_path_segments) - unless options[:only_path] rewritten_url << (options[:protocol] || "http") rewritten_url << "://" unless rewritten_url.match("://") @@ -476,9 +478,15 @@ module ActionDispatch rewritten_url << ":#{options.delete(:port)}" if options.key?(:port) end + path = [options.delete(:script_name)] + if !options.delete(:skip_prefix) + path << _generate_prefix(options) + end + path_options = options.except(*RESERVED_OPTIONS) path_options = yield(path_options) if block_given? - path = generate(path_options, path_segments || {}) + path << generate(path_options, path_segments || {}) + path = path.compact.join '' # ROUTES TODO: This can be called directly, so script_name should probably be set in the routes rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 28ec830fe8..f73067688c 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -128,7 +128,13 @@ module ActionDispatch when String options when nil, Hash - _routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys) + routes = (options ? options.delete(:routes) : nil) || _routes + + if respond_to?(:env) && env && routes.equal?(env["action_dispatch.routes"]) + options[:skip_prefix] = true + end + + routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys) else polymorphic_url(options) end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index a8c74a6064..1f14607c31 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -251,7 +251,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase map.pages 'pages', :controller => 'content', :action => 'show_page', :host => 'foo.com' end x = setup_for_named_route - x.expects(:url_for).with(:host => 'foo.com', :only_path => false, :controller => 'content', :action => 'show_page', :use_route => :pages).once + x.expects(:url_for).with(:host => 'foo.com', :only_path => false, :controller => 'content', :action => 'show_page', :use_route => :pages, :router => rs).once x.send(:pages_url) end diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb new file mode 100644 index 0000000000..95d5c1c366 --- /dev/null +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -0,0 +1,102 @@ +require 'abstract_unit' + +module TestGenerationPrefix + class WithMountedEngine < ActionDispatch::IntegrationTest + class BlogEngine + def self.routes + @routes ||= begin + routes = ActionDispatch::Routing::RouteSet.new + routes.draw do + match "/posts/:id", :to => "inside_engine_generating#index", :as => :post + end + + routes + end + end + + def self.call(env) + env['action_dispatch.routes'] = routes + routes.call(env) + end + end + + class RailsApplication + def self.routes + @routes ||= begin + routes = ActionDispatch::Routing::RouteSet.new + routes.draw do + scope "/:omg", :omg => "awesome" do + mount BlogEngine => "/blog" + end + match "/generate", :to => "outside_engine_generating#index" + end + + routes + end + end + + def self.call(env) + env['action_dispatch.routes'] = routes + routes.call(env) + end + end + + class ::InsideEngineGeneratingController < ActionController::Base + include BlogEngine.routes.url_helpers + def index + render :text => post_path(:id => params[:id]) + end + end + + class ::OutsideEngineGeneratingController < ActionController::Base + include BlogEngine.routes.url_helpers + def index + render :text => post_path(:id => 1) + end + end + + class Foo + include ActionDispatch::Routing::UrlFor + include BlogEngine.routes.url_helpers + + def foo + post_path(42) + end + end + + + RailsApplication.routes # force draw + include BlogEngine.routes.url_helpers + + test "generating URL with prefix" do + assert_equal "/awesome/blog/posts/1", post_path(:id => 1) + end + + test "use SCRIPT_NAME inside the engine" do + env = Rack::MockRequest.env_for("/posts/1") + env["SCRIPT_NAME"] = "/pure-awesomness/blog" + response = ActionDispatch::Response.new(*BlogEngine.call(env)) + assert_equal "/pure-awesomness/blog/posts/1", response.body + end + + test "prepend prefix outside the engine" do + env = Rack::MockRequest.env_for("/generate") + env["SCRIPT_NAME"] = "/something" # it could be set by passenger + response = ActionDispatch::Response.new(*RailsApplication.call(env)) + assert_equal "/something/awesome/blog/posts/1", response.body + end + + test "generating urls with options for both prefix and named_route" do + assert_equal "/pure-awesomness/blog/posts/3", post_path(:id => 3, :omg => "pure-awesomness") + end + + test "generating urls with url_for should prepend the prefix" do + path = BlogEngine.routes.url_for(:omg => 'omg', :controller => "inside_engine_generating", :action => "index", :id => 1, :only_path => true) + assert_equal "/omg/blog/posts/1", path + end + + test "generating urls from a regular class" do + assert_equal "/awesome/blog/posts/42", Foo.new.foo + end + end +end From 628b94fbc81cb02b753d0f717a875219853b5764 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 1 Jul 2010 23:35:01 +0200 Subject: [PATCH 18/87] Pass routes via env['action_dispatch.routes'], it's needed by routes to determine if it should generate prefix for mounted apps --- railties/lib/rails/application.rb | 4 ++-- railties/lib/rails/engine.rb | 8 +++++++- railties/test/railties/engine_test.rb | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index fee99faf43..4ea828c549 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -125,10 +125,10 @@ module Rails end def env_defaults - @env_defaults ||= { + @env_defaults ||= super.merge({ "action_dispatch.parameter_filter" => config.filter_parameters, "action_dispatch.secret_token" => config.secret_token - } + }) end def initializers diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 41b0a764e2..22a1a15bf5 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -159,7 +159,13 @@ module Rails end def call(env) - app.call(env) + app.call(env.reverse_merge!(env_defaults)) + end + + def env_defaults + @env_defaults ||= { + "action_dispatch.routes" => routes + } end def routes diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index e8675daef0..4257a9fa83 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -177,5 +177,27 @@ module RailtiesTest assert Bukkits::Engine.config.environment_loaded end + + test "it passes router in env" do + @plugin.write "lib/bukkits.rb", <<-RUBY + class Bukkits + class Engine < ::Rails::Engine + endpoint lambda { |env| [200, {'Content-Type' => 'text/html'}, 'hello'] } + end + end + RUBY + + boot_rails + + env = Rack::MockRequest.env_for("/") + response = Bukkits::Engine.call(env) + + assert_equal Bukkits::Engine.routes, env['action_dispatch.routes'] + + env = Rack::MockRequest.env_for("/") + response = Rails.application.call(env) + + assert_equal Rails.application.routes, env['action_dispatch.routes'] + end end end From 451c9942bb493190d5673c1b55be7506056db13b Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 7 Jul 2010 11:26:03 +0200 Subject: [PATCH 19/87] Allow to generate Application routes inside Engine This requires knowledge about original SCRIPT_NAME and the parent router. It should be pass through the env as ORIGIAL_SCRIPT_NAME and action_dispatch.parent_routes --- .../lib/action_dispatch/routing/mapper.rb | 2 +- .../lib/action_dispatch/routing/url_for.rb | 6 ++--- .../test/dispatch/prefix_generation_test.rb | 25 ++++++++++++++++--- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index f3aa09f4a7..0e138524cd 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -289,7 +289,7 @@ module ActionDispatch app.routes.class_eval do define_method :_generate_prefix do |options| keys = _route.segment_keys + ActionDispatch::Routing::RouteSet::RESERVED_OPTIONS - prefix_options = options.reject { |k, v| !(keys).include?(k) } + prefix_options = options.slice(*keys) # we must actually delete prefix segment keys to avoid passing them to next url_for _route.segment_keys.each { |k| options.delete(k) } _router.url_helpers.send("#{name}_path", prefix_options) diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index f73067688c..deaf1cdf03 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -129,9 +129,9 @@ module ActionDispatch options when nil, Hash routes = (options ? options.delete(:routes) : nil) || _routes - - if respond_to?(:env) && env && routes.equal?(env["action_dispatch.routes"]) - options[:skip_prefix] = true + if respond_to?(:env) && env + options[:skip_prefix] = true if routes.equal?(env["action_dispatch.routes"]) + options[:script_name] = env["ORIGINAL_SCRIPT_NAME"] if routes.equal?(env["action_dispatch.parent_routes"]) end routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys) diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index 95d5c1c366..efc56c067b 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -8,6 +8,7 @@ module TestGenerationPrefix routes = ActionDispatch::Routing::RouteSet.new routes.draw do match "/posts/:id", :to => "inside_engine_generating#index", :as => :post + match "/bare_url_for", :to => "inside_engine_generating#bare_url_for", :as => :bare_url_for end routes @@ -37,6 +38,10 @@ module TestGenerationPrefix def self.call(env) env['action_dispatch.routes'] = routes + + # the next to values should be set only in application + env['ORIGINAL_SCRIPT_NAME'] = env['SCRIPT_NAME'] + env['action_dispatch.parent_routes'] = routes routes.call(env) end end @@ -46,6 +51,14 @@ module TestGenerationPrefix def index render :text => post_path(:id => params[:id]) end + + def bare_url_for + path = url_for( :routes => RailsApplication.routes, + :controller => "outside_engine_generating", + :action => "index", + :only_path => true) + render :text => path + end end class ::OutsideEngineGeneratingController < ActionController::Base @@ -73,9 +86,8 @@ module TestGenerationPrefix end test "use SCRIPT_NAME inside the engine" do - env = Rack::MockRequest.env_for("/posts/1") - env["SCRIPT_NAME"] = "/pure-awesomness/blog" - response = ActionDispatch::Response.new(*BlogEngine.call(env)) + env = Rack::MockRequest.env_for("/pure-awesomness/blog/posts/1") + response = ActionDispatch::Response.new(*RailsApplication.call(env)) assert_equal "/pure-awesomness/blog/posts/1", response.body end @@ -98,5 +110,12 @@ module TestGenerationPrefix test "generating urls from a regular class" do assert_equal "/awesome/blog/posts/42", Foo.new.foo end + + test "passing :routes to url_for to change current routes" do + env = Rack::MockRequest.env_for("/pure-awesomness/blog/bare_url_for") + env["SCRIPT_NAME"] = "/something" + response = ActionDispatch::Response.new(*RailsApplication.call(env)) + assert_equal "/something/generate", response.body + end end end From 177a4bd5b7f903030a100f9b5092b1fa62c7c748 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 7 Jul 2010 18:38:14 +0200 Subject: [PATCH 20/87] Fix url generation for mounted Engine I added integration tests for generating urls in Engine and application and tweaked Engines to fully cooparate with new router's behavior: * Rails.application now sets ORIGINAL_SCRIPT_NAME * Rails.application also sets its routes as env['action_dispatch.parent_routes'] * Engine implements responds_to? class method to respond to all the instance methods, like #routes --- railties/lib/rails/application.rb | 10 +- railties/lib/rails/engine.rb | 9 +- .../railties/mounted_engine_routes_test.rb | 107 ++++++++++++++++++ 3 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 railties/test/railties/mounted_engine_routes_test.rb diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 4ea828c549..3dba5f78a2 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -121,14 +121,20 @@ module Rails alias :build_middleware_stack :app def call(env) + if Rails.application == self + env["ORIGINAL_SCRIPT_NAME"] = env["SCRIPT_NAME"] + env["action_dispatch.parent_routes"] = routes + end + + env["action_dispatch.routes"] = routes app.call(env.reverse_merge!(env_defaults)) end def env_defaults - @env_defaults ||= super.merge({ + @env_defaults ||= { "action_dispatch.parameter_filter" => config.filter_parameters, "action_dispatch.secret_token" => config.secret_token - }) + } end def initializers diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 22a1a15bf5..401c4ff56b 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -159,13 +159,8 @@ module Rails end def call(env) - app.call(env.reverse_merge!(env_defaults)) - end - - def env_defaults - @env_defaults ||= { - "action_dispatch.routes" => routes - } + env["action_dispatch.routes"] = routes + app.call(env) end def routes diff --git a/railties/test/railties/mounted_engine_routes_test.rb b/railties/test/railties/mounted_engine_routes_test.rb new file mode 100644 index 0000000000..fe598b58b1 --- /dev/null +++ b/railties/test/railties/mounted_engine_routes_test.rb @@ -0,0 +1,107 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class ApplicationRoutingTest < Test::Unit::TestCase + require 'rack/test' + include Rack::Test::Methods + include ActiveSupport::Testing::Isolation + + def setup + build_app + + add_to_config("config.action_dispatch.show_exceptions = false") + + @plugin = engine "blog" + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do |map| + match "/engine_route" => "application_generating#engine_route" + match "/url_for_engine_route" => "application_generating#url_for_engine_route" + scope "/:user", :user => "anonymous" do + mount Blog::Engine => "/blog" + end + root :to => 'main#index' + end + RUBY + + @plugin.write "lib/blog.rb", <<-RUBY + module Blog + class Engine < ::Rails::Engine + end + end + RUBY + + app_file "config/initializers/bla.rb", <<-RUBY + Blog::Engine.eager_load! + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Blog::Engine.routes.draw do + resources :posts do + get :generate_application_route + end + end + RUBY + + @plugin.write "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ActionController::Base + include Blog::Engine.routes.url_helpers + + def index + render :text => post_path(1) + end + + def generate_application_route + path = url_for( :routes => Rails.application.routes, + :controller => "main", + :action => "index", + :only_path => true) + render :text => path + end + end + RUBY + + app_file "app/controllers/application_generating_controller.rb", <<-RUBY + class ApplicationGeneratingController < ActionController::Base + include Blog::Engine.routes.url_helpers + + def engine_route + render :text => posts_path + end + + def url_for_engine_route + render :text => url_for(:controller => "posts", :action => "index", :user => "john", :only_path => true) + end + end + RUBY + + boot_rails + end + + def app + @app ||= begin + require "#{app_path}/config/environment" + Rails.application + end + end + + test "routes generation in engine and application" do + # test generating engine's route from engine + get "/john/blog/posts" + assert_equal "/john/blog/posts/1", last_response.body + + # test generating engine's route from application + get "/engine_route" + assert_equal "/anonymous/blog/posts", last_response.body + get "/url_for_engine_route" + assert_equal "/john/blog/posts", last_response.body + + # test generating application's route from engine + get "/someone/blog/generate_application_route" + assert_equal "/", last_response.body + get "/someone/blog/generate_application_route", {}, "SCRIPT_NAME" => "/foo" + assert_equal "/foo/", last_response.body + end + end +end + From eedbf87d15b99a7cae38b0d8894fc39f1e70a81e Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 8 Jul 2010 15:42:40 +0200 Subject: [PATCH 21/87] New way of generating urls for Application from Engine. It's based specifying application's script_name with: Rails.application.default_url_options = {:script_name => "/foo"} default_url_options method is delegated to routes. If router used to generate url differs from the router passed via env it always overwrites :script_name with this value. --- actionpack/lib/action_controller/metal/url_for.rb | 13 +++++++++++-- .../lib/action_dispatch/routing/route_set.rb | 4 ++-- actionpack/lib/action_dispatch/routing/url_for.rb | 15 ++++++++++----- actionpack/test/controller/routing_test.rb | 2 +- .../test/dispatch/prefix_generation_test.rb | 1 + railties/lib/rails/application.rb | 7 ++----- .../test/railties/mounted_engine_routes_test.rb | 3 +++ 7 files changed, 30 insertions(+), 15 deletions(-) diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index a51fc5b8e4..ca91e1f362 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -5,11 +5,20 @@ module ActionController include ActionDispatch::Routing::UrlFor def url_options - super.reverse_merge( + options = {} + if respond_to?(:env) && env + if _routes.equal?(env["action_dispatch.routes"]) + options[:skip_prefix] = true + elsif env["action_dispatch.routes"] + options[:script_name] = _routes.default_url_options[:script_name] + end + end + + super.merge(options).reverse_merge( :host => request.host_with_port, :protocol => request.protocol, :_path_segments => request.symbolized_path_parameters - ).merge(:script_name => request.script_name) + ).reverse_merge(:script_name => request.script_name) end def _routes diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index cb0373951f..7519d74183 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -280,10 +280,10 @@ module ActionDispatch # Yes plz - JP included do routes.install_helpers(self) - singleton_class.send(:define_method, :_routes) { routes } + singleton_class.send(:define_method, :_routes) { @_routes || routes } end - define_method(:_routes) { routes } + define_method(:_routes) { @_routes || routes } end helpers diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index deaf1cdf03..5da41df485 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -129,16 +129,21 @@ module ActionDispatch options when nil, Hash routes = (options ? options.delete(:routes) : nil) || _routes - if respond_to?(:env) && env - options[:skip_prefix] = true if routes.equal?(env["action_dispatch.routes"]) - options[:script_name] = env["ORIGINAL_SCRIPT_NAME"] if routes.equal?(env["action_dispatch.parent_routes"]) - end - routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys) + _with_routes(routes) do + routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys) + end else polymorphic_url(options) end end + + def _with_routes(routes) + old_routes, @_routes = @_routes, routes + yield + ensure + @_routes = old_routes + end end end end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 1f14607c31..a8c74a6064 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -251,7 +251,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase map.pages 'pages', :controller => 'content', :action => 'show_page', :host => 'foo.com' end x = setup_for_named_route - x.expects(:url_for).with(:host => 'foo.com', :only_path => false, :controller => 'content', :action => 'show_page', :use_route => :pages, :router => rs).once + x.expects(:url_for).with(:host => 'foo.com', :only_path => false, :controller => 'content', :action => 'show_page', :use_route => :pages).once x.send(:pages_url) end diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index efc56c067b..49c5c82ad6 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -114,6 +114,7 @@ module TestGenerationPrefix test "passing :routes to url_for to change current routes" do env = Rack::MockRequest.env_for("/pure-awesomness/blog/bare_url_for") env["SCRIPT_NAME"] = "/something" + RailsApplication.routes.default_url_options = {:script_name => "/something"} response = ActionDispatch::Response.new(*RailsApplication.call(env)) assert_equal "/something/generate", response.body end diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 3dba5f78a2..fb04351b35 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -50,6 +50,8 @@ module Rails end end + delegate :default_url_options, :default_url_options=, :to => :routes + # This method is called just after an application inherits from Rails::Application, # allowing the developer to load classes in lib and use them during application # configuration. @@ -121,11 +123,6 @@ module Rails alias :build_middleware_stack :app def call(env) - if Rails.application == self - env["ORIGINAL_SCRIPT_NAME"] = env["SCRIPT_NAME"] - env["action_dispatch.parent_routes"] = routes - end - env["action_dispatch.routes"] = routes app.call(env.reverse_merge!(env_defaults)) end diff --git a/railties/test/railties/mounted_engine_routes_test.rb b/railties/test/railties/mounted_engine_routes_test.rb index fe598b58b1..18789ab9e3 100644 --- a/railties/test/railties/mounted_engine_routes_test.rb +++ b/railties/test/railties/mounted_engine_routes_test.rb @@ -99,6 +99,9 @@ module ApplicationTests # test generating application's route from engine get "/someone/blog/generate_application_route" assert_equal "/", last_response.body + + # with script_name + Rails.application.default_url_options = {:script_name => "/foo"} get "/someone/blog/generate_application_route", {}, "SCRIPT_NAME" => "/foo" assert_equal "/foo/", last_response.body end From b1e5e233fabe8f75ebd06e7e08c15f0eeddbae79 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 8 Jul 2010 23:27:49 +0200 Subject: [PATCH 22/87] Refactored tests for prefix generation and added test for url generation in regular class with default_url_options[:script_name] set --- .../test/dispatch/prefix_generation_test.rb | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index 49c5c82ad6..6ceb07a4f1 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -2,6 +2,9 @@ require 'abstract_unit' module TestGenerationPrefix class WithMountedEngine < ActionDispatch::IntegrationTest + require 'rack/test' + include Rack::Test::Methods + class BlogEngine def self.routes @routes ||= begin @@ -30,6 +33,7 @@ module TestGenerationPrefix mount BlogEngine => "/blog" end match "/generate", :to => "outside_engine_generating#index" + root :to => "outside_engine_generating#index" end routes @@ -77,25 +81,39 @@ module TestGenerationPrefix end end + class Bar + include ActionDispatch::Routing::UrlFor + include RailsApplication.routes.url_helpers + + def bar + root_path + end + end RailsApplication.routes # force draw include BlogEngine.routes.url_helpers + def app + RailsApplication + end + + def setup + RailsApplication.routes.default_url_options = {} + end + test "generating URL with prefix" do assert_equal "/awesome/blog/posts/1", post_path(:id => 1) end test "use SCRIPT_NAME inside the engine" do - env = Rack::MockRequest.env_for("/pure-awesomness/blog/posts/1") - response = ActionDispatch::Response.new(*RailsApplication.call(env)) - assert_equal "/pure-awesomness/blog/posts/1", response.body + get "/pure-awesomness/blog/posts/1" + assert_equal "/pure-awesomness/blog/posts/1", last_response.body end test "prepend prefix outside the engine" do - env = Rack::MockRequest.env_for("/generate") - env["SCRIPT_NAME"] = "/something" # it could be set by passenger - response = ActionDispatch::Response.new(*RailsApplication.call(env)) - assert_equal "/something/awesome/blog/posts/1", response.body + RailsApplication.routes.default_url_options = {:script_name => "/something"} + get "/generate", {}, 'SCRIPT_NAME' => "/something" + assert_equal "/something/awesome/blog/posts/1", last_response.body end test "generating urls with options for both prefix and named_route" do @@ -112,11 +130,14 @@ module TestGenerationPrefix end test "passing :routes to url_for to change current routes" do - env = Rack::MockRequest.env_for("/pure-awesomness/blog/bare_url_for") - env["SCRIPT_NAME"] = "/something" RailsApplication.routes.default_url_options = {:script_name => "/something"} - response = ActionDispatch::Response.new(*RailsApplication.call(env)) - assert_equal "/something/generate", response.body + get "/pure-awesomness/blog/bare_url_for", {}, 'SCRIPT_NAME' => "/something" + assert_equal "/something/generate", last_response.body + end + + test "using default_url_options[:script_name] in regular classes" do + RailsApplication.routes.default_url_options = {:script_name => "/something"} + assert_equal "/something/", Bar.new.bar end end end From 8a077089d9b7aa89cb56ef754b4540f411453375 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 9 Jul 2010 01:22:18 +0200 Subject: [PATCH 23/87] Get rid of :skip_prefix options in routes --- actionpack/lib/action_controller/metal/url_for.rb | 12 ++++-------- actionpack/lib/action_dispatch/routing/route_set.rb | 9 +++------ actionpack/test/dispatch/url_generation_test.rb | 2 +- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index ca91e1f362..ae628df81c 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -5,20 +5,16 @@ module ActionController include ActionDispatch::Routing::UrlFor def url_options - options = {} - if respond_to?(:env) && env - if _routes.equal?(env["action_dispatch.routes"]) - options[:skip_prefix] = true - elsif env["action_dispatch.routes"] - options[:script_name] = _routes.default_url_options[:script_name] + options = {} + if respond_to?(:env) && env && _routes.equal?(env["action_dispatch.routes"]) + options[:script_name] = request.script_name end - end super.merge(options).reverse_merge( :host => request.host_with_port, :protocol => request.protocol, :_path_segments => request.symbolized_path_parameters - ).reverse_merge(:script_name => request.script_name) + ) end def _routes diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 7519d74183..6f182ae652 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -452,7 +452,7 @@ module ActionDispatch Generator.new(options, recall, self, extras).generate end - RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :script_name, :skip_prefix, :routes] + RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :script_name, :routes] def _generate_prefix(options = {}) nil @@ -478,15 +478,12 @@ module ActionDispatch rewritten_url << ":#{options.delete(:port)}" if options.key?(:port) end - path = [options.delete(:script_name)] - if !options.delete(:skip_prefix) - path << _generate_prefix(options) - end + script_name = options.delete(:script_name) + path = (script_name.blank? ? _generate_prefix(options) : script_name).to_s path_options = options.except(*RESERVED_OPTIONS) path_options = yield(path_options) if block_given? path << generate(path_options, path_segments || {}) - path = path.compact.join '' # ROUTES TODO: This can be called directly, so script_name should probably be set in the routes rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb index f83651d583..d491be2f11 100644 --- a/actionpack/test/dispatch/url_generation_test.rb +++ b/actionpack/test/dispatch/url_generation_test.rb @@ -31,7 +31,7 @@ module TestUrlGeneration end test "the request's SCRIPT_NAME takes precedence over the routes'" do - get "/foo", {}, 'SCRIPT_NAME' => "/new" + get "/foo", {}, 'SCRIPT_NAME' => "/new", 'action_dispatch.routes' => Routes assert_equal "/new/foo", response.body end From b697ba9fd72ac8701747863b42082e59f13ba678 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 9 Jul 2010 10:25:10 +0200 Subject: [PATCH 24/87] Added some more tests for url generation between Engine and Application --- .../lib/action_controller/metal/url_for.rb | 8 ++-- .../test/dispatch/prefix_generation_test.rb | 38 ++++++++++++++----- .../railties/mounted_engine_routes_test.rb | 31 +++++++++++++-- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index ae628df81c..c1f1be3bef 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -5,10 +5,10 @@ module ActionController include ActionDispatch::Routing::UrlFor def url_options - options = {} - if respond_to?(:env) && env && _routes.equal?(env["action_dispatch.routes"]) - options[:script_name] = request.script_name - end + options = {} + if respond_to?(:env) && env && _routes.equal?(env["action_dispatch.routes"]) + options[:script_name] = request.script_name + end super.merge(options).reverse_merge( :host => request.host_with_port, diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index 6ceb07a4f1..af83ced5e9 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -11,7 +11,7 @@ module TestGenerationPrefix routes = ActionDispatch::Routing::RouteSet.new routes.draw do match "/posts/:id", :to => "inside_engine_generating#index", :as => :post - match "/bare_url_for", :to => "inside_engine_generating#bare_url_for", :as => :bare_url_for + match "/url_to_application", :to => "inside_engine_generating#url_to_application" end routes @@ -42,10 +42,6 @@ module TestGenerationPrefix def self.call(env) env['action_dispatch.routes'] = routes - - # the next to values should be set only in application - env['ORIGINAL_SCRIPT_NAME'] = env['SCRIPT_NAME'] - env['action_dispatch.parent_routes'] = routes routes.call(env) end end @@ -56,7 +52,7 @@ module TestGenerationPrefix render :text => post_path(:id => params[:id]) end - def bare_url_for + def url_to_application path = url_for( :routes => RailsApplication.routes, :controller => "outside_engine_generating", :action => "index", @@ -111,12 +107,23 @@ module TestGenerationPrefix end test "prepend prefix outside the engine" do + get "/generate" + assert_equal "/awesome/blog/posts/1", last_response.body + end + + test "prepend prefix outside the engine and use default_url_options[:script_name]" do RailsApplication.routes.default_url_options = {:script_name => "/something"} - get "/generate", {}, 'SCRIPT_NAME' => "/something" + get "/generate" assert_equal "/something/awesome/blog/posts/1", last_response.body end - test "generating urls with options for both prefix and named_route" do + test "give higher priority to default_url_options[:script_name]" do + RailsApplication.routes.default_url_options = {:script_name => "/something"} + get "/generate", {}, 'SCRIPT_NAME' => "/foo" + assert_equal "/something/awesome/blog/posts/1", last_response.body + end + + test "generating urls with options for prefix and named_route" do assert_equal "/pure-awesomness/blog/posts/3", post_path(:id => 3, :omg => "pure-awesomness") end @@ -129,9 +136,20 @@ module TestGenerationPrefix assert_equal "/awesome/blog/posts/42", Foo.new.foo end - test "passing :routes to url_for to change current routes" do + test "generating application's url from engine" do + get "/pure-awesomness/blog/url_to_application" + assert_equal "/generate", last_response.body + end + + test "generating application's url from engine with default_url_options[:script_name]" do RailsApplication.routes.default_url_options = {:script_name => "/something"} - get "/pure-awesomness/blog/bare_url_for", {}, 'SCRIPT_NAME' => "/something" + get "/pure-awesomness/blog/url_to_application" + assert_equal "/something/generate", last_response.body + end + + test "generating application's url from engine should give higher priority to default_url_options[:script_name]" do + RailsApplication.routes.default_url_options = {:script_name => "/something"} + get "/pure-awesomness/blog/url_to_application", {}, 'SCRIPT_NAME' => '/foo' assert_equal "/something/generate", last_response.body end diff --git a/railties/test/railties/mounted_engine_routes_test.rb b/railties/test/railties/mounted_engine_routes_test.rb index 18789ab9e3..1a0f5f2c21 100644 --- a/railties/test/railties/mounted_engine_routes_test.rb +++ b/railties/test/railties/mounted_engine_routes_test.rb @@ -70,7 +70,7 @@ module ApplicationTests end def url_for_engine_route - render :text => url_for(:controller => "posts", :action => "index", :user => "john", :only_path => true) + render :text => url_for(:controller => "posts", :action => "index", :user => "john", :only_path => true, :routes => Blog::Engine.routes) end end RUBY @@ -85,24 +85,47 @@ module ApplicationTests end end + def reset_script_name! + Rails.application.routes.default_url_options = {} + end + + def script_name(script_name) + Rails.application.routes.default_url_options = {:script_name => script_name} + end + test "routes generation in engine and application" do # test generating engine's route from engine get "/john/blog/posts" assert_equal "/john/blog/posts/1", last_response.body + # test generating engine's route from engine with default_url_options + script_name "/foo" + get "/john/blog/posts", {}, 'SCRIPT_NAME' => "/foo" + assert_equal "/foo/john/blog/posts/1", last_response.body + reset_script_name! + # test generating engine's route from application get "/engine_route" assert_equal "/anonymous/blog/posts", last_response.body get "/url_for_engine_route" assert_equal "/john/blog/posts", last_response.body + # test generating engine's route from application with default_url_options + script_name "/foo" + get "/engine_route", {}, 'SCRIPT_NAME' => "/foo" + assert_equal "/foo/anonymous/blog/posts", last_response.body + script_name "/foo" + get "/url_for_engine_route", {}, 'SCRIPT_NAME' => "/foo" + assert_equal "/foo/john/blog/posts", last_response.body + reset_script_name! + # test generating application's route from engine get "/someone/blog/generate_application_route" assert_equal "/", last_response.body - # with script_name - Rails.application.default_url_options = {:script_name => "/foo"} - get "/someone/blog/generate_application_route", {}, "SCRIPT_NAME" => "/foo" + # test generating application's route from engine with default_url_options + script_name "/foo" + get "/someone/blog/generate_application_route", {}, 'SCRIPT_NAME' => '/foo' assert_equal "/foo/", last_response.body end end From b53efd21059b9d829a6617fb5b2dd86754684c60 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 14 Jul 2010 00:46:42 +0200 Subject: [PATCH 25/87] Extended url_for to handle specifying which router should be used. A few examples: url_for Blog::Engine, :posts_path url_for Blog::Engine, @post url_for Blog::Engine, :action => "main", :controller => "index" --- .../routing/polymorphic_routes.rb | 2 +- .../lib/action_dispatch/routing/route_set.rb | 24 ++--- .../lib/action_dispatch/routing/url_for.rb | 45 ++++++---- actionpack/test/dispatch/url_for_test.rb | 52 +++++++++++ .../test/dispatch/url_generation_test.rb | 1 + actionpack/test/template/form_helper_test.rb | 90 +++++++++---------- actionpack/test/template/test_test.rb | 2 +- 7 files changed, 136 insertions(+), 80 deletions(-) create mode 100644 actionpack/test/dispatch/url_for_test.rb diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index fb2118a8d7..15ee7c8051 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -111,7 +111,7 @@ module ActionDispatch args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options end - send(named_route, *args) + url_for _routes.url_helpers.__send__("hash_for_#{named_route}", *args) end # Returns the path component of a URL for the given record. It uses diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 6f182ae652..c94b00257b 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -158,10 +158,17 @@ module ActionDispatch # We use module_eval to avoid leaks @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - def #{selector}(options = nil) # def hash_for_users_url(options = nil) - options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false} - end # end - protected :#{selector} # protected :hash_for_users_url + def #{selector}(*args) + options = args.extract_options! + + if args.any? + options[:_positional_args] = args + options[:_positional_keys] = #{route.segment_keys.inspect} + end + + options ? #{options.inspect}.merge(options) : #{options.inspect} + end + protected :#{selector} END_EVAL helpers << selector end @@ -185,14 +192,7 @@ module ActionDispatch @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 def #{selector}(*args) - options = #{hash_access_method}(args.extract_options!) - - if args.any? - options[:_positional_args] = args - options[:_positional_keys] = #{route.segment_keys.inspect} - end - - url_for(options) + url_for(#{hash_access_method}(*args)) end END_EVAL helpers << selector diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 5da41df485..edcb7f9cbe 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -123,27 +123,40 @@ module ActionDispatch # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok' # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/' # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33' - def url_for(options = nil) - case options - when String - options - when nil, Hash - routes = (options ? options.delete(:routes) : nil) || _routes - - _with_routes(routes) do - routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys) + def url_for(*args) + if args.first.respond_to?(:routes) + app = args.shift + _with_routes(app.routes) do + if args.first.is_a? Symbol + named_route = args.shift + url_for _routes.url_helpers.__send__("hash_for_#{named_route}", *args) + else + url_for(*args) + end end else - polymorphic_url(options) + options = args.first + case options + when String + options + when nil, Hash + routes = (options ? options.delete(:routes) : nil) || _routes + _with_routes(routes) do + routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys) + end + else + polymorphic_url(options) + end end end - def _with_routes(routes) - old_routes, @_routes = @_routes, routes - yield - ensure - @_routes = old_routes - end + protected + def _with_routes(routes) + old_routes, @_routes = @_routes, routes + yield + ensure + @_routes = old_routes + end end end end diff --git a/actionpack/test/dispatch/url_for_test.rb b/actionpack/test/dispatch/url_for_test.rb new file mode 100644 index 0000000000..3dc96d27d7 --- /dev/null +++ b/actionpack/test/dispatch/url_for_test.rb @@ -0,0 +1,52 @@ +require 'abstract_unit' + +module UrlForGeneration + class UrlForTest < ActionDispatch::IntegrationTest + + Routes = ActionDispatch::Routing::RouteSet.new + Routes.draw { match "/foo", :to => "my_route_generating#index", :as => :foo } + + class BlogEngine + def self.routes + @routes ||= begin + routes = ActionDispatch::Routing::RouteSet.new + routes.draw do + resources :posts + end + routes + end + end + end + + class Post + extend ActiveModel::Naming + + def to_param + "1" + end + + def self.model_name + klass = "Post" + def klass.name; self end + + ActiveModel::Name.new(klass) + end + end + + include Routes.url_helpers + + test "url_for with named url helpers" do + assert_equal "/posts", url_for(BlogEngine, :posts_path) + end + + test "url_for with polymorphic routes" do + assert_equal "http://www.example.com/posts/1", url_for(BlogEngine, Post.new) + end + + test "url_for with named url helper with arguments" do + assert_equal "/posts/1", url_for(BlogEngine, :post_path, 1) + assert_equal "/posts/1", url_for(BlogEngine, :post_path, :id => 1) + assert_equal "/posts/1.json", url_for(BlogEngine, :post_path, :id => 1, :format => :json) + end + end +end diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb index d491be2f11..2b54bc62b0 100644 --- a/actionpack/test/dispatch/url_generation_test.rb +++ b/actionpack/test/dispatch/url_generation_test.rb @@ -41,3 +41,4 @@ module TestUrlGeneration end end end + diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 9a1fe01872..ec57b6a2ab 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -77,13 +77,35 @@ class FormHelperTest < ActionView::TestCase @post.written_on = Date.new(2004, 6, 15) end + Routes = ActionDispatch::Routing::RouteSet.new + Routes.draw do + resources :posts do + resources :comments + end + + namespace :admin do + resources :posts do + resources :comments + end + end + + match "/foo", :to => "controller#action" + root :to => "main#index" + end + + def _routes + Routes + end + + include Routes.url_helpers + def url_for(object) @url_for_options = object - if object.is_a?(Hash) - "http://www.example.com" - else - super + if object.is_a?(Hash) && object[:use_route].blank? && object[:controller].blank? + object.merge!(:controller => "main", :action => "index") end + object + super end def test_label @@ -628,7 +650,7 @@ class FormHelperTest < ActionView::TestCase end expected = - "
" + + "" + snowman + "" + "" + @@ -683,7 +705,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form("http://www.example.com", "create-post", nil, "put") do + expected = whole_form("/", "create-post", nil, "put") do "" + "" + "" + @@ -702,7 +724,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form("http://www.example.com", "create-post", nil, :method => "put", :remote => true) do + expected = whole_form("/", "create-post", nil, :method => "put", :remote => true) do "" + "" + "" + @@ -721,7 +743,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form("http://www.example.com", nil, nil, :remote => true) do + expected = whole_form("/", nil, nil, :remote => true) do "" + "" + "" + @@ -738,7 +760,7 @@ class FormHelperTest < ActionView::TestCase concat f.check_box(:secret) end - expected = whole_form("http://www.example.com", "create-post") do + expected = whole_form("/", "create-post") do "" + "" + "" + @@ -1478,7 +1500,7 @@ class FormHelperTest < ActionView::TestCase end expected = - "" + + "" + snowman + "" + "" + @@ -1502,7 +1524,7 @@ class FormHelperTest < ActionView::TestCase end expected = - whole_form("http://www.example.com", "create-post") do + whole_form("/", "create-post") do "" + "" + "" @@ -1546,7 +1568,7 @@ class FormHelperTest < ActionView::TestCase txt << %{} end - def form_text(action = "http://www.example.com", id = nil, html_class = nil, remote = nil) + def form_text(action = "/", id = nil, html_class = nil, remote = nil) txt = %{} end - def whole_form(action = "http://www.example.com", id = nil, html_class = nil, options = nil) + def whole_form(action = "/", id = nil, html_class = nil, options = nil) contents = block_given? ? yield : "" if options.is_a?(Hash) @@ -1655,7 +1677,7 @@ class FormHelperTest < ActionView::TestCase assert_deprecated do form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end end - expected = whole_form("http://www.example.com", "some_form", "some_class") + expected = whole_form("/", "some_form", "some_class") assert_dom_equal expected, output_buffer end @@ -1710,14 +1732,14 @@ class FormHelperTest < ActionView::TestCase @comment.save form_for([@post, @comment]) {} - expected = whole_form(comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put") + expected = whole_form(post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put") assert_dom_equal expected, output_buffer end def test_form_for_with_new_object_in_list form_for([@post, @comment]) {} - expected = whole_form(comments_path(@post), "new_comment", "new_comment") + expected = whole_form(post_comments_path(@post), "new_comment", "new_comment") assert_dom_equal expected, output_buffer end @@ -1725,14 +1747,14 @@ class FormHelperTest < ActionView::TestCase @comment.save form_for([:admin, @post, @comment]) {} - expected = whole_form(admin_comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put") + expected = whole_form(admin_post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put") assert_dom_equal expected, output_buffer end def test_form_for_with_new_object_and_namespace_in_list form_for([:admin, @post, @comment]) {} - expected = whole_form(admin_comments_path(@post), "new_comment", "new_comment") + expected = whole_form(admin_post_comments_path(@post), "new_comment", "new_comment") assert_dom_equal expected, output_buffer end @@ -1749,38 +1771,6 @@ class FormHelperTest < ActionView::TestCase end protected - def comments_path(post) - "/posts/#{post.id}/comments" - end - alias_method :post_comments_path, :comments_path - - def comment_path(post, comment) - "/posts/#{post.id}/comments/#{comment.id}" - end - alias_method :post_comment_path, :comment_path - - def admin_comments_path(post) - "/admin/posts/#{post.id}/comments" - end - alias_method :admin_post_comments_path, :admin_comments_path - - def admin_comment_path(post, comment) - "/admin/posts/#{post.id}/comments/#{comment.id}" - end - alias_method :admin_post_comment_path, :admin_comment_path - - def posts_path - "/posts" - end - - def post_path(post, options = {}) - if options[:format] - "/posts/#{post.id}.#{options[:format]}" - else - "/posts/#{post.id}" - end - end - def protect_against_forgery? false end diff --git a/actionpack/test/template/test_test.rb b/actionpack/test/template/test_test.rb index 68e790cf46..20c96824d6 100644 --- a/actionpack/test/template/test_test.rb +++ b/actionpack/test/template/test_test.rb @@ -39,7 +39,7 @@ class PeopleHelperTest < ActionView::TestCase with_test_route_set do person = mock(:name => "David") person.class.extend ActiveModel::Naming - expects(:mocha_mock_path).with(person).returns("/people/1") + _routes.url_helpers.expects(:hash_for_mocha_mock_path).with(person).returns("/people/1") assert_equal 'David', link_to_person(person) end end From 233be6572c96087192885924c6658a15d01a2a1b Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 14 Jul 2010 09:17:24 +0200 Subject: [PATCH 26/87] Ensure that env is always available in controllers --- actionpack/lib/action_controller/metal.rb | 6 +++++- actionpack/lib/action_controller/metal/url_for.rb | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 96ac138ba3..def28a0054 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -52,7 +52,11 @@ module ActionController class Metal < AbstractController::Base abstract! - attr_internal :env + attr_internal_writer :env + + def env + @_env ||= {} + end # Returns the last part of the controller's name, underscored, without the ending # Controller. For instance, PostsController returns posts. diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index c1f1be3bef..e7eb7485c4 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -6,7 +6,7 @@ module ActionController def url_options options = {} - if respond_to?(:env) && env && _routes.equal?(env["action_dispatch.routes"]) + if _routes.equal?(env["action_dispatch.routes"]) options[:script_name] = request.script_name end From 229a868264a1dd5f4441f4b82ccf2a51cf83511d Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 14 Jul 2010 10:20:30 +0200 Subject: [PATCH 27/87] Use new url_for API instead of including routes.url_helpers --- actionpack/test/dispatch/prefix_generation_test.rb | 5 ++--- railties/test/railties/mounted_engine_routes_test.rb | 8 ++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index af83ced5e9..c681642eda 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -47,9 +47,8 @@ module TestGenerationPrefix end class ::InsideEngineGeneratingController < ActionController::Base - include BlogEngine.routes.url_helpers def index - render :text => post_path(:id => params[:id]) + render :text => url_for(BlogEngine, :post_path, :id => params[:id]) end def url_to_application @@ -64,7 +63,7 @@ module TestGenerationPrefix class ::OutsideEngineGeneratingController < ActionController::Base include BlogEngine.routes.url_helpers def index - render :text => post_path(:id => 1) + render :text => url_for(BlogEngine, :post_path, :id => 1) end end diff --git a/railties/test/railties/mounted_engine_routes_test.rb b/railties/test/railties/mounted_engine_routes_test.rb index 1a0f5f2c21..319b99383c 100644 --- a/railties/test/railties/mounted_engine_routes_test.rb +++ b/railties/test/railties/mounted_engine_routes_test.rb @@ -45,10 +45,8 @@ module ApplicationTests @plugin.write "app/controllers/posts_controller.rb", <<-RUBY class PostsController < ActionController::Base - include Blog::Engine.routes.url_helpers - def index - render :text => post_path(1) + render :text => url_for(Blog::Engine, :post_path, 1) end def generate_application_route @@ -63,10 +61,8 @@ module ApplicationTests app_file "app/controllers/application_generating_controller.rb", <<-RUBY class ApplicationGeneratingController < ActionController::Base - include Blog::Engine.routes.url_helpers - def engine_route - render :text => posts_path + render :text => url_for(Blog::Engine, :posts_path) end def url_for_engine_route From e9791bec823e42372eca095b946c93c1712a0613 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 15 Jul 2010 01:18:02 +0200 Subject: [PATCH 28/87] Routes refactoring: * added more tests for prefix generation * fixed bug with generating host for both prefix and url * refactored url_for method * organized tests for prefix generation --- .../lib/action_dispatch/routing/mapper.rb | 3 +- .../lib/action_dispatch/routing/route_set.rb | 2 +- .../lib/action_dispatch/routing/url_for.rb | 10 +- .../test/dispatch/prefix_generation_test.rb | 131 +++++++++++------- ..._routes_test.rb => mounted_engine_test.rb} | 11 +- 5 files changed, 90 insertions(+), 67 deletions(-) rename railties/test/railties/{mounted_engine_routes_test.rb => mounted_engine_test.rb} (91%) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 0e138524cd..88152ac290 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -288,8 +288,7 @@ module ActionDispatch _router = @set app.routes.class_eval do define_method :_generate_prefix do |options| - keys = _route.segment_keys + ActionDispatch::Routing::RouteSet::RESERVED_OPTIONS - prefix_options = options.slice(*keys) + prefix_options = options.slice(*_route.segment_keys) # we must actually delete prefix segment keys to avoid passing them to next url_for _route.segment_keys.each { |k| options.delete(k) } _router.url_helpers.send("#{name}_path", prefix_options) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index c94b00257b..b3945a4963 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -452,7 +452,7 @@ module ActionDispatch Generator.new(options, recall, self, extras).generate end - RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :script_name, :routes] + RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :script_name] def _generate_prefix(options = {}) nil diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index edcb7f9cbe..30b456f3df 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -123,19 +123,17 @@ module ActionDispatch # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok' # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/' # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33' - def url_for(*args) - if args.first.respond_to?(:routes) - app = args.shift - _with_routes(app.routes) do + def url_for(options = nil, *args) + if options.respond_to?(:routes) + _with_routes(options.routes) do if args.first.is_a? Symbol named_route = args.shift - url_for _routes.url_helpers.__send__("hash_for_#{named_route}", *args) + url_for _routes.url_helpers.send("hash_for_#{named_route}", *args) else url_for(*args) end end else - options = args.first case options when String options diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index c681642eda..2eb592c8d0 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -10,7 +10,8 @@ module TestGenerationPrefix @routes ||= begin routes = ActionDispatch::Routing::RouteSet.new routes.draw do - match "/posts/:id", :to => "inside_engine_generating#index", :as => :post + match "/posts/:id", :to => "inside_engine_generating#show", :as => :post + match "/posts", :to => "inside_engine_generating#index", :as => :posts match "/url_to_application", :to => "inside_engine_generating#url_to_application" end @@ -47,13 +48,19 @@ module TestGenerationPrefix end class ::InsideEngineGeneratingController < ActionController::Base + include BlogEngine.routes.url_helpers + def index - render :text => url_for(BlogEngine, :post_path, :id => params[:id]) + render :text => posts_path + end + + def show + render :text => post_path(:id => params[:id]) end def url_to_application - path = url_for( :routes => RailsApplication.routes, - :controller => "outside_engine_generating", + path = url_for( RailsApplication, + :controller => "outside_engine_generating", :action => "index", :only_path => true) render :text => path @@ -61,100 +68,118 @@ module TestGenerationPrefix end class ::OutsideEngineGeneratingController < ActionController::Base - include BlogEngine.routes.url_helpers def index render :text => url_for(BlogEngine, :post_path, :id => 1) end end - class Foo + class EngineObject include ActionDispatch::Routing::UrlFor include BlogEngine.routes.url_helpers - - def foo - post_path(42) - end end - class Bar + class AppObject include ActionDispatch::Routing::UrlFor include RailsApplication.routes.url_helpers - - def bar - root_path - end end - RailsApplication.routes # force draw - include BlogEngine.routes.url_helpers + # force draw + RailsApplication.routes def app RailsApplication end + def engine_object + @engine_object ||= EngineObject.new + end + + def app_object + @app_object ||= AppObject.new + end + def setup RailsApplication.routes.default_url_options = {} end - test "generating URL with prefix" do - assert_equal "/awesome/blog/posts/1", post_path(:id => 1) + # Inside Engine + test "[ENGINE] generating engine's url use SCRIPT_NAME from request" do + get "/pure-awesomeness/blog/posts/1" + assert_equal "/pure-awesomeness/blog/posts/1", last_response.body end - test "use SCRIPT_NAME inside the engine" do - get "/pure-awesomness/blog/posts/1" - assert_equal "/pure-awesomness/blog/posts/1", last_response.body + test "[ENGINE] generating application's url never uses SCRIPT_NAME from request" do + get "/pure-awesomeness/blog/url_to_application" + assert_equal "/generate", last_response.body end - test "prepend prefix outside the engine" do + test "[ENGINE] generating application's url includes default_url_options[:script_name]" do + RailsApplication.routes.default_url_options = {:script_name => "/something"} + get "/pure-awesomeness/blog/url_to_application" + assert_equal "/something/generate", last_response.body + end + + test "[ENGINE] generating application's url should give higher priority to default_url_options[:script_name]" do + RailsApplication.routes.default_url_options = {:script_name => "/something"} + get "/pure-awesomeness/blog/url_to_application", {}, 'SCRIPT_NAME' => '/foo' + assert_equal "/something/generate", last_response.body + end + + # Inside Application + test "[APP] generating engine's route includes prefix" do get "/generate" assert_equal "/awesome/blog/posts/1", last_response.body end - test "prepend prefix outside the engine and use default_url_options[:script_name]" do + test "[APP] generating engine's route includes default_url_options[:script_name]" do RailsApplication.routes.default_url_options = {:script_name => "/something"} get "/generate" assert_equal "/something/awesome/blog/posts/1", last_response.body end - test "give higher priority to default_url_options[:script_name]" do + test "[APP] generating engine's route should give higher priority to default_url_options[:script_name]" do RailsApplication.routes.default_url_options = {:script_name => "/something"} get "/generate", {}, 'SCRIPT_NAME' => "/foo" assert_equal "/something/awesome/blog/posts/1", last_response.body end - test "generating urls with options for prefix and named_route" do - assert_equal "/pure-awesomness/blog/posts/3", post_path(:id => 3, :omg => "pure-awesomness") + # Inside any Object + test "[OBJECT] generating engine's route includes prefix" do + assert_equal "/awesome/blog/posts/1", engine_object.post_path(:id => 1) end - test "generating urls with url_for should prepend the prefix" do - path = BlogEngine.routes.url_for(:omg => 'omg', :controller => "inside_engine_generating", :action => "index", :id => 1, :only_path => true) + test "[OBJECT] generating engine's route includes dynamic prefix" do + assert_equal "/pure-awesomeness/blog/posts/3", engine_object.post_path(:id => 3, :omg => "pure-awesomeness") + end + + test "[OBJECT] generating engine's route includes default_url_options[:script_name]" do + RailsApplication.routes.default_url_options = {:script_name => "/something"} + assert_equal "/something/pure-awesomeness/blog/posts/3", engine_object.post_path(:id => 3, :omg => "pure-awesomeness") + end + + test "[OBJECT] generating application's route" do + assert_equal "/", app_object.root_path + end + + test "[OBJECT] generating application's route includes default_url_options[:script_name]" do + RailsApplication.routes.default_url_options = {:script_name => "/something"} + assert_equal "/something/", app_object.root_path + end + + test "[OBJECT] generating engine's route with url_for" do + path = engine_object.url_for(BlogEngine, + :controller => "inside_engine_generating", + :action => "show", + :only_path => true, + :omg => "omg", + :id => 1) assert_equal "/omg/blog/posts/1", path - end - test "generating urls from a regular class" do - assert_equal "/awesome/blog/posts/42", Foo.new.foo - end + path = engine_object.url_for(BlogEngine, :posts_path) + assert_equal "/awesome/blog/posts", path - test "generating application's url from engine" do - get "/pure-awesomness/blog/url_to_application" - assert_equal "/generate", last_response.body - end - - test "generating application's url from engine with default_url_options[:script_name]" do - RailsApplication.routes.default_url_options = {:script_name => "/something"} - get "/pure-awesomness/blog/url_to_application" - assert_equal "/something/generate", last_response.body - end - - test "generating application's url from engine should give higher priority to default_url_options[:script_name]" do - RailsApplication.routes.default_url_options = {:script_name => "/something"} - get "/pure-awesomness/blog/url_to_application", {}, 'SCRIPT_NAME' => '/foo' - assert_equal "/something/generate", last_response.body - end - - test "using default_url_options[:script_name] in regular classes" do - RailsApplication.routes.default_url_options = {:script_name => "/something"} - assert_equal "/something/", Bar.new.bar + path = engine_object.url_for(BlogEngine, :posts_url, :host => "example.com") + assert_equal "http://example.com/awesome/blog/posts", path end end end diff --git a/railties/test/railties/mounted_engine_routes_test.rb b/railties/test/railties/mounted_engine_test.rb similarity index 91% rename from railties/test/railties/mounted_engine_routes_test.rb rename to railties/test/railties/mounted_engine_test.rb index 319b99383c..87bcf9b1f3 100644 --- a/railties/test/railties/mounted_engine_routes_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -50,10 +50,10 @@ module ApplicationTests end def generate_application_route - path = url_for( :routes => Rails.application.routes, - :controller => "main", - :action => "index", - :only_path => true) + path = url_for(Rails.application, + :controller => "main", + :action => "index", + :only_path => true) render :text => path end end @@ -66,7 +66,7 @@ module ApplicationTests end def url_for_engine_route - render :text => url_for(:controller => "posts", :action => "index", :user => "john", :only_path => true, :routes => Blog::Engine.routes) + render :text => url_for(Blog::Engine, :controller => "posts", :action => "index", :user => "john", :only_path => true) end end RUBY @@ -110,6 +110,7 @@ module ApplicationTests script_name "/foo" get "/engine_route", {}, 'SCRIPT_NAME' => "/foo" assert_equal "/foo/anonymous/blog/posts", last_response.body + script_name "/foo" get "/url_for_engine_route", {}, 'SCRIPT_NAME' => "/foo" assert_equal "/foo/john/blog/posts", last_response.body From 6c95e0f879aafa5921cd7898d5951b9a926d3c9a Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Sun, 18 Jul 2010 19:49:51 +0200 Subject: [PATCH 29/87] Add mounted_helpers to routes mounted_helpers are a bit similar to url_helpers. They're automatically included in controllers for Rails.application and each of mounted Engines. Mounted helper allows to call url_for and named helpers for given application. Given Blog::Engine mounted as blog_engine, there are 2 helpers defined: app and blog_engine. You can call routes for app and engine using those helpers: app.root_url app.url_for(:controller => "foo") blog_engine.posts_path blog_engine.url_for(@post) --- .../lib/abstract_controller/rendering.rb | 1 + actionpack/lib/action_controller/railtie.rb | 3 +- .../action_controller/railties/url_helpers.rb | 26 ++++++ .../lib/action_dispatch/routing/mapper.rb | 5 +- .../lib/action_dispatch/routing/route_set.rb | 59 +++++++++++++ .../lib/action_dispatch/routing/url_for.rb | 28 ++----- .../test/dispatch/prefix_generation_test.rb | 84 +++++++++++++++---- actionpack/test/dispatch/url_for_test.rb | 52 ------------ railties/test/railties/mounted_engine_test.rb | 32 +++++-- 9 files changed, 192 insertions(+), 98 deletions(-) create mode 100644 actionpack/lib/action_controller/railties/url_helpers.rb delete mode 100644 actionpack/test/dispatch/url_for_test.rb diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index b81d5954eb..5d9b35d297 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -52,6 +52,7 @@ module AbstractController if controller.respond_to?(:_routes) include controller._routes.url_helpers + include controller._routes.mounted_helpers end # TODO: Fix RJS to not require this diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index cd2dfafbe6..7496dd57b2 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -51,6 +51,7 @@ module ActionController ActiveSupport.on_load(:action_controller) do include app.routes.url_helpers + include app.routes.mounted_helpers(:app) options.each { |k,v| send("#{k}=", v) } end end @@ -63,4 +64,4 @@ module ActionController ActionController::Routing::Routes = proxy end end -end \ No newline at end of file +end diff --git a/actionpack/lib/action_controller/railties/url_helpers.rb b/actionpack/lib/action_controller/railties/url_helpers.rb new file mode 100644 index 0000000000..3e6f211cda --- /dev/null +++ b/actionpack/lib/action_controller/railties/url_helpers.rb @@ -0,0 +1,26 @@ +module ActionController + module Railties + + module UrlHelpers + def self.with(routes) + Module.new do + define_method(:inherited) do |klass| + super(klass) + klass.send(:include, routes.url_helpers) + end + end + end + end + + module MountedHelpers + def self.with(routes, name = nil) + Module.new do + define_method(:inherited) do |klass| + super(klass) + klass.send(:include, routes.mounted_helpers(name)) + end + end + end + end + end +end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 88152ac290..ef1bee106a 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -285,13 +285,14 @@ module ActionDispatch return unless app.respond_to?(:routes) _route = @set.named_routes.routes[name.to_sym] - _router = @set + _routes = @set + app.routes.define_mounted_helper(name) app.routes.class_eval do define_method :_generate_prefix do |options| prefix_options = options.slice(*_route.segment_keys) # we must actually delete prefix segment keys to avoid passing them to next url_for _route.segment_keys.each { |k| options.delete(k) } - _router.url_helpers.send("#{name}_path", prefix_options) + _routes.url_helpers.send("#{name}_path", prefix_options) end end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index b3945a4963..0f8bb5c504 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -261,6 +261,65 @@ module ActionDispatch named_routes.install(destinations, regenerate_code) end + class RoutesProxy + include ActionDispatch::Routing::UrlFor + + %w(url_options polymorphic_url polymorphic_path).each do |method| + self.class_eval <<-RUBY, __FILE__, __LINE__ +1 + def #{method}(*args) + scope.send(:_with_routes, routes) do + scope.#{method}(*args) + end + end + RUBY + end + + attr_accessor :scope, :routes + alias :_routes :routes + + def initialize(routes, scope) + @routes, @scope = routes, scope + end + + def method_missing(method, *args) + if routes.url_helpers.respond_to?(method) + self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(*args) + options = args.extract_options! + args << url_options.merge((options || {}).symbolize_keys) + routes.url_helpers.#{method}(*args) + end + RUBY + send(method, *args) + else + super + end + end + end + + module MountedHelpers + end + + def mounted_helpers(name = nil) + define_mounted_helper(name) if name + MountedHelpers + end + + def define_mounted_helper(name, helpers = nil) + routes = self + MountedHelpers.class_eval do + define_method "_#{name}" do + RoutesProxy.new(routes, self) + end + end + + MountedHelpers.class_eval <<-RUBY + def #{name} + @#{name} ||= _#{name} + end + RUBY + end + def url_helpers @url_helpers ||= begin routes = self diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 30b456f3df..19db730b6a 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -123,28 +123,14 @@ module ActionDispatch # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok' # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/' # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33' - def url_for(options = nil, *args) - if options.respond_to?(:routes) - _with_routes(options.routes) do - if args.first.is_a? Symbol - named_route = args.shift - url_for _routes.url_helpers.send("hash_for_#{named_route}", *args) - else - url_for(*args) - end - end + def url_for(options = nil) + case options + when String + options + when nil, Hash + _routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys) else - case options - when String - options - when nil, Hash - routes = (options ? options.delete(:routes) : nil) || _routes - _with_routes(routes) do - routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys) - end - else - polymorphic_url(options) - end + polymorphic_url(options) end end diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index 2eb592c8d0..7fe11447b8 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -13,6 +13,7 @@ module TestGenerationPrefix match "/posts/:id", :to => "inside_engine_generating#show", :as => :post match "/posts", :to => "inside_engine_generating#index", :as => :posts match "/url_to_application", :to => "inside_engine_generating#url_to_application" + match "/polymorphic_path_for_engine", :to => "inside_engine_generating#polymorphic_path_for_engine" end routes @@ -31,9 +32,11 @@ module TestGenerationPrefix routes = ActionDispatch::Routing::RouteSet.new routes.draw do scope "/:omg", :omg => "awesome" do - mount BlogEngine => "/blog" + mount BlogEngine => "/blog", :as => "blog_engine" end match "/generate", :to => "outside_engine_generating#index" + match "/polymorphic_path_for_engine", :to => "outside_engine_generating#polymorphic_path_for_engine" + match "/polymorphic_with_url_for", :to => "outside_engine_generating#polymorphic_with_url_for" root :to => "outside_engine_generating#index" end @@ -47,8 +50,27 @@ module TestGenerationPrefix end end + # force draw + RailsApplication.routes + + class Post + extend ActiveModel::Naming + + def to_param + "1" + end + + def self.model_name + klass = "Post" + def klass.name; self end + + ActiveModel::Name.new(klass) + end + end + class ::InsideEngineGeneratingController < ActionController::Base include BlogEngine.routes.url_helpers + include RailsApplication.routes.mounted_helpers(:app) def index render :text => posts_path @@ -59,17 +81,30 @@ module TestGenerationPrefix end def url_to_application - path = url_for( RailsApplication, - :controller => "outside_engine_generating", - :action => "index", - :only_path => true) + path = app.url_for( :controller => "outside_engine_generating", + :action => "index", + :only_path => true) render :text => path end + + def polymorphic_path_for_engine + render :text => polymorphic_path(Post.new) + end end class ::OutsideEngineGeneratingController < ActionController::Base + include BlogEngine.routes.mounted_helpers + def index - render :text => url_for(BlogEngine, :post_path, :id => 1) + render :text => blog_engine.post_path(:id => 1) + end + + def polymorphic_path_for_engine + render :text => blog_engine.polymorphic_path(Post.new) + end + + def polymorphic_with_url_for + render :text => blog_engine.url_for(Post.new) end end @@ -83,9 +118,6 @@ module TestGenerationPrefix include RailsApplication.routes.url_helpers end - # force draw - RailsApplication.routes - def app RailsApplication end @@ -124,7 +156,12 @@ module TestGenerationPrefix get "/pure-awesomeness/blog/url_to_application", {}, 'SCRIPT_NAME' => '/foo' assert_equal "/something/generate", last_response.body end - + + test "[ENGINE] generating engine's url with polymorphic path" do + get "/pure-awesomeness/blog/polymorphic_path_for_engine" + assert_equal "/pure-awesomeness/blog/posts/1", last_response.body + end + # Inside Application test "[APP] generating engine's route includes prefix" do get "/generate" @@ -143,6 +180,16 @@ module TestGenerationPrefix assert_equal "/something/awesome/blog/posts/1", last_response.body end + test "[APP] generating engine's url with polymorphic path" do + get "/polymorphic_path_for_engine" + assert_equal "/awesome/blog/posts/1", last_response.body + end + + test "[APP] generating engine's url with url_for(@post)" do + get "/polymorphic_with_url_for" + assert_equal "http://example.org/awesome/blog/posts/1", last_response.body + end + # Inside any Object test "[OBJECT] generating engine's route includes prefix" do assert_equal "/awesome/blog/posts/1", engine_object.post_path(:id => 1) @@ -167,19 +214,28 @@ module TestGenerationPrefix end test "[OBJECT] generating engine's route with url_for" do - path = engine_object.url_for(BlogEngine, - :controller => "inside_engine_generating", + path = engine_object.url_for(:controller => "inside_engine_generating", :action => "show", :only_path => true, :omg => "omg", :id => 1) assert_equal "/omg/blog/posts/1", path + end - path = engine_object.url_for(BlogEngine, :posts_path) + test "[OBJECT] generating engine's route with named helpers" do + path = engine_object.posts_path assert_equal "/awesome/blog/posts", path - path = engine_object.url_for(BlogEngine, :posts_url, :host => "example.com") + path = engine_object.posts_url(:host => "example.com") assert_equal "http://example.com/awesome/blog/posts", path end + + test "[OBJECT] generating engine's route with polymorphic_url" do + path = engine_object.polymorphic_path(Post.new) + assert_equal "/awesome/blog/posts/1", path + + path = engine_object.polymorphic_url(Post.new, :host => "www.example.com") + assert_equal "http://www.example.com/awesome/blog/posts/1", path + end end end diff --git a/actionpack/test/dispatch/url_for_test.rb b/actionpack/test/dispatch/url_for_test.rb deleted file mode 100644 index 3dc96d27d7..0000000000 --- a/actionpack/test/dispatch/url_for_test.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'abstract_unit' - -module UrlForGeneration - class UrlForTest < ActionDispatch::IntegrationTest - - Routes = ActionDispatch::Routing::RouteSet.new - Routes.draw { match "/foo", :to => "my_route_generating#index", :as => :foo } - - class BlogEngine - def self.routes - @routes ||= begin - routes = ActionDispatch::Routing::RouteSet.new - routes.draw do - resources :posts - end - routes - end - end - end - - class Post - extend ActiveModel::Naming - - def to_param - "1" - end - - def self.model_name - klass = "Post" - def klass.name; self end - - ActiveModel::Name.new(klass) - end - end - - include Routes.url_helpers - - test "url_for with named url helpers" do - assert_equal "/posts", url_for(BlogEngine, :posts_path) - end - - test "url_for with polymorphic routes" do - assert_equal "http://www.example.com/posts/1", url_for(BlogEngine, Post.new) - end - - test "url_for with named url helper with arguments" do - assert_equal "/posts/1", url_for(BlogEngine, :post_path, 1) - assert_equal "/posts/1", url_for(BlogEngine, :post_path, :id => 1) - assert_equal "/posts/1.json", url_for(BlogEngine, :post_path, :id => 1, :format => :json) - end - end -end diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index 87bcf9b1f3..21c8658436 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -16,9 +16,10 @@ module ApplicationTests app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do |map| match "/engine_route" => "application_generating#engine_route" + match "/engine_route_in_view" => "application_generating#engine_route_in_view" match "/url_for_engine_route" => "application_generating#url_for_engine_route" scope "/:user", :user => "anonymous" do - mount Blog::Engine => "/blog" + mount Blog::Engine => "/blog", :as => "blog_engine" end root :to => 'main#index' end @@ -39,6 +40,7 @@ module ApplicationTests Blog::Engine.routes.draw do resources :posts do get :generate_application_route + get :application_route_in_view end end RUBY @@ -46,27 +48,34 @@ module ApplicationTests @plugin.write "app/controllers/posts_controller.rb", <<-RUBY class PostsController < ActionController::Base def index - render :text => url_for(Blog::Engine, :post_path, 1) + render :text => blog_engine.post_path(1) end def generate_application_route - path = url_for(Rails.application, - :controller => "main", - :action => "index", - :only_path => true) + path = app.url_for(:controller => "main", + :action => "index", + :only_path => true) render :text => path end + + def application_route_in_view + render :inline => "<%= app.root_path %>" + end end RUBY app_file "app/controllers/application_generating_controller.rb", <<-RUBY class ApplicationGeneratingController < ActionController::Base def engine_route - render :text => url_for(Blog::Engine, :posts_path) + render :text => blog_engine.posts_path + end + + def engine_route_in_view + render :inline => "<%= blog_engine.posts_path %>" end def url_for_engine_route - render :text => url_for(Blog::Engine, :controller => "posts", :action => "index", :user => "john", :only_path => true) + render :text => blog_engine.url_for(:controller => "posts", :action => "index", :user => "john", :only_path => true) end end RUBY @@ -103,6 +112,10 @@ module ApplicationTests # test generating engine's route from application get "/engine_route" assert_equal "/anonymous/blog/posts", last_response.body + + get "/engine_route_in_view" + assert_equal "/anonymous/blog/posts", last_response.body + get "/url_for_engine_route" assert_equal "/john/blog/posts", last_response.body @@ -120,6 +133,9 @@ module ApplicationTests get "/someone/blog/generate_application_route" assert_equal "/", last_response.body + get "/somone/blog/application_route_in_view" + assert_equal "/", last_response.body + # test generating application's route from engine with default_url_options script_name "/foo" get "/someone/blog/generate_application_route", {}, 'SCRIPT_NAME' => '/foo' From 1e6612ef80b019e602231e6833c934817cccb858 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 19 Jul 2010 13:32:34 +0200 Subject: [PATCH 30/87] Ensure that url_helpers included after application's ones have higher priority --- .../test/dispatch/prefix_generation_test.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index 7fe11447b8..3b47a1b72d 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -14,6 +14,7 @@ module TestGenerationPrefix match "/posts", :to => "inside_engine_generating#index", :as => :posts match "/url_to_application", :to => "inside_engine_generating#url_to_application" match "/polymorphic_path_for_engine", :to => "inside_engine_generating#polymorphic_path_for_engine" + match "/conflicting_url", :to => "inside_engine_generating#conflicting" end routes @@ -37,6 +38,7 @@ module TestGenerationPrefix match "/generate", :to => "outside_engine_generating#index" match "/polymorphic_path_for_engine", :to => "outside_engine_generating#polymorphic_path_for_engine" match "/polymorphic_with_url_for", :to => "outside_engine_generating#polymorphic_with_url_for" + match "/conflicting_url", :to => "outside_engine_generating#conflicting" root :to => "outside_engine_generating#index" end @@ -90,6 +92,10 @@ module TestGenerationPrefix def polymorphic_path_for_engine render :text => polymorphic_path(Post.new) end + + def conflicting + render :text => "engine" + end end class ::OutsideEngineGeneratingController < ActionController::Base @@ -106,6 +112,10 @@ module TestGenerationPrefix def polymorphic_with_url_for render :text => blog_engine.url_for(Post.new) end + + def conflicting + render :text => "application" + end end class EngineObject @@ -162,6 +172,11 @@ module TestGenerationPrefix assert_equal "/pure-awesomeness/blog/posts/1", last_response.body end + test "[ENGINE] url_helpers from engine have higher priotity than application's url_helpers" do + get "/awesome/blog/conflicting_url" + assert_equal "engine", last_response.body + end + # Inside Application test "[APP] generating engine's route includes prefix" do get "/generate" From ccd422f792a3c912e3da63570c82af7815f53e51 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 20 Jul 2010 18:08:35 +0200 Subject: [PATCH 31/87] We don't need that initializer in tests --- railties/test/railties/mounted_engine_test.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index 21c8658436..174ccb4b4b 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -32,10 +32,6 @@ module ApplicationTests end RUBY - app_file "config/initializers/bla.rb", <<-RUBY - Blog::Engine.eager_load! - RUBY - @plugin.write "config/routes.rb", <<-RUBY Blog::Engine.routes.draw do resources :posts do From 153df92f9f670055505dd91c429b010478fac9d6 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 22 Jul 2010 11:26:36 +0200 Subject: [PATCH 32/87] Added documentation on endpoint, middeware stack and routes for Engine --- railties/lib/rails/engine.rb | 60 ++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 401c4ff56b..d4a654fd08 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -87,6 +87,66 @@ module Rails # all folders under "app" are automatically added to the load path. So if you have # "app/observers", it's added by default. # + # == Endpoint + # + # Engine can be also a rack application. It can be useful if you have a rack application that + # you would like to wrap with Engine and provide some of the Engine's features. + # + # To do that, use endpoint method: + # module MyEngine + # class Engine < Rails::Engine + # endpoint MyRackApplication + # end + # end + # + # Now you can mount your engine in application's routes just like that: + # + # MyRailsApp::Application.routes.draw do + # mount MyEngine::Engine => "/engine" + # end + # + # == Middleware stack + # + # As Engine can now be rack endpoint, it can also have a middleware stack. The usage is exactly + # the same as in application: + # + # module MyEngine + # class Engine < Rails::Engine + # middleware.use SomeMiddleware + # end + # end + # + # == Routes + # + # If you don't specify endpoint, routes will be used as default endpoint. You can use them + # just like you use application's routes: + # + # # ENGINE/config/routes.rb + # MyEngine::Engine.routes.draw do + # match "/" => "posts#index" + # end + # + # == Mount priority + # + # Note that now there can be more than one router in you application and it's better to avoid + # passing requests through many routers. Consider such situation: + # + # MyRailsApp::Application.routes.draw do + # mount MyEngine::Engine => "/blog" + # match "/blog/omg" => "main#omg" + # end + # + # MyEngine is mounted at "/blog" path and additionaly "/blog/omg" points application's controller. + # In such situation request to "/blog/omg" will go through MyEngine and if there is no such route + # in Engine's routes, it will be dispatched to "main#omg". It's much better to swap that: + # + # MyRailsApp::Application.routes.draw do + # match "/blog/omg" => "main#omg" + # mount MyEngine::Engine => "/blog" + # end + # + # Now, Engine will get only requests that were not handled by application. + # class Engine < Railtie autoload :Configurable, "rails/engine/configurable" autoload :Configuration, "rails/engine/configuration" From abeb0ff2cc99967b852c509e5d3bd186fa8a184c Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 22 Jul 2010 12:07:45 +0200 Subject: [PATCH 33/87] Ensure that Rails.application.initialize! is called only once --- railties/lib/rails/application.rb | 2 ++ railties/test/application/loading_test.rb | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index fb04351b35..300d4c6ab9 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -95,7 +95,9 @@ module Rails end def initialize! + raise "Application has been already initialized." if @initialized run_initializers(self) + @initialized = true self end diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index 7ea712d27a..a2abf642b8 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -89,6 +89,11 @@ class LoadingTest < Test::Unit::TestCase assert_equal [], ActiveRecord::Base.descendants end + test "initialize_cant_be_called_twice" do + require "#{app_path}/config/environment" + assert_raise(RuntimeError) { ::AppTemplate::Application.initialize! } + end + protected def setup_ar! From a132229d7b4382d9ffe8847fa58f469cb8f2ecfc Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 22 Jul 2010 22:11:32 +0200 Subject: [PATCH 34/87] Added ability to set asset_path for engines --- .../action_dispatch/testing/test_request.rb | 2 +- .../action_view/helpers/asset_tag_helper.rb | 3 + .../test/template/asset_tag_helper_test.rb | 23 +++++++ railties/lib/rails/application.rb | 14 ++--- .../lib/rails/application/configuration.rb | 12 ++++ railties/lib/rails/engine.rb | 23 ++++++- railties/lib/rails/engine/configuration.rb | 2 +- .../test/application/configuration_test.rb | 15 +++++ railties/test/railties/engine_test.rb | 60 +++++++++++++++++++ 9 files changed, 141 insertions(+), 13 deletions(-) diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index b3e67f6e36..c587a36930 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -10,7 +10,7 @@ module ActionDispatch end def initialize(env = {}) - env = Rails.application.env_defaults.merge(env) if defined?(Rails.application) + env = Rails.application.env_config.merge(env) if defined?(Rails.application) super(DEFAULT_ENV.merge(env)) self.host = 'test.host' diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index a3c43d3e93..3329a8b368 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -727,6 +727,9 @@ module ActionView source += ".#{ext}" if rewrite_extension?(source, dir, ext) source = "/#{dir}/#{source}" unless source[0] == ?/ + if controller.respond_to?(:env) && controller.env["action_dispatch.asset_path"] + source = rewrite_asset_path(source, controller.env["action_dispatch.asset_path"]) + end source = rewrite_asset_path(source, config.asset_path) has_request = controller.respond_to?(:request) diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 6d5e4893c4..2b83cfe1a9 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -387,6 +387,15 @@ class AssetTagHelperTest < ActionView::TestCase assert_equal %(Rails), image_tag("rails.png") end + def test_env_asset_path + @controller.config.asset_path = "/assets%s" + def @controller.env; @_env ||= {} end + @controller.env["action_dispatch.asset_path"] = "/omg%s" + + expected_path = "/assets/omg/images/rails.png" + assert_equal %(Rails), image_tag("rails.png") + end + def test_proc_asset_id @controller.config.asset_path = Proc.new do |asset_path| "/assets.v12345#{asset_path}" @@ -396,6 +405,20 @@ class AssetTagHelperTest < ActionView::TestCase assert_equal %(Rails), image_tag("rails.png") end + def test_env_proc_asset_path + @controller.config.asset_path = Proc.new do |asset_path| + "/assets.v12345#{asset_path}" + end + + def @controller.env; @_env ||= {} end + @controller.env["action_dispatch.asset_path"] = Proc.new do |asset_path| + "/omg#{asset_path}" + end + + expected_path = "/assets.v12345/omg/images/rails.png" + assert_equal %(Rails), image_tag("rails.png") + end + def test_image_tag_interpreting_email_cid_correctly # An inline image has no need for an alt tag to be automatically generated from the cid: assert_equal '', image_tag("cid:thi%25%25sis@acontentid") diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 300d4c6ab9..7c590b701e 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -124,16 +124,12 @@ module Rails alias :build_middleware_stack :app - def call(env) - env["action_dispatch.routes"] = routes - app.call(env.reverse_merge!(env_defaults)) - end - - def env_defaults - @env_defaults ||= { + def env_config + @env_config ||= super.merge({ "action_dispatch.parameter_filter" => config.filter_parameters, - "action_dispatch.secret_token" => config.secret_token - } + "action_dispatch.secret_token" => config.secret_token, + "action_dispatch.asset_path" => nil + }) end def initializers diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 7e34a16487..29fa9d14eb 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -25,6 +25,18 @@ module Rails @middleware = app_middleware end + def asset_path=(value) + action_mailer.asset_path = value if respond_to?(:action_mailer) && action_mailer + action_controller.asset_path = value if respond_to?(:action_controller) && action_controller + super(value) + end + + def asset_host=(value) + action_mailer.asset_host = value if action_mailer + action_controller.asset_host = value if action_controller + super(value) + end + def encoding=(value) @encoding = value if "ruby".encoding_aware? diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index d4a654fd08..6b1e21a798 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -147,6 +147,19 @@ module Rails # # Now, Engine will get only requests that were not handled by application. # + # == Asset path + # + # When you use engine with its own public directory, you will probably want to copy or symlink it + # to application's public directory. To simplify generating paths for assets, you can set asset_path + # for an Engine: + # + # class MyEngine::Engine < Rails::Engine + # config.asset_path = "/my_engine/%s" + # end + # + # With such config, asset paths will be automatically modified inside Engine: + # image_path("foo.jpg") #=> "/my_engine/images/foo.jpg" + # class Engine < Railtie autoload :Configurable, "rails/engine/configurable" autoload :Configuration, "rails/engine/configuration" @@ -219,8 +232,14 @@ module Rails end def call(env) - env["action_dispatch.routes"] = routes - app.call(env) + app.call(env.merge!(env_config)) + end + + def env_config + @env_config ||= { + 'action_dispatch.routes' => routes, + 'action_dispatch.asset_path' => config.asset_path + } end def routes diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index bf3ad1f26b..4588c27277 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -5,7 +5,7 @@ module Rails class Configuration < ::Rails::Railtie::Configuration attr_reader :root attr_writer :eager_load_paths, :autoload_once_paths, :autoload_paths - attr_accessor :middleware, :plugins + attr_accessor :middleware, :plugins, :asset_path def initialize(root=nil) super() diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 63d53fff90..6bf56f7052 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -260,5 +260,20 @@ module ApplicationTests get "/" assert_not_equal res, last_response.body end + + test "config.asset_path is not passed through env" do + make_basic_app do |app| + app.config.asset_path = "/omg%s" + end + + class ::OmgController < ActionController::Base + def index + render :inline => "<%= image_path('foo.jpg') %>" + end + end + + get "/" + assert_equal "/omg/images/foo.jpg", last_response.body + end end end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 4257a9fa83..3df6e110d5 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -199,5 +199,65 @@ module RailtiesTest assert_equal Rails.application.routes, env['action_dispatch.routes'] end + + test "it allows to set asset_path" do + @plugin.write "lib/bukkits.rb", <<-RUBY + class Bukkits + class Engine < ::Rails::Engine + config.asset_path = "/bukkits%s" + end + end + RUBY + + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + match "/foo" => "foo#index" + end + RUBY + + @plugin.write "app/controllers/foo_controller.rb", <<-RUBY + class FooController < ActionController::Base + def index + render :index + end + end + RUBY + + @plugin.write "app/views/foo/index.html.erb", <<-RUBY + <%= compute_public_path("/foo", "") %> + <%= image_path("foo.png") %> + <%= javascript_include_tag("foo") %> + <%= stylesheet_link_tag("foo") %> + RUBY + + + app_file "app/controllers/bar_controller.rb", <<-RUBY + class BarController < ActionController::Base + def index + render :index + end + end + RUBY + + app_file "app/views/bar/index.html.erb", <<-RUBY + <%= compute_public_path("/foo", "") %> + RUBY + + add_to_config 'config.asset_path = "/omg%s"' + + boot_rails + + env = Rack::MockRequest.env_for("/foo") + response = Bukkits::Engine.call(env) + stripped_body = response[2].body.split("\n").map(&:strip).join("\n") + + expected = "/omg/bukkits/foo\n" + + "/omg/bukkits/images/foo.png\n" + + "\n" + + "" + assert_equal expected, stripped_body + + end end end From 4cd6f7752658f0ec13d082fa2ee2a6f766410645 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Sat, 24 Jul 2010 17:43:33 +0200 Subject: [PATCH 35/87] We don't need delegating polymorphic_url and polymorphic_path anymore --- .../lib/action_dispatch/routing/route_set.rb | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 0f8bb5c504..121e7c2c75 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -264,16 +264,6 @@ module ActionDispatch class RoutesProxy include ActionDispatch::Routing::UrlFor - %w(url_options polymorphic_url polymorphic_path).each do |method| - self.class_eval <<-RUBY, __FILE__, __LINE__ +1 - def #{method}(*args) - scope.send(:_with_routes, routes) do - scope.#{method}(*args) - end - end - RUBY - end - attr_accessor :scope, :routes alias :_routes :routes @@ -281,6 +271,12 @@ module ActionDispatch @routes, @scope = routes, scope end + def url_options + scope.send(:_with_routes, routes) do + scope.url_options + end + end + def method_missing(method, *args) if routes.url_helpers.respond_to?(method) self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 From 0c1cd15470b6f953e1af8f47d072270b55474c31 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Sat, 24 Jul 2010 17:43:50 +0200 Subject: [PATCH 36/87] to_param shoul return a string --- actionpack/test/lib/controller/fake_models.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index bf3e175f1f..37b200d57a 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -83,7 +83,7 @@ class Comment def to_key; id ? [id] : nil end def save; @id = 1; @post_id = 1 end def persisted?; @id.present? end - def to_param; @id; end + def to_param; @id.to_s; end def name @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" end From b52dfc6726d6471b5fea4ef1bde988157002a224 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Sun, 25 Jul 2010 19:04:32 +0200 Subject: [PATCH 37/87] Added Rails.application.config.paths.db.migrate to remove hardcoded db/migrate paths --- activerecord/lib/active_record/migration.rb | 10 ++++++---- .../lib/active_record/railties/databases.rake | 13 +++++++------ railties/lib/rails/engine/configuration.rb | 2 ++ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 198f0a18cb..932ded414a 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -409,6 +409,8 @@ module ActiveRecord class Migrator#:nodoc: class << self + attr_writer :migrations_path + def migrate(migrations_path, target_version = nil) case when target_version.nil? @@ -441,10 +443,6 @@ module ActiveRecord self.new(direction, migrations_path, target_version).run end - def migrations_path - 'db/migrate' - end - def schema_migrations_table_name Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix end @@ -468,6 +466,10 @@ module ActiveRecord name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}" end + def migrations_path + @migrations_path ||= 'db/migrate' + end + private def move(direction, migrations_path, steps) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index b1aad0d496..f8a5114870 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -2,6 +2,7 @@ namespace :db do task :load_config => :rails_env do require 'active_record' ActiveRecord::Base.configurations = Rails.application.config.database_configuration + ActiveRecord::Migrator.migrations_path = Rails.application.config.paths.db.migrate.to_a.first end namespace :create do @@ -139,7 +140,7 @@ namespace :db do desc "Migrate the database (options: VERSION=x, VERBOSE=false)." task :migrate => :environment do ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true - ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil) + ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_path, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby end @@ -162,7 +163,7 @@ namespace :db do task :up => :environment do version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil raise "VERSION is required" unless version - ActiveRecord::Migrator.run(:up, "db/migrate/", version) + ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_path, version) Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby end @@ -170,7 +171,7 @@ namespace :db do task :down => :environment do version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil raise "VERSION is required" unless version - ActiveRecord::Migrator.run(:down, "db/migrate/", version) + ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_path, version) Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby end @@ -208,14 +209,14 @@ namespace :db do desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n).' task :rollback => :environment do step = ENV['STEP'] ? ENV['STEP'].to_i : 1 - ActiveRecord::Migrator.rollback('db/migrate/', step) + ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_path, step) Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby end # desc 'Pushes the schema to the next version (specify steps w/ STEP=n).' task :forward => :environment do step = ENV['STEP'] ? ENV['STEP'].to_i : 1 - ActiveRecord::Migrator.forward('db/migrate/', step) + ActiveRecord::Migrator.forward(ActiveRecord::Migrator.migrations_path, step) Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby end @@ -260,7 +261,7 @@ namespace :db do # desc "Raises an error if there are pending migrations" task :abort_if_pending_migrations => :environment do if defined? ActiveRecord - pending_migrations = ActiveRecord::Migrator.new(:up, 'db/migrate').pending_migrations + pending_migrations = ActiveRecord::Migrator.new(:up, ActiveRecord::Migrator.migrations_path).pending_migrations if pending_migrations.any? puts "You have #{pending_migrations.size} pending migrations:" diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 4588c27277..9aac9c81d0 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -34,6 +34,8 @@ module Rails paths.public.stylesheets "public/stylesheets" paths.vendor "vendor", :load_path => true paths.vendor.plugins "vendor/plugins" + paths.db "db" + paths.db.migrate "db/migrate" paths end end From bfccbc6df91a3c24bbf99262383c6f1e9069e1dd Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 26 Jul 2010 17:05:04 +0200 Subject: [PATCH 38/87] Add Rails::Railtie.railtie_name method to allow setting custom name for railtie --- actionpack/lib/action_dispatch/routing/mapper.rb | 9 +++++++-- railties/lib/rails/plugin.rb | 4 ++++ railties/lib/rails/railtie.rb | 7 +++++++ railties/test/railties/engine_test.rb | 1 + 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index ef1bee106a..b437d7a17d 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -277,8 +277,13 @@ module ActionDispatch private def app_name(app) return unless app.respond_to?(:routes) - class_name = app.class.is_a?(Class) ? app.name : app.class.name - ActiveSupport::Inflector.underscore(class_name).gsub("/", "_") + + if app.respond_to?(:railtie_name) + app.railtie_name + else + class_name = app.class.is_a?(Class) ? app.name : app.class.name + ActiveSupport::Inflector.underscore(class_name).gsub("/", "_") + end end def define_generate_prefix(app, name) diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb index 22a0eb10a8..c07ff2f9cf 100644 --- a/railties/lib/rails/plugin.rb +++ b/railties/lib/rails/plugin.rb @@ -48,6 +48,10 @@ module Rails attr_reader :name, :path + def railtie_name + name.to_s + end + def load_tasks super load_deprecated_tasks diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 0514e425fd..3e2a3c5231 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -164,8 +164,15 @@ module Rails def abstract_railtie? ABSTRACT_RAILTIES.include?(name) end + + def railtie_name(name = nil) + @railtie_name = name if name + @railtie_name ||= ActiveSupport::Inflector.underscore(self.name).gsub("/", "_") + end end + delegate :railtie_name, :to => "self.class" + def config @config ||= Railtie::Configuration.new end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 3df6e110d5..d83c2e92ba 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -28,6 +28,7 @@ module RailtiesTest plugin.write "lib/bukkits.rb", <<-RUBY class Bukkits class Engine < ::Rails::Engine + railtie_name "bukkits" end end RUBY From 2068b8cb6a8508fae9cd1a7f57e68d938c6403e6 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 26 Jul 2010 18:32:42 +0200 Subject: [PATCH 39/87] Added tests for railtie_name and aliased it in engine as engine_name --- railties/lib/rails/engine.rb | 2 ++ railties/test/railties/railtie_test.rb | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 6b1e21a798..de16c55990 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -166,6 +166,7 @@ module Rails class << self attr_accessor :called_from + alias :engine_name :railtie_name def inherited(base) unless base.abstract_railtie? @@ -201,6 +202,7 @@ module Rails end delegate :middleware, :root, :paths, :to => :config + delegate :engine_name, :to => "self.class" def load_tasks super diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb index 6715003d3d..406d5d764f 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -19,6 +19,22 @@ module RailtiesTest assert !Rails::Railtie.respond_to?(:config) end + test "Railtie provides railtie_name" do + begin + class ::Foo < Rails::Railtie ; end + assert_equal "foo", ::Foo.railtie_name + ensure + Object.send(:remove_const, :"Foo") + end + end + + test "railtie_name can be set manualy" do + class Foo < Rails::Railtie + railtie_name "bar" + end + assert_equal "bar", Foo.railtie_name + end + test "cannot inherit from a railtie" do class Foo < Rails::Railtie ; end assert_raise RuntimeError do From 75f8ac6ea71e4b2337f870b91ac05df98f33a8d2 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 26 Jul 2010 14:06:14 +0200 Subject: [PATCH 40/87] Implemented ActiveRecord::Migrations#copy based on James Adam's idea ActiveRecord::Migration#copy allows to copy migrations from one place to another, changing migrations versions and adding scope to filename. For example: ActiveRecord::Migration.copy("db/migrate", :blog_engine => "vendor/gems/blog/db/migrate") will copy all migrations from vendor/gems/blog/db/migrate to db/migrate with such format: Versions of copied migrations will be reversioned to be appended after migrations that already exists in db/migrate --- activerecord/lib/active_record/migration.rb | 86 ++++++++++---- .../lib/rails/generators/active_record.rb | 6 + activerecord/test/cases/helper.rb | 18 +++ activerecord/test/cases/migration_test.rb | 111 ++++++++++++++++++ .../to_copy/1_people_have_hobbies.rb | 9 ++ .../to_copy/2_people_have_descriptions.rb | 9 ++ .../20090101010101_people_have_hobbies.rb | 9 ++ ...20090101010202_people_have_descriptions.rb | 9 ++ .../20100101010101_people_have_last_names.rb | 9 ++ .../20100201010101_we_need_reminders.rb | 12 ++ .../20100301010101_innocent_jointable.rb | 12 ++ 11 files changed, 265 insertions(+), 25 deletions(-) create mode 100644 activerecord/test/migrations/to_copy/1_people_have_hobbies.rb create mode 100644 activerecord/test/migrations/to_copy/2_people_have_descriptions.rb create mode 100644 activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb create mode 100644 activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb create mode 100644 activerecord/test/migrations/valid_with_timestamps/20100101010101_people_have_last_names.rb create mode 100644 activerecord/test/migrations/valid_with_timestamps/20100201010101_we_need_reminders.rb create mode 100644 activerecord/test/migrations/valid_with_timestamps/20100301010101_innocent_jointable.rb diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 932ded414a..293c9f3c08 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -383,6 +383,37 @@ module ActiveRecord connection.send(method, *arguments, &block) end end + + def copy(destination, sources) + copied = [] + + sources.each do |scope, path| + destination_migrations = ActiveRecord::Migrator.migrations(destination) + source_migrations = ActiveRecord::Migrator.migrations(path) + last = destination_migrations.last + + source_migrations.each do |migration| + next if destination_migrations.any? { |m| m.name == migration.name && m.scope == scope.to_s } + + migration.version = next_migration_number(last.version + 1).to_i + last = migration + + new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb") + FileUtils.cp(migration.filename, new_path) + copied << new_path + end + end + + copied + end + + def next_migration_number(number) + if ActiveRecord::Base.timestamped_migrations + [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max + else + "%.3d" % number + end + end end end @@ -390,7 +421,7 @@ module ActiveRecord # until they are needed class MigrationProxy - attr_accessor :name, :version, :filename + attr_accessor :name, :version, :filename, :scope delegate :migrate, :announce, :write, :to=>:migration @@ -470,6 +501,34 @@ module ActiveRecord @migrations_path ||= 'db/migrate' end + def migrations(path) + files = Dir["#{path}/[0-9]*_*.rb"] + + migrations = files.inject([]) do |klasses, file| + version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first + + raise IllegalMigrationNameError.new(file) unless version + version = version.to_i + + if klasses.detect { |m| m.version == version } + raise DuplicateMigrationVersionError.new(version) + end + + if klasses.detect { |m| m.name == name.camelize && m.scope == scope } + raise DuplicateMigrationNameError.new(name.camelize) + end + + migration = MigrationProxy.new + migration.name = name.camelize + migration.version = version + migration.filename = file + migration.scope = scope + klasses << migration + end + + migrations.sort_by(&:version) + end + private def move(direction, migrations_path, steps) @@ -548,30 +607,7 @@ module ActiveRecord def migrations @migrations ||= begin - files = Dir["#{@migrations_path}/[0-9]*_*.rb"] - - migrations = files.inject([]) do |klasses, file| - version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first - - raise IllegalMigrationNameError.new(file) unless version - version = version.to_i - - if klasses.detect { |m| m.version == version } - raise DuplicateMigrationVersionError.new(version) - end - - if klasses.detect { |m| m.name == name.camelize } - raise DuplicateMigrationNameError.new(name.camelize) - end - - migration = MigrationProxy.new - migration.name = name.camelize - migration.version = version - migration.filename = file - klasses << migration - end - - migrations = migrations.sort_by { |m| m.version } + migrations = self.class.migrations(@migrations_path) down? ? migrations.reverse : migrations end end diff --git a/activerecord/lib/rails/generators/active_record.rb b/activerecord/lib/rails/generators/active_record.rb index 26bc977e19..4b3d1db216 100644 --- a/activerecord/lib/rails/generators/active_record.rb +++ b/activerecord/lib/rails/generators/active_record.rb @@ -14,6 +14,12 @@ module ActiveRecord def self.base_root File.dirname(__FILE__) end + + # Implement the required interface for Rails::Generators::Migration. + def self.next_migration_number(dirname) #:nodoc: + next_migration_number = current_migration_number(dirname) + 1 + ActiveRecord::Migration.next_migration_number(next_migration_number) + end end end end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 1fb59d3589..4bf3c25d28 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -83,3 +83,21 @@ begin ensure $stdout = original_stdout end + +class << Time + unless method_defined? :now_before_time_travel + alias_method :now_before_time_travel, :now + end + + def now + (@now ||= nil) || now_before_time_travel + end + + def travel_to(time, &block) + @now = time + block.call + ensure + @now = nil + end +end + diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 96b97fdd8a..85e1898b15 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1875,5 +1875,116 @@ if ActiveRecord::Base.connection.supports_migrations? end end end + + class CopyMigrationsTest < ActiveRecord::TestCase + def setup + end + + def clear + ActiveRecord::Base.timestamped_migrations = true + to_delete = Dir[@migrations_path + "/*.rb"] - @existing_migrations + File.delete(*to_delete) + end + + def test_copying_migrations_without_timestamps + ActiveRecord::Base.timestamped_migrations = false + @migrations_path = MIGRATIONS_ROOT + "/valid" + @existing_migrations = Dir[@migrations_path + "/*.rb"] + + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"}) + assert File.exists?(@migrations_path + "/4_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/5_people_have_descriptions.bukkits.rb") + assert_equal [@migrations_path + "/4_people_have_hobbies.bukkits.rb", @migrations_path + "/5_people_have_descriptions.bukkits.rb"], copied + + files_count = Dir[@migrations_path + "/*.rb"].length + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"}) + assert_equal files_count, Dir[@migrations_path + "/*.rb"].length + assert copied.empty? + ensure + clear + end + + def test_copying_migrations_without_timestamps_from_2_sources + ActiveRecord::Base.timestamped_migrations = false + @migrations_path = MIGRATIONS_ROOT + "/valid" + @existing_migrations = Dir[@migrations_path + "/*.rb"] + + sources = ActiveSupport::OrderedHash.new + sources[:bukkits] = sources[:omg] = MIGRATIONS_ROOT + "/to_copy" + ActiveRecord::Migration.copy(@migrations_path, sources) + assert File.exists?(@migrations_path + "/4_people_have_hobbies.omg.rb") + assert File.exists?(@migrations_path + "/5_people_have_descriptions.omg.rb") + assert File.exists?(@migrations_path + "/6_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/7_people_have_descriptions.bukkits.rb") + + files_count = Dir[@migrations_path + "/*.rb"].length + ActiveRecord::Migration.copy(@migrations_path, sources) + assert_equal files_count, Dir[@migrations_path + "/*.rb"].length + ensure + clear + end + + def test_copying_migrations_with_timestamps + @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" + @existing_migrations = Dir[@migrations_path + "/*.rb"] + + Time.travel_to(created_at = Time.utc(2010, 7, 26, 10, 10, 10)) do + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") + expected = [@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb", + @migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb"] + assert_equal expected, copied + + files_count = Dir[@migrations_path + "/*.rb"].length + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + assert_equal files_count, Dir[@migrations_path + "/*.rb"].length + assert copied.empty? + end + ensure + clear + end + + def test_copying_migrations_with_timestamps_from_2_sources + @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" + @existing_migrations = Dir[@migrations_path + "/*.rb"] + + sources = ActiveSupport::OrderedHash.new + sources[:bukkits] = sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" + + Time.travel_to(created_at = Time.utc(2010, 7, 26, 10, 10, 10)) do + copied = ActiveRecord::Migration.copy(@migrations_path, sources) + assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.omg.rb") + assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.omg.rb") + assert File.exists?(@migrations_path + "/20100726101012_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101013_people_have_descriptions.bukkits.rb") + assert_equal 4, copied.length + + files_count = Dir[@migrations_path + "/*.rb"].length + ActiveRecord::Migration.copy(@migrations_path, sources) + assert_equal files_count, Dir[@migrations_path + "/*.rb"].length + end + ensure + clear + end + + def test_copying_migrations_with_timestamps_to_destination_with_timestamps_in_future + @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" + @existing_migrations = Dir[@migrations_path + "/*.rb"] + + Time.travel_to(created_at = Time.utc(2010, 2, 20, 10, 10, 10)) do + ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + assert File.exists?(@migrations_path + "/20100301010102_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/20100301010103_people_have_descriptions.bukkits.rb") + + files_count = Dir[@migrations_path + "/*.rb"].length + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + assert_equal files_count, Dir[@migrations_path + "/*.rb"].length + assert copied.empty? + end + ensure + clear + end + end end diff --git a/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb new file mode 100644 index 0000000000..639841f663 --- /dev/null +++ b/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb @@ -0,0 +1,9 @@ +class PeopleHaveLastNames < ActiveRecord::Migration + def self.up + add_column "people", "hobbies", :text + end + + def self.down + remove_column "people", "hobbies" + end +end diff --git a/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb b/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb new file mode 100644 index 0000000000..b3d0b30640 --- /dev/null +++ b/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb @@ -0,0 +1,9 @@ +class PeopleHaveLastNames < ActiveRecord::Migration + def self.up + add_column "people", "description", :text + end + + def self.down + remove_column "people", "description" + end +end diff --git a/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb new file mode 100644 index 0000000000..639841f663 --- /dev/null +++ b/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb @@ -0,0 +1,9 @@ +class PeopleHaveLastNames < ActiveRecord::Migration + def self.up + add_column "people", "hobbies", :text + end + + def self.down + remove_column "people", "hobbies" + end +end diff --git a/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb b/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb new file mode 100644 index 0000000000..b3d0b30640 --- /dev/null +++ b/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb @@ -0,0 +1,9 @@ +class PeopleHaveLastNames < ActiveRecord::Migration + def self.up + add_column "people", "description", :text + end + + def self.down + remove_column "people", "description" + end +end diff --git a/activerecord/test/migrations/valid_with_timestamps/20100101010101_people_have_last_names.rb b/activerecord/test/migrations/valid_with_timestamps/20100101010101_people_have_last_names.rb new file mode 100644 index 0000000000..81af5fef5e --- /dev/null +++ b/activerecord/test/migrations/valid_with_timestamps/20100101010101_people_have_last_names.rb @@ -0,0 +1,9 @@ +class PeopleHaveLastNames < ActiveRecord::Migration + def self.up + add_column "people", "last_name", :string + end + + def self.down + remove_column "people", "last_name" + end +end \ No newline at end of file diff --git a/activerecord/test/migrations/valid_with_timestamps/20100201010101_we_need_reminders.rb b/activerecord/test/migrations/valid_with_timestamps/20100201010101_we_need_reminders.rb new file mode 100644 index 0000000000..d5e71ce8ef --- /dev/null +++ b/activerecord/test/migrations/valid_with_timestamps/20100201010101_we_need_reminders.rb @@ -0,0 +1,12 @@ +class WeNeedReminders < ActiveRecord::Migration + def self.up + create_table("reminders") do |t| + t.column :content, :text + t.column :remind_at, :datetime + end + end + + def self.down + drop_table "reminders" + end +end \ No newline at end of file diff --git a/activerecord/test/migrations/valid_with_timestamps/20100301010101_innocent_jointable.rb b/activerecord/test/migrations/valid_with_timestamps/20100301010101_innocent_jointable.rb new file mode 100644 index 0000000000..21c9ca5328 --- /dev/null +++ b/activerecord/test/migrations/valid_with_timestamps/20100301010101_innocent_jointable.rb @@ -0,0 +1,12 @@ +class InnocentJointable < ActiveRecord::Migration + def self.up + create_table("people_reminders", :id => false) do |t| + t.column :reminder_id, :integer + t.column :person_id, :integer + end + end + + def self.down + drop_table "people_reminders" + end +end \ No newline at end of file From 43a2aef3165793d9265aea1257063a60766f2226 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 26 Jul 2010 14:21:30 +0200 Subject: [PATCH 41/87] rake db:copy_migrations task, which copies railties migrations into application's db/migrate directory --- .../lib/active_record/railties/databases.rake | 22 +++++++++ railties/test/railties/shared_tests.rb | 49 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index f8a5114870..aedda26ba5 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -5,6 +5,28 @@ namespace :db do ActiveRecord::Migrator.migrations_path = Rails.application.config.paths.db.migrate.to_a.first end + desc "Copies missing migrations from Railties (e.g. plugins, engines). You can specify Railties to use with FROM=railtie1,railtie2" + task :copy_migrations => :load_config do + to_load = ENV["FROM"].blank? ? :all : ENV["FROM"].split(",").map {|n| n.strip } + railties = {} + Rails.application.railties.all do |railtie| + next unless to_load == :all || to_load.include?(railtie.railtie_name) + + if railtie.config.respond_to?(:paths) && railtie.config.paths.db + railties[railtie.railtie_name] = railtie.config.paths.db.migrate.to_a.first + end + end + + copied = ActiveRecord::Migration.copy(ActiveRecord::Migrator.migrations_path, railties) + + if copied.blank? + puts "No migrations were copied, project is up to date." + else + puts "The following migrations were copied:" + puts copied.map{ |path| File.basename(path) }.join("\n") + end + end + namespace :create do # desc 'Create all the local databases defined in config/database.yml' task :all => :load_config do diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb index ce7c55c11c..6aae17c237 100644 --- a/railties/test/railties/shared_tests.rb +++ b/railties/test/railties/shared_tests.rb @@ -10,6 +10,55 @@ module RailtiesTest @app ||= Rails.application end + def test_copying_migrations + @plugin.write "db/migrate/1_create_users.rb", <<-RUBY + class CreateUsers < ActiveRecord::Migration + end + RUBY + + @plugin.write "db/migrate/2_add_last_name_to_users.rb", <<-RUBY + class AddLastNameToUsers < ActiveRecord::Migration + end + RUBY + + app_file "db/migrate/1_create_sessions.rb", <<-RUBY + class CreateSessions < ActiveRecord::Migration + end + RUBY + + yaffle = plugin "acts_as_yaffle", "::LEVEL = config.log_level" do |plugin| + plugin.write "lib/acts_as_yaffle.rb", "class ActsAsYaffle; end" + end + + yaffle.write "db/migrate/1_create_yaffles.rb", <<-RUBY + class CreateYaffles < ActiveRecord::Migration + end + RUBY + + add_to_config "ActiveRecord::Base.timestamped_migrations = false" + + Dir.chdir(app_path) do + output = `rake db:copy_migrations FROM=bukkits` + + assert File.exists?("#{app_path}/db/migrate/2_create_users.bukkits.rb") + assert File.exists?("#{app_path}/db/migrate/3_add_last_name_to_users.bukkits.rb") + assert_match /2_create_users/, output + assert_match /3_add_last_name_to_users/, output + assert_equal 3, Dir["#{app_path}/db/migrate/*.rb"].length + + output = `rake db:copy_migrations` + + assert File.exists?("#{app_path}/db/migrate/4_create_yaffles.acts_as_yaffle.rb") + assert_match /4_create_yaffles/, output + + migrations_count = Dir["#{app_path}/db/migrate/*.rb"].length + output = `rake db:copy_migrations` + + assert_equal migrations_count, Dir["#{app_path}/db/migrate/*.rb"].length + assert_match /No migrations were copied/, output + end + end + def test_puts_its_lib_directory_on_load_path boot_rails require "another" From 559979b9844486a920993fdbcb719cb527b837c5 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 26 Jul 2010 19:33:58 +0200 Subject: [PATCH 42/87] Always convert railtie_name to string --- railties/lib/rails/railtie.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 3e2a3c5231..ab565b0cdc 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -166,7 +166,7 @@ module Rails end def railtie_name(name = nil) - @railtie_name = name if name + @railtie_name = name.to_s if name @railtie_name ||= ActiveSupport::Inflector.underscore(self.name).gsub("/", "_") end end From 5b6553ebb57037efc171e67b0f4b662f74bcb9a0 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 28 Jul 2010 19:36:13 +0200 Subject: [PATCH 43/87] Set asset_path to engine_name by default --- railties/lib/rails/engine.rb | 6 +++- railties/test/railties/engine_test.rb | 41 ++++++++++++++++++--------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index de16c55990..49553a57f3 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -256,7 +256,11 @@ module Rails end def config - @config ||= Engine::Configuration.new(find_root_with_flag("lib")) + @config ||= begin + config = Engine::Configuration.new(find_root_with_flag("lib")) + config.asset_path = "/#{engine_name}%s" if File.exists?(config.paths.public.to_a.first) + config + end end # Add configured load paths to ruby load paths and remove duplicates. diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index d83c2e92ba..788bc77620 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -84,11 +84,13 @@ module RailtiesTest end RUBY - boot_rails + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + mount(Bukkits::Engine => "/bukkits") + end + RUBY - Rails.application.routes.draw do |map| - mount(Bukkits::Engine => "/bukkits") - end + boot_rails env = Rack::MockRequest.env_for("/bukkits") response = Rails.application.call(env) @@ -104,16 +106,20 @@ module RailtiesTest end RUBY + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + match "/foo" => lambda { |env| [200, {'Content-Type' => 'text/html'}, 'foo'] } + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + mount(Bukkits::Engine => "/bukkits") + end + RUBY + boot_rails - Bukkits::Engine.routes.draw do |map| - match "/foo" => lambda { |env| [200, {'Content-Type' => 'text/html'}, 'foo'] } - end - - Rails.application.routes.draw do |map| - mount(Bukkits::Engine => "/bukkits") - end - env = Rack::MockRequest.env_for("/bukkits/foo") response = Rails.application.call(env) @@ -205,7 +211,6 @@ module RailtiesTest @plugin.write "lib/bukkits.rb", <<-RUBY class Bukkits class Engine < ::Rails::Engine - config.asset_path = "/bukkits%s" end end RUBY @@ -247,8 +252,17 @@ module RailtiesTest add_to_config 'config.asset_path = "/omg%s"' + @plugin.write 'public/touch.txt', <<-RUBY + touch + RUBY + boot_rails + # should set asset_path with engine name by default + assert_equal "/bukkits_engine%s", ::Bukkits::Engine.config.asset_path + + ::Bukkits::Engine.config.asset_path = "/bukkits%s" + env = Rack::MockRequest.env_for("/foo") response = Bukkits::Engine.call(env) stripped_body = response[2].body.split("\n").map(&:strip).join("\n") @@ -258,7 +272,6 @@ module RailtiesTest "\n" + "" assert_equal expected, stripped_body - end end end From 401cd97923fb52c8f8c458b8cb276b338e0b20f3 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 29 Jul 2010 14:12:25 +0200 Subject: [PATCH 44/87] Modified ActionDispatch::Static to allow passing multiple roots --- .../lib/action_dispatch/middleware/static.rb | 60 +++++++++++++++--- actionpack/test/dispatch/static_test.rb | 61 +++++++++++++++---- .../test/fixtures/blog_public/.gitignore | 1 + .../test/fixtures/blog_public/blog.html | 1 + .../test/fixtures/blog_public/index.html | 1 + .../fixtures/blog_public/subdir/index.html | 1 + 6 files changed, 104 insertions(+), 21 deletions(-) create mode 100644 actionpack/test/fixtures/blog_public/.gitignore create mode 100644 actionpack/test/fixtures/blog_public/blog.html create mode 100644 actionpack/test/fixtures/blog_public/index.html create mode 100644 actionpack/test/fixtures/blog_public/subdir/index.html diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index d7e88a54e4..c2d686f514 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -2,11 +2,43 @@ require 'rack/utils' module ActionDispatch class Static + class FileHandler + def initialize(at, root) + @at = at.chomp("/") + @file_server = ::Rack::File.new(root) + end + + def file_exist?(path) + (path = full_readable_path(path)) && File.file?(path) + end + + def directory_exist?(path) + (path = full_readable_path(path)) && File.directory?(path) + end + + def call(env) + env["PATH_INFO"].gsub!(/^#{@at}/, "") + @file_server.call(env) + end + + private + def includes_path?(path) + @at == "" || path =~ /^#{@at}/ + end + + def full_readable_path(path) + return unless includes_path?(path) + path = path.gsub(/^#{@at}/, "") + File.join(@file_server.root, ::Rack::Utils.unescape(path)) + end + end + FILE_METHODS = %w(GET HEAD).freeze - def initialize(app, root) + def initialize(app, roots) @app = app - @file_server = ::Rack::File.new(root) + roots = normalize_roots(roots) + @file_handlers = file_handlers(roots) end def call(env) @@ -14,15 +46,15 @@ module ActionDispatch method = env['REQUEST_METHOD'] if FILE_METHODS.include?(method) - if file_exist?(path) - return @file_server.call(env) + if file_handler = file_exist?(path) + return file_handler.call(env) else cached_path = directory_exist?(path) ? "#{path}/index" : path cached_path += ::ActionController::Base.page_cache_extension - if file_exist?(cached_path) + if file_handler = file_exist?(cached_path) env['PATH_INFO'] = cached_path - return @file_server.call(env) + return file_handler.call(env) end end end @@ -32,13 +64,21 @@ module ActionDispatch private def file_exist?(path) - full_path = File.join(@file_server.root, ::Rack::Utils.unescape(path)) - File.file?(full_path) && File.readable?(full_path) + @file_handlers.detect { |f| f.file_exist?(path) } end def directory_exist?(path) - full_path = File.join(@file_server.root, ::Rack::Utils.unescape(path)) - File.directory?(full_path) && File.readable?(full_path) + @file_handlers.detect { |f| f.directory_exist?(path) } + end + + def normalize_roots(roots) + roots.is_a?(Hash) ? roots : { "/" => roots.chomp("/") } + end + + def file_handlers(roots) + roots.map do |at, root| + FileHandler.new(at, root) + end end end end diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index e6957bb0ea..2eb82fc5d8 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -1,28 +1,23 @@ require 'abstract_unit' -class StaticTest < ActiveSupport::TestCase - DummyApp = lambda { |env| - [200, {"Content-Type" => "text/plain"}, ["Hello, World!"]] - } - App = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/public") - - test "serves dynamic content" do +module StaticTests + def test_serves_dynamic_content assert_equal "Hello, World!", get("/nofile") end - test "serves static index at root" do + def test_serves_static_index_at_root assert_equal "/index.html", get("/index.html") assert_equal "/index.html", get("/index") assert_equal "/index.html", get("/") end - test "serves static file in directory" do + def test_serves_static_file_in_directory assert_equal "/foo/bar.html", get("/foo/bar.html") assert_equal "/foo/bar.html", get("/foo/bar/") assert_equal "/foo/bar.html", get("/foo/bar") end - test "serves static index file in directory" do + def test_serves_static_index_file_in_directory assert_equal "/foo/index.html", get("/foo/index.html") assert_equal "/foo/index.html", get("/foo/") assert_equal "/foo/index.html", get("/foo") @@ -30,6 +25,50 @@ class StaticTest < ActiveSupport::TestCase private def get(path) - Rack::MockRequest.new(App).request("GET", path).body + Rack::MockRequest.new(@app).request("GET", path).body end end + +class StaticTest < ActiveSupport::TestCase + DummyApp = lambda { |env| + [200, {"Content-Type" => "text/plain"}, ["Hello, World!"]] + } + App = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/public") + + def setup + @app = App + end + + include StaticTests +end + +class MultipleDirectorisStaticTest < ActiveSupport::TestCase + DummyApp = lambda { |env| + [200, {"Content-Type" => "text/plain"}, ["Hello, World!"]] + } + App = ActionDispatch::Static.new(DummyApp, + { "/" => "#{FIXTURE_LOAD_PATH}/public", + "/blog" => "#{FIXTURE_LOAD_PATH}/blog_public", + "/foo" => "#{FIXTURE_LOAD_PATH}/non_existing_dir" + }) + + def setup + @app = App + end + + include StaticTests + + test "serves files from other mounted directories" do + assert_equal "/blog/index.html", get("/blog/index.html") + assert_equal "/blog/index.html", get("/blog/index") + assert_equal "/blog/index.html", get("/blog/") + + assert_equal "/blog/blog.html", get("/blog/blog/") + assert_equal "/blog/blog.html", get("/blog/blog.html") + assert_equal "/blog/blog.html", get("/blog/blog") + + assert_equal "/blog/subdir/index.html", get("/blog/subdir/index.html") + assert_equal "/blog/subdir/index.html", get("/blog/subdir/") + assert_equal "/blog/subdir/index.html", get("/blog/subdir") + end +end diff --git a/actionpack/test/fixtures/blog_public/.gitignore b/actionpack/test/fixtures/blog_public/.gitignore new file mode 100644 index 0000000000..312e635ee6 --- /dev/null +++ b/actionpack/test/fixtures/blog_public/.gitignore @@ -0,0 +1 @@ +absolute/* diff --git a/actionpack/test/fixtures/blog_public/blog.html b/actionpack/test/fixtures/blog_public/blog.html new file mode 100644 index 0000000000..79ad44c010 --- /dev/null +++ b/actionpack/test/fixtures/blog_public/blog.html @@ -0,0 +1 @@ +/blog/blog.html \ No newline at end of file diff --git a/actionpack/test/fixtures/blog_public/index.html b/actionpack/test/fixtures/blog_public/index.html new file mode 100644 index 0000000000..2de3825481 --- /dev/null +++ b/actionpack/test/fixtures/blog_public/index.html @@ -0,0 +1 @@ +/blog/index.html \ No newline at end of file diff --git a/actionpack/test/fixtures/blog_public/subdir/index.html b/actionpack/test/fixtures/blog_public/subdir/index.html new file mode 100644 index 0000000000..517bded335 --- /dev/null +++ b/actionpack/test/fixtures/blog_public/subdir/index.html @@ -0,0 +1 @@ +/blog/subdir/index.html \ No newline at end of file From 937f41919c6033272b40b147f7ac5e888207ac89 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 29 Jul 2010 16:23:15 +0200 Subject: [PATCH 45/87] Engine's assets are now served with ActionDispatch::Static --- railties/lib/rails/application.rb | 18 +++++++++++++++++- railties/lib/rails/engine.rb | 9 ++++----- railties/test/railties/engine_test.rb | 25 +++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 7c590b701e..c403863007 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -145,9 +145,25 @@ module Rails protected + def static_paths + @static_paths ||= begin + static_paths = ActiveSupport::OrderedHash.new + static_paths["/"] = paths.public.to_a.first + + railties.all do |railtie| + if railtie.config.respond_to?(:asset_path) && railtie.config.asset_path + public_path = railtie.config.paths.public.to_a.first + static_paths[railtie.config.asset_path % ""] = public_path if File.exists?(public_path) + end + end + + static_paths + end + end + def default_middleware_stack ActionDispatch::MiddlewareStack.new.tap do |middleware| - middleware.use ::ActionDispatch::Static, paths.public.to_a.first if config.serve_static_assets + middleware.use ::ActionDispatch::Static, static_paths if config.serve_static_assets middleware.use ::Rack::Lock if !config.allow_concurrency middleware.use ::Rack::Runtime middleware.use ::Rails::Rack::Logger diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 49553a57f3..85ff09d2af 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -256,11 +256,7 @@ module Rails end def config - @config ||= begin - config = Engine::Configuration.new(find_root_with_flag("lib")) - config.asset_path = "/#{engine_name}%s" if File.exists?(config.paths.public.to_a.first) - config - end + @config ||= Engine::Configuration.new(find_root_with_flag("lib")) end # Add configured load paths to ruby load paths and remove duplicates. @@ -335,6 +331,9 @@ module Rails require environment if environment end + initializer :default_asset_path do + config.asset_path = "/#{engine_name}%s" unless config.asset_path + end protected def find_root_with_flag(flag, default=nil) root_path = self.class.called_from diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 788bc77620..d4ecdf4742 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -273,5 +273,30 @@ module RailtiesTest "" assert_equal expected, stripped_body end + + test "engine's files are served via ActionDispatch::Static" do + add_to_config "config.serve_static_assets = true" + + @plugin.write "lib/bukkits.rb", <<-RUBY + class Bukkits + class Engine < ::Rails::Engine + engine_name :bukkits + end + end + RUBY + + @plugin.write "public/bukkits.html", "/bukkits/bukkits.html" + app_file "public/app.html", "/app.html" + + boot_rails + + env = Rack::MockRequest.env_for("/app.html") + response = Rails.application.call(env) + assert_equal response[2].path, File.join(app_path, "public/app.html") + + env = Rack::MockRequest.env_for("/bukkits/bukkits.html") + response = Rails.application.call(env) + assert_equal response[2].path, File.join(@plugin.path, "public/bukkits.html") + end end end From 2734d3819f4621bf797ea436d267b102deae67f7 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 29 Jul 2010 20:29:57 +0200 Subject: [PATCH 46/87] This is not needed --- .../action_controller/railties/url_helpers.rb | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 actionpack/lib/action_controller/railties/url_helpers.rb diff --git a/actionpack/lib/action_controller/railties/url_helpers.rb b/actionpack/lib/action_controller/railties/url_helpers.rb deleted file mode 100644 index 3e6f211cda..0000000000 --- a/actionpack/lib/action_controller/railties/url_helpers.rb +++ /dev/null @@ -1,26 +0,0 @@ -module ActionController - module Railties - - module UrlHelpers - def self.with(routes) - Module.new do - define_method(:inherited) do |klass| - super(klass) - klass.send(:include, routes.url_helpers) - end - end - end - end - - module MountedHelpers - def self.with(routes, name = nil) - Module.new do - define_method(:inherited) do |klass| - super(klass) - klass.send(:include, routes.mounted_helpers(name)) - end - end - end - end - end -end From 8fdeff0fa5c4ebf01856b0048dd86f7151fd11ba Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 30 Jul 2010 08:04:16 +0200 Subject: [PATCH 47/87] mounted helpers should be included in ActionMailer --- actionmailer/lib/action_mailer/railtie.rb | 3 ++- railties/test/application/initializers/frameworks_test.rb | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index ce6d8cc5b5..c888e51b93 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -19,8 +19,9 @@ module ActionMailer ActiveSupport.on_load(:action_mailer) do include app.routes.url_helpers + include app.routes.mounted_helpers(:app) options.each { |k,v| send("#{k}=", v) } end end end -end \ No newline at end of file +end diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 4ff10091b1..6e9ceb6ef7 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -61,6 +61,7 @@ module ApplicationTests require "#{app_path}/config/environment" assert Foo.method_defined?(:foo_path) + assert Foo.method_defined?(:app) assert_equal ["notify"], Foo.action_methods end From 99131939316230065b4297573d080d1585e4e5a7 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Sat, 31 Jul 2010 11:27:08 +0200 Subject: [PATCH 48/87] For view_context we need to initialize RoutesProxy in context of controller, not view, quick fix, I need to dig into it later --- actionpack/lib/action_dispatch/routing/route_set.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 121e7c2c75..c67b0199ce 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -301,11 +301,13 @@ module ActionDispatch MountedHelpers end - def define_mounted_helper(name, helpers = nil) + def define_mounted_helper(name) + return if MountedHelpers.method_defined?(name) + routes = self MountedHelpers.class_eval do define_method "_#{name}" do - RoutesProxy.new(routes, self) + RoutesProxy.new(routes, (self.respond_to?(:controller) ? controller : self)) end end From c7664d112fadb313146da33f48d1da318f249927 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 30 Jul 2010 07:14:48 +0200 Subject: [PATCH 49/87] Include application's helpers and router helpers by default, but include engine's ones for controllers inside isolated namespace --- actionmailer/lib/action_mailer/railtie.rb | 2 + .../action_mailer/railties/routes_helpers.rb | 12 ++ actionpack/lib/action_controller/base.rb | 8 +- .../lib/action_controller/metal/helpers.rb | 6 +- actionpack/lib/action_controller/railtie.rb | 3 +- .../railties/routes_helpers.rb | 17 +++ railties/lib/rails/engine.rb | 9 ++ railties/test/railties/engine_test.rb | 129 ++++++++++++++++++ 8 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 actionmailer/lib/action_mailer/railties/routes_helpers.rb create mode 100644 actionpack/lib/action_controller/railties/routes_helpers.rb diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index c888e51b93..26fe125fd8 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -1,5 +1,6 @@ require "action_mailer" require "rails" +require "action_mailer/railties/routes_helpers" module ActionMailer class Railtie < Rails::Railtie @@ -20,6 +21,7 @@ module ActionMailer ActiveSupport.on_load(:action_mailer) do include app.routes.url_helpers include app.routes.mounted_helpers(:app) + extend ::ActionMailer::Railties::RoutesHelpers options.each { |k,v| send("#{k}=", v) } end end diff --git a/actionmailer/lib/action_mailer/railties/routes_helpers.rb b/actionmailer/lib/action_mailer/railties/routes_helpers.rb new file mode 100644 index 0000000000..3464ec38e2 --- /dev/null +++ b/actionmailer/lib/action_mailer/railties/routes_helpers.rb @@ -0,0 +1,12 @@ +module ActionMailer + module Railties + module RoutesHelpers + def inherited(klass) + super(klass) + if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) } + klass.send(:include, namespace._railtie.routes.url_helpers) + end + end + end + end +end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 7a1464c2aa..3560ac5b8c 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -223,9 +223,13 @@ module ActionController def self.inherited(klass) super - klass.helper :all if klass.superclass == ActionController::Base + if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) } + klass.helper(all_helpers_from_path(namespace._railtie.config.paths.app.helpers.to_a)) + else + klass.helper :all if klass.superclass == ActionController::Base + end end ActiveSupport.run_load_hooks(:action_controller, self) end -end \ No newline at end of file +end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 4b6897c5dd..c5d7842db3 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -101,8 +101,12 @@ module ActionController # Extract helper names from files in app/helpers/**/*_helper.rb def all_application_helpers + all_helpers_from_path(helpers_path) + end + + def all_helpers_from_path(path) helpers = [] - Array.wrap(helpers_path).each do |path| + Array.wrap(path).each do |path| extract = /^#{Regexp.quote(path.to_s)}\/?(.*)_helper.rb$/ helpers += Dir["#{path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') } end diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 7496dd57b2..23622b19e8 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -4,6 +4,7 @@ require "action_dispatch/railtie" require "action_view/railtie" require "active_support/deprecation/proxy_wrappers" require "active_support/deprecation" +require "action_controller/railties/routes_helpers" module ActionController class Railtie < Rails::Railtie @@ -50,7 +51,7 @@ module ActionController options.helpers_path ||= paths.app.helpers.to_a ActiveSupport.on_load(:action_controller) do - include app.routes.url_helpers + extend ::ActionController::Railties::RoutesHelpers.with(app.routes) include app.routes.mounted_helpers(:app) options.each { |k,v| send("#{k}=", v) } end diff --git a/actionpack/lib/action_controller/railties/routes_helpers.rb b/actionpack/lib/action_controller/railties/routes_helpers.rb new file mode 100644 index 0000000000..a23f703f0b --- /dev/null +++ b/actionpack/lib/action_controller/railties/routes_helpers.rb @@ -0,0 +1,17 @@ +module ActionController + module Railties + module RoutesHelpers + def self.with(routes) + Module.new do + define_method(:inherited) do |klass| + super(klass) + if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) } + routes = namespace._railtie.routes + end + klass.send(:include, routes.url_helpers) + end + end + end + end + end +end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 85ff09d2af..7f53e2dc72 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -199,6 +199,15 @@ module Rails @endpoint = endpoint if endpoint @endpoint end + + def isolated_engine_for(mod) + _engine = self + mod.singleton_class.instance_eval do + define_method(:_railtie) do + _engine + end + end + end end delegate :middleware, :root, :paths, :to => :config diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index d4ecdf4742..db610451bd 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -298,5 +298,134 @@ module RailtiesTest response = Rails.application.call(env) assert_equal response[2].path, File.join(@plugin.path, "public/bukkits.html") end + + test "shared engine should include application's helpers" do + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + match "/foo" => "bukkits/foo#index", :as => "foo" + match "/foo/show" => "bukkits/foo#show" + end + RUBY + + app_file "app/helpers/some_helper.rb", <<-RUBY + module SomeHelper + def something + "Something... Something... Something..." + end + end + RUBY + + @plugin.write "app/controllers/bukkits/foo_controller.rb", <<-RUBY + class Bukkits::FooController < ActionController::Base + def index + render :inline => "<%= something %>" + end + + def show + render :text => foo_path + end + end + RUBY + + boot_rails + + env = Rack::MockRequest.env_for("/foo") + response = Rails.application.call(env) + assert_equal "Something... Something... Something...", response[2].body + + env = Rack::MockRequest.env_for("/foo/show") + response = Rails.application.call(env) + assert_equal "/foo", response[2].body + end + + test "isolated engine should include only its own routes and helpers" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + isolated_engine_for Bukkits + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + match "/bar" => "bar#index", :as => "bar" + mount Bukkits::Engine => "/bukkits", :as => "bukkits" + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + match "/foo" => "bukkits/foo#index", :as => "foo" + match "/foo/show" => "bukkits/foo#show" + match "/from_app" => "bukkits/foo#from_app" + match "/routes_helpers_in_view" => "bukkits/foo#routes_helpers_in_view" + end + RUBY + + app_file "app/helpers/some_helper.rb", <<-RUBY + module SomeHelper + def something + "Something... Something... Something..." + end + end + RUBY + + @plugin.write "app/helpers/engine_helper.rb", <<-RUBY + module EngineHelper + def help_the_engine + "Helped." + end + end + RUBY + + @plugin.write "app/controllers/bukkits/foo_controller.rb", <<-RUBY + class Bukkits::FooController < ActionController::Base + def index + render :inline => "<%= help_the_engine %>" + end + + def show + render :text => foo_path + end + + def from_app + render :inline => "<%= (self.respond_to?(:bar_path) || self.respond_to?(:something)) %>" + end + + def routes_helpers_in_view + render :inline => "<%= foo_path %>, <%= app.bar_path %>" + end + end + RUBY + + @plugin.write "app/mailers/bukkits/my_mailer.rb", <<-RUBY + module Bukkits + class MyMailer < ActionMailer::Base + end + end + RUBY + + boot_rails + + assert_equal Bukkits._railtie, Bukkits::Engine + assert ::Bukkits::MyMailer.method_defined?(:foo_path) + + env = Rack::MockRequest.env_for("/bukkits/from_app") + response = AppTemplate::Application.call(env) + assert_equal "false", response[2].body + + env = Rack::MockRequest.env_for("/bukkits/foo/show") + response = AppTemplate::Application.call(env) + assert_equal "/bukkits/foo", response[2].body + + env = Rack::MockRequest.env_for("/bukkits/foo") + response = AppTemplate::Application.call(env) + assert_equal "Helped.", response[2].body + + env = Rack::MockRequest.env_for("/bukkits/routes_helpers_in_view") + response = AppTemplate::Application.call(env) + assert_equal "/bukkits/foo, /bar", response[2].body + end end end From e063879daf55f100f316ad55f44a0df6d545693d Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Sat, 31 Jul 2010 14:43:41 +0200 Subject: [PATCH 50/87] Fix copying migrations to empty directory --- activerecord/lib/active_record/migration.rb | 2 +- activerecord/test/cases/migration_test.rb | 14 ++++++++++++++ activerecord/test/migrations/empty/.gitkeep | 0 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 activerecord/test/migrations/empty/.gitkeep diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 293c9f3c08..e708b3fbcf 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -395,7 +395,7 @@ module ActiveRecord source_migrations.each do |migration| next if destination_migrations.any? { |m| m.name == migration.name && m.scope == scope.to_s } - migration.version = next_migration_number(last.version + 1).to_i + migration.version = next_migration_number(last ? last.version + 1 : 0).to_i last = migration new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb") diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 85e1898b15..03a8cc90f6 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1985,6 +1985,20 @@ if ActiveRecord::Base.connection.supports_migrations? ensure clear end + + def test_copying_migrations_to_empty_directory + @migrations_path = MIGRATIONS_ROOT + "/empty" + @existing_migrations = [] + + Time.travel_to(created_at = Time.utc(2010, 7, 26, 10, 10, 10)) do + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") + assert_equal 2, copied.length + end + ensure + clear + end end end diff --git a/activerecord/test/migrations/empty/.gitkeep b/activerecord/test/migrations/empty/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 From befa77fc18ba54c1be89553466312039c1073f02 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 2 Aug 2010 02:55:01 +0200 Subject: [PATCH 51/87] Fix generating urls with mounted helpers in view context There were actually 2 problems with this one: * script_name was added to options as a string and then it was used in RouteSet#url_for with usage of <<, which was changing the original script_name * the second issue was with _with_routes method. It was called in RoutesProxy to modify _routes in view_context, but url_helpers in views is just delegating it to controller, so another _with_routes call is needed there --- actionpack/lib/action_controller/metal/url_for.rb | 2 +- actionpack/lib/action_dispatch/routing/route_set.rb | 2 +- actionpack/lib/action_view/helpers/url_helper.rb | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index e7eb7485c4..1e34f21c77 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -7,7 +7,7 @@ module ActionController def url_options options = {} if _routes.equal?(env["action_dispatch.routes"]) - options[:script_name] = request.script_name + options[:script_name] = request.script_name.dup end super.merge(options).reverse_merge( diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index c67b0199ce..6e7e133cc5 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -307,7 +307,7 @@ module ActionDispatch routes = self MountedHelpers.class_eval do define_method "_#{name}" do - RoutesProxy.new(routes, (self.respond_to?(:controller) ? controller : self)) + RoutesProxy.new(routes, self) end end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index b8df2d9a69..f21cffea26 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -29,7 +29,9 @@ module ActionView # def url_options return super unless controller.respond_to?(:url_options) - controller.url_options + controller.send(:_with_routes, _routes) do + controller.url_options + end end # Returns the URL for the set of +options+ provided. This takes the From 32baa278925c53f8885e94ea1f3d7c228d42c75f Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 2 Aug 2010 16:04:39 +0200 Subject: [PATCH 52/87] Include routes helpers only for inherited classes in ActionMailer --- actionmailer/lib/action_mailer/base.rb | 30 ++++-------- .../lib/action_mailer/hide_actions.rb | 46 +++++++++++++++++++ actionmailer/lib/action_mailer/railtie.rb | 3 +- .../action_mailer/railties/routes_helpers.rb | 14 ++++-- railties/test/railties/engine_test.rb | 1 + 5 files changed, 67 insertions(+), 27 deletions(-) create mode 100644 actionmailer/lib/action_mailer/hide_actions.rb diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 31e1b26afd..d1fb446366 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -5,6 +5,7 @@ require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/proc' require 'action_mailer/log_subscriber' +require 'action_mailer/hide_actions' module ActionMailer #:nodoc: # Action Mailer allows you to send email from your application using a mailer model and views. @@ -346,6 +347,7 @@ module ActionMailer #:nodoc: helper ActionMailer::MailHelper include ActionMailer::OldApi + include ActionMailer::HideActions delegate :register_observer, :to => Mail delegate :register_interceptor, :to => Mail @@ -361,6 +363,11 @@ module ActionMailer #:nodoc: }.freeze class << self + def inherited(klass) + klass.with_hiding_actions do + super(klass) + end + end def mailer_name @mailer_name ||= name.underscore @@ -725,27 +732,8 @@ module ActionMailer #:nodoc: container.add_part(part) end - module DeprecatedUrlOptions - def default_url_options - deprecated_url_options - end - - def default_url_options=(val) - deprecated_url_options - end - - def deprecated_url_options - raise "You can no longer call ActionMailer::Base.default_url_options " \ - "directly. You need to set config.action_mailer.default_url_options. " \ - "If you are using ActionMailer standalone, you need to include the " \ - "routing url_helpers directly." - end - end - - # This module will complain if the user tries to set default_url_options - # directly instead of through the config object. In Action Mailer's Railtie, - # we include the router's url_helpers, which will override this module. - extend DeprecatedUrlOptions + class_attribute :default_url_options + self.default_url_options = {} ActiveSupport.run_load_hooks(:action_mailer, self) end diff --git a/actionmailer/lib/action_mailer/hide_actions.rb b/actionmailer/lib/action_mailer/hide_actions.rb new file mode 100644 index 0000000000..876da95c3a --- /dev/null +++ b/actionmailer/lib/action_mailer/hide_actions.rb @@ -0,0 +1,46 @@ +require 'active_support/core_ext/class/attribute' + +module ActionMailer + # ActionController::HideActions adds the ability to prevent public methods on a controller + # to be called as actions. + module HideActions + extend ActiveSupport::Concern + + included do + class_attribute :hidden_actions + self.hidden_actions = Set.new.freeze + end + + private + + module ClassMethods + # Sets all of the actions passed in as hidden actions. + # + # ==== Parameters + # *args<#to_s>:: A list of actions + def hide_action(*args) + self.hidden_actions = hidden_actions.dup.merge(args.map(&:to_s)).freeze + end + + # Run block and add all the new action_methods to hidden_actions. + # This is used in inherited method. + def with_hiding_actions + yield + clear_action_methods! + hide_action(*action_methods) + clear_action_methods! + end + + def clear_action_methods! + @action_methods = nil + end + + # Overrides AbstractController::Base#action_methods to remove any methods + # that are listed as hidden methods. + def action_methods + @action_methods ||= super.reject { |name| hidden_actions.include?(name) } + end + end + end +end + diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index 26fe125fd8..ec0aa5f2e6 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -19,9 +19,8 @@ module ActionMailer options.stylesheets_dir ||= paths.public.stylesheets.to_a.first ActiveSupport.on_load(:action_mailer) do - include app.routes.url_helpers + extend ::ActionMailer::Railties::RoutesHelpers.with(app.routes) include app.routes.mounted_helpers(:app) - extend ::ActionMailer::Railties::RoutesHelpers options.each { |k,v| send("#{k}=", v) } end end diff --git a/actionmailer/lib/action_mailer/railties/routes_helpers.rb b/actionmailer/lib/action_mailer/railties/routes_helpers.rb index 3464ec38e2..b16d581fc6 100644 --- a/actionmailer/lib/action_mailer/railties/routes_helpers.rb +++ b/actionmailer/lib/action_mailer/railties/routes_helpers.rb @@ -1,10 +1,16 @@ module ActionMailer module Railties module RoutesHelpers - def inherited(klass) - super(klass) - if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) } - klass.send(:include, namespace._railtie.routes.url_helpers) + def self.with(routes) + Module.new do + define_method(:inherited) do |klass| + super(klass) + if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) } + klass.send(:include, namespace._railtie.routes.url_helpers) + else + klass.send(:include, routes.url_helpers) + end + end end end end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index db610451bd..3d51d3cf2b 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -410,6 +410,7 @@ module RailtiesTest assert_equal Bukkits._railtie, Bukkits::Engine assert ::Bukkits::MyMailer.method_defined?(:foo_path) + assert !::Bukkits::MyMailer.method_defined?(:bar_path) env = Rack::MockRequest.env_for("/bukkits/from_app") response = AppTemplate::Application.call(env) From 4131a2d804c54960ac70984e7453069fe8688365 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 2 Aug 2010 17:38:44 +0200 Subject: [PATCH 53/87] Move ActionController::Railties::RoutesHelpers and ActionMailer::Railties::RoutesHelper to AbstractController::Railties::RoutesHelpers --- actionmailer/lib/action_mailer/railtie.rb | 4 ++-- .../railties/routes_helpers.rb | 2 +- actionpack/lib/action_controller/railtie.rb | 4 ++-- .../railties/routes_helpers.rb | 17 ----------------- 4 files changed, 5 insertions(+), 22 deletions(-) rename {actionmailer/lib/action_mailer => actionpack/lib/abstract_controller}/railties/routes_helpers.rb (94%) delete mode 100644 actionpack/lib/action_controller/railties/routes_helpers.rb diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index ec0aa5f2e6..9468fd03e2 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -1,6 +1,6 @@ require "action_mailer" require "rails" -require "action_mailer/railties/routes_helpers" +require "abstract_controller/railties/routes_helpers" module ActionMailer class Railtie < Rails::Railtie @@ -19,7 +19,7 @@ module ActionMailer options.stylesheets_dir ||= paths.public.stylesheets.to_a.first ActiveSupport.on_load(:action_mailer) do - extend ::ActionMailer::Railties::RoutesHelpers.with(app.routes) + extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) include app.routes.mounted_helpers(:app) options.each { |k,v| send("#{k}=", v) } end diff --git a/actionmailer/lib/action_mailer/railties/routes_helpers.rb b/actionpack/lib/abstract_controller/railties/routes_helpers.rb similarity index 94% rename from actionmailer/lib/action_mailer/railties/routes_helpers.rb rename to actionpack/lib/abstract_controller/railties/routes_helpers.rb index b16d581fc6..dec1e9d6d9 100644 --- a/actionmailer/lib/action_mailer/railties/routes_helpers.rb +++ b/actionpack/lib/abstract_controller/railties/routes_helpers.rb @@ -1,4 +1,4 @@ -module ActionMailer +module AbstractController module Railties module RoutesHelpers def self.with(routes) diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 23622b19e8..4b5a897b90 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -4,7 +4,7 @@ require "action_dispatch/railtie" require "action_view/railtie" require "active_support/deprecation/proxy_wrappers" require "active_support/deprecation" -require "action_controller/railties/routes_helpers" +require "abstract_controller/railties/routes_helpers" module ActionController class Railtie < Rails::Railtie @@ -51,7 +51,7 @@ module ActionController options.helpers_path ||= paths.app.helpers.to_a ActiveSupport.on_load(:action_controller) do - extend ::ActionController::Railties::RoutesHelpers.with(app.routes) + extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) include app.routes.mounted_helpers(:app) options.each { |k,v| send("#{k}=", v) } end diff --git a/actionpack/lib/action_controller/railties/routes_helpers.rb b/actionpack/lib/action_controller/railties/routes_helpers.rb deleted file mode 100644 index a23f703f0b..0000000000 --- a/actionpack/lib/action_controller/railties/routes_helpers.rb +++ /dev/null @@ -1,17 +0,0 @@ -module ActionController - module Railties - module RoutesHelpers - def self.with(routes) - Module.new do - define_method(:inherited) do |klass| - super(klass) - if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) } - routes = namespace._railtie.routes - end - klass.send(:include, routes.url_helpers) - end - end - end - end - end -end From 79bd92b7833d52b74f50259cf8a21f9b05f3e9e3 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 3 Aug 2010 19:36:53 +0200 Subject: [PATCH 54/87] Refactor ActionMailer to not use hide_actions --- actionmailer/lib/action_mailer.rb | 1 + actionmailer/lib/action_mailer/base.rb | 11 ++--- .../lib/action_mailer/hide_actions.rb | 46 ------------------- actionpack/lib/abstract_controller.rb | 1 + actionpack/lib/abstract_controller/url_for.rb | 28 +++++++++++ .../lib/action_controller/metal/url_for.rb | 15 +----- .../lib/action_dispatch/routing/route_set.rb | 7 ++- 7 files changed, 40 insertions(+), 69 deletions(-) delete mode 100644 actionmailer/lib/action_mailer/hide_actions.rb create mode 100644 actionpack/lib/abstract_controller/url_for.rb diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index 05ba12197a..706ba74c2d 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -26,6 +26,7 @@ $:.unshift(actionpack_path) if File.directory?(actionpack_path) && !$:.include?( require 'abstract_controller' require 'action_view' +require 'action_dispatch' # Common Active Support usage in Action Mailer require 'active_support/core_ext/class' diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index d1fb446366..f7acb36341 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -5,7 +5,6 @@ require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/proc' require 'action_mailer/log_subscriber' -require 'action_mailer/hide_actions' module ActionMailer #:nodoc: # Action Mailer allows you to send email from your application using a mailer model and views. @@ -341,13 +340,13 @@ module ActionMailer #:nodoc: include AbstractController::Helpers include AbstractController::Translation include AbstractController::AssetPaths + include AbstractController::UrlFor cattr_reader :protected_instance_variables @@protected_instance_variables = [] helper ActionMailer::MailHelper include ActionMailer::OldApi - include ActionMailer::HideActions delegate :register_observer, :to => Mail delegate :register_interceptor, :to => Mail @@ -364,9 +363,8 @@ module ActionMailer #:nodoc: class << self def inherited(klass) - klass.with_hiding_actions do - super(klass) - end + super(klass) + klass.class_eval { @action_methods = nil } end def mailer_name @@ -732,9 +730,6 @@ module ActionMailer #:nodoc: container.add_part(part) end - class_attribute :default_url_options - self.default_url_options = {} - ActiveSupport.run_load_hooks(:action_mailer, self) end end diff --git a/actionmailer/lib/action_mailer/hide_actions.rb b/actionmailer/lib/action_mailer/hide_actions.rb deleted file mode 100644 index 876da95c3a..0000000000 --- a/actionmailer/lib/action_mailer/hide_actions.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'active_support/core_ext/class/attribute' - -module ActionMailer - # ActionController::HideActions adds the ability to prevent public methods on a controller - # to be called as actions. - module HideActions - extend ActiveSupport::Concern - - included do - class_attribute :hidden_actions - self.hidden_actions = Set.new.freeze - end - - private - - module ClassMethods - # Sets all of the actions passed in as hidden actions. - # - # ==== Parameters - # *args<#to_s>:: A list of actions - def hide_action(*args) - self.hidden_actions = hidden_actions.dup.merge(args.map(&:to_s)).freeze - end - - # Run block and add all the new action_methods to hidden_actions. - # This is used in inherited method. - def with_hiding_actions - yield - clear_action_methods! - hide_action(*action_methods) - clear_action_methods! - end - - def clear_action_methods! - @action_methods = nil - end - - # Overrides AbstractController::Base#action_methods to remove any methods - # that are listed as hidden methods. - def action_methods - @action_methods ||= super.reject { |name| hidden_actions.include?(name) } - end - end - end -end - diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index f8fc79936f..cc5878c88e 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -24,4 +24,5 @@ module AbstractController autoload :Translation autoload :AssetPaths autoload :ViewPaths + autoload :UrlFor end diff --git a/actionpack/lib/abstract_controller/url_for.rb b/actionpack/lib/abstract_controller/url_for.rb new file mode 100644 index 0000000000..2e9de22ecd --- /dev/null +++ b/actionpack/lib/abstract_controller/url_for.rb @@ -0,0 +1,28 @@ +module AbstractController + module UrlFor + extend ActiveSupport::Concern + + include ActionDispatch::Routing::UrlFor + + def _routes + raise "In order to use #url_for, you must include routing helpers explicitly. " \ + "For instance, `include Rails.application.routes.url_helpers" + end + + module ClassMethods + def _routes + nil + end + + def action_methods + @action_methods ||= begin + if _routes + super - _routes.named_routes.helper_names + else + super + end + end + end + end + end +end diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 1e34f21c77..85c6b0a9b5 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -2,7 +2,7 @@ module ActionController module UrlFor extend ActiveSupport::Concern - include ActionDispatch::Routing::UrlFor + include AbstractController::UrlFor def url_options options = {} @@ -16,18 +16,5 @@ module ActionController :_path_segments => request.symbolized_path_parameters ) end - - def _routes - raise "In order to use #url_for, you must include routing helpers explicitly. " \ - "For instance, `include Rails.application.routes.url_helpers" - end - - module ClassMethods - def action_methods - @action_methods ||= begin - super - _routes.named_routes.helper_names - end - end - end end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 6e7e133cc5..219fa37fcb 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -337,7 +337,12 @@ module ActionDispatch # Yes plz - JP included do routes.install_helpers(self) - singleton_class.send(:define_method, :_routes) { @_routes || routes } + singleton_class.send(:define_method, :_routes) { routes } + end + + def initialize(*) + @_routes = nil + super end define_method(:_routes) { @_routes || routes } From a7c6fe4c2a577bd7e9871aa4746d70c0984325db Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 3 Aug 2010 21:25:50 +0200 Subject: [PATCH 55/87] Rename isolated_engine_for to namespace --- railties/lib/rails/engine.rb | 6 +++--- railties/test/railties/engine_test.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 7f53e2dc72..13629e0de3 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -200,11 +200,11 @@ module Rails @endpoint end - def isolated_engine_for(mod) - _engine = self + def namespace(mod) + _railtie = self mod.singleton_class.instance_eval do define_method(:_railtie) do - _engine + _railtie end end end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 3d51d3cf2b..c845843095 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -338,11 +338,11 @@ module RailtiesTest assert_equal "/foo", response[2].body end - test "isolated engine should include only its own routes and helpers" do + test "namespaced engine should include only its own routes and helpers" do @plugin.write "lib/bukkits.rb", <<-RUBY module Bukkits class Engine < ::Rails::Engine - isolated_engine_for Bukkits + namespace Bukkits end end RUBY From db8a864e693034b5a0d03396a0c297d5557862ed Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 3 Aug 2010 22:36:12 +0200 Subject: [PATCH 56/87] Add table_name_prefix to Engine's namespace automatically --- railties/lib/rails/engine.rb | 8 ++++++++ railties/lib/rails/railtie.rb | 7 ++++++- railties/test/railties/engine_test.rb | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 13629e0de3..663feb4e32 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -201,11 +201,19 @@ module Rails end def namespace(mod) + # TODO: extract that into a module + engine_name(generate_railtie_name(mod)) + _railtie = self + name = engine_name mod.singleton_class.instance_eval do define_method(:_railtie) do _railtie end + + define_method(:table_name_prefix) do + "#{name}_" + end end end end diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index ab565b0cdc..09650789ac 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -167,8 +167,13 @@ module Rails def railtie_name(name = nil) @railtie_name = name.to_s if name - @railtie_name ||= ActiveSupport::Inflector.underscore(self.name).gsub("/", "_") + @railtie_name ||= generate_railtie_name(self.name) end + + protected + def generate_railtie_name(class_or_module) + ActiveSupport::Inflector.underscore(class_or_module).gsub("/", "_") + end end delegate :railtie_name, :to => "self.class" diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index c845843095..47a38d7aeb 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -408,6 +408,8 @@ module RailtiesTest boot_rails + assert_equal "bukkits_", Bukkits.table_name_prefix + assert_equal "bukkits", Bukkits::Engine.engine_name assert_equal Bukkits._railtie, Bukkits::Engine assert ::Bukkits::MyMailer.method_defined?(:foo_path) assert !::Bukkits::MyMailer.method_defined?(:bar_path) From 2e4e1e7d0ce9bd0e9ea116a05a4ffcd989541cbd Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 3 Aug 2010 22:54:56 +0200 Subject: [PATCH 57/87] Added documentation for namespaced Engine --- railties/lib/rails/engine.rb | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 663feb4e32..bf0d476e48 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -153,13 +153,45 @@ module Rails # to application's public directory. To simplify generating paths for assets, you can set asset_path # for an Engine: # - # class MyEngine::Engine < Rails::Engine - # config.asset_path = "/my_engine/%s" + # module MyEngine + # class Engine < Rails::Engine + # config.asset_path = "/my_engine/%s" + # end # end # # With such config, asset paths will be automatically modified inside Engine: # image_path("foo.jpg") #=> "/my_engine/images/foo.jpg" # + # == Namespaced Engine + # + # Normally, when you create controllers, helpers and models inside engine, they are treated + # as they would be created inside application. One of the cosequences of that is including + # application's helpers and url_helpers inside controller. Sometimes, especially when your + # engine provides its own routes, you don't want that. To isolate engine's stuff from application + # you can use namespace method: + # + # module MyEngine + # class Engine < Rails::Engine + # namespace MyEngine + # end + # end + # + # With such Engine, everything that is inside MyEngine module, will be isolated from application. + # + # Consider such controller: + # + # module MyEngine + # class FooController < ActionController::Base + # end + # end + # + # If engine is marked as namespaced, FooController has access only to helpers from engine and + # url_helpers from MyEngine::Engine.routes. + # + # Additionaly namespaced engine will set its name according to namespace, so in that case: + # MyEngine::Engine.engine_name #=> "my_engine" + # and it will set MyEngine.table_name_prefix to "my_engine_" + # class Engine < Railtie autoload :Configurable, "rails/engine/configurable" autoload :Configuration, "rails/engine/configuration" From 1a161c75eda50d58fe2a9c5bf3aee947ed17f5ea Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 3 Aug 2010 23:52:58 +0200 Subject: [PATCH 58/87] Document engine_name --- railties/lib/rails/engine.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index bf0d476e48..5b324663be 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -162,6 +162,22 @@ module Rails # With such config, asset paths will be automatically modified inside Engine: # image_path("foo.jpg") #=> "/my_engine/images/foo.jpg" # + # == Engine name + # + # There are some places where engine's name is used. + # * routes: when you mount engine with mount(MyEngine::Engine => '/my_engine'), it's used as default :as option + # * migrations: when you copy engine's migrations, they will be decorated with suffix based on engine_name, for example: + # 2010010203121314_create_users.my_engine.rb + # + # Engine name is set by default based on class name. For MyEngine::Engine it will be my_engine_engine. + # You can change it manually it manually using engine_name method: + # + # module MyEngine + # class Engine < Rails::Engine + # engine_name "my_engine" + # end + # end + # # == Namespaced Engine # # Normally, when you create controllers, helpers and models inside engine, they are treated From 434139f89fd2ec550b8c5ab1309e5a8af06142d7 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 4 Aug 2010 00:03:26 +0200 Subject: [PATCH 59/87] Documented mounted helpers --- railties/lib/rails/engine.rb | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 5b324663be..1b0ab07b11 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -208,6 +208,35 @@ module Rails # MyEngine::Engine.engine_name #=> "my_engine" # and it will set MyEngine.table_name_prefix to "my_engine_" # + # == Using Engine's routes outside Engine + # + # Since you can mount engine inside application's routes now, you do not have direct access to engine's + # url_helpers inside application. When you mount Engine in application's routes special helper is + # created to allow doing that. Consider such scenario: + # + # # APP/config/routes.rb + # MyApplication::Application.routes.draw do + # mount MyEngine::Engine => "/my_engine", :as => "my_engine" + # match "/foo" => "foo#index" + # end + # + # Now, you can use my_engine helper: + # + # class FooController < ApplicationController + # def index + # my_engine.root_url #=> /my_engine/ + # end + # end + # + # There is also 'app' helper that gives you access to application's routes inside Engine: + # + # module MyEngine + # class BarController + # app.foo_path #=> /foo + # end + # end + # + # Note that :as option takes engine_name as default, so most of the time you can ommit it. class Engine < Railtie autoload :Configurable, "rails/engine/configurable" autoload :Configuration, "rails/engine/configuration" From f3c703a32f6c7833705e46b8e14f172330a1c916 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 4 Aug 2010 16:20:56 +0200 Subject: [PATCH 60/87] Refactor RoutesProxy to avoid using _with_routes in helpers --- actionpack/lib/action_dispatch/routing/route_set.rb | 2 +- actionpack/lib/action_dispatch/routing/url_for.rb | 4 ++++ actionpack/lib/action_view/helpers/url_helper.rb | 8 +++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 219fa37fcb..363bcbd2b0 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -307,7 +307,7 @@ module ActionDispatch routes = self MountedHelpers.class_eval do define_method "_#{name}" do - RoutesProxy.new(routes, self) + RoutesProxy.new(routes, self._routes_context) end end diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 19db730b6a..e836cf7c8e 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -141,6 +141,10 @@ module ActionDispatch ensure @_routes = old_routes end + + def _routes_context + self + end end end end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index f21cffea26..555be6ed2b 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -22,6 +22,10 @@ module ActionView include ActionDispatch::Routing::UrlFor include TagHelper + def _routes_context + controller + end + # Need to map default url options to controller one. # def default_url_options(*args) #:nodoc: # controller.send(:default_url_options, *args) @@ -29,9 +33,7 @@ module ActionView # def url_options return super unless controller.respond_to?(:url_options) - controller.send(:_with_routes, _routes) do - controller.url_options - end + controller.url_options end # Returns the URL for the set of +options+ provided. This takes the From 56ef192374f7dc5b21120bbe94cacf852b33be54 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 4 Aug 2010 17:15:34 +0200 Subject: [PATCH 61/87] ActionMailer should not depend on ActionDispatch --- actionmailer/lib/action_mailer.rb | 1 - actionmailer/lib/action_mailer/base.rb | 3 +-- actionmailer/lib/action_mailer/railtie.rb | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index 706ba74c2d..05ba12197a 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -26,7 +26,6 @@ $:.unshift(actionpack_path) if File.directory?(actionpack_path) && !$:.include?( require 'abstract_controller' require 'action_view' -require 'action_dispatch' # Common Active Support usage in Action Mailer require 'active_support/core_ext/class' diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index f7acb36341..b70121d544 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -340,7 +340,6 @@ module ActionMailer #:nodoc: include AbstractController::Helpers include AbstractController::Translation include AbstractController::AssetPaths - include AbstractController::UrlFor cattr_reader :protected_instance_variables @@protected_instance_variables = [] @@ -364,7 +363,7 @@ module ActionMailer #:nodoc: class << self def inherited(klass) super(klass) - klass.class_eval { @action_methods = nil } + klass.clear_action_methods! end def mailer_name diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index 9468fd03e2..a2b00addc9 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -19,6 +19,7 @@ module ActionMailer options.stylesheets_dir ||= paths.public.stylesheets.to_a.first ActiveSupport.on_load(:action_mailer) do + include AbstractController::UrlFor extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) include app.routes.mounted_helpers(:app) options.each { |k,v| send("#{k}=", v) } From d81267727770198f4aa0cefede042c8d0c638c59 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 4 Aug 2010 20:25:21 +0200 Subject: [PATCH 62/87] We don't need to clear action_methods on inherited hook as they are cleaned on method_added hook --- actionmailer/lib/action_mailer/base.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index b70121d544..6ae3940e6d 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -361,11 +361,6 @@ module ActionMailer #:nodoc: }.freeze class << self - def inherited(klass) - super(klass) - klass.clear_action_methods! - end - def mailer_name @mailer_name ||= name.underscore end From 8284fd38551c00c30cf89fa22d1afd503a08c516 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 4 Aug 2010 22:22:06 +0200 Subject: [PATCH 63/87] Get rid of static_paths method and instead configure paths for ActionDispatch::Static in initializers --- railties/lib/rails/application.rb | 18 +----------------- .../lib/rails/application/configuration.rb | 4 ++++ railties/lib/rails/engine.rb | 7 +++++++ railties/lib/rails/engine/configuration.rb | 4 ++++ railties/lib/rails/railtie/configuration.rb | 8 ++++++++ railties/test/railties/engine_test.rb | 5 +++++ 6 files changed, 29 insertions(+), 17 deletions(-) diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index c403863007..8631a5df3e 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -145,25 +145,9 @@ module Rails protected - def static_paths - @static_paths ||= begin - static_paths = ActiveSupport::OrderedHash.new - static_paths["/"] = paths.public.to_a.first - - railties.all do |railtie| - if railtie.config.respond_to?(:asset_path) && railtie.config.asset_path - public_path = railtie.config.paths.public.to_a.first - static_paths[railtie.config.asset_path % ""] = public_path if File.exists?(public_path) - end - end - - static_paths - end - end - def default_middleware_stack ActionDispatch::MiddlewareStack.new.tap do |middleware| - middleware.use ::ActionDispatch::Static, static_paths if config.serve_static_assets + middleware.use ::ActionDispatch::Static, config.static_asset_paths if config.serve_static_assets middleware.use ::Rack::Lock if !config.allow_concurrency middleware.use ::Rack::Runtime middleware.use ::Rails::Rack::Logger diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 29fa9d14eb..477bbbc1e7 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -37,6 +37,10 @@ module Rails super(value) end + def compiled_asset_path + "/" + end + def encoding=(value) @encoding = value if "ruby".encoding_aware? diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 1b0ab07b11..3d4bbca5d7 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -428,6 +428,13 @@ module Rails initializer :default_asset_path do config.asset_path = "/#{engine_name}%s" unless config.asset_path end + + initializer :append_asset_paths do + public_path = config.paths.public.to_a.first + if config.compiled_asset_path && File.exist?(public_path) + config.static_asset_paths[config.compiled_asset_path] = public_path + end + end protected def find_root_with_flag(flag, default=nil) root_path = self.class.called_from diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 9aac9c81d0..bce1cd6580 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -55,6 +55,10 @@ module Rails def autoload_paths @autoload_paths ||= paths.autoload_paths end + + def compiled_asset_path + asset_path % "" if asset_path + end end end end diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index 96d9bd0da2..f09e3940cc 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -5,6 +5,7 @@ module Rails class Configuration def initialize @@options ||= {} + @@static_asset_paths = ActiveSupport::OrderedHash.new end # This allows you to modify the application's middlewares from Engines. @@ -65,6 +66,13 @@ module Rails super || @@options.key?(name.to_sym) end + # static_asset_paths is a Hash containing asset_paths + # with associated public folders, like: + # { "/" => "/app/public", "/my_engine" => "app/engines/my_engine/public" } + def static_asset_paths + @@static_asset_paths + end + private def method_missing(name, *args, &blk) diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 47a38d7aeb..cda1d12fa8 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -287,6 +287,7 @@ module RailtiesTest @plugin.write "public/bukkits.html", "/bukkits/bukkits.html" app_file "public/app.html", "/app.html" + app_file "public/bukkits/file_from_app.html", "/bukkits/file_from_app.html" boot_rails @@ -297,6 +298,10 @@ module RailtiesTest env = Rack::MockRequest.env_for("/bukkits/bukkits.html") response = Rails.application.call(env) assert_equal response[2].path, File.join(@plugin.path, "public/bukkits.html") + + env = Rack::MockRequest.env_for("/bukkits/file_from_app.html") + response = Rails.application.call(env) + assert_equal response[2].path, File.join(app_path, "public/bukkits/file_from_app.html") end test "shared engine should include application's helpers" do From 8fb9df535e9fcf4c117ffd3254027e0fe2425cb7 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 4 Aug 2010 23:00:33 +0200 Subject: [PATCH 64/87] Modified polymorphic_url to check for model's namespace This change allows using namespaced models with polymorphic_url, in the way that you would use them without namespace. Let's say that you have Blog::Post model in namespaced Engine. When you use polymorphic_path with Blog::Post instances, like in form_for(@post), it will look for blog_posts_path named url helper. As we are inside Blog::Engine, it's annoying to always use the prefix. With this commit, blog_ prefix will be removed and posts_path will be called. --- .../routing/polymorphic_routes.rb | 4 +++ railties/test/railties/engine_test.rb | 34 ++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 15ee7c8051..037b94b577 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -111,6 +111,10 @@ module ActionDispatch args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options end + if namespace = record.class.parents.detect { |n| n.respond_to?(:_railtie) } + named_route.sub!(/#{namespace._railtie.railtie_name}_/, '') + end + url_for _routes.url_helpers.__send__("hash_for_#{named_route}", *args) end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index cda1d12fa8..fff925404d 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -352,6 +352,18 @@ module RailtiesTest end RUBY + @plugin.write "app/models/bukkits/post.rb", <<-RUBY + module Bukkits + class Post + extend ActiveModel::Naming + + def to_param + "1" + end + end + end + RUBY + app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do match "/bar" => "bar#index", :as => "bar" @@ -361,10 +373,14 @@ module RailtiesTest @plugin.write "config/routes.rb", <<-RUBY Bukkits::Engine.routes.draw do - match "/foo" => "bukkits/foo#index", :as => "foo" - match "/foo/show" => "bukkits/foo#show" - match "/from_app" => "bukkits/foo#from_app" - match "/routes_helpers_in_view" => "bukkits/foo#routes_helpers_in_view" + namespace(:bukkits, :path => nil, :shallow_path => nil, :as => nil) do + match "/foo" => "foo#index", :as => "foo" + match "/foo/show" => "foo#show" + match "/from_app" => "foo#from_app" + match "/routes_helpers_in_view" => "foo#routes_helpers_in_view" + match "/polymorphic_path_without_namespace" => "foo#polymorphic_path_without_namespace" + resources :posts + end end RUBY @@ -401,6 +417,10 @@ module RailtiesTest def routes_helpers_in_view render :inline => "<%= foo_path %>, <%= app.bar_path %>" end + + def polymorphic_path_without_namespace + render :text => polymorphic_path(Post.new) + end end RUBY @@ -411,6 +431,8 @@ module RailtiesTest end RUBY + add_to_config("config.action_dispatch.show_exceptions = false") + boot_rails assert_equal "bukkits_", Bukkits.table_name_prefix @@ -434,6 +456,10 @@ module RailtiesTest env = Rack::MockRequest.env_for("/bukkits/routes_helpers_in_view") response = AppTemplate::Application.call(env) assert_equal "/bukkits/foo, /bar", response[2].body + + env = Rack::MockRequest.env_for("/bukkits/polymorphic_path_without_namespace") + response = AppTemplate::Application.call(env) + assert_equal "/bukkits/posts/1", response[2].body end end end From e5af8b7d85abb94f21f4e873c1c267e27be2aad8 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 6 Aug 2010 16:34:48 +0200 Subject: [PATCH 65/87] Moved ActionMailer and ActionController railties options to inherited hook This change is needed, because we must take namespace into account and if controller's/mailer's class is namespaced, engine's paths should be set instead of application's ones. The nice side effect of this is removing unneeded logic in ActionController::Base.inherited - now the helpers_path should be set correctly even for engine's controllers, so helper(:all) will always include correct helpers. --- actionmailer/lib/action_mailer/railtie.rb | 9 ++---- .../lib/action_mailer/railties/paths.rb | 26 +++++++++++++++++ actionpack/lib/action_controller/base.rb | 6 +--- actionpack/lib/action_controller/railtie.rb | 14 ++-------- .../lib/action_controller/railties/paths.rb | 28 +++++++++++++++++++ 5 files changed, 61 insertions(+), 22 deletions(-) create mode 100644 actionmailer/lib/action_mailer/railties/paths.rb create mode 100644 actionpack/lib/action_controller/railties/paths.rb diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index a2b00addc9..3c1f045e79 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -1,6 +1,7 @@ require "action_mailer" require "rails" require "abstract_controller/railties/routes_helpers" +require "action_mailer/railties/paths" module ActionMailer class Railtie < Rails::Railtie @@ -11,17 +12,13 @@ module ActionMailer end initializer "action_mailer.set_configs" do |app| - paths = app.config.paths options = app.config.action_mailer - options.assets_dir ||= paths.public.to_a.first - options.javascripts_dir ||= paths.public.javascripts.to_a.first - options.stylesheets_dir ||= paths.public.stylesheets.to_a.first - ActiveSupport.on_load(:action_mailer) do include AbstractController::UrlFor - extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) include app.routes.mounted_helpers(:app) + extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) + extend ::ActionMailer::Railties::Paths.with(app) options.each { |k,v| send("#{k}=", v) } end end diff --git a/actionmailer/lib/action_mailer/railties/paths.rb b/actionmailer/lib/action_mailer/railties/paths.rb new file mode 100644 index 0000000000..fa9188be77 --- /dev/null +++ b/actionmailer/lib/action_mailer/railties/paths.rb @@ -0,0 +1,26 @@ +module ActionMailer + module Railties + module Paths + def self.with(_app) + Module.new do + define_method(:inherited) do |klass| + super(klass) + if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) } + app = namespace._railtie + else + app = _app + end + + paths = app.config.paths + options = app.config.action_mailer + + options.assets_dir ||= paths.public.to_a.first + options.javascripts_dir ||= paths.public.javascripts.to_a.first + options.stylesheets_dir ||= paths.public.stylesheets.to_a.first + options.each { |k,v| klass.send("#{k}=", v) } + end + end + end + end + end +end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 3560ac5b8c..1953e1869f 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -223,11 +223,7 @@ module ActionController def self.inherited(klass) super - if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) } - klass.helper(all_helpers_from_path(namespace._railtie.config.paths.app.helpers.to_a)) - else - klass.helper :all if klass.superclass == ActionController::Base - end + klass.helper :all if klass.superclass == ActionController::Base end ActiveSupport.run_load_hooks(:action_controller, self) diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 4b5a897b90..2271a51e4e 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -5,6 +5,7 @@ require "action_view/railtie" require "active_support/deprecation/proxy_wrappers" require "active_support/deprecation" require "abstract_controller/railties/routes_helpers" +require "action_controller/railties/paths" module ActionController class Railtie < Rails::Railtie @@ -41,19 +42,10 @@ module ActionController end initializer "action_controller.set_configs" do |app| - paths = app.config.paths - options = app.config.action_controller - - options.assets_dir ||= paths.public.to_a.first - options.javascripts_dir ||= paths.public.javascripts.to_a.first - options.stylesheets_dir ||= paths.public.stylesheets.to_a.first - options.page_cache_directory ||= paths.public.to_a.first - options.helpers_path ||= paths.app.helpers.to_a - ActiveSupport.on_load(:action_controller) do - extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) include app.routes.mounted_helpers(:app) - options.each { |k,v| send("#{k}=", v) } + extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) + extend ::ActionController::Railties::Paths.with(app) end end diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb new file mode 100644 index 0000000000..095beb7a2f --- /dev/null +++ b/actionpack/lib/action_controller/railties/paths.rb @@ -0,0 +1,28 @@ +module ActionController + module Railties + module Paths + def self.with(_app) + Module.new do + define_method(:inherited) do |klass| + super(klass) + if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) } + app = namespace._railtie + else + app = _app + end + + paths = app.config.paths + options = app.config.action_controller + + options.assets_dir ||= paths.public.to_a.first + options.javascripts_dir ||= paths.public.javascripts.to_a.first + options.stylesheets_dir ||= paths.public.stylesheets.to_a.first + options.page_cache_directory ||= paths.public.to_a.first + options.helpers_path ||= paths.app.helpers.to_a + options.each { |k,v| klass.send("#{k}=", v) } + end + end + end + end + end +end From 00874a2009ce209d0c3a3cc2bf6c26b1bb15f3e5 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Sun, 22 Aug 2010 18:06:03 +0200 Subject: [PATCH 66/87] This was used only to clear warning in ActionMailer tests, it shouldn't be done like that --- actionpack/lib/action_dispatch/routing/route_set.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 363bcbd2b0..c09c1fef6f 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -340,11 +340,6 @@ module ActionDispatch singleton_class.send(:define_method, :_routes) { routes } end - def initialize(*) - @_routes = nil - super - end - define_method(:_routes) { @_routes || routes } end From 8ec2175aee93ecfd928de67c0a125bccc5e1c152 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 23 Aug 2010 14:02:05 +0200 Subject: [PATCH 67/87] Added more tests for polymorphic_url with namespaced models and implemented missing use cases --- .../routing/polymorphic_routes.rb | 15 +++-- .../activerecord/polymorphic_routes_test.rb | 56 +++++++++++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 037b94b577..517936bc96 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -111,10 +111,6 @@ module ActionDispatch args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options end - if namespace = record.class.parents.detect { |n| n.respond_to?(:_railtie) } - named_route.sub!(/#{namespace._railtie.railtie_name}_/, '') - end - url_for _routes.url_helpers.__send__("hash_for_#{named_route}", *args) end @@ -159,7 +155,8 @@ module ActionDispatch if parent.is_a?(Symbol) || parent.is_a?(String) parent else - ActiveModel::Naming.plural(parent).singularize + str = ActiveModel::Naming.plural(parent).singularize + remove_namespace(str, parent) end end end @@ -168,6 +165,7 @@ module ActionDispatch route << record else route << ActiveModel::Naming.plural(record) + remove_namespace(route, record) route = [route.join("_").singularize] if inflection == :singular route << "index" if ActiveModel::Naming.uncountable?(record) && inflection == :plural end @@ -177,6 +175,13 @@ module ActionDispatch action_prefix(options) + route.join("_") end + def remove_namespace(string, parent) + if namespace = parent.class.parents.detect { |n| n.respond_to?(:_railtie) } + string.sub!(/#{namespace._railtie.railtie_name}_/, '') + end + string + end + def extract_record(record_or_hash_or_array) case record_or_hash_or_array when Array; record_or_hash_or_array.last diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index 90a1ef982c..94b8a8c7c3 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -25,6 +25,22 @@ class Series < ActiveRecord::Base set_table_name 'projects' end +module Blog + class Post < ActiveRecord::Base + set_table_name 'projects' + end + + class Blog < ActiveRecord::Base + set_table_name 'projects' + end + + def self._railtie + o = Object.new + def o.railtie_name; "blog" end + o + end +end + class PolymorphicRoutesTest < ActionController::TestCase include SharedTestRoutes.url_helpers self.default_url_options[:host] = 'example.com' @@ -37,6 +53,30 @@ class PolymorphicRoutesTest < ActionController::TestCase @tax = Tax.new @fax = Fax.new @series = Series.new + @blog_post = Blog::Post.new + @blog_blog = Blog::Blog.new + end + + def test_namespaced_model + with_namespaced_routes(:blog) do + @blog_post.save + assert_equal "http://example.com/posts/#{@blog_post.id}", polymorphic_url(@blog_post) + end + end + + def test_namespaced_model_with_name_the_same_as_namespace + with_namespaced_routes(:blog) do + @blog_blog.save + assert_equal "http://example.com/blogs/#{@blog_blog.id}", polymorphic_url(@blog_blog) + end + end + + def test_namespaced_model_with_nested_resources + with_namespaced_routes(:blog) do + @blog_post.save + @blog_blog.save + assert_equal "http://example.com/blogs/#{@blog_blog.id}/posts/#{@blog_post.id}", polymorphic_url([@blog_blog, @blog_post]) + end end def test_with_record @@ -385,6 +425,22 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def with_namespaced_routes(name) + with_routing do |set| + set.draw do + namespace(name, :shallow_path => nil, :path => nil, :as => nil) do + resources :blogs do + resources :posts + end + resources :posts + end + end + + self.class.send(:include, @routes.url_helpers) + yield + end + end + def with_test_routes(options = {}) with_routing do |set| set.draw do |map| From 98ab4ded376c3d04540bdbdfe6dbbf88c0738701 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 23 Aug 2010 18:31:29 +0200 Subject: [PATCH 68/87] Set only helpers_path on inherited hook in action_controller/railtie.rb and use helper(:all) just after that --- actionmailer/lib/action_mailer/railtie.rb | 9 ++++--- .../lib/action_mailer/railties/paths.rb | 26 ------------------- actionpack/lib/action_controller/base.rb | 5 ---- actionpack/lib/action_controller/railtie.rb | 9 +++++++ .../lib/action_controller/railties/paths.rb | 6 ++--- 5 files changed, 17 insertions(+), 38 deletions(-) delete mode 100644 actionmailer/lib/action_mailer/railties/paths.rb diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index 3c1f045e79..a2b00addc9 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -1,7 +1,6 @@ require "action_mailer" require "rails" require "abstract_controller/railties/routes_helpers" -require "action_mailer/railties/paths" module ActionMailer class Railtie < Rails::Railtie @@ -12,13 +11,17 @@ module ActionMailer end initializer "action_mailer.set_configs" do |app| + paths = app.config.paths options = app.config.action_mailer + options.assets_dir ||= paths.public.to_a.first + options.javascripts_dir ||= paths.public.javascripts.to_a.first + options.stylesheets_dir ||= paths.public.stylesheets.to_a.first + ActiveSupport.on_load(:action_mailer) do include AbstractController::UrlFor - include app.routes.mounted_helpers(:app) extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) - extend ::ActionMailer::Railties::Paths.with(app) + include app.routes.mounted_helpers(:app) options.each { |k,v| send("#{k}=", v) } end end diff --git a/actionmailer/lib/action_mailer/railties/paths.rb b/actionmailer/lib/action_mailer/railties/paths.rb deleted file mode 100644 index fa9188be77..0000000000 --- a/actionmailer/lib/action_mailer/railties/paths.rb +++ /dev/null @@ -1,26 +0,0 @@ -module ActionMailer - module Railties - module Paths - def self.with(_app) - Module.new do - define_method(:inherited) do |klass| - super(klass) - if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) } - app = namespace._railtie - else - app = _app - end - - paths = app.config.paths - options = app.config.action_mailer - - options.assets_dir ||= paths.public.to_a.first - options.javascripts_dir ||= paths.public.javascripts.to_a.first - options.stylesheets_dir ||= paths.public.stylesheets.to_a.first - options.each { |k,v| klass.send("#{k}=", v) } - end - end - end - end - end -end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 1953e1869f..b37bc02127 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -221,11 +221,6 @@ module ActionController # Rails 2.x compatibility include ActionController::Compatibility - def self.inherited(klass) - super - klass.helper :all if klass.superclass == ActionController::Base - end - ActiveSupport.run_load_hooks(:action_controller, self) end end diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 2271a51e4e..0cb4041855 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -42,10 +42,19 @@ module ActionController end initializer "action_controller.set_configs" do |app| + paths = app.config.paths + options = app.config.action_controller + + options.assets_dir ||= paths.public.to_a.first + options.javascripts_dir ||= paths.public.javascripts.to_a.first + options.stylesheets_dir ||= paths.public.stylesheets.to_a.first + options.page_cache_directory ||= paths.public.to_a.first + ActiveSupport.on_load(:action_controller) do include app.routes.mounted_helpers(:app) extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) extend ::ActionController::Railties::Paths.with(app) + options.each { |k,v| send("#{k}=", v) } end end diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb index 095beb7a2f..81d03f5e73 100644 --- a/actionpack/lib/action_controller/railties/paths.rb +++ b/actionpack/lib/action_controller/railties/paths.rb @@ -14,12 +14,10 @@ module ActionController paths = app.config.paths options = app.config.action_controller - options.assets_dir ||= paths.public.to_a.first - options.javascripts_dir ||= paths.public.javascripts.to_a.first - options.stylesheets_dir ||= paths.public.stylesheets.to_a.first - options.page_cache_directory ||= paths.public.to_a.first options.helpers_path ||= paths.app.helpers.to_a options.each { |k,v| klass.send("#{k}=", v) } + + klass.helper :all end end end From e35c2043b135a95104e3eeb3e12cbcde541fa1b4 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 23 Aug 2010 19:25:04 +0200 Subject: [PATCH 69/87] Include all helpers from non-namespaced engines --- .../lib/action_controller/railties/paths.rb | 12 +++--------- railties/lib/rails/engine.rb | 18 ++++++++++++++++-- railties/lib/rails/engine/configuration.rb | 1 + railties/test/railties/engine_test.rb | 19 ++++++++++++++++++- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb index 81d03f5e73..e1b318b566 100644 --- a/actionpack/lib/action_controller/railties/paths.rb +++ b/actionpack/lib/action_controller/railties/paths.rb @@ -1,22 +1,16 @@ module ActionController module Railties module Paths - def self.with(_app) + def self.with(app) Module.new do define_method(:inherited) do |klass| super(klass) if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) } - app = namespace._railtie + klass.helpers_path = namespace._railtie.config.paths.app.helpers.to_a else - app = _app + klass.helpers_path = app.config.helpers_paths end - paths = app.config.paths - options = app.config.action_controller - - options.helpers_path ||= paths.app.helpers.to_a - options.each { |k,v| klass.send("#{k}=", v) } - klass.helper :all end end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 3d4bbca5d7..bc3014adaa 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -242,7 +242,7 @@ module Rails autoload :Configuration, "rails/engine/configuration" class << self - attr_accessor :called_from + attr_accessor :called_from, :namespaced alias :engine_name :railtie_name def inherited(base) @@ -292,11 +292,17 @@ module Rails "#{name}_" end end + + self.namespaced = true + end + + def namespaced? + !!namespaced end end delegate :middleware, :root, :paths, :to => :config - delegate :engine_name, :to => "self.class" + delegate :engine_name, :namespaced?, :to => "self.class" def load_tasks super @@ -435,6 +441,14 @@ module Rails config.static_asset_paths[config.compiled_asset_path] = public_path end end + + initializer :prepend_helpers_path do + unless namespaced? + config.helpers_paths = [] unless config.respond_to?(:helpers_paths) + config.helpers_paths = config.paths.app.helpers.to_a + config.helpers_paths + end + end + protected def find_root_with_flag(flag, default=nil) root_path = self.class.called_from diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index bce1cd6580..3ac8911ba8 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -11,6 +11,7 @@ module Rails super() @root = root @middleware = Rails::Configuration::MiddlewareStackProxy.new + @helpers_paths = [] end def paths diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index fff925404d..6deca9cf1e 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -304,11 +304,12 @@ module RailtiesTest assert_equal response[2].path, File.join(app_path, "public/bukkits/file_from_app.html") end - test "shared engine should include application's helpers" do + test "shared engine should include application's helpers and own helpers" do app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do match "/foo" => "bukkits/foo#index", :as => "foo" match "/foo/show" => "bukkits/foo#show" + match "/foo/bar" => "bukkits/foo#bar" end RUBY @@ -320,6 +321,14 @@ module RailtiesTest end RUBY + @plugin.write "app/helpers/bar_helper.rb", <<-RUBY + module BarHelper + def bar + "It's a bar." + end + end + RUBY + @plugin.write "app/controllers/bukkits/foo_controller.rb", <<-RUBY class Bukkits::FooController < ActionController::Base def index @@ -329,6 +338,10 @@ module RailtiesTest def show render :text => foo_path end + + def bar + render :inline => "<%= bar %>" + end end RUBY @@ -341,6 +354,10 @@ module RailtiesTest env = Rack::MockRequest.env_for("/foo/show") response = Rails.application.call(env) assert_equal "/foo", response[2].body + + env = Rack::MockRequest.env_for("/foo/bar") + response = Rails.application.call(env) + assert_equal "It's a bar.", response[2].body end test "namespaced engine should include only its own routes and helpers" do From b1c66f060bba22d16abf6d24d3df762c240e367c Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 25 Aug 2010 13:41:56 +0200 Subject: [PATCH 70/87] Move RoutesProxy to separate file --- actionpack/lib/action_dispatch/routing.rb | 1 + .../lib/action_dispatch/routing/route_set.rb | 32 ----------------- .../action_dispatch/routing/routes_proxy.rb | 35 +++++++++++++++++++ 3 files changed, 36 insertions(+), 32 deletions(-) create mode 100644 actionpack/lib/action_dispatch/routing/routes_proxy.rb diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 0b9689dc88..b2b0f4c08e 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -268,6 +268,7 @@ module ActionDispatch autoload :Mapper, 'action_dispatch/routing/mapper' autoload :Route, 'action_dispatch/routing/route' autoload :RouteSet, 'action_dispatch/routing/route_set' + autoload :RoutesProxy, 'action_dispatch/routing/routes_proxy' autoload :UrlFor, 'action_dispatch/routing/url_for' autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes' diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index c09c1fef6f..0d83ca956b 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -261,38 +261,6 @@ module ActionDispatch named_routes.install(destinations, regenerate_code) end - class RoutesProxy - include ActionDispatch::Routing::UrlFor - - attr_accessor :scope, :routes - alias :_routes :routes - - def initialize(routes, scope) - @routes, @scope = routes, scope - end - - def url_options - scope.send(:_with_routes, routes) do - scope.url_options - end - end - - def method_missing(method, *args) - if routes.url_helpers.respond_to?(method) - self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{method}(*args) - options = args.extract_options! - args << url_options.merge((options || {}).symbolize_keys) - routes.url_helpers.#{method}(*args) - end - RUBY - send(method, *args) - else - super - end - end - end - module MountedHelpers end diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb new file mode 100644 index 0000000000..f7d5f6397d --- /dev/null +++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb @@ -0,0 +1,35 @@ +module ActionDispatch + module Routing + class RoutesProxy #:nodoc: + include ActionDispatch::Routing::UrlFor + + attr_accessor :scope, :routes + alias :_routes :routes + + def initialize(routes, scope) + @routes, @scope = routes, scope + end + + def url_options + scope.send(:_with_routes, routes) do + scope.url_options + end + end + + def method_missing(method, *args) + if routes.url_helpers.respond_to?(method) + self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(*args) + options = args.extract_options! + args << url_options.merge((options || {}).symbolize_keys) + routes.url_helpers.#{method}(*args) + end + RUBY + send(method, *args) + else + super + end + end + end + end +end From 16dcaf83681d9d84c26cfab23249a6f069ca5c08 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 25 Aug 2010 15:08:45 +0200 Subject: [PATCH 71/87] Updated tests to use scope(:module => :engine_name) instead of namespace and updated mounted engine tests to actually use the namespacing --- railties/test/railties/engine_test.rb | 2 +- railties/test/railties/mounted_engine_test.rb | 45 ++++++++++--------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 6deca9cf1e..56f9eae8ca 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -390,7 +390,7 @@ module RailtiesTest @plugin.write "config/routes.rb", <<-RUBY Bukkits::Engine.routes.draw do - namespace(:bukkits, :path => nil, :shallow_path => nil, :as => nil) do + scope(:module => :bukkits) do match "/foo" => "foo#index", :as => "foo" match "/foo/show" => "foo#show" match "/from_app" => "foo#from_app" diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index 174ccb4b4b..be5bb30a9a 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -19,7 +19,7 @@ module ApplicationTests match "/engine_route_in_view" => "application_generating#engine_route_in_view" match "/url_for_engine_route" => "application_generating#url_for_engine_route" scope "/:user", :user => "anonymous" do - mount Blog::Engine => "/blog", :as => "blog_engine" + mount Blog::Engine => "/blog" end root :to => 'main#index' end @@ -28,34 +28,39 @@ module ApplicationTests @plugin.write "lib/blog.rb", <<-RUBY module Blog class Engine < ::Rails::Engine + namespace(Blog) end end RUBY @plugin.write "config/routes.rb", <<-RUBY Blog::Engine.routes.draw do - resources :posts do - get :generate_application_route - get :application_route_in_view + scope(:module => :blog) do + resources :posts do + get :generate_application_route + get :application_route_in_view + end end end RUBY - @plugin.write "app/controllers/posts_controller.rb", <<-RUBY - class PostsController < ActionController::Base - def index - render :text => blog_engine.post_path(1) - end + @plugin.write "app/controllers/blog/posts_controller.rb", <<-RUBY + module Blog + class PostsController < ActionController::Base + def index + render :text => blog.post_path(1) + end - def generate_application_route - path = app.url_for(:controller => "main", - :action => "index", - :only_path => true) - render :text => path - end + def generate_application_route + path = app.url_for(:controller => "/main", + :action => "index", + :only_path => true) + render :text => path + end - def application_route_in_view - render :inline => "<%= app.root_path %>" + def application_route_in_view + render :inline => "<%= app.root_path %>" + end end end RUBY @@ -63,15 +68,15 @@ module ApplicationTests app_file "app/controllers/application_generating_controller.rb", <<-RUBY class ApplicationGeneratingController < ActionController::Base def engine_route - render :text => blog_engine.posts_path + render :text => blog.posts_path end def engine_route_in_view - render :inline => "<%= blog_engine.posts_path %>" + render :inline => "<%= blog.posts_path %>" end def url_for_engine_route - render :text => blog_engine.url_for(:controller => "posts", :action => "index", :user => "john", :only_path => true) + render :text => blog.url_for(:controller => "blog/posts", :action => "index", :user => "john", :only_path => true) end end RUBY From 613cbe1f0075eed7ebf1ac521e99f652f6891330 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 25 Aug 2010 15:42:24 +0200 Subject: [PATCH 72/87] Add possibility to explicitly call engine's routes through polymorphic_routes, for example: polymorphic_url([blog, @post]) --- .../routing/polymorphic_routes.rb | 21 ++++++++++++- .../activerecord/polymorphic_routes_test.rb | 8 +++++ railties/test/railties/mounted_engine_test.rb | 30 +++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 517936bc96..e8ba1de40e 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -42,6 +42,18 @@ module ActionDispatch # # edit_polymorphic_path(@post) # => "/posts/1/edit" # polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf" + # + # == Using with mounted engines + # + # If you use mounted engine, there is a possibility that you will need to use + # polymorphic_url pointing at engine's routes. To do that, just pass proxy used + # to reach engine's routes as a first argument: + # + # For example: + # + # polymorphic_url([blog, @post]) # it will call blog.post_path(@post) + # form_for([blog, @post]) # => "/blog/posts/1 + # module PolymorphicRoutes # Constructs a call to a named RESTful route for the given record and returns the # resulting URL string. For example: @@ -78,6 +90,9 @@ module ActionDispatch def polymorphic_url(record_or_hash_or_array, options = {}) if record_or_hash_or_array.kind_of?(Array) record_or_hash_or_array = record_or_hash_or_array.compact + if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy) + proxy = record_or_hash_or_array.shift + end record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1 end @@ -111,7 +126,11 @@ module ActionDispatch args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options end - url_for _routes.url_helpers.__send__("hash_for_#{named_route}", *args) + if proxy + proxy.send(named_route, *args) + else + url_for _routes.url_helpers.__send__("hash_for_#{named_route}", *args) + end end # Returns the path component of a URL for the given record. It uses diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index 94b8a8c7c3..23c9fb839e 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -57,6 +57,14 @@ class PolymorphicRoutesTest < ActionController::TestCase @blog_blog = Blog::Blog.new end + def test_passing_routes_proxy + with_namespaced_routes(:blog) do + proxy = ActionDispatch::Routing::RoutesProxy.new(_routes, self) + @blog_post.save + assert_equal "http://example.com/posts/#{@blog_post.id}", polymorphic_url([proxy, @blog_post]) + end + end + def test_namespaced_model with_namespaced_routes(:blog) do @blog_post.save diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index be5bb30a9a..1df6326d26 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -18,6 +18,7 @@ module ApplicationTests match "/engine_route" => "application_generating#engine_route" match "/engine_route_in_view" => "application_generating#engine_route_in_view" match "/url_for_engine_route" => "application_generating#url_for_engine_route" + match "/polymorphic_route" => "application_generating#polymorphic_route" scope "/:user", :user => "anonymous" do mount Blog::Engine => "/blog" end @@ -25,6 +26,26 @@ module ApplicationTests end RUBY + @plugin.write "app/models/blog/post.rb", <<-RUBY + module Blog + class Post + extend ActiveModel::Naming + + def id + 44 + end + + def to_param + id.to_s + end + + def new_record? + false + end + end + end + RUBY + @plugin.write "lib/blog.rb", <<-RUBY module Blog class Engine < ::Rails::Engine @@ -78,6 +99,10 @@ module ApplicationTests def url_for_engine_route render :text => blog.url_for(:controller => "blog/posts", :action => "index", :user => "john", :only_path => true) end + + def polymorphic_route + render :text => polymorphic_url([blog, Blog::Post.new]) + end end RUBY @@ -141,6 +166,11 @@ module ApplicationTests script_name "/foo" get "/someone/blog/generate_application_route", {}, 'SCRIPT_NAME' => '/foo' assert_equal "/foo/", last_response.body + reset_script_name! + + # test polymorphic routes + get "/polymorphic_route" + assert_equal "http://example.org/anonymous/blog/posts/44", last_response.body end end end From 706a3223a303d56feeee2cc7601da1bd9f381243 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 25 Aug 2010 15:59:25 +0200 Subject: [PATCH 73/87] Add short note on using url_for instead of directly calling named route in polymorphic_url --- actionpack/lib/action_dispatch/routing/polymorphic_routes.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index e8ba1de40e..ecc1651dfe 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -129,6 +129,9 @@ module ActionDispatch if proxy proxy.send(named_route, *args) else + # we need to use url_for, because polymorphic_url can be used in context of other than + # current routes (e.g. engine's routes). As named routes from engine are not included + # calling engine's named route directly would fail. url_for _routes.url_helpers.__send__("hash_for_#{named_route}", *args) end end From 34cd8a68b1a06384d7acf3843e1eb9bea1e5f811 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 25 Aug 2010 19:02:21 +0200 Subject: [PATCH 74/87] Add some more docs on polymorphic_url with routes proxy --- railties/lib/rails/engine.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index bc3014adaa..4cd4dbd2e8 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -237,6 +237,16 @@ module Rails # end # # Note that :as option takes engine_name as default, so most of the time you can ommit it. + # + # If you want to generate url to engine's route using polymorphic_url, you can also use that helpers. + # + # Let's say that you want to create a form pointing to one of the engine's routes. All you need to do + # is passing helper as the first element in array with attributes for url: + # + # form_for([my_engine, @user]) + # + # This code will use my_engine.user_path(@user) to generate proper route. + # class Engine < Railtie autoload :Configurable, "rails/engine/configurable" autoload :Configuration, "rails/engine/configuration" From 6e5aed057f52ba7dc244104c4c9dd0a9415910ae Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 31 Aug 2010 22:17:42 +0200 Subject: [PATCH 75/87] Prepared ActiveModel::Naming to handle cases for namespaced isolated engines --- activemodel/lib/active_model/naming.rb | 26 +++++-- activemodel/test/cases/naming_test.rb | 99 ++++++++++++++++++++++++++ activemodel/test/models/blog_post.rb | 13 ++++ 3 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 activemodel/test/models/blog_post.rb diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index d79635cfb3..1eb1abdb52 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -2,18 +2,22 @@ require 'active_support/inflector' module ActiveModel class Name < String - attr_reader :singular, :plural, :element, :collection, :partial_path + attr_reader :singular, :plural, :element, :collection, :partial_path, :route_key, :param_key alias_method :cache_key, :collection - def initialize(klass) + def initialize(klass, namespace = nil) super(klass.name) + @unnamespaced = self.sub(/^#{namespace.name}::/, '') if namespace + @klass = klass - @singular = ActiveSupport::Inflector.underscore(self).tr('/', '_').freeze + @singular = _singularize(self).freeze @plural = ActiveSupport::Inflector.pluralize(@singular).freeze @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze @human = ActiveSupport::Inflector.humanize(@element).freeze @collection = ActiveSupport::Inflector.tableize(self).freeze @partial_path = "#{@collection}/#{@element}".freeze + @param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze + @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural).freeze end # Transform the model name into a more humane format, using I18n. By default, @@ -36,6 +40,11 @@ module ActiveModel options.reverse_merge! :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults I18n.translate(defaults.shift, options) end + + private + def _singularize(str) + ActiveSupport::Inflector.underscore(str).tr('/', '_') + end end # == Active Model Naming @@ -58,7 +67,8 @@ module ActiveModel # Returns an ActiveModel::Name object for module. It can be # used to retrieve all kinds of naming-related information. def model_name - @_model_name ||= ActiveModel::Name.new(self) + namespace = self.parents.detect { |n| n.respond_to?(:_railtie) } + @_model_name ||= ActiveModel::Name.new(self, namespace) end # Returns the plural class name of a record or class. Examples: @@ -85,6 +95,14 @@ module ActiveModel plural(record_or_class) == singular(record_or_class) end + def self.route_key(record_or_class) + model_name_from_record_or_class(record_or_class).route_key + end + + def self.param_key(record_or_class) + model_name_from_record_or_class(record_or_class).param_key + end + private def self.model_name_from_record_or_class(record_or_class) (record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb index 5a8bff378a..c6b663ef93 100644 --- a/activemodel/test/cases/naming_test.rb +++ b/activemodel/test/cases/naming_test.rb @@ -2,6 +2,7 @@ require 'cases/helper' require 'models/contact' require 'models/sheep' require 'models/track_back' +require 'models/blog_post' class NamingTest < ActiveModel::TestCase def setup @@ -29,6 +30,86 @@ class NamingTest < ActiveModel::TestCase end end +class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase + def setup + @model_name = ActiveModel::Name.new(Blog::Post, Blog) + end + + def test_singular + assert_equal 'blog_post', @model_name.singular + end + + def test_plural + assert_equal 'blog_posts', @model_name.plural + end + + def test_element + assert_equal 'post', @model_name.element + end + + def test_collection + assert_equal 'blog/posts', @model_name.collection + end + + def test_partial_path + assert_equal 'blog/posts/post', @model_name.partial_path + end + + def test_human + assert_equal 'Post', @model_name.human + end + + def test_route_key + assert_equal 'posts', @model_name.route_key + end + + def test_param_key + assert_equal 'post', @model_name.param_key + end + + def test_recognizing_namespace + assert_equal 'Post', Blog::Post.model_name.instance_variable_get("@unnamespaced") + end +end + +class NamingWithNamespacedModelInSharedNamespaceTest < ActiveModel::TestCase + def setup + @model_name = ActiveModel::Name.new(Blog::Post) + end + + def test_singular + assert_equal 'blog_post', @model_name.singular + end + + def test_plural + assert_equal 'blog_posts', @model_name.plural + end + + def test_element + assert_equal 'post', @model_name.element + end + + def test_collection + assert_equal 'blog/posts', @model_name.collection + end + + def test_partial_path + assert_equal 'blog/posts/post', @model_name.partial_path + end + + def test_human + assert_equal 'Post', @model_name.human + end + + def test_route_key + assert_equal 'blog_posts', @model_name.route_key + end + + def test_param_key + assert_equal 'blog_post', @model_name.param_key + end +end + class NamingHelpersTest < Test::Unit::TestCase def setup @klass = Contact @@ -36,6 +117,8 @@ class NamingHelpersTest < Test::Unit::TestCase @singular = 'contact' @plural = 'contacts' @uncountable = Sheep + @route_key = 'contacts' + @param_key = 'contact' end def test_singular @@ -54,6 +137,22 @@ class NamingHelpersTest < Test::Unit::TestCase assert_equal @plural, plural(@klass) end + def test_route_key + assert_equal @route_key, route_key(@record) + end + + def test_route_key_for_class + assert_equal @route_key, route_key(@klass) + end + + def test_param_key + assert_equal @param_key, param_key(@record) + end + + def test_param_key_for_class + assert_equal @param_key, param_key(@klass) + end + def test_uncountable assert uncountable?(@uncountable), "Expected 'sheep' to be uncoutable" assert !uncountable?(@klass), "Expected 'contact' to be countable" diff --git a/activemodel/test/models/blog_post.rb b/activemodel/test/models/blog_post.rb new file mode 100644 index 0000000000..d289177259 --- /dev/null +++ b/activemodel/test/models/blog_post.rb @@ -0,0 +1,13 @@ +module Blog + def self._railtie + Object.new + end + + def self.table_name_prefix + "blog_" + end + + class Post + extend ActiveModel::Naming + end +end From 2607def8621c41d5b0bee09e379ae26890b27f7d Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 31 Aug 2010 23:43:32 +0200 Subject: [PATCH 76/87] Use new ActiveModel::Naming.route_key in polymorphic_routes --- .../action_dispatch/routing/polymorphic_routes.rb | 13 ++----------- .../test/activerecord/polymorphic_routes_test.rb | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index ecc1651dfe..02ba5236ee 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -177,8 +177,7 @@ module ActionDispatch if parent.is_a?(Symbol) || parent.is_a?(String) parent else - str = ActiveModel::Naming.plural(parent).singularize - remove_namespace(str, parent) + ActiveModel::Naming.route_key(parent).singularize end end end @@ -186,8 +185,7 @@ module ActionDispatch if record.is_a?(Symbol) || record.is_a?(String) route << record else - route << ActiveModel::Naming.plural(record) - remove_namespace(route, record) + route << ActiveModel::Naming.route_key(record) route = [route.join("_").singularize] if inflection == :singular route << "index" if ActiveModel::Naming.uncountable?(record) && inflection == :plural end @@ -197,13 +195,6 @@ module ActionDispatch action_prefix(options) + route.join("_") end - def remove_namespace(string, parent) - if namespace = parent.class.parents.detect { |n| n.respond_to?(:_railtie) } - string.sub!(/#{namespace._railtie.railtie_name}_/, '') - end - string - end - def extract_record(record_or_hash_or_array) case record_or_hash_or_array when Array; record_or_hash_or_array.last diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index 23c9fb839e..448aaa5eee 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -436,7 +436,7 @@ class PolymorphicRoutesTest < ActionController::TestCase def with_namespaced_routes(name) with_routing do |set| set.draw do - namespace(name, :shallow_path => nil, :path => nil, :as => nil) do + scope(:module => name) do resources :blogs do resources :posts end From 6f3119d3c298c007e7a4eed8375d9fc30b961d06 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 1 Sep 2010 01:37:02 +0200 Subject: [PATCH 77/87] Remove namespace for isolated namespaced models in forms --- .../lib/action_view/helpers/form_helper.rb | 10 +-- actionpack/test/lib/controller/fake_models.rb | 15 +++++ actionpack/test/template/form_helper_test.rb | 17 +++++ railties/test/railties/engine_test.rb | 65 ++++++++++++++++++- 4 files changed, 101 insertions(+), 6 deletions(-) diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 94dc25eb85..43dbedc448 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -304,12 +304,12 @@ module ActionView object_name = record_or_name_or_array when Array object = record_or_name_or_array.last - object_name = options[:as] || ActiveModel::Naming.singular(object) + object_name = options[:as] || ActiveModel::Naming.param_key(object) apply_form_for_options!(record_or_name_or_array, options) args.unshift object else object = record_or_name_or_array - object_name = options[:as] || ActiveModel::Naming.singular(object) + object_name = options[:as] || ActiveModel::Naming.param_key(object) apply_form_for_options!([object], options) args.unshift object end @@ -539,7 +539,7 @@ module ActionView object_name = record else object = record - object_name = ActiveModel::Naming.singular(object) + object_name = ActiveModel::Naming.param_key(object) end builder = options[:builder] || ActionView::Base.default_form_builder @@ -1168,11 +1168,11 @@ module ActionView end when Array object = record_or_name_or_array.last - name = "#{object_name}#{index}[#{ActiveModel::Naming.singular(object)}]" + name = "#{object_name}#{index}[#{ActiveModel::Naming.param_key(object)}]" args.unshift(object) else object = record_or_name_or_array - name = "#{object_name}#{index}[#{ActiveModel::Naming.singular(object)}]" + name = "#{object_name}#{index}[#{ActiveModel::Naming.param_key(object)}]" args.unshift(object) end diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index 37b200d57a..c4127ee699 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -149,3 +149,18 @@ class Author < Comment attr_accessor :post def post_attributes=(attributes); end end + +module Blog + def self._railtie + self + end + + class Post < Struct.new(:title, :id) + extend ActiveModel::Naming + include ActiveModel::Conversion + + def persisted? + id.present? + end + end +end diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index ec57b6a2ab..97a08d45ba 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -75,6 +75,8 @@ class FormHelperTest < ActionView::TestCase @post.body = "Back to the hill and over it again!" @post.secret = 1 @post.written_on = Date.new(2004, 6, 15) + + @blog_post = Blog::Post.new("And his name will be forty and four.", 44) end Routes = ActionDispatch::Routing::RouteSet.new @@ -675,6 +677,21 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_for_with_isolated_namespaced_model + form_for(@blog_post) do |f| + concat f.text_field :title + concat f.submit('Edit post') + end + + expected = + "" + + snowman + + "" + + "" + + "" + + "" + end + def test_form_for_with_symbol_object_name form_for(@post, :as => "other_name", :html => { :id => 'create-post' }) do |f| concat f.label(:title, :class => 'post_title') diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 56f9eae8ca..73d9b61719 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -360,7 +360,7 @@ module RailtiesTest assert_equal "It's a bar.", response[2].body end - test "namespaced engine should include only its own routes and helpers" do + test "isolated engine should include only its own routes and helpers" do @plugin.write "lib/bukkits.rb", <<-RUBY module Bukkits class Engine < ::Rails::Engine @@ -478,5 +478,68 @@ module RailtiesTest response = AppTemplate::Application.call(env) assert_equal "/bukkits/posts/1", response[2].body end + + test "isolated engine should avoid namespace in names if that's possible" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + namespace Bukkits + end + end + RUBY + + @plugin.write "app/models/bukkits/post.rb", <<-RUBY + module Bukkits + class Post + extend ActiveModel::Naming + include ActiveModel::Conversion + attr_accessor :title + + def to_param + "1" + end + + def persisted? + false + end + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + mount Bukkits::Engine => "/bukkits", :as => "bukkits" + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + scope(:module => :bukkits) do + resources :posts + end + end + RUBY + + @plugin.write "app/controllers/bukkits/posts_controller.rb", <<-RUBY + class Bukkits::PostsController < ActionController::Base + def new + end + end + RUBY + + @plugin.write "app/views/bukkits/posts/new.html.erb", <<-RUBY + <%= form_for(Bukkits::Post.new) do |f| %> + <%= f.text_field :title %> + <% end %> + RUBY + + add_to_config("config.action_dispatch.show_exceptions = false") + + boot_rails + + env = Rack::MockRequest.env_for("/bukkits/posts/new") + response = AppTemplate::Application.call(env) + assert response[2].body =~ /name="post\[title\]"/ + end end end From bf1ac82cecfb3291a9bc215974b385a1b3ccb545 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 1 Sep 2010 11:02:28 +0200 Subject: [PATCH 78/87] Add some documantation on new route_key and param_key in ActiveModel::Naming --- activemodel/lib/active_model/naming.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 1eb1abdb52..dadb1882e4 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -95,10 +95,26 @@ module ActiveModel plural(record_or_class) == singular(record_or_class) end + # Returns string to use while generating route names. It differs for + # namespaced models regarding whether it's inside isolated engine. + # + # For isolated engine: + # ActiveModel::Naming.route_key(Blog::Post) #=> posts + # + # For shared engine: + # ActiveModel::Naming.route_key(Blog::Post) #=> blog_posts def self.route_key(record_or_class) model_name_from_record_or_class(record_or_class).route_key end + # Returns string to use for params names. It differs for + # namespaced models regarding whether it's inside isolated engine. + # + # For isolated engine: + # ActiveModel::Naming.route_key(Blog::Post) #=> post + # + # For shared engine: + # ActiveModel::Naming.route_key(Blog::Post) #=> blog_post def self.param_key(record_or_class) model_name_from_record_or_class(record_or_class).param_key end From a5e509d6b1a26f12fad65a69d01f450c69e136c1 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 1 Sep 2010 11:07:08 +0200 Subject: [PATCH 79/87] We should avoid creating additional initializers when we can, adding them makes boot process slower --- railties/lib/rails/engine.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 4cd4dbd2e8..2028246585 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -441,11 +441,9 @@ module Rails require environment if environment end - initializer :default_asset_path do - config.asset_path = "/#{engine_name}%s" unless config.asset_path - end - initializer :append_asset_paths do + config.asset_path = "/#{engine_name}%s" unless config.asset_path + public_path = config.paths.public.to_a.first if config.compiled_asset_path && File.exist?(public_path) config.static_asset_paths[config.compiled_asset_path] = public_path From b43b686b022c4253a4a6f6f1b663b99d7a9adf4f Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 1 Sep 2010 11:07:39 +0200 Subject: [PATCH 80/87] engines_blank_point should always be the last initializer in Engine --- railties/lib/rails/engine.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 2028246585..be63363242 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -431,11 +431,6 @@ module Rails end end - initializer :engines_blank_point do - # We need this initializer so all extra initializers added in engines are - # consistently executed after all the initializers above across all engines. - end - initializer :load_environment_config, :before => :load_environment_hook do environment = config.paths.config.environments.to_a.first require environment if environment @@ -457,6 +452,11 @@ module Rails end end + initializer :engines_blank_point do + # We need this initializer so all extra initializers added in engines are + # consistently executed after all the initializers above across all engines. + end + protected def find_root_with_flag(flag, default=nil) root_path = self.class.called_from From b8d6dc3c84321c751ab2ca8232e1e3fb332a844c Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 1 Sep 2010 19:17:47 +0200 Subject: [PATCH 81/87] Implemented RouteSet#default_scope, which allows to set the scope for the entire routes object --- .../lib/action_dispatch/routing/mapper.rb | 6 ++++ .../lib/action_dispatch/routing/route_set.rb | 8 +++-- actionpack/test/dispatch/routing_test.rb | 29 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index b437d7a17d..900900ee24 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -274,6 +274,12 @@ module ActionDispatch end alias_method :default_url_options, :default_url_options= + def with_default_scope(scope, &block) + scope(scope) do + instance_exec(&block) + end + end + private def app_name(app) return unless app.respond_to?(:routes) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 0d83ca956b..107e44287d 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -199,7 +199,7 @@ module ActionDispatch end end - attr_accessor :set, :routes, :named_routes + attr_accessor :set, :routes, :named_routes, :default_scope attr_accessor :disable_clear_and_finalize, :resources_path_names attr_accessor :default_url_options, :request_class, :valid_conditions @@ -230,7 +230,11 @@ module ActionDispatch if block.arity == 1 mapper.instance_exec(DeprecatedMapper.new(self), &block) else - mapper.instance_exec(&block) + if default_scope + mapper.with_default_scope(default_scope, &block) + else + mapper.instance_exec(&block) + end end finalize! unless @disable_clear_and_finalize diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index c90c1041ed..b642adc06b 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2151,3 +2151,32 @@ private %(You are being redirected.) end end + + +class TestDefaultScope < ActionController::IntegrationTest + module ::Blog + class PostsController < ActionController::Base + def index + render :text => "blog/posts#index" + end + end + end + + DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new + DefaultScopeRoutes.default_scope = {:module => :blog} + DefaultScopeRoutes.draw do + resources :posts + end + + def app + DefaultScopeRoutes + end + + include DefaultScopeRoutes.url_helpers + + def test_default_scope + get '/posts' + assert_equal "blog/posts#index", @response.body + end +end + From 6c906bf59111e09997787c1d4c5ba2ec2b783cdd Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Wed, 1 Sep 2010 19:31:29 +0200 Subject: [PATCH 82/87] Use default_scope in isolated Engines to not force user to scope his routes --- railties/lib/rails/engine.rb | 2 ++ railties/test/railties/engine_test.rb | 18 +++++++----------- railties/test/railties/mounted_engine_test.rb | 8 +++----- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index be63363242..1990aa5261 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -303,6 +303,8 @@ module Rails end end + self.routes.default_scope = {:module => name} + self.namespaced = true end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 73d9b61719..3049011477 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -390,14 +390,12 @@ module RailtiesTest @plugin.write "config/routes.rb", <<-RUBY Bukkits::Engine.routes.draw do - scope(:module => :bukkits) do - match "/foo" => "foo#index", :as => "foo" - match "/foo/show" => "foo#show" - match "/from_app" => "foo#from_app" - match "/routes_helpers_in_view" => "foo#routes_helpers_in_view" - match "/polymorphic_path_without_namespace" => "foo#polymorphic_path_without_namespace" - resources :posts - end + match "/foo" => "foo#index", :as => "foo" + match "/foo/show" => "foo#show" + match "/from_app" => "foo#from_app" + match "/routes_helpers_in_view" => "foo#routes_helpers_in_view" + match "/polymorphic_path_without_namespace" => "foo#polymorphic_path_without_namespace" + resources :posts end RUBY @@ -514,9 +512,7 @@ module RailtiesTest @plugin.write "config/routes.rb", <<-RUBY Bukkits::Engine.routes.draw do - scope(:module => :bukkits) do - resources :posts - end + resources :posts end RUBY diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index 1df6326d26..b1e3e274b8 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -56,11 +56,9 @@ module ApplicationTests @plugin.write "config/routes.rb", <<-RUBY Blog::Engine.routes.draw do - scope(:module => :blog) do - resources :posts do - get :generate_application_route - get :application_route_in_view - end + resources :posts do + get :generate_application_route + get :application_route_in_view end end RUBY From 9af189ac8fde1988b9695cf1459b04c9a8a042ee Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 2 Sep 2010 12:09:16 +0200 Subject: [PATCH 83/87] I've changed that test along the way, it should actually stay without changes --- railties/lib/rails/engine.rb | 12 ++++++------ railties/test/railties/engine_test.rb | 11 +++++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 1990aa5261..e10980a6d9 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -427,12 +427,6 @@ module Rails end end - initializer :load_config_initializers do - paths.config.initializers.to_a.sort.each do |initializer| - load(initializer) - end - end - initializer :load_environment_config, :before => :load_environment_hook do environment = config.paths.config.environments.to_a.first require environment if environment @@ -454,6 +448,12 @@ module Rails end end + initializer :load_config_initializers do + paths.config.initializers.to_a.sort.each do |initializer| + load(initializer) + end + end + initializer :engines_blank_point do # We need this initializer so all extra initializers added in engines are # consistently executed after all the initializers above across all engines. diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 3049011477..0cc729907e 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -55,12 +55,19 @@ module RailtiesTest initializers = Rails.application.initializers.tsort index = initializers.index { |i| i.name == "dummy_initializer" } + selection = initializers[(index-3)..(index)].map(&:name).map(&:to_s) + + assert_equal %w( + load_config_initializers + load_config_initializers + engines_blank_point + dummy_initializer + ), selection - assert index > initializers.index { |i| i.name == :load_config_initializers } - assert index > initializers.index { |i| i.name == :engines_blank_point } assert index < initializers.index { |i| i.name == :build_middleware_stack } end + class Upcaser def initialize(app) @app = app From ffa2acad5cee6b8c09ac95e973668d6d1a6b31e0 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Thu, 2 Sep 2010 15:28:59 +0200 Subject: [PATCH 84/87] Fixed tests after rebase --- railties/test/railties/mounted_engine_test.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index b1e3e274b8..36dd01198f 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -56,10 +56,9 @@ module ApplicationTests @plugin.write "config/routes.rb", <<-RUBY Blog::Engine.routes.draw do - resources :posts do - get :generate_application_route - get :application_route_in_view - end + resources :posts + match '/generate_application_route', :to => 'posts#generate_application_route' + match '/application_route_in_view', :to => 'posts#application_route_in_view' end RUBY From 89bd715f6be4235ac7632de10349e9939be04e75 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 3 Sep 2010 11:01:24 +0200 Subject: [PATCH 85/87] Forgot to move that line to railtie on rebase --- actionpack/lib/action_controller/railties/paths.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb index e1b318b566..fa71d55946 100644 --- a/actionpack/lib/action_controller/railties/paths.rb +++ b/actionpack/lib/action_controller/railties/paths.rb @@ -11,7 +11,7 @@ module ActionController klass.helpers_path = app.config.helpers_paths end - klass.helper :all + klass.helper :all if klass.superclass == ActionController::Base end end end From 9f0a1ae14e2a9306605d7b572612ccf36fa8b2da Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 3 Sep 2010 12:48:21 +0200 Subject: [PATCH 86/87] Optimize ActionDispatch::Static --- .../lib/action_dispatch/middleware/static.rb | 90 ++++++++----------- 1 file changed, 37 insertions(+), 53 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index c2d686f514..e7e335df49 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -1,44 +1,43 @@ require 'rack/utils' module ActionDispatch - class Static - class FileHandler - def initialize(at, root) - @at = at.chomp("/") - @file_server = ::Rack::File.new(root) - end + class FileHandler + def initialize(at, root) + @at, @root = at.chomp('/'), root.chomp('/') + @compiled_at = Regexp.compile(/^#{Regexp.escape(at)}/) unless @at.blank? + @compiled_root = Regexp.compile(/^#{Regexp.escape(root)}/) + @file_server = ::Rack::File.new(root) - def file_exist?(path) - (path = full_readable_path(path)) && File.file?(path) - end - - def directory_exist?(path) - (path = full_readable_path(path)) && File.directory?(path) - end - - def call(env) - env["PATH_INFO"].gsub!(/^#{@at}/, "") - @file_server.call(env) - end - - private - def includes_path?(path) - @at == "" || path =~ /^#{@at}/ - end - - def full_readable_path(path) - return unless includes_path?(path) - path = path.gsub(/^#{@at}/, "") - File.join(@file_server.root, ::Rack::Utils.unescape(path)) - end + ext = ::ActionController::Base.page_cache_extension + @ext = "{,#{ext},/index#{ext}}" end + def match?(path) + path = path.dup + if @compiled_at.blank? || path.sub!(@compiled_at, '') + full_path = File.join(@root, ::Rack::Utils.unescape(path)) + paths = "#{full_path}#{@ext}" + + matches = Dir[paths] + match = matches.detect { |m| File.file?(m) } + if match + match.sub!(@compiled_root, '') + match + end + end + end + + def call(env) + @file_server.call(env) + end + end + + class Static FILE_METHODS = %w(GET HEAD).freeze def initialize(app, roots) @app = app - roots = normalize_roots(roots) - @file_handlers = file_handlers(roots) + @file_handlers = create_file_handlers(roots) end def call(env) @@ -46,14 +45,9 @@ module ActionDispatch method = env['REQUEST_METHOD'] if FILE_METHODS.include?(method) - if file_handler = file_exist?(path) - return file_handler.call(env) - else - cached_path = directory_exist?(path) ? "#{path}/index" : path - cached_path += ::ActionController::Base.page_cache_extension - - if file_handler = file_exist?(cached_path) - env['PATH_INFO'] = cached_path + @file_handlers.each do |file_handler| + if match = file_handler.match?(path) + env["PATH_INFO"] = match return file_handler.call(env) end end @@ -63,22 +57,12 @@ module ActionDispatch end private - def file_exist?(path) - @file_handlers.detect { |f| f.file_exist?(path) } - end + def create_file_handlers(roots) + roots = { '' => roots } unless roots.is_a?(Hash) - def directory_exist?(path) - @file_handlers.detect { |f| f.directory_exist?(path) } - end - - def normalize_roots(roots) - roots.is_a?(Hash) ? roots : { "/" => roots.chomp("/") } - end - - def file_handlers(roots) roots.map do |at, root| - FileHandler.new(at, root) - end + FileHandler.new(at, root) if File.exist?(root) + end.compact end end end From c3c1a1e14859e6716970283caeab0c4c3720862e Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Fri, 3 Sep 2010 23:25:57 +0200 Subject: [PATCH 87/87] Do not use ActionController::Base.page_cache_extension in initialize to not load more ActiveSupport than we need --- actionpack/lib/action_dispatch/middleware/static.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index e7e335df49..581cadbeb4 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -7,16 +7,13 @@ module ActionDispatch @compiled_at = Regexp.compile(/^#{Regexp.escape(at)}/) unless @at.blank? @compiled_root = Regexp.compile(/^#{Regexp.escape(root)}/) @file_server = ::Rack::File.new(root) - - ext = ::ActionController::Base.page_cache_extension - @ext = "{,#{ext},/index#{ext}}" end def match?(path) path = path.dup if @compiled_at.blank? || path.sub!(@compiled_at, '') full_path = File.join(@root, ::Rack::Utils.unescape(path)) - paths = "#{full_path}#{@ext}" + paths = "#{full_path}#{ext}" matches = Dir[paths] match = matches.detect { |m| File.file?(m) } @@ -30,6 +27,13 @@ module ActionDispatch def call(env) @file_server.call(env) end + + def ext + @ext ||= begin + ext = ::ActionController::Base.page_cache_extension + "{,#{ext},/index#{ext}}" + end + end end class Static