aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb2
-rw-r--r--actionpack/lib/abstract_controller/view_paths.rb8
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb2
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb2
-rw-r--r--actionpack/lib/action_controller/test_case.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb21
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb41
-rw-r--r--actionpack/lib/action_dispatch/routing/route.rb26
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb7
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb31
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb8
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb2
-rw-r--r--actionpack/lib/action_view/locale/en.yml2
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb2
-rw-r--r--actionpack/lib/action_view/template/resolver.rb107
-rw-r--r--actionpack/lib/action_view/test_case.rb1
-rw-r--r--actionpack/lib/action_view/testing/resolvers.rb12
23 files changed, 212 insertions, 90 deletions
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 95992c2698..1943ca4436 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -14,7 +14,7 @@ module AbstractController
# Override AbstractController::Base's process_action to run the
# process_action callbacks around the normal behavior.
def process_action(method_name, *args)
- run_callbacks(:process_action, method_name) do
+ run_callbacks(:process_action, action_name) do
super
end
end
diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb
index 6544c8949a..cea0f5ad1e 100644
--- a/actionpack/lib/abstract_controller/view_paths.rb
+++ b/actionpack/lib/abstract_controller/view_paths.rb
@@ -36,7 +36,7 @@ module AbstractController
# ==== Parameters
# * <tt>path</tt> - If a String is provided, it gets converted into
# the default view path. You may also provide a custom view path
- # (see ActionView::ViewPathSet for more information)
+ # (see ActionView::PathSet for more information)
def append_view_path(path)
self.view_paths = view_paths.dup + Array(path)
end
@@ -46,7 +46,7 @@ module AbstractController
# ==== Parameters
# * <tt>path</tt> - If a String is provided, it gets converted into
# the default view path. You may also provide a custom view path
- # (see ActionView::ViewPathSet for more information)
+ # (see ActionView::PathSet for more information)
def prepend_view_path(path)
self.view_paths = Array(path) + view_paths.dup
end
@@ -59,8 +59,8 @@ module AbstractController
# Set the view paths.
#
# ==== Parameters
- # * <tt>paths</tt> - If a ViewPathSet is provided, use that;
- # otherwise, process the parameter into a ViewPathSet.
+ # * <tt>paths</tt> - If a PathSet is provided, use that;
+ # otherwise, process the parameter into a PathSet.
def view_paths=(paths)
self._view_paths = ActionView::Base.process_view_paths(paths)
self._view_paths.freeze
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index a1c582560c..2c8a6e4d4d 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -80,7 +80,7 @@ module ActionController #:nodoc:
# header the Content-Type of the cached response could be wrong because
# no information about the MIME type is stored in the cache key. So, if
# you first ask for MIME type M in the Accept header, a cache entry is
- # created, and then perform a second resquest to the same resource asking
+ # created, and then perform a second request to the same resource asking
# for a different MIME type, you'd get the content cached for M.
#
# The <tt>:format</tt> parameter is taken into account though. The safest
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index b08d9a8434..dc3ea939e6 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -78,7 +78,7 @@ module ActionController
yield
end
- # Everytime after an action is processed, this method is invoked
+ # Every time after an action is processed, this method is invoked
# with the payload, so you can add more information.
# :api: plugin
def append_info_to_payload(payload) #:nodoc:
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 0f43527a56..bc4f8bb9ce 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -172,6 +172,10 @@ module ActionController
end
def recycle!
+ write_cookies!
+ @env.delete('HTTP_COOKIE') if @cookies.blank?
+ @env.delete('action_dispatch.cookies')
+ @cookies = nil
@formats = nil
@env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
@env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
@@ -301,7 +305,11 @@ module ActionController
# and cookies, though. For sessions, you just do:
#
# @request.session[:key] = "value"
- # @request.cookies["key"] = "value"
+ # @request.cookies[:key] = "value"
+ #
+ # To clear the cookies for a test just clear the request's cookies hash:
+ #
+ # @request.cookies.clear
#
# == \Testing named routes
#
@@ -416,6 +424,7 @@ module ActionController
@controller.process_with_new_base_test(@request, @response)
@assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
@request.session.delete('flash') if @request.session['flash'].blank?
+ @request.cookies.merge!(@response.cookies)
@response
end
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 1ab48ae04d..8dd1af7f3d 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -5,10 +5,10 @@ require 'active_support/core_ext/object/duplicable'
module ActionDispatch
module Http
# Allows you to specify sensitive parameters which will be replaced from
- # the request log by looking in all subhashes of the param hash for keys
- # to filter. If a block is given, each key and value of the parameter
- # hash and all subhashes is passed to it, the value or key can be replaced
- # using String#replace or similar method.
+ # the request log by looking in the query string of the request and all
+ # subhashes of the params hash to filter. If a block is given, each key and
+ # value of the params hash and all subhashes is passed to it, the value
+ # or key can be replaced using String#replace or similar method.
#
# Examples:
#
@@ -38,6 +38,11 @@ module ActionDispatch
@filtered_env ||= env_filter.filter(@env)
end
+ # Reconstructed a path with all sensitive GET parameters replaced.
+ def filtered_path
+ @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
+ end
+
protected
def parameter_filter
@@ -52,6 +57,14 @@ module ActionDispatch
@@parameter_filter_for[filters] ||= ParameterFilter.new(filters)
end
+ KV_RE = '[^&;=]+'
+ PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
+ def filtered_query_string
+ query_string.gsub(PAIR_RE) do |_|
+ parameter_filter.filter([[$1, $2]]).first.join("=")
+ end
+ end
+
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 535ff42b90..ac0fd9607d 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -41,7 +41,7 @@ module ActionDispatch
path = options.delete(:path) || ''
params = options[:params] || {}
- params.reject! {|k,v| !v }
+ params.reject! {|k,v| v.to_param.nil? }
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
rewritten_url << "?#{params.to_query}" unless params.empty?
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 589df218a8..14c424f24b 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -107,7 +107,7 @@ module ActionDispatch
if @options[:format] == false
@options.delete(:format)
path
- elsif path.include?(":format")
+ elsif path.include?(":format") || path.end_with?('/') || path.match(/^\/?\*/)
path
else
"#{path}(.:format)"
@@ -195,8 +195,8 @@ module ActionDispatch
def request_method_condition
if via = @options[:via]
- via = Array(via).map { |m| m.to_s.dasherize.upcase }
- { :request_method => %r[^#{via.join('|')}$] }
+ list = Array(via).map { |m| m.to_s.dasherize.upcase }
+ { :request_method => list }
else
{ }
end
@@ -243,10 +243,6 @@ module ActionDispatch
end
module Base
- def initialize(set) #:nodoc:
- @set = set
- end
-
# You can specify what Rails should route "/" to with the root method:
#
# root :to => 'pages#main'
@@ -368,9 +364,17 @@ module ActionDispatch
# match 'path' => 'c#a', :defaults => { :format => 'jpg' }
#
# See <tt>Scoping#defaults</tt> for its scope equivalent.
+ #
+ # [:anchor]
+ # Boolean to anchor a #match pattern. Default is true. When set to
+ # false, the pattern matches any request prefixed with the given path.
+ #
+ # # Matches any request starting with 'path'
+ # match 'path' => 'c#a', :anchor => false
def match(path, options=nil)
- mapping = Mapping.new(@set, @scope, path, options || {}).to_route
- @set.add_route(*mapping)
+ mapping = Mapping.new(@set, @scope, path, options || {})
+ app, conditions, requirements, defaults, as, anchor = mapping.to_route
+ @set.add_route(app, conditions, requirements, defaults, as, anchor)
self
end
@@ -558,11 +562,6 @@ module ActionDispatch
# PUT /admin/posts/1
# DELETE /admin/posts/1
module Scoping
- def initialize(*args) #:nodoc:
- @scope = {}
- super
- end
-
# Scopes a set of routes to the given default options.
#
# Take the following route definition as an example:
@@ -700,7 +699,7 @@ module ActionDispatch
# Now routes such as +/posts/1+ will no longer be valid, but +/posts/1.1+ will be.
# The +id+ parameter must match the constraint passed in for this example.
#
- # You may use this to also resrict other parameters:
+ # You may use this to also restrict other parameters:
#
# resources :posts do
# constraints(:post_id => /\d+\.\d+) do
@@ -720,7 +719,7 @@ module ActionDispatch
#
# === Dynamic request matching
#
- # Requests to routes can be constrained based on specific critera:
+ # Requests to routes can be constrained based on specific criteria:
#
# constraints(lambda { |req| req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
# resources :iphones
@@ -956,11 +955,6 @@ module ActionDispatch
alias :nested_scope :path
end
- def initialize(*args) #:nodoc:
- super
- @scope[:path_names] = @set.resources_path_names
- end
-
def resources_path_names(options)
@scope[:path_names].merge!(options)
end
@@ -1473,6 +1467,11 @@ module ActionDispatch
end
end
+ def initialize(set) #:nodoc:
+ @set = set
+ @scope = { :path_names => @set.resources_path_names }
+ end
+
include Base
include HttpHelpers
include Redirection
diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb
index 08a8408f25..a049510182 100644
--- a/actionpack/lib/action_dispatch/routing/route.rb
+++ b/actionpack/lib/action_dispatch/routing/route.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/module/deprecation'
+
module ActionDispatch
module Routing
class Route #:nodoc:
@@ -10,6 +12,8 @@ module ActionDispatch
@defaults = defaults
@name = name
+ # FIXME: we should not be doing this much work in a constructor.
+
@requirements = requirements.merge(defaults)
@requirements.delete(:controller) if @requirements[:controller].is_a?(Regexp)
@requirements.delete_if { |k, v|
@@ -21,21 +25,22 @@ module ActionDispatch
conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS, anchor)
end
- @conditions = Hash[conditions.map { |k,v| [k, Rack::Mount::RegexpWithNamedGroups.new(v)] }]
+ @verbs = conditions[:request_method] || []
+
+ @conditions = conditions.dup
+
+ # Rack-Mount requires that :request_method be a regular expression.
+ # :request_method represents the HTTP verb that matches this route.
+ #
+ # Here we munge values before they get sent on to rack-mount.
+ @conditions[:request_method] = %r[^#{verb}$] unless @verbs.empty?
+ @conditions[:path_info] = Rack::Mount::RegexpWithNamedGroups.new(@conditions[:path_info]) if @conditions[:path_info]
@conditions.delete_if{ |k,v| k != :path_info && !valid_condition?(k) }
@requirements.delete_if{ |k,v| !valid_condition?(k) }
end
def verb
- if method = conditions[:request_method]
- case method
- when Regexp
- source = method.source.upcase
- source =~ /\A\^[-A-Z|]+\$\Z/ ? source[1..-2] : source
- else
- method.to_s.upcase
- end
- end
+ @verbs.join '|'
end
def segment_keys
@@ -45,6 +50,7 @@ module ActionDispatch
def to_a
[@app, @conditions, @defaults, @name]
end
+ deprecate :to_a
def to_s
@to_s ||= begin
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index fc86d52a3a..b28f6c2297 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,5 +1,6 @@
require 'rack/mount'
require 'forwardable'
+require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/hash/slice'
@@ -330,8 +331,9 @@ module ActionDispatch
end
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
+ raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
route = Route.new(self, app, conditions, requirements, defaults, name, anchor)
- @set.add_route(*route)
+ @set.add_route(route.app, route.conditions, route.defaults, route.name)
named_routes[name] = route if name
routes << route
route
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index 16f3674164..d430691429 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -22,7 +22,7 @@ module ActionDispatch
end
def cookies
- HashWithIndifferentAccess.new(@request.cookies.merge(@response.cookies))
+ @request.cookies.merge(@response.cookies).with_indifferent_access
end
def redirect_to_url
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index cf440a1fad..822adb6a47 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/reverse_merge'
+require 'rack/utils'
module ActionDispatch
class TestRequest < Request
@@ -77,10 +78,14 @@ module ActionDispatch
private
def write_cookies!
unless @cookies.blank?
- @env['HTTP_COOKIE'] = @cookies.map { |name, value| "#{name}=#{value};" }.join(' ')
+ @env['HTTP_COOKIE'] = @cookies.map { |name, value| escape_cookie(name, value) }.join('; ')
end
end
+ def escape_cookie(name, value)
+ "#{Rack::Utils.escape(name)}=#{Rack::Utils.escape(value)}"
+ end
+
def delete_nil_values!
@env.delete_if { |k, v| v.nil? }
end
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index dc8e4bc316..6cd1565031 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -617,7 +617,7 @@ module ActionView
@options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute]
# If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
- # valid (otherwise it could be 31 and february wouldn't be a valid date)
+ # valid (otherwise it could be 31 and February wouldn't be a valid date)
if @datetime && @options[:discard_day] && !@options[:discard_month]
@datetime = @datetime.change(:day => 1)
end
@@ -644,7 +644,7 @@ module ActionView
@options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
# If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
- # valid (otherwise it could be 31 and february wouldn't be a valid date)
+ # valid (otherwise it could be 31 and February wouldn't be a valid date)
if @datetime && @options[:discard_day] && !@options[:discard_month]
@datetime = @datetime.change(:day => 1)
end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index befaa3e8d9..48abf119f1 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -6,6 +6,7 @@ require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
+require 'active_support/core_ext/array/extract_options'
module ActionView
# = Action View Form Helpers
@@ -262,6 +263,24 @@ module ActionView
# ...
# </form>
#
+ # === Removing hidden model id's
+ #
+ # The form_for method automatically includes the model id as a hidden field in the form.
+ # This is used to maintain the correlation between the form data and its associated model.
+ # Some ORM systems do not use IDs on nested models so in this case you want to be able
+ # to disable the hidden id.
+ #
+ # In the following example the Post model has many Comments stored within it in a NoSQL database,
+ # thus there is no primary key for comments.
+ #
+ # Example:
+ #
+ # <%= form(@post) do |f| %>
+ # <% f.fields_for(:comments, :include_id => false) do |cf| %>
+ # ...
+ # <% end %>
+ # <% end %>
+ #
# === Customized form builders
#
# You can also build forms using a customized FormBuilder class. Subclass
@@ -332,7 +351,7 @@ module ActionView
options[:html][:remote] = options.delete(:remote)
options[:html][:authenticity_token] = options.delete(:authenticity_token)
-
+
builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &proc)
fields_for = fields_for(object_name, object, options, &proc)
default_options = builder.multipart? ? { :multipart => true } : {}
@@ -862,9 +881,9 @@ module ActionView
private
- def instantiate_builder(record, record_object = nil, options = nil, &block)
- options, record_object = record_object, nil if record_object.is_a?(Hash)
- options ||= {}
+ def instantiate_builder(record, *args, &block)
+ options = args.extract_options!
+ record_object = args.shift
case record
when String, Symbol
@@ -1326,7 +1345,9 @@ module ActionView
def fields_for_nested_model(name, object, options, block)
object = convert_to_model(object)
- options[:hidden_field_id] = object.persisted?
+ parent_include_id = self.options.fetch(:include_id, true)
+ include_id = options.fetch(:include_id, parent_include_id)
+ options[:hidden_field_id] = object.persisted? && include_id
@template.fields_for(name, object, options, &block)
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 71f8534cbf..49aa434020 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -592,7 +592,7 @@ module ActionView
options.stringify_keys.tap do |html_options|
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
# The following URL is unescaped, this is just a hash of options, and it is the
- # responsability of the caller to escape all the values.
+ # responsibility of the caller to escape all the values.
html_options["action"] = url_for(url_for_options, *parameters_for_url)
html_options["accept-charset"] = "UTF-8"
html_options["data-remote"] = true if html_options.delete("remote")
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 26f8dce3c3..05a9c5b4f1 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -369,7 +369,7 @@ module ActionView
# See <tt>number_to_human_size</tt> if you want to print a file size.
#
# You can also define you own unit-quantifier names if you want to use other decimal units
- # (eg.: 1500 becomes "1.5 kilometers", 0.150 becomes "150 mililiters", etc). You may define
+ # (eg.: 1500 becomes "1.5 kilometers", 0.150 becomes "150 milliliters", etc). You may define
# a wide range of unit quantifiers, even fractional ones (centi, deci, mili, etc).
#
# ==== Options
@@ -425,13 +425,13 @@ module ActionView
# thousand:
# one: "kilometer"
# other: "kilometers"
- # billion: "gazilion-distance"
+ # billion: "gazillion-distance"
#
# Then you could do:
#
# number_to_human(543934, :units => :distance) # => "544 kilometers"
# number_to_human(54393498, :units => :distance) # => "54400 kilometers"
- # number_to_human(54393498000, :units => :distance) # => "54.4 gazilion-distance"
+ # number_to_human(54393498000, :units => :distance) # => "54.4 gazillion-distance"
# number_to_human(343, :units => :distance, :precision => 1) # => "300 meters"
# number_to_human(1, :units => :distance) # => "1 meter"
# number_to_human(0.34, :units => :distance) # => "34 centimeters"
@@ -472,7 +472,7 @@ module ActionView
end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e}
number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
- display_exponent = unit_exponents.find{|e| number_exponent >= e }
+ display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
number /= 10 ** display_exponent
unit = case units
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index f3a429fcce..18e303778c 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -103,7 +103,7 @@ module ActionView
:form, :with, :update, :script, :type ]).merge(CALLBACKS)
# Returns the JavaScript needed for a remote function.
- # See the link_to_remote documentation at http://github.com/rails/prototype_legacy_helper as it takes the same arguments.
+ # See the link_to_remote documentation at https://github.com/rails/prototype_legacy_helper as it takes the same arguments.
#
# Example:
# # Generates: <select id="options" onchange="new Ajax.Updater('options',
@@ -131,7 +131,6 @@ module ActionView
"new Ajax.Updater(#{update}, "
url_options = options[:url]
- url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash)
function << "'#{ERB::Util.html_escape(escape_javascript(url_for(url_options)))}'"
function << ", #{javascript_options})"
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index e7ec1df2c8..59e6ce878f 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -26,7 +26,7 @@ module ActionView
#
# E.g. the value returned for a missing translation key :"blog.post.title" will be
# <span class="translation_missing" title="translation missing: blog.post.title">Title</span>.
- # This way your views will display rather reasonableful strings but it will still
+ # This way your views will display rather reasonable strings but it will still
# be easy to spot missing translations.
#
# Second, it'll scope the key by the current partial if the key starts
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index 9004e52c5b..eb816b9446 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -5,7 +5,7 @@
format:
# Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
separator: "."
- # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three)
+ # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
delimiter: ","
# Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
precision: 3
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index 0803114f44..a36837afc8 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -63,7 +63,7 @@ module ActionView
class_attribute :default_format
self.default_format = Mime::HTML
- # Default implemenation used.
+ # Default implementation used.
class_attribute :erb_implementation
self.erb_implementation = Erubis
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 4d999fb3b2..6c1063592f 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -5,6 +5,25 @@ require "action_view/template"
module ActionView
# = Action View Resolver
class Resolver
+ # Keeps all information about view path and builds virtual path.
+ class Path < String
+ attr_reader :name, :prefix, :partial, :virtual
+ alias_method :partial?, :partial
+
+ def initialize(name, prefix, partial)
+ @name, @prefix, @partial = name, prefix, partial
+ rebuild(@name, @prefix, @partial)
+ end
+
+ def rebuild(name, prefix, partial)
+ @virtual = ""
+ @virtual << "#{prefix}/" unless prefix.empty?
+ @virtual << (partial ? "_#{name}" : name)
+
+ self.replace(@virtual)
+ end
+ end
+
cattr_accessor :caching
self.caching = true
@@ -36,15 +55,12 @@ module ActionView
# because Resolver guarantees that the arguments are present and
# normalized.
def find_templates(name, prefix, partial, details)
- raise NotImplementedError
+ raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details) method"
end
# Helpers that builds a path. Useful for building virtual paths.
def build_path(name, prefix, partial)
- path = ""
- path << "#{prefix}/" unless prefix.empty?
- path << (partial ? "_#{name}" : name)
- path
+ Path.new(name, prefix, partial)
end
# Handles templates caching. If a key is given and caching is on
@@ -97,25 +113,24 @@ module ActionView
end
class PathResolver < Resolver
- EXTENSION_ORDER = [:locale, :formats, :handlers]
+ EXTENSIONS = [:locale, :formats, :handlers]
+ DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}"
+
+ def initialize(pattern=nil)
+ @pattern = pattern || DEFAULT_PATTERN
+ super()
+ end
private
def find_templates(name, prefix, partial, details)
path = build_path(name, prefix, partial)
- query(path, EXTENSION_ORDER.map { |ext| details[ext] }, details[:formats])
+ extensions = Hash[EXTENSIONS.map { |ext| [ext, details[ext]] }.flatten(0)]
+ query(path, extensions, details[:formats])
end
def query(path, exts, formats)
- query = File.join(@path, path)
-
- query << exts.map { |ext|
- "{#{ext.compact.map { |e| ".#{e}" }.join(',')},}"
- }.join
-
- query.gsub!(/\{\.html,/, "{.html,.text.html,")
- query.gsub!(/\{\.text,/, "{.text,.text.plain,")
-
+ query = build_query(path, exts)
templates = []
sanitizer = Hash.new { |h,k| h[k] = Dir["#{File.dirname(k)}/*"] }
@@ -126,12 +141,28 @@ module ActionView
contents = File.open(p, "rb") {|io| io.read }
templates << Template.new(contents, File.expand_path(p), handler,
- :virtual_path => path, :format => format, :updated_at => mtime(p))
+ :virtual_path => path.virtual, :format => format, :updated_at => mtime(p))
end
templates
end
+ # Helper for building query glob string based on resolver's pattern.
+ def build_query(path, exts)
+ query = @pattern.dup
+ query.gsub!(/\:prefix(\/)?/, path.prefix.empty? ? "" : "#{path.prefix}\\1") # prefix can be empty...
+ query.gsub!(/\:action/, path.partial? ? "_#{path.name}" : path.name)
+
+ exts.each { |ext, variants|
+ query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
+ }
+
+ query.gsub!(/\.{html,/, ".{html,text.html,")
+ query.gsub!(/\.{text,/, ".{text,text.plain,")
+
+ File.expand_path(query, @path)
+ end
+
# Returns the file mtime from the filesystem.
def mtime(p)
File.stat(p).mtime
@@ -149,11 +180,47 @@ module ActionView
end
end
- # A resolver that loads files from the filesystem.
+ # A resolver that loads files from the filesystem. It allows to set your own
+ # resolving pattern. Such pattern can be a glob string supported by some variables.
+ #
+ # ==== Examples
+ #
+ # Default pattern, loads views the same way as previous versions of rails, eg. when you're
+ # looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml,rjs},}`
+ #
+ # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}")
+ #
+ # This one allows you to keep files with different formats in seperated subdirectories,
+ # eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
+ # `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
+ #
+ # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}")
+ #
+ # If you don't specify pattern then the default will be used.
+ #
+ # In order to use any of the customized resolvers above in a Rails application, you just need
+ # to configure ActionController::Base.view_paths in an initializer, for example:
+ #
+ # ActionController::Base.view_paths = FileSystemResolver.new(
+ # Rails.root.join("app/views"),
+ # ":prefix{/:locale}/:action{.:formats,}{.:handlers,}"
+ # )
+ #
+ # ==== Pattern format and variables
+ #
+ # Pattern have to be a valid glob string, and it allows you to use the
+ # following variables:
+ #
+ # * <tt>:prefix</tt> - usualy the controller path
+ # * <tt>:action</tt> - name of the action
+ # * <tt>:locale</tt> - possible locale versions
+ # * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
+ # * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
+ #
class FileSystemResolver < PathResolver
- def initialize(path)
+ def initialize(path, pattern=nil)
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
- super()
+ super(pattern)
@path = File.expand_path(path)
end
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 2ce109ea99..3e2ddffa16 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -185,6 +185,7 @@ module ActionView
@request
@routes
@templates
+ @options
@test_passed
@view
@view_context_class
diff --git a/actionpack/lib/action_view/testing/resolvers.rb b/actionpack/lib/action_view/testing/resolvers.rb
index 5c5cab7c7d..773dfcbb1d 100644
--- a/actionpack/lib/action_view/testing/resolvers.rb
+++ b/actionpack/lib/action_view/testing/resolvers.rb
@@ -8,8 +8,8 @@ module ActionView #:nodoc:
class FixtureResolver < PathResolver
attr_reader :hash
- def initialize(hash = {})
- super()
+ def initialize(hash = {}, pattern=nil)
+ super(pattern)
@hash = hash
end
@@ -21,8 +21,8 @@ module ActionView #:nodoc:
def query(path, exts, formats)
query = ""
- exts.each do |ext|
- query << '(' << ext.map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'
+ EXTENSIONS.each do |ext|
+ query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'
end
query = /^(#{Regexp.escape(path)})#{query}$/
@@ -32,9 +32,9 @@ module ActionView #:nodoc:
next unless _path =~ query
handler, format = extract_handler_and_format(_path, formats)
templates << Template.new(source, _path, handler,
- :virtual_path => $1, :format => format, :updated_at => updated_at)
+ :virtual_path => path.virtual, :format => format, :updated_at => updated_at)
end
-
+
templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
end
end