aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@gmail.com>2010-09-04 00:31:35 +0200
committerJosé Valim <jose.valim@gmail.com>2010-09-04 00:31:43 +0200
commit23a9455962f0362cf242ffa96db7a9e7fdb0804b (patch)
treec046d1285d078649db6833fa1b73a6c23f7f16ea
parent63032c1162e96d3380168ca63ac42aa044dbebd6 (diff)
parentc3c1a1e14859e6716970283caeab0c4c3720862e (diff)
downloadrails-23a9455962f0362cf242ffa96db7a9e7fdb0804b.tar.gz
rails-23a9455962f0362cf242ffa96db7a9e7fdb0804b.tar.bz2
rails-23a9455962f0362cf242ffa96db7a9e7fdb0804b.zip
This commit merges most of the work done by Piotr Sarnacki in his Ruby Summer of Code project.
His work brings several capabilities from app to engines, as routes, middleware stack, asset handling and much more. Please check Rails::Engine documentation for more refenrences. Merge remote branch 'drogus/engines'
-rw-r--r--actionmailer/lib/action_mailer/base.rb23
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb7
-rw-r--r--actionpack/lib/abstract_controller.rb1
-rw-r--r--actionpack/lib/abstract_controller/railties/routes_helpers.rb18
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb1
-rw-r--r--actionpack/lib/abstract_controller/url_for.rb28
-rw-r--r--actionpack/lib/action_controller/base.rb7
-rw-r--r--actionpack/lib/action_controller/metal.rb6
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb6
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb24
-rw-r--r--actionpack/lib/action_controller/railtie.rb9
-rw-r--r--actionpack/lib/action_controller/railties/paths.rb20
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb64
-rw-r--r--actionpack/lib/action_dispatch/routing.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb38
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb28
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb76
-rw-r--r--actionpack/lib/action_dispatch/routing/routes_proxy.rb35
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb12
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb4
-rw-r--r--actionpack/test/activerecord/polymorphic_routes_test.rb64
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb256
-rw-r--r--actionpack/test/dispatch/routing_test.rb29
-rw-r--r--actionpack/test/dispatch/static_test.rb61
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb3
-rw-r--r--actionpack/test/fixtures/blog_public/.gitignore1
-rw-r--r--actionpack/test/fixtures/blog_public/blog.html1
-rw-r--r--actionpack/test/fixtures/blog_public/index.html1
-rw-r--r--actionpack/test/fixtures/blog_public/subdir/index.html1
-rw-r--r--actionpack/test/lib/controller/fake_models.rb17
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb23
-rw-r--r--actionpack/test/template/form_helper_test.rb107
-rw-r--r--actionpack/test/template/test_test.rb2
-rw-r--r--activemodel/lib/active_model/naming.rb42
-rw-r--r--activemodel/test/cases/naming_test.rb99
-rw-r--r--activemodel/test/models/blog_post.rb13
-rw-r--r--activerecord/lib/active_record/migration.rb96
-rw-r--r--activerecord/lib/active_record/railties/databases.rake35
-rw-r--r--activerecord/lib/rails/generators/active_record.rb6
-rw-r--r--activerecord/test/cases/helper.rb18
-rw-r--r--activerecord/test/cases/migration_test.rb125
-rw-r--r--activerecord/test/migrations/empty/.gitkeep0
-rw-r--r--activerecord/test/migrations/to_copy/1_people_have_hobbies.rb9
-rw-r--r--activerecord/test/migrations/to_copy/2_people_have_descriptions.rb9
-rw-r--r--activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb9
-rw-r--r--activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb9
-rw-r--r--activerecord/test/migrations/valid_with_timestamps/20100101010101_people_have_last_names.rb9
-rw-r--r--activerecord/test/migrations/valid_with_timestamps/20100201010101_we_need_reminders.rb12
-rw-r--r--activerecord/test/migrations/valid_with_timestamps/20100301010101_innocent_jointable.rb12
-rw-r--r--railties/lib/rails.rb5
-rw-r--r--railties/lib/rails/application.rb66
-rw-r--r--railties/lib/rails/application/bootstrap.rb7
-rw-r--r--railties/lib/rails/application/configurable.rb19
-rw-r--r--railties/lib/rails/application/configuration.rb32
-rw-r--r--railties/lib/rails/application/railties.rb26
-rw-r--r--railties/lib/rails/configuration.rb81
-rw-r--r--railties/lib/rails/engine.rb278
-rw-r--r--railties/lib/rails/engine/configurable.rb25
-rw-r--r--railties/lib/rails/engine/configuration.rb12
-rw-r--r--railties/lib/rails/engine/railties.rb23
-rw-r--r--railties/lib/rails/plugin.rb15
-rw-r--r--railties/lib/rails/railtie.rb29
-rw-r--r--railties/lib/rails/railtie/configurable.rb26
-rw-r--r--railties/lib/rails/railtie/configuration.rb10
-rw-r--r--railties/test/application/configuration_test.rb44
-rw-r--r--railties/test/application/initializers/frameworks_test.rb1
-rw-r--r--railties/test/application/loading_test.rb22
-rw-r--r--railties/test/railties/engine_test.rb494
-rw-r--r--railties/test/railties/mounted_engine_test.rb174
-rw-r--r--railties/test/railties/railtie_test.rb16
-rw-r--r--railties/test/railties/shared_tests.rb49
75 files changed, 2479 insertions, 468 deletions
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 31e1b26afd..6ae3940e6d 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -361,7 +361,6 @@ module ActionMailer #:nodoc:
}.freeze
class << self
-
def mailer_name
@mailer_name ||= name.underscore
end
@@ -725,28 +724,6 @@ 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
-
ActiveSupport.run_load_hooks(:action_mailer, self)
end
end
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index ce6d8cc5b5..a2b00addc9 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 "abstract_controller/railties/routes_helpers"
module ActionMailer
class Railtie < Rails::Railtie
@@ -18,9 +19,11 @@ module ActionMailer
options.stylesheets_dir ||= paths.public.stylesheets.to_a.first
ActiveSupport.on_load(:action_mailer) do
- include app.routes.url_helpers
+ include AbstractController::UrlFor
+ extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
+ 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/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/railties/routes_helpers.rb b/actionpack/lib/abstract_controller/railties/routes_helpers.rb
new file mode 100644
index 0000000000..dec1e9d6d9
--- /dev/null
+++ b/actionpack/lib/abstract_controller/railties/routes_helpers.rb
@@ -0,0 +1,18 @@
+module AbstractController
+ 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) }
+ klass.send(:include, namespace._railtie.routes.url_helpers)
+ else
+ klass.send(:include, routes.url_helpers)
+ end
+ end
+ end
+ end
+ end
+ end
+end
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/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/base.rb b/actionpack/lib/action_controller/base.rb
index 7a1464c2aa..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 \ No newline at end of file
+end
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
# <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>.
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 <tt>app/helpers/**/*_helper.rb</tt>
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/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index a51fc5b8e4..85c6b0a9b5 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -2,27 +2,19 @@ module ActionController
module UrlFor
extend ActiveSupport::Concern
- include ActionDispatch::Routing::UrlFor
+ include AbstractController::UrlFor
def url_options
- super.reverse_merge(
+ options = {}
+ if _routes.equal?(env["action_dispatch.routes"])
+ options[:script_name] = request.script_name.dup
+ 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)
- 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_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index cd2dfafbe6..0cb4041855 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -4,6 +4,8 @@ require "action_dispatch/railtie"
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
@@ -47,10 +49,11 @@ module ActionController
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
- include app.routes.url_helpers
+ 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
@@ -63,4 +66,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/paths.rb b/actionpack/lib/action_controller/railties/paths.rb
new file mode 100644
index 0000000000..fa71d55946
--- /dev/null
+++ b/actionpack/lib/action_controller/railties/paths.rb
@@ -0,0 +1,20 @@
+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) }
+ klass.helpers_path = namespace._railtie.config.paths.app.helpers.to_a
+ else
+ klass.helpers_path = app.config.helpers_paths
+ end
+
+ klass.helper :all if klass.superclass == ActionController::Base
+ end
+ end
+ end
+ end
+ end
+end
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
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index d7e88a54e4..581cadbeb4 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -1,12 +1,47 @@
require 'rack/utils'
module ActionDispatch
+ 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)
+ 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
+
+ def ext
+ @ext ||= begin
+ ext = ::ActionController::Base.page_cache_extension
+ "{,#{ext},/index#{ext}}"
+ end
+ end
+ end
+
class Static
FILE_METHODS = %w(GET HEAD).freeze
- def initialize(app, root)
+ def initialize(app, roots)
@app = app
- @file_server = ::Rack::File.new(root)
+ @file_handlers = create_file_handlers(roots)
end
def call(env)
@@ -14,15 +49,10 @@ module ActionDispatch
method = env['REQUEST_METHOD']
if FILE_METHODS.include?(method)
- if file_exist?(path)
- return @file_server.call(env)
- else
- cached_path = directory_exist?(path) ? "#{path}/index" : path
- cached_path += ::ActionController::Base.page_cache_extension
-
- if file_exist?(cached_path)
- env['PATH_INFO'] = cached_path
- return @file_server.call(env)
+ @file_handlers.each do |file_handler|
+ if match = file_handler.match?(path)
+ env["PATH_INFO"] = match
+ return file_handler.call(env)
end
end
end
@@ -31,14 +61,12 @@ module ActionDispatch
end
private
- def file_exist?(path)
- full_path = File.join(@file_server.root, ::Rack::Utils.unescape(path))
- File.file?(full_path) && File.readable?(full_path)
- end
+ def create_file_handlers(roots)
+ roots = { '' => roots } unless roots.is_a?(Hash)
- def directory_exist?(path)
- full_path = File.join(@file_server.root, ::Rack::Utils.unescape(path))
- File.directory?(full_path) && File.readable?(full_path)
+ roots.map do |at, root|
+ FileHandler.new(at, root) if File.exist?(root)
+ end.compact
end
end
end
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/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index a2570cb877..900900ee24 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,40 @@ module ActionDispatch
@set.default_url_options = options
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)
+
+ 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)
+ return unless app.respond_to?(:routes)
+
+ _route = @set.named_routes.routes[name.to_sym]
+ _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) }
+ _routes.url_helpers.send("#{name}_path", prefix_options)
+ end
+ end
+ end
end
module HttpHelpers
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index fb2118a8d7..02ba5236ee 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,14 @@ module ActionDispatch
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
end
- send(named_route, *args)
+ 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
# Returns the path component of a URL for the given record. It uses
@@ -155,7 +177,7 @@ module ActionDispatch
if parent.is_a?(Symbol) || parent.is_a?(String)
parent
else
- ActiveModel::Naming.plural(parent).singularize
+ ActiveModel::Naming.route_key(parent).singularize
end
end
end
@@ -163,7 +185,7 @@ module ActionDispatch
if record.is_a?(Symbol) || record.is_a?(String)
route << record
else
- route << ActiveModel::Naming.plural(record)
+ route << ActiveModel::Naming.route_key(record)
route = [route.join("_").singularize] if inflection == :singular
route << "index" if ActiveModel::Naming.uncountable?(record) && inflection == :plural
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index b531cc1a8e..107e44287d 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,21 +192,14 @@ 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
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
@@ -261,6 +265,31 @@ module ActionDispatch
named_routes.install(destinations, regenerate_code)
end
+ module MountedHelpers
+ end
+
+ def mounted_helpers(name = nil)
+ define_mounted_helper(name) if name
+ MountedHelpers
+ end
+
+ 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._routes_context)
+ end
+ end
+
+ MountedHelpers.class_eval <<-RUBY
+ def #{name}
+ @#{name} ||= _#{name}
+ end
+ RUBY
+ end
+
def url_helpers
@url_helpers ||= begin
routes = self
@@ -283,7 +312,7 @@ module ActionDispatch
singleton_class.send(:define_method, :_routes) { routes }
end
- define_method(:_routes) { routes }
+ define_method(:_routes) { @_routes || routes }
end
helpers
@@ -303,10 +332,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 +429,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 +481,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]
+
+ def _generate_prefix(options = {})
+ nil
+ end
def url_for(options)
finalize!
@@ -464,7 +496,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 +507,12 @@ module ActionDispatch
rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
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 << generate(path_options, path_segments || {})
# 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/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
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 28ec830fe8..e836cf7c8e 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -133,6 +133,18 @@ module ActionDispatch
polymorphic_url(options)
end
end
+
+ protected
+ def _with_routes(routes)
+ old_routes, @_routes = @_routes, routes
+ yield
+ ensure
+ @_routes = old_routes
+ end
+
+ def _routes_context
+ self
+ end
end
end
end
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/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/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index b8df2d9a69..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)
diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb
index 90a1ef982c..448aaa5eee 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,38 @@ 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_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
+ 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 +433,22 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
+ def with_namespaced_routes(name)
+ with_routing do |set|
+ set.draw do
+ scope(:module => name) 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|
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
new file mode 100644
index 0000000000..3b47a1b72d
--- /dev/null
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -0,0 +1,256 @@
+require 'abstract_unit'
+
+module TestGenerationPrefix
+ class WithMountedEngine < ActionDispatch::IntegrationTest
+ require 'rack/test'
+ include Rack::Test::Methods
+
+ class BlogEngine
+ def self.routes
+ @routes ||= begin
+ routes = ActionDispatch::Routing::RouteSet.new
+ routes.draw do
+ 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"
+ match "/conflicting_url", :to => "inside_engine_generating#conflicting"
+ 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", :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"
+ match "/conflicting_url", :to => "outside_engine_generating#conflicting"
+ root :to => "outside_engine_generating#index"
+ end
+
+ routes
+ end
+ end
+
+ def self.call(env)
+ env['action_dispatch.routes'] = routes
+ routes.call(env)
+ 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
+ end
+
+ def show
+ render :text => post_path(:id => params[:id])
+ end
+
+ def url_to_application
+ 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
+
+ def conflicting
+ render :text => "engine"
+ end
+ end
+
+ class ::OutsideEngineGeneratingController < ActionController::Base
+ include BlogEngine.routes.mounted_helpers
+
+ def index
+ 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
+
+ def conflicting
+ render :text => "application"
+ end
+ end
+
+ class EngineObject
+ include ActionDispatch::Routing::UrlFor
+ include BlogEngine.routes.url_helpers
+ end
+
+ class AppObject
+ include ActionDispatch::Routing::UrlFor
+ include RailsApplication.routes.url_helpers
+ end
+
+ 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
+
+ # 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 "[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 "[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
+
+ 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
+
+ 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"
+ assert_equal "/awesome/blog/posts/1", last_response.body
+ end
+
+ 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 "[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 "[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)
+ end
+
+ 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(:controller => "inside_engine_generating",
+ :action => "show",
+ :only_path => true,
+ :omg => "omg",
+ :id => 1)
+ assert_equal "/omg/blog/posts/1", path
+ end
+
+ test "[OBJECT] generating engine's route with named helpers" do
+ path = engine_object.posts_path
+ assert_equal "/awesome/blog/posts", path
+
+ 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/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
%(<html><body>You are being <a href="#{ERB::Util.h(url)}">redirected</a>.</body></html>)
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
+
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/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index f83651d583..2b54bc62b0 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
@@ -41,3 +41,4 @@ module TestUrlGeneration
end
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
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index bf3e175f1f..c4127ee699 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
@@ -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/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 %(<img alt="Rails" src="#{expected_path}" />), 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 %(<img alt="Rails" src="#{expected_path}" />), 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 %(<img alt="Rails" src="#{expected_path}" />), 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 %(<img alt="Rails" src="#{expected_path}" />), 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 '<img src="cid:thi%25%25sis@acontentid" />', image_tag("cid:thi%25%25sis@acontentid")
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 9a1fe01872..97a08d45ba 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -75,15 +75,39 @@ 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
+ 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 +652,7 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<form accept-charset='UTF-8' action='http://www.example.com' id='create-post' method='post'>" +
+ "<form accept-charset='UTF-8' action='/' id='create-post' method='post'>" +
snowman +
"<label for='post_title'>The Title</label>" +
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
@@ -653,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 =
+ "<form accept-charset='UTF-8' action='/posts/44' method='post'>" +
+ snowman +
+ "<label for='post_title'>The Title</label>" +
+ "<input name='post[title]' size='30' type='text' id='post_title' value='And his name will be forty and four.' />" +
+ "<input name='commit' id='post_submit' type='submit' value='Edit post' />" +
+ "</form>"
+ 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')
@@ -683,7 +722,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
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
@@ -702,7 +741,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
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
@@ -721,7 +760,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
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
@@ -738,7 +777,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
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
@@ -1478,7 +1517,7 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<form accept-charset='UTF-8' action='http://www.example.com' id='create-post' method='post'>" +
+ "<form accept-charset='UTF-8' action='/' id='create-post' method='post'>" +
snowman +
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
@@ -1502,7 +1541,7 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- whole_form("http://www.example.com", "create-post") do
+ whole_form("/", "create-post") do
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' size='30' />"
@@ -1546,7 +1585,7 @@ class FormHelperTest < ActionView::TestCase
txt << %{</div>}
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 = %{<form accept-charset="UTF-8" action="#{action}"}
txt << %{ data-remote="true"} if remote
txt << %{ class="#{html_class}"} if html_class
@@ -1554,7 +1593,7 @@ class FormHelperTest < ActionView::TestCase
txt << %{ method="post">}
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 +1694,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 +1749,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 +1764,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 +1788,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 '<a href="/people/1">David</a>', link_to_person(person)
end
end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index d79635cfb3..dadb1882e4 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,30 @@ 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
+
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
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 198f0a18cb..e708b3fbcf 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 ? last.version + 1 : 0).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
@@ -409,6 +440,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 +474,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 +497,38 @@ 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
+
+ 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)
@@ -546,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/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index b1aad0d496..aedda26ba5 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -2,6 +2,29 @@ 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
+
+ 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
@@ -139,7 +162,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 +185,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 +193,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 +231,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 +283,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/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..03a8cc90f6 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -1875,5 +1875,130 @@ 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
+
+ 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
--- /dev/null
+++ b/activerecord/test/migrations/empty/.gitkeep
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
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.rb b/railties/lib/rails/application.rb
index 5b26333486..8631a5df3e 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -41,24 +41,6 @@ module Rails
autoload :Railties, 'rails/application/railties'
class << self
- private :new
-
- def configure(&block)
- class_eval(&block)
- end
-
- 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
@@ -66,19 +48,9 @@ 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
-
- protected
-
- def method_missing(*args, &block)
- instance.send(*args, &block)
- end
end
- delegate :middleware, :to => :config
+ 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
@@ -108,14 +80,6 @@ module Rails
super
end
- def routes
- @routes ||= ActionDispatch::Routing::RouteSet.new
- end
-
- def railties
- @railties ||= Railties.new(config)
- end
-
def routes_reloader
@routes_reloader ||= ActiveSupport::FileUpdateChecker.new([]){ reload_routes! }
end
@@ -131,7 +95,9 @@ module Rails
end
def initialize!
+ raise "Application has been already initialized." if @initialized
run_initializers(self)
+ @initialized = true
self
end
@@ -156,38 +122,32 @@ 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)
- 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
initializers = Bootstrap.initializers_for(self)
- railties.all { |r| initializers += r.initializers }
initializers += super
initializers += Finisher.initializers_for(self)
initializers
end
+ def config
+ @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd))
+ end
+
protected
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, 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/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/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/configuration.rb b/railties/lib/rails/application/configuration.rb
index c3418e0d80..477bbbc1e7 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -1,16 +1,13 @@
-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, :middleware,
- :plugins, :preload_frameworks, :reload_plugins,
+ :filter_parameters, :log_level, :logger,
+ :preload_frameworks, :reload_plugins,
:secret_token, :serve_static_assets, :session_options,
:time_zone, :whiny_nils
@@ -28,6 +25,22 @@ 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 compiled_asset_path
+ "/"
+ end
+
def encoding=(value)
@encoding = value
if "ruby".encoding_aware?
@@ -48,19 +61,10 @@ 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"
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 " <<
- "automatically to load paths anymore in future releases"
- paths.mocks_path "test/mocks", :autoload => true, :glob => Rails.env
- end
paths
end
diff --git a/railties/lib/rails/application/railties.rb b/railties/lib/rails/application/railties.rb
index b3e6693f89..c1d2de571f 100644
--- a/railties/lib/rails/application/railties.rb
+++ b/railties/lib/rails/application/railties.rb
@@ -1,31 +1,21 @@
-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
def railties
- @railties ||= ::Rails::Railtie.subclasses.map(&:new)
+ @railties ||= ::Rails::Railtie.subclasses.map(&:instance)
end
def engines
- @engines ||= ::Rails::Engine.subclasses.map(&:new)
- end
-
- def plugins
- @plugins ||= begin
- plugin_names = (@config.plugins || [:all]).map { |p| p.to_sym }
- Plugin.all(plugin_names, @config.paths.vendor.plugins)
- end
+ @engines ||= ::Rails::Engine.subclasses.map(&:instance)
end
end
end
-end \ No newline at end of file
+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 555bc9dbc8..e10980a6d9 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
@@ -86,14 +87,172 @@ 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.
+ #
+ # == 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:
+ #
+ # 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"
+ #
+ # == 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
+ # 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_"
+ #
+ # == 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.
+ #
+ # 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"
class << self
- attr_accessor :called_from
-
- # TODO Remove this. It's deprecated.
+ attr_accessor :called_from, :namespaced
alias :engine_name :railtie_name
def inherited(base)
@@ -122,9 +281,40 @@ module Rails
RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ?
Pathname.new(root).expand_path : Pathname.new(root).realpath
end
+
+ def endpoint(endpoint = nil)
+ @endpoint = endpoint if endpoint
+ @endpoint
+ 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
+
+ self.routes.default_scope = {:module => name}
+
+ self.namespaced = true
+ end
+
+ def namespaced?
+ !!namespaced
+ end
end
- delegate :paths, :root, :to => :config
+ delegate :middleware, :root, :paths, :to => :config
+ delegate :engine_name, :namespaced?, :to => "self.class"
def load_tasks
super
@@ -140,6 +330,47 @@ module Rails
end
end
+ def railties
+ @railties ||= self.class::Railties.new(config)
+ end
+
+ def app
+ @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 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
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ def initializers
+ initializers = []
+ railties.all { |r| initializers += r.initializers }
+ initializers += super
+ 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|
@@ -196,6 +427,27 @@ module Rails
end
end
+ initializer :load_environment_config, :before => :load_environment_hook do
+ environment = config.paths.config.environments.to_a.first
+ require environment if environment
+ 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
+ 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
+
initializer :load_config_initializers do
paths.config.initializers.to_a.sort.each do |initializer|
load(initializer)
@@ -208,6 +460,24 @@ 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
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
deleted file mode 100644
index 9a370f0abb..0000000000
--- a/railties/lib/rails/engine/configurable.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module Rails
- class Engine
- module Configurable
- def self.included(base)
- base.extend ClassMethods
- end
-
- module ClassMethods
- delegate :middleware, :root, :paths, :to => :config
-
- 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
- end
-
- def config
- self.class.config
- end
- end
- end
-end \ No newline at end of file
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 521ed95447..3ac8911ba8 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -5,10 +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, :asset_path
def initialize(root=nil)
super()
@root = root
+ @middleware = Rails::Configuration::MiddlewareStackProxy.new
+ @helpers_paths = []
end
def paths
@@ -26,9 +29,14 @@ 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"
+ paths.vendor "vendor", :load_path => true
+ paths.vendor.plugins "vendor/plugins"
+ paths.db "db"
+ paths.db.migrate "db/migrate"
paths
end
end
@@ -48,6 +56,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/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/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb
index 8d5132a5ca..c07ff2f9cf 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
@@ -39,6 +48,10 @@ module Rails
attr_reader :name, :path
+ def railtie_name
+ name.to_s
+ end
+
def load_tasks
super
load_deprecated_tasks
@@ -78,6 +91,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
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 58b0d851f7..09650789ac 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
@@ -131,25 +130,19 @@ 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
- 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
@@ -171,6 +164,22 @@ module Rails
def abstract_railtie?
ABSTRACT_RAILTIES.include?(name)
end
+
+ def railtie_name(name = nil)
+ @railtie_name = name.to_s if name
+ @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"
+
+ def config
+ @config ||= Railtie::Configuration.new
end
def eager_load!
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/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb
index 4e6f94c534..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)
@@ -78,4 +86,4 @@ module Rails
end
end
end
-end \ No newline at end of file
+end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index d63d25b42e..6bf56f7052 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
@@ -125,22 +124,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|
@@ -277,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/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
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index ecf7904c39..a2abf642b8 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
@@ -72,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!
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 7410a10712..0cc729907e 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
@@ -13,6 +28,7 @@ module RailtiesTest
plugin.write "lib/bukkits.rb", <<-RUBY
class Bukkits
class Engine < ::Rails::Engine
+ railtie_name "bukkits"
end
end
RUBY
@@ -50,5 +66,483 @@ 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
+
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ mount(Bukkits::Engine => "/bukkits")
+ end
+ RUBY
+
+ boot_rails
+
+ env = Rack::MockRequest.env_for("/bukkits")
+ response = Rails.application.call(env)
+
+ 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
+
+ @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
+
+ 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
+ end
+ end
+ RUBY
+
+ @plugin.write "vendor/plugins/yaffle/init.rb", <<-RUBY
+ config.yaffle_loaded = true
+ RUBY
+
+ boot_rails
+
+ 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
+
+ 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
+
+ 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
+
+ test "it allows to set asset_path" do
+ @plugin.write "lib/bukkits.rb", <<-RUBY
+ class Bukkits
+ class Engine < ::Rails::Engine
+ 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"'
+
+ @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")
+
+ expected = "/omg/bukkits/foo\n" +
+ "/omg/bukkits/images/foo.png\n" +
+ "<script src=\"/omg/bukkits/javascripts/foo.js\" type=\"text/javascript\"></script>\n" +
+ "<link href=\"/omg/bukkits/stylesheets/foo.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />"
+ 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"
+ app_file "public/bukkits/file_from_app.html", "/bukkits/file_from_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")
+
+ 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 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
+
+ app_file "app/helpers/some_helper.rb", <<-RUBY
+ module SomeHelper
+ def something
+ "Something... Something... Something..."
+ end
+ 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
+ render :inline => "<%= something %>"
+ end
+
+ def show
+ render :text => foo_path
+ end
+
+ def bar
+ render :inline => "<%= bar %>"
+ 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
+
+ env = Rack::MockRequest.env_for("/foo/bar")
+ response = Rails.application.call(env)
+ assert_equal "It's a bar.", 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
+ namespace Bukkits
+ end
+ 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"
+ mount Bukkits::Engine => "/bukkits", :as => "bukkits"
+ end
+ RUBY
+
+ @plugin.write "config/routes.rb", <<-RUBY
+ Bukkits::Engine.routes.draw 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
+ 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
+
+ def polymorphic_path_without_namespace
+ render :text => polymorphic_path(Post.new)
+ end
+ end
+ RUBY
+
+ @plugin.write "app/mailers/bukkits/my_mailer.rb", <<-RUBY
+ module Bukkits
+ class MyMailer < ActionMailer::Base
+ end
+ end
+ RUBY
+
+ add_to_config("config.action_dispatch.show_exceptions = false")
+
+ 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)
+
+ 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
+
+ env = Rack::MockRequest.env_for("/bukkits/polymorphic_path_without_namespace")
+ 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
+ resources :posts
+ 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
diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb
new file mode 100644
index 0000000000..36dd01198f
--- /dev/null
+++ b/railties/test/railties/mounted_engine_test.rb
@@ -0,0 +1,174 @@
+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 "/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
+ root :to => 'main#index'
+ 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
+ namespace(Blog)
+ end
+ end
+ RUBY
+
+ @plugin.write "config/routes.rb", <<-RUBY
+ Blog::Engine.routes.draw do
+ resources :posts
+ match '/generate_application_route', :to => 'posts#generate_application_route'
+ match '/application_route_in_view', :to => 'posts#application_route_in_view'
+ end
+ RUBY
+
+ @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 application_route_in_view
+ render :inline => "<%= app.root_path %>"
+ end
+ end
+ end
+ RUBY
+
+ app_file "app/controllers/application_generating_controller.rb", <<-RUBY
+ class ApplicationGeneratingController < ActionController::Base
+ def engine_route
+ render :text => blog.posts_path
+ end
+
+ def engine_route_in_view
+ render :inline => "<%= blog.posts_path %>"
+ end
+
+ 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
+
+ boot_rails
+ end
+
+ def app
+ @app ||= begin
+ require "#{app_path}/config/environment"
+ Rails.application
+ 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 "/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
+
+ # 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
+
+ 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'
+ 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
+
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
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"