aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/abstract_controller/base.rb13
-rw-r--r--actionpack/lib/action_controller/metal.rb11
-rw-r--r--actionpack/lib/action_controller/metal/rack_delegation.rb4
-rw-r--r--actionpack/lib/action_controller/polymorphic_routes.rb3
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb14
-rw-r--r--[-rwxr-xr-x]actionpack/lib/action_dispatch/http/request.rb0
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb446
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb32
-rw-r--r--actionpack/lib/action_view/render/partials.rb2
-rw-r--r--actionpack/lib/action_view/test_case.rb1
16 files changed, 343 insertions, 205 deletions
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 4d7b4019d8..8a8337858b 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -1,4 +1,5 @@
require 'active_support/configurable'
+require 'active_support/descendants_tracker'
require 'active_support/core_ext/module/anonymous'
module AbstractController
@@ -10,6 +11,7 @@ module AbstractController
attr_internal :action_name
include ActiveSupport::Configurable
+ extend ActiveSupport::DescendantsTracker
class << self
attr_reader :abstract
@@ -21,17 +23,6 @@ module AbstractController
@abstract = true
end
- def inherited(klass)
- ::AbstractController::Base.descendants << klass.to_s
- super
- end
-
- # A list of all descendants of AbstractController::Base. This is
- # useful for initializers which need to add behavior to all controllers.
- def descendants
- @descendants ||= []
- end
-
# A list of all internal methods for a controller. This finds the first
# abstract superclass of a controller, and gets a list of all public
# instance methods on that abstract class. Public instance methods of
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 775a5002e2..159d1f0748 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -52,8 +52,7 @@ module ActionController
class Metal < AbstractController::Base
abstract!
- # :api: public
- attr_internal :params, :env
+ attr_internal :env
# Returns the last part of the controller's name, underscored, without the ending
# "Controller". For instance, MyApp::MyPostsController would return "my_posts" for
@@ -85,6 +84,14 @@ module ActionController
super
end
+ def params
+ @_params ||= request.parameters
+ end
+
+ def params=(val)
+ @_params = val
+ end
+
# Basic implementations for content_type=, location=, and headers are
# provided to reduce the dependency on the RackDelegation module
# in Renderer and Redirector.
diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb
index 508ea6e2b7..544b4989c7 100644
--- a/actionpack/lib/action_controller/metal/rack_delegation.rb
+++ b/actionpack/lib/action_controller/metal/rack_delegation.rb
@@ -14,10 +14,6 @@ module ActionController
super(action, request)
end
- def params
- @_params ||= @_request.parameters
- end
-
def response_body=(body)
response.body = body if response
super
diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb
index 7f2eb4306b..bee50a7a3b 100644
--- a/actionpack/lib/action_controller/polymorphic_routes.rb
+++ b/actionpack/lib/action_controller/polymorphic_routes.rb
@@ -11,7 +11,7 @@ module ActionController
# polymorphic_url([:admin, @article, @comment])
#
# results in:
- #
+ #
# admin_article_comment_url(@article, @comment)
#
# == Usage within the framework
@@ -166,6 +166,7 @@ module ActionController
route << RecordIdentifier.__send__("plural_class_name", record)
route = route.singularize if inflection == :singular
route << "_"
+ route << "index_" if RecordIdentifier.uncountable?(record) && inflection == :plural
end
action_prefix(options) + route + routing_type(options).to_s
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index 907c369218..d20c3b64c5 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -1,7 +1,7 @@
require 'active_support/core_ext/module'
module ActionController
- # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
+ # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
# Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate
# the view actions to a higher logical level. Example:
#
@@ -28,7 +28,7 @@ module ActionController
# end
#
# As the example above shows, you can stop caring to a large extent what the actual id of the post is. You just know
- # that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
+ # that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
# convention and allows you to write less code if you follow it.
module RecordIdentifier
extend self
@@ -59,7 +59,7 @@ module ActionController
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
#
# dom_id(Post.find(45), :edit) # => "edit_post_45"
- def dom_id(record, prefix = nil)
+ def dom_id(record, prefix = nil)
if record_id = record_key_for_dom_id(record)
"#{dom_class(record, prefix)}#{JOIN}#{record_id}"
else
@@ -102,6 +102,14 @@ module ActionController
model_name_from_record_or_class(record_or_class).singular
end
+ # Identifies whether the class name of a record or class is uncountable. Examples:
+ #
+ # uncountable?(Sheep) # => true
+ # uncountable?(Post) => false
+ def uncountable?(record_or_class)
+ plural_class_name(record_or_class) == singular_class_name(record_or_class)
+ end
+
private
def model_name_from_record_or_class(record_or_class)
(record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 98f4f5ae18..98f4f5ae18 100755..100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index d07841218a..e4ae480bfb 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -8,7 +8,7 @@ module ActionDispatch
class Callbacks
include ActiveSupport::Callbacks
- define_callbacks :call, :terminator => "result == false", :rescuable => true
+ define_callbacks :call, :rescuable => true
define_callbacks :prepare, :scope => :name
# Add a preparation callback. Preparation callbacks are run before every
@@ -37,12 +37,12 @@ module ActionDispatch
def initialize(app, prepare_each_request = false)
@app, @prepare_each_request = app, prepare_each_request
- run_callbacks(:prepare)
+ _run_prepare_callbacks
end
def call(env)
- run_callbacks(:call) do
- run_callbacks(:prepare) if @prepare_each_request
+ _run_call_callbacks do
+ _run_prepare_callbacks if @prepare_each_request
@app.call(env)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 0a6d2bfc8a..e095b51342 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -122,7 +122,7 @@ module ActionDispatch
end
def render(status, body)
- [status, {'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s}, [body]]
+ [status, {'Content-Type' => 'text/html', 'Content-Length' => body.bytesize.to_s}, [body]]
end
def public_path
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 38da44d7e7..ed93211255 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -10,7 +10,7 @@ module ActionDispatch
# Prepare dispatcher callbacks and run 'prepare' callbacks
initializer "action_dispatch.prepare_dispatcher" do |app|
- ActionDispatch::Callbacks.to_prepare { app.routes_reloader.reload_if_changed }
+ ActionDispatch::Callbacks.to_prepare { app.routes_reloader.execute_if_updated }
end
end
end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 7b79b6bde3..c31f681411 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -33,7 +33,7 @@ module ActionDispatch
end
class Mapping #:nodoc:
- IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor]
+ IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
def initialize(set, scope, args)
@set, @scope = set, scope
@@ -102,7 +102,7 @@ module ActionDispatch
end
def requirements
- @requirements ||= (@options[:constraints] || {}).tap do |requirements|
+ @requirements ||= (@options[:constraints].is_a?(Hash) ? @options[:constraints] : {}).tap do |requirements|
requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints]
@options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) }
end
@@ -343,7 +343,7 @@ module ActionDispatch
def namespace(path)
path = path.to_s
- scope(:path => path, :name_prefix => path, :module => path) { yield }
+ scope(:path => path, :name_prefix => path, :module => path, :shallow_path => path, :shallow_prefix => path) { yield }
end
def constraints(constraints = {})
@@ -378,10 +378,18 @@ module ActionDispatch
Mapper.normalize_path("#{parent}/#{child}")
end
+ def merge_shallow_path_scope(parent, child)
+ Mapper.normalize_path("#{parent}/#{child}")
+ end
+
def merge_name_prefix_scope(parent, child)
parent ? "#{parent}_#{child}" : child
end
+ def merge_shallow_prefix_scope(parent, child)
+ parent ? "#{parent}_#{child}" : child
+ end
+
def merge_module_scope(parent, child)
parent ? "#{parent}/#{child}" : child
end
@@ -409,11 +417,13 @@ module ActionDispatch
def merge_options_scope(parent, child)
(parent || {}).merge(child)
end
+
+ def merge_shallow_scope(parent, child)
+ child ? true : false
+ end
end
module Resources
- CRUD_ACTIONS = [:index, :show, :create, :update, :destroy] #:nodoc:
-
class Resource #:nodoc:
def self.default_actions
[:index, :create, :new, :show, :update, :destroy, :edit]
@@ -442,15 +452,6 @@ module ActionDispatch
end
end
- def action_type(action)
- case action
- when :index, :create
- :collection
- when :show, :update, :destroy
- :member
- end
- end
-
def name
options[:as] || @name
end
@@ -463,34 +464,19 @@ module ActionDispatch
name.to_s.singularize
end
- def member_prefix
- ':id'
- end
-
def member_name
singular
end
+ alias_method :nested_name, :member_name
+
# Checks for uncountable plurals, and appends "_index" if they're.
def collection_name
- uncountable? ? "#{plural}_index" : plural
+ singular == plural ? "#{plural}_index" : plural
end
- def uncountable?
- singular == plural
- end
-
- def name_for_action(action)
- case action_type(action)
- when :collection
- collection_name
- when :member
- member_name
- end
- end
-
- def id_segment
- ":#{singular}_id"
+ def shallow?
+ options[:shallow] ? true : false
end
def constraints
@@ -506,21 +492,43 @@ module ActionDispatch
end
def collection_options
- (options || {}).dup.tap do |options|
- options.delete(:id)
- options[:constraints] = options[:constraints].dup if options[:constraints]
- options[:constraints].delete(:id) if options[:constraints].is_a?(Hash)
+ (options || {}).dup.tap do |opts|
+ opts.delete(:id)
+ opts[:constraints] = options[:constraints].dup if options[:constraints]
+ opts[:constraints].delete(:id) if options[:constraints].is_a?(Hash)
end
end
- def nested_prefix
- id_segment
+ def nested_path
+ "#{path}/:#{singular}_id"
end
def nested_options
- options = { :name_prefix => member_name }
- options["#{singular}_id".to_sym] = id_constraint if id_constraint?
- options
+ {}.tap do |opts|
+ opts[:name_prefix] = member_name
+ opts["#{singular}_id".to_sym] = id_constraint if id_constraint?
+ opts[:options] = { :shallow => shallow? } unless options[:shallow].nil?
+ end
+ end
+
+ def resource_scope
+ [{ :controller => controller }]
+ end
+
+ def collection_scope
+ [path, collection_options]
+ end
+
+ def member_scope
+ ["#{path}/:id", options]
+ end
+
+ def new_scope
+ [path]
+ end
+
+ def nested_scope
+ [nested_path, nested_options]
end
end
@@ -533,27 +541,28 @@ module ActionDispatch
super
end
- def action_type(action)
- case action
- when :show, :create, :update, :destroy
- :member
- end
+ def member_name
+ name
end
+ alias_method :collection_name, :member_name
- def member_prefix
- ''
+ def nested_path
+ path
end
- def member_name
- name
+ def nested_options
+ {}.tap do |opts|
+ opts[:name_prefix] = member_name
+ opts[:options] = { :shallow => shallow? } unless @options[:shallow].nil?
+ end
end
- def nested_prefix
- ''
+ def shallow?
+ false
end
- def nested_options
- { :name_prefix => member_name }
+ def member_scope
+ [path, options]
end
end
@@ -565,28 +574,25 @@ module ActionDispatch
def resource(*resources, &block)
options = resources.extract_options!
options = (@scope[:options] || {}).merge(options)
+ options[:shallow] = true if @scope[:shallow] && !options.has_key?(:shallow)
if apply_common_behavior_for(:resource, resources, options, &block)
return self
end
- resource = SingletonResource.new(resources.pop, options)
-
- scope(:path => resource.path, :controller => resource.controller) do
- with_scope_level(:resource, resource) do
+ resource_scope(SingletonResource.new(resources.pop, options)) do
+ yield if block_given?
- yield if block_given?
+ collection_scope do
+ post :create if parent_resource.actions.include?(:create)
+ get :new if parent_resource.actions.include?(:new)
+ end
- with_scope_level(:member) do
- scope(resource.options) do
- get :show if resource.actions.include?(:show)
- post :create if resource.actions.include?(:create)
- put :update if resource.actions.include?(:update)
- delete :destroy if resource.actions.include?(:destroy)
- get :new, :as => resource.name if resource.actions.include?(:new)
- get :edit, :as => resource.name if resource.actions.include?(:edit)
- end
- end
+ member_scope do
+ get :show if parent_resource.actions.include?(:show)
+ put :update if parent_resource.actions.include?(:update)
+ delete :destroy if parent_resource.actions.include?(:destroy)
+ get :edit if parent_resource.actions.include?(:edit)
end
end
@@ -596,35 +602,26 @@ module ActionDispatch
def resources(*resources, &block)
options = resources.extract_options!
options = (@scope[:options] || {}).merge(options)
+ options[:shallow] = true if @scope[:shallow] && !options.has_key?(:shallow)
if apply_common_behavior_for(:resources, resources, options, &block)
return self
end
- resource = Resource.new(resources.pop, options)
-
- scope(:path => resource.path, :controller => resource.controller) do
- with_scope_level(:resources, resource) do
- yield if block_given?
+ resource_scope(Resource.new(resources.pop, options)) do
+ yield if block_given?
- with_scope_level(:collection) do
- scope(resource.collection_options) do
- get :index if resource.actions.include?(:index)
- post :create if resource.actions.include?(:create)
- get :new, :as => resource.singular if resource.actions.include?(:new)
- end
- end
+ collection_scope do
+ get :index if parent_resource.actions.include?(:index)
+ post :create if parent_resource.actions.include?(:create)
+ get :new if parent_resource.actions.include?(:new)
+ end
- with_scope_level(:member) do
- scope(':id') do
- scope(resource.options) do
- get :show if resource.actions.include?(:show)
- put :update if resource.actions.include?(:update)
- delete :destroy if resource.actions.include?(:destroy)
- get :edit, :as => resource.singular if resource.actions.include?(:edit)
- end
- end
- end
+ member_scope do
+ get :show if parent_resource.actions.include?(:show)
+ put :update if parent_resource.actions.include?(:update)
+ delete :destroy if parent_resource.actions.include?(:destroy)
+ get :edit if parent_resource.actions.include?(:edit)
end
end
@@ -636,10 +633,8 @@ module ActionDispatch
raise ArgumentError, "can't use collection outside resources scope"
end
- with_scope_level(:collection) do
- scope(:name_prefix => parent_resource.collection_name, :as => "") do
- yield
- end
+ collection_scope do
+ yield
end
end
@@ -648,10 +643,8 @@ module ActionDispatch
raise ArgumentError, "can't use member outside resource(s) scope"
end
- with_scope_level(:member) do
- scope(parent_resource.member_prefix, :name_prefix => parent_resource.member_name, :as => "") do
- yield
- end
+ member_scope do
+ yield
end
end
@@ -659,10 +652,12 @@ module ActionDispatch
unless resource_scope?
raise ArgumentError, "can't use new outside resource(s) scope"
end
-
+
with_scope_level(:new) do
- scope(new_scope_prefix, :name_prefix => parent_resource.member_name, :as => "") do
- yield
+ scope(*parent_resource.new_scope) do
+ scope(action_path(:new)) do
+ yield
+ end
end
end
end
@@ -673,8 +668,18 @@ module ActionDispatch
end
with_scope_level(:nested) do
- scope(parent_resource.nested_prefix, parent_resource.nested_options) do
- yield
+ if parent_resource.shallow?
+ with_exclusive_scope do
+ if @scope[:shallow_path].blank?
+ scope(*parent_resource.nested_scope) { yield }
+ else
+ scope(@scope[:shallow_path], :name_prefix => @scope[:shallow_prefix]) do
+ scope(*parent_resource.nested_scope) { yield }
+ end
+ end
+ end
+ else
+ scope(*parent_resource.nested_scope) { yield }
end
end
end
@@ -687,63 +692,79 @@ module ActionDispatch
end
end
+ def shallow
+ scope(:shallow => true) do
+ yield
+ end
+ end
+
def match(*args)
options = args.extract_options!
options[:anchor] = true unless options.key?(:anchor)
if args.length > 1
- args.each { |path| match(path, options) }
+ args.each { |path| match(path, options.dup) }
return self
end
- path_names = options.delete(:path_names)
+ if [:collection, :member, :new].include?(options[:on])
+ args.push(options)
- if args.first.is_a?(Symbol)
- action = args.first
- if CRUD_ACTIONS.include?(action)
- begin
- old_path = @scope[:path]
- @scope[:path] = "#{@scope[:path]}(.:format)"
- return match(options.reverse_merge(
- :to => action,
- :as => parent_resource.name_for_action(action)
- ))
- ensure
- @scope[:path] = old_path
- end
- else
- with_exclusive_name_prefix(action_name_prefix(action, options)) do
- return match("#{action_path(action, path_names)}(.:format)", options.reverse_merge(:to => action))
- end
+ case options.delete(:on)
+ when :collection
+ return collection { match(*args) }
+ when :member
+ return member { match(*args) }
+ when :new
+ return new { match(*args) }
end
end
- args.push(options)
-
- case options.delete(:on)
- when :collection
- return collection { match(*args) }
- when :member
+ if @scope[:scope_level] == :resource
+ args.push(options)
return member { match(*args) }
- when :new
- return new { match(*args) }
end
- if @scope[:scope_level] == :resource
- return member { match(*args) }
+ path_names = options.delete(:path_names)
+
+ if args.first.is_a?(Symbol)
+ path = path_for_action(args.first, path_names)
+ options = options_for_action(args.first, options)
+
+ with_exclusive_scope do
+ return super(path, options)
+ end
+ elsif resource_method_scope?
+ path = path_for_custom_action
+ options[:as] = name_for_action(options[:as]) if options[:as]
+ args.push(options)
+
+ with_exclusive_scope do
+ scope(path) do
+ return super
+ end
+ end
end
if resource_scope?
raise ArgumentError, "can't define route directly in resource(s) scope"
end
+ args.push(options)
super
end
def root(options={})
- options[:on] ||= :collection if @scope[:scope_level] == :resources
- super(options)
+ if @scope[:scope_level] == :resources
+ with_scope_level(:nested) do
+ scope(parent_resource.path, :name_prefix => parent_resource.collection_name) do
+ super(options)
+ end
+ end
+ else
+ super(options)
+ end
end
protected
@@ -752,15 +773,6 @@ module ActionDispatch
end
private
- def action_path(name, path_names = nil)
- path_names ||= @scope[:path_names]
- path_names[name.to_sym] || name.to_s
- end
-
- def action_name_prefix(action, options = {})
- (options[:on] == :new || @scope[:scope_level] == :new) ? "#{action}_new" : action
- end
-
def apply_common_behavior_for(method, resources, options, &block)
if resources.length > 1
resources.each { |r| send(method, r, options, &block) }
@@ -784,27 +796,24 @@ module ActionDispatch
false
end
- def new_scope_prefix
- @scope[:path_names][:new] || 'new'
- end
-
def resource_scope?
[:resource, :resources].include?(@scope[:scope_level])
end
- def with_exclusive_name_prefix(prefix)
+ def resource_method_scope?
+ [:collection, :member, :new].include?(@scope[:scope_level])
+ end
+
+ def with_exclusive_scope
begin
- old_name_prefix = @scope[:name_prefix]
+ old_name_prefix, old_path = @scope[:name_prefix], @scope[:path]
+ @scope[:name_prefix], @scope[:path] = nil, nil
- if !old_name_prefix.blank?
- @scope[:name_prefix] = "#{prefix}_#{@scope[:name_prefix]}"
- else
- @scope[:name_prefix] = prefix.to_s
+ with_scope_level(:exclusive) do
+ yield
end
-
- yield
ensure
- @scope[:name_prefix] = old_name_prefix
+ @scope[:name_prefix], @scope[:path] = old_name_prefix, old_path
end
end
@@ -816,6 +825,125 @@ module ActionDispatch
@scope[:scope_level] = old
@scope[:scope_level_resource] = old_resource
end
+
+ def resource_scope(resource)
+ with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do
+ scope(*parent_resource.resource_scope) do
+ yield
+ end
+ end
+ end
+
+ def collection_scope
+ with_scope_level(:collection) do
+ scope(*parent_resource.collection_scope) do
+ yield
+ end
+ end
+ end
+
+ def member_scope
+ with_scope_level(:member) do
+ scope(*parent_resource.member_scope) do
+ yield
+ end
+ end
+ end
+
+ def path_for_action(action, path_names)
+ case action
+ when :index, :create
+ "#{@scope[:path]}(.:format)"
+ when :show, :update, :destroy
+ if parent_resource.shallow?
+ "#{@scope[:shallow_path]}/#{parent_resource.path}/:id(.:format)"
+ else
+ "#{@scope[:path]}(.:format)"
+ end
+ when :new
+ "#{@scope[:path]}/#{action_path(:new)}(.:format)"
+ when :edit
+ if parent_resource.shallow?
+ "#{@scope[:shallow_path]}/#{parent_resource.path}/:id/#{action_path(:edit)}(.:format)"
+ else
+ "#{@scope[:path]}/#{action_path(:edit)}(.:format)"
+ end
+ else
+ case @scope[:scope_level]
+ when :collection, :new
+ "#{@scope[:path]}/#{action_path(action)}(.:format)"
+ else
+ if parent_resource.shallow?
+ "#{@scope[:shallow_path]}/#{parent_resource.path}/:id/#{action_path(action)}(.:format)"
+ else
+ "#{@scope[:path]}/#{action_path(action)}(.:format)"
+ end
+ end
+ end
+ end
+
+ def path_for_custom_action
+ case @scope[:scope_level]
+ when :collection, :new
+ @scope[:path]
+ else
+ if parent_resource.shallow?
+ "#{@scope[:shallow_path]}/#{parent_resource.path}/:id"
+ else
+ @scope[:path]
+ end
+ end
+ end
+
+ def action_path(name, path_names = nil)
+ path_names ||= @scope[:path_names]
+ path_names[name.to_sym] || name.to_s
+ end
+
+ def options_for_action(action, options)
+ options.reverse_merge(
+ :to => action,
+ :as => name_for_action(action)
+ )
+ end
+
+ def name_for_action(action)
+ name_prefix = @scope[:name_prefix].blank? ? "" : "#{@scope[:name_prefix]}_"
+ shallow_prefix = @scope[:shallow_prefix].blank? ? "" : "#{@scope[:shallow_prefix]}_"
+
+ case action
+ when :index, :create
+ "#{name_prefix}#{parent_resource.collection_name}"
+ when :show, :update, :destroy
+ if parent_resource.shallow?
+ "#{shallow_prefix}#{parent_resource.member_name}"
+ else
+ "#{name_prefix}#{parent_resource.member_name}"
+ end
+ when :edit
+ if parent_resource.shallow?
+ "edit_#{shallow_prefix}#{parent_resource.member_name}"
+ else
+ "edit_#{name_prefix}#{parent_resource.member_name}"
+ end
+ when :new
+ "new_#{name_prefix}#{parent_resource.member_name}"
+ else
+ case @scope[:scope_level]
+ when :collection
+ "#{action}_#{name_prefix}#{parent_resource.collection_name}"
+ when :new
+ "#{action}_new_#{name_prefix}#{parent_resource.member_name}"
+ else
+ if parent_resource.shallow?
+ "#{action}_#{shallow_prefix}#{parent_resource.member_name}"
+ else
+ "#{action}_#{name_prefix}#{parent_resource.member_name}"
+ end
+ end
+ end
+ end
+
end
include Base
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 6f387bc95a..7d7b6a1d91 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -896,8 +896,10 @@ module ActionView
# Returns the separator for a given datetime component
def separator(type)
case type
- when :month, :day
- @options[:date_separator]
+ when :month
+ @options[:discard_month] ? "" : @options[:date_separator]
+ when :day
+ @options[:discard_day] ? "" : @options[:date_separator]
when :hour
(@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
when :minute
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index a8887a804e..8efed98bd2 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -303,7 +303,7 @@ module ActionView
args.unshift object
end
- options[:html][:remote] = true if options.delete(:remote)
+ (options[:html] ||= {})[:remote] = true if options.delete(:remote)
output = form_tag(options.delete(:url) || {}, options.delete(:html) || {})
output << fields_for(object_name, *(args << options), &proc)
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index c564d30302..6f9d14de8b 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -399,7 +399,7 @@ module ActionView
options_for_select += "<optgroup label=\"#{html_escape(group_label_string)}\">"
options_for_select += options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key)
options_for_select += '</optgroup>'
- end
+ end.html_safe
end
# Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 9afa989453..0be8a2c36e 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -40,7 +40,10 @@ module ActionView
# for a total length not exceeding <tt>:length</tt>.
#
# Pass a <tt>:separator</tt> to truncate +text+ at a natural break.
- # Pass a <tt>:safe</tt> value as "true" to not to escape the content.
+ #
+ # The result is not marked as HTML-safe, so will be subject to the default escaping when
+ # used in views, unless wrapped by <tt>raw()</tt>. Care should be taken if +text+ contains HTML tags
+ # or entities, because truncation may produce invalid HTML (such as unbalanced or incomplete tags).
#
# ==== Examples
#
@@ -57,12 +60,6 @@ module ActionView
# # => "And they f... (continued)"
#
# truncate("<p>Once upon a time in a world far far away</p>")
- # # => "&lt;p&gt;Once upon a time i..."
- #
- # truncate("<p>Once upon a time in a world far far away</p>", :safe => true)
- # # => "<p>Once upon a time in a wo..."
- #
- # truncate("<p>Once upon a time in a world far far away</p>".html_safe)
# # => "<p>Once upon a time in a wo..."
#
# You can still use <tt>truncate</tt> with the old API that accepts the
@@ -85,7 +82,6 @@ module ActionView
options.reverse_merge!(:length => 30)
- text = h(text) unless text.html_safe? || options[:safe]
text.truncate(options.delete(:length), options) if text
end
@@ -117,13 +113,13 @@ module ActionView
end
options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>')
- text = h(text) unless text.html_safe? || options[:safe]
+ text = sanitize(text) unless options[:sanitize] == false
if text.blank? || phrases.blank?
text
else
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
text.gsub(/(#{match})(?!(?:[^<]*?)(?:["'])[^<>]*>)/i, options[:highlighter])
- end
+ end.html_safe
end
# Extracts an excerpt from +text+ that matches the first instance of +phrase+.
@@ -253,9 +249,9 @@ module ActionView
# simple_format("Look ma! A class!", :class => 'description')
# # => "<p class='description'>Look ma! A class!</p>"
def simple_format(text, html_options={}, options={})
- text = '' if text.nil?
+ text = ''.html_safe if text.nil?
start_tag = tag('p', html_options, true)
- text = h(text) unless text.html_safe? || options[:safe]
+ text = sanitize(text) unless options[:sanitize] == false
text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n
text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph
text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
@@ -499,7 +495,11 @@ module ActionView
link_text = block_given?? yield(href) : href
href = 'http://' + href unless scheme
- content_tag(:a, link_text, link_attributes.merge('href' => href), !(options[:safe] || text.html_safe?)) + punctuation.reverse.join('')
+ unless options[:sanitize] == false
+ link_text = sanitize(link_text)
+ href = sanitize(href)
+ end
+ content_tag(:a, link_text, link_attributes.merge('href' => href), !!options[:sanitize]) + punctuation.reverse.join('')
end
end.html_safe
end
@@ -514,7 +514,11 @@ module ActionView
text.html_safe
else
display_text = (block_given?) ? yield(text) : text
- display_text = h(display_text) unless options[:safe]
+
+ unless options[:sanitize] == false
+ text = sanitize(text)
+ display_text = sanitize(display_text) unless text == display_text
+ end
mail_to text, display_text, html_options
end
end
diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb
index a2c191c580..459aae94a2 100644
--- a/actionpack/lib/action_view/render/partials.rb
+++ b/actionpack/lib/action_view/render/partials.rb
@@ -318,7 +318,7 @@ module ActionView
object.class.model_name.partial_path.dup.tap do |partial|
path = @view.controller_path
- partial.insert(0, "#{File.dirname(path)}/") if path.include?(?/)
+ partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/)
end
end
end
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index dc7f901f07..b698b4cfec 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -85,6 +85,7 @@ module ActionView
def setup_with_controller
@controller = ActionView::TestCase::TestController.new
+ @request = @controller.request
@output_buffer = ActiveSupport::SafeBuffer.new
@rendered = ''