提交 23a94559 编写于 作者: J José Valim

This commit merges most of the work done by Piotr Sarnacki in his Ruby Summer of Code project.

His work brings several capabilities from app to engines, as routes, middleware stack, asset handling and much more. Please check Rails::Engine documentation for more refenrences.

Merge remote branch 'drogus/engines'
......@@ -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
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
......@@ -24,4 +24,5 @@ module AbstractController
autoload :Translation
autoload :AssetPaths
autoload :ViewPaths
autoload :UrlFor
end
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
......@@ -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
......
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
......@@ -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
......@@ -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
# <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>.
......
......@@ -101,8 +101,12 @@ def modules_for_helpers(args)
# Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt>
def all_application_helpers
all_helpers_from_path(helpers_path)
end
def all_helpers_from_path(path)
helpers = []
Array.wrap(helpers_path).each do |path|
Array.wrap(path).each do |path|
extract = /^#{Regexp.quote(path.to_s)}\/?(.*)_helper.rb$/
helpers += Dir["#{path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
end
......
......@@ -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
......@@ -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
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
require "active_support/inflector/methods"
require "active_support/dependencies"
module ActionDispatch
class MiddlewareStack < Array
......
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
......@@ -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'
......
......@@ -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
......
......@@ -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
......
......@@ -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)
......
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
......@@ -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
......@@ -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'
......
......@@ -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)
......
......@@ -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
......
......@@ -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)
......
......@@ -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|
......
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
......@@ -2151,3 +2151,32 @@ def expected_redirect_body(url)
%(<html><body>You are being <a href="#{ERB::Util.h(url)}">redirected</a>.</body></html>)
end
end
class TestDefaultScope < ActionController::IntegrationTest
module ::Blog
class PostsController < ActionController::Base
def index
render :text => "blog/posts#index"
end
end
end
DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new
DefaultScopeRoutes.default_scope = {:module => :blog}
DefaultScopeRoutes.draw do
resources :posts
end
def app
DefaultScopeRoutes
end
include DefaultScopeRoutes.url_helpers
def test_default_scope
get '/posts'
assert_equal "blog/posts#index", @response.body
end
end
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
......@@ -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
/blog/blog.html
\ No newline at end of file
/blog/index.html
\ No newline at end of file
/blog/subdir/index.html
\ No newline at end of file
......@@ -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
......@@ -387,6 +387,15 @@ def test_string_asset_id
assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
end
def test_env_asset_path
@controller.config.asset_path = "/assets%s"
def @controller.env; @_env ||= {} end
@controller.env["action_dispatch.asset_path"] = "/omg%s"
expected_path = "/assets/omg/images/rails.png"
assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
end
def test_proc_asset_id
@controller.config.asset_path = Proc.new do |asset_path|
"/assets.v12345#{asset_path}"
......@@ -396,6 +405,20 @@ def test_proc_asset_id
assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
end
def test_env_proc_asset_path
@controller.config.asset_path = Proc.new do |asset_path|
"/assets.v12345#{asset_path}"
end
def @controller.env; @_env ||= {} end
@controller.env["action_dispatch.asset_path"] = Proc.new do |asset_path|
"/omg#{asset_path}"
end
expected_path = "/assets.v12345/omg/images/rails.png"
assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png")
end
def test_image_tag_interpreting_email_cid_correctly
# An inline image has no need for an alt tag to be automatically generated from the cid:
assert_equal '<img src="cid:thi%25%25sis@acontentid" />', image_tag("cid:thi%25%25sis@acontentid")
......
......@@ -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 =
"<form accept-charset='UTF-8' action='http://www.example.com' id='create-post' method='post'>" +
"<form accept-charset='UTF-8' action='/' id='create-post' method='post'>" +
snowman +
"<label for='post_title'>The Title</label>" +
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
......@@ -653,6 +677,21 @@ 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 =
"<form accept-charset='UTF-8' action='/posts/44' method='post'>" +
snowman +
"<label for='post_title'>The Title</label>" +
"<input name='post[title]' size='30' type='text' id='post_title' value='And his name will be forty and four.' />" +
"<input name='commit' id='post_submit' type='submit' value='Edit post' />" +
"</form>"
end
def test_form_for_with_symbol_object_name
form_for(@post, :as => "other_name", :html => { :id => 'create-post' }) do |f|
concat f.label(:title, :class => 'post_title')
......@@ -683,7 +722,7 @@ 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
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
......@@ -702,7 +741,7 @@ 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
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
......@@ -721,7 +760,7 @@ 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
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
......@@ -738,7 +777,7 @@ 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
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
......@@ -1478,7 +1517,7 @@ def test_form_for_and_fields_for
end
expected =
"<form accept-charset='UTF-8' action='http://www.example.com' id='create-post' method='post'>" +
"<form accept-charset='UTF-8' action='/' id='create-post' method='post'>" +
snowman +
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
......@@ -1502,7 +1541,7 @@ 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
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' size='30' />"
......@@ -1546,7 +1585,7 @@ def snowman(method = nil)
txt << %{</div>}
end
def form_text(action = "http://www.example.com", id = nil, html_class = nil, remote = nil)
def form_text(action = "/", id = nil, html_class = nil, remote = nil)
txt = %{<form accept-charset="UTF-8" action="#{action}"}
txt << %{ data-remote="true"} if remote
txt << %{ class="#{html_class}"} if html_class
......@@ -1554,7 +1593,7 @@ def form_text(action = "http://www.example.com", id = nil, html_class = nil, rem
txt << %{ method="post">}
end
def whole_form(action = "http://www.example.com", id = nil, html_class = nil, options = nil)
def whole_form(action = "/", id = nil, html_class = nil, options = nil)
contents = block_given? ? yield : ""
if options.is_a?(Hash)
......@@ -1655,7 +1694,7 @@ 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
......
......@@ -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 '<a href="/people/1">David</a>', link_to_person(person)
end
end
......
......@@ -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
......
......@@ -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"
......
module Blog
def self._railtie
Object.new
end
def self.table_name_prefix
"blog_"
end
class Post
extend ActiveModel::Naming
end
end
......@@ -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
......
......@@ -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:"
......
......@@ -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
......@@ -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
......@@ -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
class PeopleHaveLastNames < ActiveRecord::Migration
def self.up
add_column "people", "hobbies", :text
end
def self.down
remove_column "people", "hobbies"
end
end
class PeopleHaveLastNames < ActiveRecord::Migration
def self.up
add_column "people", "description", :text
end
def self.down
remove_column "people", "description"
end
end
class PeopleHaveLastNames < ActiveRecord::Migration
def self.up
add_column "people", "hobbies", :text
end
def self.down
remove_column "people", "hobbies"
end
end
class PeopleHaveLastNames < ActiveRecord::Migration
def self.up
add_column "people", "description", :text
end
def self.down
remove_column "people", "description"
end
end
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
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
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
......@@ -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
......@@ -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
......
......@@ -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
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
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
......
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
......@@ -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
......@@ -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
......
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
......@@ -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
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
......@@ -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
......
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!
......
......@@ -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
......
......@@ -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
......@@ -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
......@@ -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
......
......@@ -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!
......
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" +
"<script src=\"/omg/bukkits/javascripts/foo.js\" type=\"text/javascript\"></script>\n" +
"<link href=\"/omg/bukkits/stylesheets/foo.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />"
assert_equal expected, stripped_body
end
test "engine's files are served via ActionDispatch::Static" do
add_to_config "config.serve_static_assets = true"
@plugin.write "lib/bukkits.rb", <<-RUBY
class Bukkits
class Engine < ::Rails::Engine
engine_name :bukkits
end
end
RUBY
@plugin.write "public/bukkits.html", "/bukkits/bukkits.html"
app_file "public/app.html", "/app.html"
app_file "public/bukkits/file_from_app.html", "/bukkits/file_from_app.html"
boot_rails
env = Rack::MockRequest.env_for("/app.html")
response = Rails.application.call(env)
assert_equal response[2].path, File.join(app_path, "public/app.html")
env = Rack::MockRequest.env_for("/bukkits/bukkits.html")
response = Rails.application.call(env)
assert_equal response[2].path, File.join(@plugin.path, "public/bukkits.html")
env = Rack::MockRequest.env_for("/bukkits/file_from_app.html")
response = Rails.application.call(env)
assert_equal response[2].path, File.join(app_path, "public/bukkits/file_from_app.html")
end
test "shared engine should include application's helpers and own helpers" do
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
match "/foo" => "bukkits/foo#index", :as => "foo"
match "/foo/show" => "bukkits/foo#show"
match "/foo/bar" => "bukkits/foo#bar"
end
RUBY
app_file "app/helpers/some_helper.rb", <<-RUBY
module SomeHelper
def something
"Something... Something... Something..."
end
end
RUBY
@plugin.write "app/helpers/bar_helper.rb", <<-RUBY
module BarHelper
def bar
"It's a bar."
end
end
RUBY
@plugin.write "app/controllers/bukkits/foo_controller.rb", <<-RUBY
class Bukkits::FooController < ActionController::Base
def index
render :inline => "<%= something %>"
end
def show
render :text => foo_path
end
def bar
render :inline => "<%= bar %>"
end
end
RUBY
boot_rails
env = Rack::MockRequest.env_for("/foo")
response = Rails.application.call(env)
assert_equal "Something... Something... Something...", response[2].body
env = Rack::MockRequest.env_for("/foo/show")
response = Rails.application.call(env)
assert_equal "/foo", response[2].body
env = Rack::MockRequest.env_for("/foo/bar")
response = Rails.application.call(env)
assert_equal "It's a bar.", response[2].body
end
test "isolated engine should include only its own routes and helpers" do
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
class Engine < ::Rails::Engine
namespace Bukkits
end
end
RUBY
@plugin.write "app/models/bukkits/post.rb", <<-RUBY
module Bukkits
class Post
extend ActiveModel::Naming
def to_param
"1"
end
end
end
RUBY
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
match "/bar" => "bar#index", :as => "bar"
mount Bukkits::Engine => "/bukkits", :as => "bukkits"
end
RUBY
@plugin.write "config/routes.rb", <<-RUBY
Bukkits::Engine.routes.draw do
match "/foo" => "foo#index", :as => "foo"
match "/foo/show" => "foo#show"
match "/from_app" => "foo#from_app"
match "/routes_helpers_in_view" => "foo#routes_helpers_in_view"
match "/polymorphic_path_without_namespace" => "foo#polymorphic_path_without_namespace"
resources :posts
end
RUBY
app_file "app/helpers/some_helper.rb", <<-RUBY
module SomeHelper
def something
"Something... Something... Something..."
end
end
RUBY
@plugin.write "app/helpers/engine_helper.rb", <<-RUBY
module EngineHelper
def help_the_engine
"Helped."
end
end
RUBY
@plugin.write "app/controllers/bukkits/foo_controller.rb", <<-RUBY
class Bukkits::FooController < ActionController::Base
def index
render :inline => "<%= help_the_engine %>"
end
def show
render :text => foo_path
end
def from_app
render :inline => "<%= (self.respond_to?(:bar_path) || self.respond_to?(:something)) %>"
end
def routes_helpers_in_view
render :inline => "<%= foo_path %>, <%= app.bar_path %>"
end
def polymorphic_path_without_namespace
render :text => polymorphic_path(Post.new)
end
end
RUBY
@plugin.write "app/mailers/bukkits/my_mailer.rb", <<-RUBY
module Bukkits
class MyMailer < ActionMailer::Base
end
end
RUBY
add_to_config("config.action_dispatch.show_exceptions = false")
boot_rails
assert_equal "bukkits_", Bukkits.table_name_prefix
assert_equal "bukkits", Bukkits::Engine.engine_name
assert_equal Bukkits._railtie, Bukkits::Engine
assert ::Bukkits::MyMailer.method_defined?(:foo_path)
assert !::Bukkits::MyMailer.method_defined?(:bar_path)
env = Rack::MockRequest.env_for("/bukkits/from_app")
response = AppTemplate::Application.call(env)
assert_equal "false", response[2].body
env = Rack::MockRequest.env_for("/bukkits/foo/show")
response = AppTemplate::Application.call(env)
assert_equal "/bukkits/foo", response[2].body
env = Rack::MockRequest.env_for("/bukkits/foo")
response = AppTemplate::Application.call(env)
assert_equal "Helped.", response[2].body
env = Rack::MockRequest.env_for("/bukkits/routes_helpers_in_view")
response = AppTemplate::Application.call(env)
assert_equal "/bukkits/foo, /bar", response[2].body
env = Rack::MockRequest.env_for("/bukkits/polymorphic_path_without_namespace")
response = AppTemplate::Application.call(env)
assert_equal "/bukkits/posts/1", response[2].body
end
test "isolated engine should avoid namespace in names if that's possible" do
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
class Engine < ::Rails::Engine
namespace Bukkits
end
end
RUBY
@plugin.write "app/models/bukkits/post.rb", <<-RUBY
module Bukkits
class Post
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_accessor :title
def to_param
"1"
end
def persisted?
false
end
end
end
RUBY
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
mount Bukkits::Engine => "/bukkits", :as => "bukkits"
end
RUBY
@plugin.write "config/routes.rb", <<-RUBY
Bukkits::Engine.routes.draw do
resources :posts
end
RUBY
@plugin.write "app/controllers/bukkits/posts_controller.rb", <<-RUBY
class Bukkits::PostsController < ActionController::Base
def new
end
end
RUBY
@plugin.write "app/views/bukkits/posts/new.html.erb", <<-RUBY
<%= form_for(Bukkits::Post.new) do |f| %>
<%= f.text_field :title %>
<% end %>
RUBY
add_to_config("config.action_dispatch.show_exceptions = false")
boot_rails
env = Rack::MockRequest.env_for("/bukkits/posts/new")
response = AppTemplate::Application.call(env)
assert response[2].body =~ /name="post\[title\]"/
end
end
end
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
......@@ -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
......
......@@ -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"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册