diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 31e1b26afd54b909c40d6eb27e9e076a1b93aa5d..6ae3940e6d10ba357ba306e8c8f1778063a65969 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -361,7 +361,6 @@ class Base < AbstractController::Base }.freeze class << self - def mailer_name @mailer_name ||= name.underscore end @@ -725,28 +724,6 @@ def insert_part(container, response, charset) #: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 ce6d8cc5b5b230729f5bc622f547ac9a23d966c1..a2b00addc9c9026523dfbec22c59b39e24eb69fc 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 @@ class Railtie < Rails::Railtie 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 f8fc79936f54644db7d2fb273c0a5286f725a394..cc5878c88ea5c37b0cdcc5ab8181c6122e98d2f5 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 0000000000000000000000000000000000000000..dec1e9d6d92e87f0c57ab0e52214428636f2d63f --- /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 b81d5954eb9c8a2b8335245438bcd7682e3aa509..5d9b35d297a3f3488ac9ae803bde2318731d37ba 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -52,6 +52,7 @@ def view_context_class 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 0000000000000000000000000000000000000000..2e9de22ecda95caa15b2bb73f2d9333380e6892e --- /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 7a1464c2aae0b8f0080550ee7fd60b630e9622b3..b37bc0212788d935935b59c9fa5d6c7bc8441c1b 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -221,11 +221,6 @@ def self.without_modules(*modules) # 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 96ac138ba3ea3e05cb326eba45b8a23985e8ccfb..def28a005494a752371c7328ae618250e61498ff 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -52,7 +52,11 @@ def build(action, app=nil, &block) class Metal < AbstractController::Base abstract! - attr_internal :env + attr_internal_writer :env + + def env + @_env ||= {} + end # Returns the last part of the controller's name, underscored, without the ending # Controller. For instance, PostsController returns posts. diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 4b6897c5dd82bf48fdb75bdb9ca16c5fb0dd973f..c5d7842db3c9939b26d8b1be3e5c23373abe8948 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -101,8 +101,12 @@ def modules_for_helpers(args) # Extract helper names from files in app/helpers/**/*_helper.rb def all_application_helpers + all_helpers_from_path(helpers_path) + end + + def all_helpers_from_path(path) helpers = [] - Array.wrap(helpers_path).each do |path| + Array.wrap(path).each do |path| extract = /^#{Regexp.quote(path.to_s)}\/?(.*)_helper.rb$/ helpers += Dir["#{path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') } end diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index a51fc5b8e4e1f21897eb8eb4d78a2e8896424c9b..85c6b0a9b544dd42e111974a8cd10387149ad27c 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 cd2dfafbe6898aa09add991d25ea955484468f6d..0cb40418550817f14141a81d1dfd55edd5194d82 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -4,6 +4,8 @@ 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 @@ class Railtie < Rails::Railtie 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 @@ class Railtie < Rails::Railtie 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 0000000000000000000000000000000000000000..fa71d55946d8ecac11630353b1ab856b875ed475 --- /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 5a029a60d1f8f05b155770a2af60e75eddf62f2f..6f243574e45300f74d6e8d52b78145b14225db3e 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 d7e88a54e478cf498d2f9e9542e16c014d1105f5..581cadbeb49a8fcb84862820b7d0ea924624d680 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 @@ def call(env) 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 @@ def call(env) 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 0b9689dc881397033f9ff777dd6b928226a624ce..b2b0f4c08e3c376fff4d778dd6243a10b01efb06 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -268,6 +268,7 @@ module Routing 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 a2570cb877f319af97ff80d3d069732a2491020a..900900ee24145443ff2aebc3a3a0a89a7fbbf97c 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -261,7 +261,11 @@ def mount(app, options = nil) 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 @@ def default_url_options=(options) @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 fb2118a8d769849b331d1e18fa05e6393991a5fd..02ba5236ee72c9b1c324be28527e9d8da43f2dac 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -42,6 +42,18 @@ module Routing # # 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 PolymorphicRoutes 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 @@ def polymorphic_url(record_or_hash_or_array, options = {}) 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 @@ def build_named_route_call(records, inflection, options = {}) 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 @@ def build_named_route_call(records, inflection, options = {}) 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 b531cc1a8e3d39c932bc0bcf5123cd93dc68aced..107e44287de18ec94f130e3e567d5b0dd14778cf 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -158,10 +158,17 @@ def define_hash_access(route, name, kind, options) # 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 @@ def define_url_helper(route, name, kind, options) @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 @@ def draw(&block) 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 @@ def install_helpers(destinations = [ActionController::Base, ActionView::Base], r 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 @@ class << self singleton_class.send(:define_method, :_routes) { routes } end - define_method(:_routes) { routes } + define_method(:_routes) { @_routes || routes } end helpers @@ -303,10 +332,9 @@ def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil 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 @@ def generate 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 @@ def generate(options, recall = {}, extras = false) 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 @@ def url_for(options) 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 @@ def url_for(options) 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 0000000000000000000000000000000000000000..f7d5f6397d0be267b13f77e75805f2f2c16b5e76 --- /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 28ec830fe8a1b0e955c0fa979d97ebacdb7950b1..e836cf7c8e26f52d9153be7ae969f28e0dcaba4a 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -133,6 +133,18 @@ def url_for(options = nil) 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 b3e67f6e36582e0fa65d6f3cb32dba65a0007947..c587a36930757e62d4dfeb5302d73c5265124e69 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -10,7 +10,7 @@ def self.new(env = {}) 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 a3c43d3e9323b82097cbf68bef5074deda2c01de..3329a8b368b0d49d15d2860cb27811e6f10fcb66 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 @@ def compute_public_path(source, dir, ext = nil, include_host = true) 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 94dc25eb85b9b24f12a8d9b39e39f6ea379d4342..43dbedc448f8a39453cc9d870877cd59f477b311 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -304,12 +304,12 @@ def form_for(record_or_name_or_array, *args, &proc) 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 @@ def fields_for(record, record_object = nil, options = nil, &block) 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 @@ def fields_for(record_or_name_or_array, *args, &block) 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 b8df2d9a69b4580c131b37a1026ed757721d3431..555be6ed2b117b5a1254c635f91cc596a67f300d 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -22,6 +22,10 @@ module UrlHelper 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 90a1ef982c350010d99772009aed1aadfba990a8..448aaa5eeef2436db426fe44246b01a56a7325ee 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 @@ def setup @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 @@ def test_uncountable_resource 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 0000000000000000000000000000000000000000..3b47a1b72d7258a81bd77590546a50a6f6459624 --- /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 c90c1041ed2b98f39c7878d723719b9f1b117531..b642adc06bc450c019a56acddd0bf5fe9037dd66 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2151,3 +2151,32 @@ def expected_redirect_body(url) %(You are being redirected.) end end + + +class TestDefaultScope < ActionController::IntegrationTest + module ::Blog + class PostsController < ActionController::Base + def index + render :text => "blog/posts#index" + end + end + end + + DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new + DefaultScopeRoutes.default_scope = {:module => :blog} + DefaultScopeRoutes.draw do + resources :posts + end + + def app + DefaultScopeRoutes + end + + include DefaultScopeRoutes.url_helpers + + def test_default_scope + get '/posts' + assert_equal "blog/posts#index", @response.body + end +end + diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index e6957bb0ea1b370a782d53aa9143a9059e036e35..2eb82fc5d897f028f2a463812f4a85cfaa4e0d17 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 f83651d5835c9eaeac45c988b04ce8ba213ebccb..2b54bc62b0e5a8e35c45acec47100f42d84ef419 100644 --- a/actionpack/test/dispatch/url_generation_test.rb +++ b/actionpack/test/dispatch/url_generation_test.rb @@ -31,7 +31,7 @@ def app 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 @@ def app end end end + diff --git a/actionpack/test/fixtures/blog_public/.gitignore b/actionpack/test/fixtures/blog_public/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..312e635ee658be737380ccb8e62c7b29ad8dabd2 --- /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 0000000000000000000000000000000000000000..79ad44c010a0d990aa4e8a68d7e5253fefcadbfa --- /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 0000000000000000000000000000000000000000..2de3825481fa324355dd9575a46737134c93924c --- /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 0000000000000000000000000000000000000000..517bded33514eb381d919ac0fb5b86fa536b51fd --- /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 bf3e175f1fc6edcc8acb79d3f321c8697b9d8388..c4127ee699c7c02e67ba6f272cb03e56242efce4 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -83,7 +83,7 @@ def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end 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 6d5e4893c4704d30ff13d05ef6a3691ad66ee7aa..2b83cfe1a9ceeced537685caa854937c841f7efb 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -387,6 +387,15 @@ def test_string_asset_id assert_equal %(Rails), image_tag("rails.png") end + def test_env_asset_path + @controller.config.asset_path = "/assets%s" + def @controller.env; @_env ||= {} end + @controller.env["action_dispatch.asset_path"] = "/omg%s" + + expected_path = "/assets/omg/images/rails.png" + assert_equal %(Rails), image_tag("rails.png") + end + def test_proc_asset_id @controller.config.asset_path = Proc.new do |asset_path| "/assets.v12345#{asset_path}" @@ -396,6 +405,20 @@ def test_proc_asset_id assert_equal %(Rails), image_tag("rails.png") end + def test_env_proc_asset_path + @controller.config.asset_path = Proc.new do |asset_path| + "/assets.v12345#{asset_path}" + end + + def @controller.env; @_env ||= {} end + @controller.env["action_dispatch.asset_path"] = Proc.new do |asset_path| + "/omg#{asset_path}" + end + + expected_path = "/assets.v12345/omg/images/rails.png" + assert_equal %(Rails), image_tag("rails.png") + end + def test_image_tag_interpreting_email_cid_correctly # An inline image has no need for an alt tag to be automatically generated from the cid: assert_equal '', image_tag("cid:thi%25%25sis@acontentid") diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 9a1fe01872968fe8ac8f90377791ce4d82c5a804..97a08d45ba2c2e33877bb05282026997b4f469a9 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -75,15 +75,39 @@ def @post.to_param; '123'; end @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 @@ def test_form_for end expected = - "
" + + "" + snowman + "" + "" + @@ -653,6 +677,21 @@ def test_form_for_with_format assert_dom_equal expected, output_buffer end + def test_form_for_with_isolated_namespaced_model + form_for(@blog_post) do |f| + concat f.text_field :title + concat f.submit('Edit post') + end + + expected = + "" + + snowman + + "" + + "" + + "" + + "
" + end + def test_form_for_with_symbol_object_name form_for(@post, :as => "other_name", :html => { :id => 'create-post' }) do |f| concat f.label(:title, :class => 'post_title') @@ -683,7 +722,7 @@ def test_form_for_with_method end end - expected = whole_form("http://www.example.com", "create-post", nil, "put") do + expected = whole_form("/", "create-post", nil, "put") do "" + "" + "" + @@ -702,7 +741,7 @@ def test_form_for_with_remote end end - expected = whole_form("http://www.example.com", "create-post", nil, :method => "put", :remote => true) do + expected = whole_form("/", "create-post", nil, :method => "put", :remote => true) do "" + "" + "" + @@ -721,7 +760,7 @@ def test_form_for_with_remote_without_html end end - expected = whole_form("http://www.example.com", nil, nil, :remote => true) do + expected = whole_form("/", nil, nil, :remote => true) do "" + "" + "" + @@ -738,7 +777,7 @@ def test_form_for_without_object concat f.check_box(:secret) end - expected = whole_form("http://www.example.com", "create-post") do + expected = whole_form("/", "create-post") do "" + "" + "" + @@ -1478,7 +1517,7 @@ def test_form_for_and_fields_for end expected = - "
" + + "" + snowman + "" + "" + @@ -1502,7 +1541,7 @@ def test_form_for_and_fields_for_with_object end expected = - whole_form("http://www.example.com", "create-post") do + whole_form("/", "create-post") do "" + "" + "" @@ -1546,7 +1585,7 @@ def snowman(method = nil) txt << %{} end - def form_text(action = "http://www.example.com", id = nil, html_class = nil, remote = nil) + def form_text(action = "/", id = nil, html_class = nil, remote = nil) txt = %{} end - def whole_form(action = "http://www.example.com", id = nil, html_class = nil, options = nil) + def whole_form(action = "/", id = nil, html_class = nil, options = nil) contents = block_given? ? yield : "" if options.is_a?(Hash) @@ -1655,7 +1694,7 @@ def test_form_for_with_html_options_adds_options_to_form_tag 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 @@ def test_form_for_with_existing_object_in_list @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 @@ def test_form_for_with_existing_object_and_namespace_in_list @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 @@ def test_fields_for_returns_block_result 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 68e790cf46f8db5fca60a9af67321cdc95026a3f..20c96824d6e6bd6f724714197086c7c1727cf779 100644 --- a/actionpack/test/template/test_test.rb +++ b/actionpack/test/template/test_test.rb @@ -39,7 +39,7 @@ def test_link_to_person with_test_route_set do person = mock(:name => "David") person.class.extend ActiveModel::Naming - expects(:mocha_mock_path).with(person).returns("/people/1") + _routes.url_helpers.expects(:hash_for_mocha_mock_path).with(person).returns("/people/1") assert_equal 'David', link_to_person(person) end end diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index d79635cfb3e2e7027247ae9bc7bc18966bff0f6e..dadb1882e41b5edbb2e18cb62dd2fb782d91e3f1 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -2,18 +2,22 @@ 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 @@ def human(options={}) 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 Naming # 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 @@ def self.uncountable?(record_or_class) 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 5a8bff378ae3c13bb534ac9a3b0e5246f9e081f6..c6b663ef93d3345987c1d1c91cdc60acc9356fb2 100644 --- a/activemodel/test/cases/naming_test.rb +++ b/activemodel/test/cases/naming_test.rb @@ -2,6 +2,7 @@ require 'models/contact' require 'models/sheep' require 'models/track_back' +require 'models/blog_post' class NamingTest < ActiveModel::TestCase def setup @@ -29,6 +30,86 @@ def test_partial_path 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 @@ def setup @singular = 'contact' @plural = 'contacts' @uncountable = Sheep + @route_key = 'contacts' + @param_key = 'contact' end def test_singular @@ -54,6 +137,22 @@ def test_plural_for_class 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 0000000000000000000000000000000000000000..d289177259c76639201902c5c9b9423e62ef9618 --- /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 198f0a18cbbab03cda8c6ce37974037455d32e53..e708b3fbcf319299f5a30cd1db4d92db6bfcb86c 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -383,6 +383,37 @@ def method_missing(method, *arguments, &block) 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 @@ def method_missing(method, *arguments, &block) # 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 @@ def load_migration 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 @@ def run(direction, migrations_path, target_version) 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 @@ def proper_table_name(name) 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 @@ def migrate 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 b1aad0d496aa9b5b476e00be425a1f5ffecb1287..aedda26ba5325b4f9558b6509011653379dc25fa 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 26bc977e19ba7a2839201c64c1c2c2306fa83808..4b3d1db216000b7e0bd6c735f6b841225b443173 100644 --- a/activerecord/lib/rails/generators/active_record.rb +++ b/activerecord/lib/rails/generators/active_record.rb @@ -14,6 +14,12 @@ class Base < Rails::Generators::NamedBase #:nodoc: 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 1fb59d358910810fda633818ec82bccd7a81f681..4bf3c25d28b2135569a7e61bf212dd6980688aa4 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -83,3 +83,21 @@ def create_fixtures(*table_names, &block) 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 96b97fdd8aa5539adbfabc040fcca9044c8cebe1..03a8cc90f6709ba78c08e7430cb612470b415f60 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1875,5 +1875,130 @@ def with_change_table 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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 0000000000000000000000000000000000000000..639841f663d41ae45f8f6a12c2b1f0868be428b7 --- /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 0000000000000000000000000000000000000000..b3d0b306401571f8ebf466180a8ef44c6a861f3c --- /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 0000000000000000000000000000000000000000..639841f663d41ae45f8f6a12c2b1f0868be428b7 --- /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 0000000000000000000000000000000000000000..b3d0b306401571f8ebf466180a8ef44c6a861f3c --- /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 0000000000000000000000000000000000000000..81af5fef5e6cf5db1133c058184dc6f2a9f2e053 --- /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 0000000000000000000000000000000000000000..d5e71ce8ef6367aa6433a1e0d508b2fe996a548e --- /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 0000000000000000000000000000000000000000..21c9ca532858e2e786e9789ecd9676f6e3a8ccbd --- /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 7c41367a8450ede42e65ebecc803716570e73ffb..36639102812cbe5b21b8557627b1571e05852d88 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -94,10 +94,5 @@ def version 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 5b263334863aa01d8233f8087ffa67aea8a25e15..8631a5df3ef13b4dd5790cc279be68dcb5d5253b 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -41,24 +41,6 @@ class Application < Engine 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 @@ def inherited(base) 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 @@ def eager_load! #:nodoc: 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 @@ def reload_routes! end def initialize! + raise "Application has been already initialized." if @initialized run_initializers(self) + @initialized = true self end @@ -156,38 +122,32 @@ def load_console(sandbox=false) 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 44e26b5713fd3dde94b6a297c0cbf115a669f1e9..e39b3bc70511b7a75ec8c2e23d6f8b3d0619a51f 100644 --- a/railties/lib/rails/application/bootstrap.rb +++ b/railties/lib/rails/application/bootstrap.rb @@ -6,10 +6,7 @@ class Application 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 Bootstrap 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 f598e339656fb08cd3cc434f395e4acb0367ea09..0000000000000000000000000000000000000000 --- 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 c3418e0d809862bcae47679d51dca3493d1f6d86..477bbbc1e7a7cb96856cbd6240a32e238bfd6c6f 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 @@ def initialize(*) @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 @@ def paths 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 b3e6693f89b3ef6f88c17f1b50f824755a47ea68..c1d2de571fbbd319382260b40bbb777c6ccb9578 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 e5af12b901c9991bd7c33be400fcdf76ed43c304..8369795e71d2d8e89f2c980caa5fc09bcf2f5412 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -71,86 +71,5 @@ def method_missing(method, *args) 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 555bc9dbc82b2c1b108455b1b73473f096b5c278..e10980a6d928b2fe7e3f8144b6c59040e6b8c9bd 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -2,6 +2,7 @@ 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 @@ def find_root_with_flag(flag, default=nil) 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 @@ def eager_load! 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 @@ def eager_load! 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 @@ def eager_load! 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 9a370f0abbadff7a408fead54da734da1e3db6c9..0000000000000000000000000000000000000000 --- 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 521ed9544756dbf7f2fb1f629616a4500a6c646f..3ac8911ba86f3f8ca0f92eacc3267ff879c007ce 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -5,10 +5,13 @@ class Engine 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 @@ def paths 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 @@ def autoload_once_paths 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 0000000000000000000000000000000000000000..389a7602c693c61e7d590659a6efc8834848960d --- /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 8d5132a5ca209de4c832c2ed678229cc163d2b3b..c07ff2f9cf73e4d1f3dac40ba40cf33313eae76b 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 @@ def self.all(list, paths) 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 @@ def self.all(list, paths) attr_reader :name, :path + def railtie_name + name.to_s + end + def load_tasks super load_deprecated_tasks @@ -78,6 +91,8 @@ def config 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 58b0d851f7c7533c08e37004b13354e576c159ba..09650789acae7f91ff08ea6352ed54e51c5064b8 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 @@ class Railtie 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 @@ def generators(&blk) 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 a2eb938c5a4b097966bdef8eab8c20b0a6cd0d61..b6d4ed2312aa524bc4b537e5bae87e444ae47327 100644 --- a/railties/lib/rails/railtie/configurable.rb +++ b/railties/lib/rails/railtie/configurable.rb @@ -6,17 +6,29 @@ def self.included(base) 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 4e6f94c53404b8ee5c15f59d69c3ed0fe2f3598d..f09e3940ccb10762fa6aac46035f56791ee1de5e 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -5,6 +5,7 @@ class Railtie 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 @@ def respond_to?(name) 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 @@ def method_missing(name, *args, &blk) 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 d63d25b42eb91e81a69c91fa473c9a3fe8c96a2a..6bf56f705282b31c67300a35f30060a7425b0b6d 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -26,18 +26,17 @@ def teardown 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 @@ def teardown 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 @@ def index 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 4ff10091b120ffce3cfe0d01cc1dc6794acf988b..6e9ceb6ef75cdc128be5cb7faa138060807de9da 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -61,6 +61,7 @@ def notify 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 ecf7904c391d6ac69cf9a3293575ab7f4638ab31..a2abf642b8bd41490f2fd10597d5c4f0d3a1e087 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -42,6 +42,23 @@ class User < ActiveRecord::Base 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 Post < ActiveRecord::Base 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 7410a10712effb64f951b23073f0df05562fd024..0cc729907e950e3f15808ad21ff2e54852062f19 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 @@ def setup plugin.write "lib/bukkits.rb", <<-RUBY class Bukkits class Engine < ::Rails::Engine + railtie_name "bukkits" end end RUBY @@ -50,5 +66,483 @@ class Engine < ::Rails::Engine 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" + + "\n" + + "" + 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 0000000000000000000000000000000000000000..36dd01198f0c2dfe21868b9f39f768ea73660d6f --- /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 6715003d3d193d0e0e8586b241fd0df953b8ec96..406d5d764f50c0f1e6a89d7bcd26c8eb502337b5 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -19,6 +19,22 @@ def app 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 ce7c55c11c659a9ba0a3f353a1981881aa5907e2..6aae17c23772e5a74c7582cccd7cf52ed6b8ba16 100644 --- a/railties/test/railties/shared_tests.rb +++ b/railties/test/railties/shared_tests.rb @@ -10,6 +10,55 @@ def app @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"