diff options
Diffstat (limited to 'actionpack')
43 files changed, 646 insertions, 98 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index fc3410ba6e..5ab92c8cfc 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,11 @@ *Rails 3.1.0 (unreleased)* +* Sensitive query string parameters (specified in config.filter_parameters) will now be filtered out from the request paths in the log file. [Prem Sichanugrist, fxn] + +* URL parameters which return false for to_param now appear in the query string (previously they were removed) [Andrew White] + +* URL parameters which return nil for to_param are now removed from the query string [Andrew White] + * ActionDispatch::MiddlewareStack now uses composition over inheritance. It is no longer an array which means there may be methods missing that were not tested. diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc index a28d78f688..3661d27d51 100644 --- a/actionpack/README.rdoc +++ b/actionpack/README.rdoc @@ -323,7 +323,7 @@ The latest version of Action Pack can be installed with Rubygems: Source code can be downloaded as part of the Rails project on GitHub -* http://github.com/rails/rails/tree/master/actionpack/ +* https://github.com/rails/rails/tree/master/actionpack/ == License 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 diff --git a/actionpack/test/action_dispatch/routing/mapper_test.rb b/actionpack/test/action_dispatch/routing/mapper_test.rb new file mode 100644 index 0000000000..e21b271907 --- /dev/null +++ b/actionpack/test/action_dispatch/routing/mapper_test.rb @@ -0,0 +1,58 @@ +require 'abstract_unit' + +module ActionDispatch + module Routing + class MapperTest < ActiveSupport::TestCase + class FakeSet + attr_reader :routes + + def initialize + @routes = [] + end + + def resources_path_names + {} + end + + def request_class + ActionDispatch::Request + end + + def add_route(*args) + routes << args + end + + def conditions + routes.map { |x| x[1] } + end + end + + def test_initialize + Mapper.new FakeSet.new + end + + def test_map_slash + fakeset = FakeSet.new + mapper = Mapper.new fakeset + mapper.match '/', :to => 'posts#index', :as => :main + assert_equal '/', fakeset.conditions.first[:path_info] + end + + def test_map_more_slashes + fakeset = FakeSet.new + mapper = Mapper.new fakeset + + # FIXME: is this a desired behavior? + mapper.match '/one/two/', :to => 'posts#index', :as => :main + assert_equal '/one/two(.:format)', fakeset.conditions.first[:path_info] + end + + def test_map_wildcard + fakeset = FakeSet.new + mapper = Mapper.new fakeset + mapper.match '/*path', :to => 'pages#show', :as => :page + assert_equal '/*path', fakeset.conditions.first[:path_info] + end + end + end +end diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 330fa276d0..9e44e8e088 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -505,6 +505,21 @@ class FilterTest < ActionController::TestCase end end + class ImplicitActionsController < ActionController::Base + before_filter :find_only, :only => :edit + before_filter :find_except, :except => :edit + + private + + def find_only + @only = 'Only' + end + + def find_except + @except = 'Except' + end + end + def test_sweeper_should_not_block_rendering response = test_process(SweeperTestController) assert_equal 'hello world', response.body @@ -783,6 +798,18 @@ class FilterTest < ActionController::TestCase assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body) end + def test_filters_obey_only_and_except_for_implicit_actions + test_process(ImplicitActionsController, 'show') + assert_equal 'Except', assigns(:except) + assert_nil assigns(:only) + assert_equal 'show', response.body + + test_process(ImplicitActionsController, 'edit') + assert_equal 'Only', assigns(:only) + assert_nil assigns(:except) + assert_equal 'edit', response.body + end + private def test_process(controller, action = "show") @controller = controller.is_a?(Class) ? controller.new : controller diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 5f6f1b61c0..18cf944f46 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -407,7 +407,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase assert_equal '/page/foo', url_for(rs, { :controller => 'content', :action => 'show_page', :id => 'foo' }) assert_equal({ :controller => "content", :action => 'show_page', :id => 'foo' }, rs.recognize_path("/page/foo")) - token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in russian + token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in Russian token.force_encoding(Encoding::BINARY) if token.respond_to?(:force_encoding) escaped_token = CGI::escape(token) diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 1c28da33bd..3f3d6dcc2f 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -311,6 +311,14 @@ module AbstractController assert_equal("/c/a", W.new.url_for(HashWithIndifferentAccess.new('controller' => 'c', 'action' => 'a', 'only_path' => true))) end + def test_url_params_with_nil_to_param_are_not_in_url + assert_equal("/c/a", W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => Struct.new(:to_param).new(nil))) + end + + def test_false_url_params_are_included_in_query + assert_equal("/c/a?show=false", W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :show => false)) + end + private def extract_params(url) url.split('?', 2).last.split('&').sort diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 1cfea6aa12..39159fd629 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -124,6 +124,20 @@ class CookiesTest < ActionController::TestCase cookies['user_name'] = "david" head :ok end + + def symbol_key_mock + cookies[:user_name] = "david" if cookies[:user_name] == "andrew" + head :ok + end + + def string_key_mock + cookies['user_name'] = "david" if cookies['user_name'] == "andrew" + head :ok + end + + def noop + head :ok + end end tests TestController @@ -411,6 +425,57 @@ class CookiesTest < ActionController::TestCase end end + def test_setting_request_cookies_is_indifferent_access + @request.cookies.clear + @request.cookies[:user_name] = "andrew" + get :string_key_mock + assert_equal "david", cookies[:user_name] + + @request.cookies.clear + @request.cookies['user_name'] = "andrew" + get :symbol_key_mock + assert_equal "david", cookies['user_name'] + end + + def test_cookies_retained_across_requests + get :symbol_key + assert_equal "user_name=david; path=/", @response.headers["Set-Cookie"] + assert_equal "david", cookies[:user_name] + + get :noop + assert_nil @response.headers["Set-Cookie"] + assert_equal "user_name=david", @request.env['HTTP_COOKIE'] + assert_equal "david", cookies[:user_name] + + get :noop + assert_nil @response.headers["Set-Cookie"] + assert_equal "user_name=david", @request.env['HTTP_COOKIE'] + assert_equal "david", cookies[:user_name] + end + + def test_cookies_can_be_cleared + get :symbol_key + assert_equal "user_name=david; path=/", @response.headers["Set-Cookie"] + assert_equal "david", cookies[:user_name] + + @request.cookies.clear + get :noop + assert_nil @response.headers["Set-Cookie"] + assert_nil @request.env['HTTP_COOKIE'] + assert_nil cookies[:user_name] + + get :symbol_key + assert_equal "user_name=david; path=/", @response.headers["Set-Cookie"] + assert_equal "david", cookies[:user_name] + end + + def test_cookies_are_escaped + @request.cookies[:user_ids] = '1;2' + get :noop + assert_equal "user_ids=1%3B2", @request.env['HTTP_COOKIE'] + assert_equal "1;2", cookies[:user_ids] + end + private def assert_cookie_header(expected) header = @response.headers["Set-Cookie"] diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index dd5bf5ec2d..f03ae7f2b3 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -518,6 +518,44 @@ class RequestTest < ActiveSupport::TestCase assert_equal "1", request.params["step"] end + test "filtered_path returns path with filtered query string" do + %w(; &).each do |sep| + request = stub_request('QUERY_STRING' => %w(username=sikachu secret=bd4f21f api_key=b1bc3b3cd352f68d79d7).join(sep), + 'PATH_INFO' => '/authenticate', + 'action_dispatch.parameter_filter' => [:secret, :api_key]) + + path = request.filtered_path + assert_equal %w(/authenticate?username=sikachu secret=[FILTERED] api_key=[FILTERED]).join(sep), path + end + end + + test "filtered_path should not unescape a genuine '[FILTERED]' value" do + request = stub_request('QUERY_STRING' => "secret=bd4f21f&genuine=%5BFILTERED%5D", + 'PATH_INFO' => '/authenticate', + 'action_dispatch.parameter_filter' => [:secret]) + + path = request.filtered_path + assert_equal "/authenticate?secret=[FILTERED]&genuine=%5BFILTERED%5D", path + end + + test "filtered_path should preserve duplication of keys in query string" do + request = stub_request('QUERY_STRING' => "username=sikachu&secret=bd4f21f&username=fxn", + 'PATH_INFO' => '/authenticate', + 'action_dispatch.parameter_filter' => [:secret]) + + path = request.filtered_path + assert_equal "/authenticate?username=sikachu&secret=[FILTERED]&username=fxn", path + end + + test "filtered_path should ignore searchparts" do + request = stub_request('QUERY_STRING' => "secret", + 'PATH_INFO' => '/authenticate', + 'action_dispatch.parameter_filter' => [:secret]) + + path = request.filtered_path + assert_equal "/authenticate?secret", path + end + protected def stub_request(env = {}) diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 1a96587836..5e5758a60e 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2313,6 +2313,38 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_invalid_route_name_raises_error + assert_raise(ArgumentError) do + self.class.stub_controllers do |routes| + routes.draw { get '/products', :to => 'products#index', :as => 'products ' } + end + end + + assert_raise(ArgumentError) do + self.class.stub_controllers do |routes| + routes.draw { get '/products', :to => 'products#index', :as => ' products' } + end + end + + assert_raise(ArgumentError) do + self.class.stub_controllers do |routes| + routes.draw { get '/products', :to => 'products#index', :as => 'products!' } + end + end + + assert_raise(ArgumentError) do + self.class.stub_controllers do |routes| + routes.draw { get '/products', :to => 'products#index', :as => 'products index' } + end + end + + assert_raise(ArgumentError) do + self.class.stub_controllers do |routes| + routes.draw { get '/products', :to => 'products#index', :as => '1products' } + end + end + end + def test_nested_route_in_nested_resource get "/posts/1/comments/2/views" assert_equal "comments#views", @response.body diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb index e42ade73d1..81a8c24525 100644 --- a/actionpack/test/dispatch/test_request_test.rb +++ b/actionpack/test/dispatch/test_request_test.rb @@ -36,10 +36,10 @@ class TestRequestTest < ActiveSupport::TestCase req.cookies["user_name"] = "david" assert_equal({"user_name" => "david"}, req.cookies) - assert_equal "user_name=david;", req.env["HTTP_COOKIE"] + assert_equal "user_name=david", req.env["HTTP_COOKIE"] req.cookies["login"] = "XJ-122" assert_equal({"user_name" => "david", "login" => "XJ-122"}, req.cookies) - assert_equal %w(login=XJ-122 user_name=david), req.env["HTTP_COOKIE"].split(/; ?/).sort + assert_equal %w(login=XJ-122 user_name=david), req.env["HTTP_COOKIE"].split(/; /).sort end end diff --git a/actionpack/test/fixtures/custom_pattern/another.html.erb b/actionpack/test/fixtures/custom_pattern/another.html.erb new file mode 100644 index 0000000000..6d7f3bafbb --- /dev/null +++ b/actionpack/test/fixtures/custom_pattern/another.html.erb @@ -0,0 +1 @@ +Hello custom patterns!
\ No newline at end of file diff --git a/actionpack/test/fixtures/custom_pattern/html/another.erb b/actionpack/test/fixtures/custom_pattern/html/another.erb new file mode 100644 index 0000000000..dbd7e96ab6 --- /dev/null +++ b/actionpack/test/fixtures/custom_pattern/html/another.erb @@ -0,0 +1 @@ +Another template!
\ No newline at end of file diff --git a/actionpack/test/fixtures/custom_pattern/html/path.erb b/actionpack/test/fixtures/custom_pattern/html/path.erb new file mode 100644 index 0000000000..6d7f3bafbb --- /dev/null +++ b/actionpack/test/fixtures/custom_pattern/html/path.erb @@ -0,0 +1 @@ +Hello custom patterns!
\ No newline at end of file diff --git a/actionpack/test/fixtures/filter_test/implicit_actions/edit.html.erb b/actionpack/test/fixtures/filter_test/implicit_actions/edit.html.erb new file mode 100644 index 0000000000..8491ab9f80 --- /dev/null +++ b/actionpack/test/fixtures/filter_test/implicit_actions/edit.html.erb @@ -0,0 +1 @@ +edit
\ No newline at end of file diff --git a/actionpack/test/fixtures/filter_test/implicit_actions/show.html.erb b/actionpack/test/fixtures/filter_test/implicit_actions/show.html.erb new file mode 100644 index 0000000000..0a89cecf05 --- /dev/null +++ b/actionpack/test/fixtures/filter_test/implicit_actions/show.html.erb @@ -0,0 +1 @@ +show
\ No newline at end of file diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 31da26de7f..359b078466 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -1103,6 +1103,61 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_nested_fields_for_with_an_existing_record_on_a_nested_attributes_one_to_one_association_with_disabled_hidden_id + @post.author = Author.new(321) + + form_for(@post) do |f| + concat f.text_field(:title) + concat f.fields_for(:author, :include_id => false) { |af| + af.text_field(:name) + } + end + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + end + + assert_dom_equal expected, output_buffer + end + + def test_nested_fields_for_with_an_existing_record_on_a_nested_attributes_one_to_one_association_with_disabled_hidden_id_inherited + @post.author = Author.new(321) + + form_for(@post, :include_id => false) do |f| + concat f.text_field(:title) + concat f.fields_for(:author) { |af| + af.text_field(:name) + } + end + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + end + + assert_dom_equal expected, output_buffer + end + + def test_nested_fields_for_with_an_existing_record_on_a_nested_attributes_one_to_one_association_with_disabled_hidden_id_override + @post.author = Author.new(321) + + form_for(@post, :include_id => false) do |f| + concat f.text_field(:title) + concat f.fields_for(:author, :include_id => true) { |af| + af.text_field(:name) + } + end + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + end + + assert_dom_equal expected, output_buffer + end + def test_nested_fields_for_with_existing_records_on_a_nested_attributes_one_to_one_association_with_explicit_hidden_field_placement @post.author = Author.new(321) @@ -1146,6 +1201,86 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_nested_fields_for_with_existing_records_on_a_nested_attributes_collection_association_with_disabled_hidden_id + @post.comments = Array.new(2) { |id| Comment.new(id + 1) } + @post.author = Author.new(321) + + form_for(@post) do |f| + concat f.text_field(:title) + concat f.fields_for(:author) { |af| + concat af.text_field(:name) + } + @post.comments.each do |comment| + concat f.fields_for(:comments, comment, :include_id => false) { |cf| + concat cf.text_field(:name) + } + end + end + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + end + + assert_dom_equal expected, output_buffer + end + + def test_nested_fields_for_with_existing_records_on_a_nested_attributes_collection_association_with_disabled_hidden_id_inherited + @post.comments = Array.new(2) { |id| Comment.new(id + 1) } + @post.author = Author.new(321) + + form_for(@post, :include_id => false) do |f| + concat f.text_field(:title) + concat f.fields_for(:author) { |af| + concat af.text_field(:name) + } + @post.comments.each do |comment| + concat f.fields_for(:comments, comment) { |cf| + concat cf.text_field(:name) + } + end + end + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + end + + assert_dom_equal expected, output_buffer + end + + def test_nested_fields_for_with_existing_records_on_a_nested_attributes_collection_association_with_disabled_hidden_id_override + @post.comments = Array.new(2) { |id| Comment.new(id + 1) } + @post.author = Author.new(321) + + form_for(@post, :include_id => false) do |f| + concat f.text_field(:title) + concat f.fields_for(:author, :include_id => true) { |af| + concat af.text_field(:name) + } + @post.comments.each do |comment| + concat f.fields_for(:comments, comment) { |cf| + concat cf.text_field(:name) + } + end + end + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + end + + assert_dom_equal expected, output_buffer + end + def test_nested_fields_for_with_existing_records_on_a_nested_attributes_collection_association_using_erb_and_inline_block @post.comments = Array.new(2) { |id| Comment.new(id + 1) } diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index 69b1d4ff7b..93ff7ba0fd 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -1,6 +1,12 @@ require 'abstract_unit' require 'tzinfo' +class Map < Hash + def category + "<mus>" + end +end + TZInfo::Timezone.cattr_reader :loaded_zones class FormOptionsHelperTest < ActionView::TestCase @@ -394,6 +400,19 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_fields_for_with_record_inherited_from_hash + map = Map.new + + output_buffer = fields_for :map, map do |f| + concat f.select(:category, %w( abe <mus> hest)) + end + + assert_dom_equal( + "<select id=\"map_category\" name=\"map[category]\"><option value=\"abe\">abe</option>\n<option value=\"<mus>\" selected=\"selected\"><mus></option>\n<option value=\"hest\">hest</option></select>", + output_buffer + ) + end + def test_select_under_fields_for_with_index @post = Post.new @post.category = "<mus>" diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 156b7cb5ff..c8d50ebf75 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -140,7 +140,7 @@ class NumberHelperTest < ActionView::TestCase def test_number_with_precision_with_significant_true_and_zero_precision # Zero precision with significant is a mistake (would always return zero), - # so we treat it as if significant was false (increases backwards compatibily for number_to_human_size) + # so we treat it as if significant was false (increases backwards compatibility for number_to_human_size) assert_equal "124", number_with_precision(123.987, :precision => 0, :significant => true) assert_equal "12", number_with_precision(12, :precision => 0, :significant => true ) assert_equal "12", number_with_precision("12.3", :precision => 0, :significant => true ) @@ -195,7 +195,9 @@ class NumberHelperTest < ActionView::TestCase def test_number_to_human assert_equal '-123', number_to_human(-123) - assert_equal '0', number_to_human(0) + assert_equal '-0.5', number_to_human(-0.5) + assert_equal '0', number_to_human(0) + assert_equal '0.5', number_to_human(0.5) assert_equal '123', number_to_human(123) assert_equal '1.23 Thousand', number_to_human(1234) assert_equal '12.3 Thousand', number_to_human(12345) diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 034fb6c210..dd86bfed04 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -381,7 +381,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase end def test_render_utf8_template_with_incompatible_external_encoding - with_external_encoding Encoding::SJIS do + with_external_encoding Encoding::SHIFT_JIS do begin result = @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield") flunk 'Should have raised incompatible encoding error' @@ -392,7 +392,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase end def test_render_utf8_template_with_partial_with_incompatible_encoding - with_external_encoding Encoding::SJIS do + with_external_encoding Encoding::SHIFT_JIS do begin result = @view.render(:file => "test/utf8_magic_with_bare_partial.html.erb", :layouts => "layouts/yield") flunk 'Should have raised incompatible encoding error' diff --git a/actionpack/test/template/resolver_patterns_test.rb b/actionpack/test/template/resolver_patterns_test.rb new file mode 100644 index 0000000000..97b1bad055 --- /dev/null +++ b/actionpack/test/template/resolver_patterns_test.rb @@ -0,0 +1,31 @@ +require 'abstract_unit' + +class ResolverPatternsTest < ActiveSupport::TestCase + def setup + path = File.expand_path("../../fixtures/", __FILE__) + pattern = ":prefix/{:formats/,}:action{.:formats,}{.:handlers,}" + @resolver = ActionView::FileSystemResolver.new(path, pattern) + end + + def test_should_return_empty_list_for_unknown_path + templates = @resolver.find_all("unknown", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]}) + assert_equal [], templates, "expected an empty list of templates" + end + + def test_should_return_template_for_declared_path + templates = @resolver.find_all("path", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]}) + assert_equal 1, templates.size, "expected one template" + assert_equal "Hello custom patterns!", templates.first.source + assert_equal "custom_pattern/path", templates.first.virtual_path + assert_equal [:html], templates.first.formats + end + + def test_should_return_all_templates_when_ambigous_pattern + templates = @resolver.find_all("another", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]}) + assert_equal 2, templates.size, "expected two templates" + assert_equal "Another template!", templates[0].source + assert_equal "custom_pattern/another", templates[0].virtual_path + assert_equal "Hello custom patterns!", templates[1].source + assert_equal "custom_pattern/another", templates[1].virtual_path + end +end |