diff options
54 files changed, 1129 insertions, 903 deletions
@@ -8,7 +8,7 @@ gem "rails", "3.0.pre", :path => "railties" end # AS -gem "i18n", ">= 0.3.0" +gem "i18n", "0.3.3" # AR gem "arel", "0.2.pre", :git => "git://github.com/rails/arel.git" diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index c2ffa5a0e9..e644c47e9c 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -259,10 +259,9 @@ module ActionMailer #:nodoc: include AbstractController::LocalizedCache include AbstractController::Layouts include AbstractController::Helpers + include AbstractController::UrlFor helper ActionMailer::MailHelper - - include ActionController::UrlWriter include ActionMailer::DeprecatedBody private_class_method :new #:nodoc: diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 237ab577ba..efc35a7e56 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -15,4 +15,5 @@ module AbstractController autoload :LocalizedCache autoload :Logger autoload :Rendering + autoload :UrlFor end diff --git a/actionpack/lib/abstract_controller/url_for.rb b/actionpack/lib/abstract_controller/url_for.rb new file mode 100644 index 0000000000..6b7d2b1f34 --- /dev/null +++ b/actionpack/lib/abstract_controller/url_for.rb @@ -0,0 +1,156 @@ +module AbstractController + # In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse + # is also possible: an URL can be generated from one of your routing definitions. + # URL generation functionality is centralized in this module. + # + # See AbstractController::Routing and AbstractController::Resources for general + # information about routing and routes.rb. + # + # <b>Tip:</b> If you need to generate URLs from your models or some other place, + # then AbstractController::UrlFor is what you're looking for. Read on for + # an introduction. + # + # == URL generation from parameters + # + # As you may know, some functions - such as AbstractController::Base#url_for + # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set + # of parameters. For example, you've probably had the chance to write code + # like this in one of your views: + # + # <%= link_to('Click here', :controller => 'users', + # :action => 'new', :message => 'Welcome!') %> + # + # #=> Generates a link to: /users/new?message=Welcome%21 + # + # link_to, and all other functions that require URL generation functionality, + # actually use AbstractController::UrlFor under the hood. And in particular, + # they use the AbstractController::UrlFor#url_for method. One can generate + # the same path as the above example by using the following code: + # + # include UrlFor + # url_for(:controller => 'users', + # :action => 'new', + # :message => 'Welcome!', + # :only_path => true) + # # => "/users/new?message=Welcome%21" + # + # Notice the <tt>:only_path => true</tt> part. This is because UrlFor has no + # information about the website hostname that your Rails app is serving. So if you + # want to include the hostname as well, then you must also pass the <tt>:host</tt> + # argument: + # + # include UrlFor + # url_for(:controller => 'users', + # :action => 'new', + # :message => 'Welcome!', + # :host => 'www.example.com') # Changed this. + # # => "http://www.example.com/users/new?message=Welcome%21" + # + # By default, all controllers and views have access to a special version of url_for, + # that already knows what the current hostname is. So if you use url_for in your + # controllers or your views, then you don't need to explicitly pass the <tt>:host</tt> + # argument. + # + # For convenience reasons, mailers provide a shortcut for AbstractController::UrlFor#url_for. + # So within mailers, you only have to type 'url_for' instead of 'AbstractController::UrlFor#url_for' + # in full. However, mailers don't have hostname information, and what's why you'll still + # have to specify the <tt>:host</tt> argument when generating URLs in mailers. + # + # + # == URL generation for named routes + # + # UrlFor also allows one to access methods that have been auto-generated from + # named routes. For example, suppose that you have a 'users' resource in your + # <b>routes.rb</b>: + # + # map.resources :users + # + # This generates, among other things, the method <tt>users_path</tt>. By default, + # this method is accessible from your controllers, views and mailers. If you need + # to access this auto-generated method from other places (such as a model), then + # you can do that by including AbstractController::UrlFor in your class: + # + # class User < ActiveRecord::Base + # include AbstractController::UrlFor + # + # def base_uri + # user_path(self) + # end + # end + # + # User.find(1).base_uri # => "/users/1" + # + module UrlFor + extend ActiveSupport::Concern + + included do + ActionController::Routing::Routes.install_helpers(self) + extlib_inheritable_accessor :default_url_options, + :instance_writer => false, :instance_reader => false + self.default_url_options ||= {} + end + + # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in + # the form of a hash, just like the one you would use for url_for directly. Example: + # + # def default_url_options(options) + # { :project => @project.active? ? @project.url_name : "unknown" } + # end + # + # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the + # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set + # by this method. + def default_url_options(options = nil) + self.class.default_url_options + end + + def rewrite_options(options) #:nodoc: + if options.delete(:use_defaults) != false && (defaults = default_url_options(options)) + defaults.merge(options) + else + options + end + end + + # Generate a url based on the options provided, default_url_options and the + # routes defined in routes.rb. The following options are supported: + # + # * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+. + # * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'. + # * <tt>:host</tt> - Specifies the host the link should be targeted at. + # If <tt>:only_path</tt> is false, this option must be + # provided either explicitly, or via +default_url_options+. + # * <tt>:port</tt> - Optionally specify the port to connect to. + # * <tt>:anchor</tt> - An anchor name to be appended to the path. + # * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the + # +relative_url_root+ set in AbstractController::Base.relative_url_root. + # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/" + # + # Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to + # +url_for+ is forwarded to the Routes module. + # + # Examples: + # + # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing' + # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok' + # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/' + # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33' + def url_for(options = {}) + options ||= {} + case options + when String + options + when Hash + _url_rewriter.rewrite(rewrite_options(options)) + else + polymorphic_url(options) + end + end + + protected + + def _url_rewriter + ActionController::UrlRewriter + end + end +end diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index e6cd055a59..8271f6f3e7 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -49,7 +49,6 @@ module ActionController eager_autoload do autoload :RecordIdentifier autoload :UrlRewriter - autoload :UrlWriter, 'action_controller/url_rewriter' # TODO: Don't autoload exceptions, setup explicit # requires for files that need them diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index c82d9cf369..37be8b3999 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -1,6 +1,7 @@ module ActionController module Head - include UrlFor + extend ActiveSupport::Concern + include ActionController::UrlFor # Return a response that has no content (merely headers). The options # argument is interpreted to be a hash of header names and values. diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 7a2f9a6fc5..7b277c0ae0 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -9,7 +9,9 @@ module ActionController module Redirecting extend ActiveSupport::Concern + include AbstractController::Logger + include ActionController::UrlFor # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: # diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 8c3810ebcb..73feacb872 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -2,40 +2,14 @@ module ActionController module UrlFor extend ActiveSupport::Concern - include RackDelegation + include AbstractController::UrlFor + include ActionController::RackDelegation - # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in - # the form of a hash, just like the one you would use for url_for directly. Example: - # - # def default_url_options(options) - # { :project => @project.active? ? @project.url_name : "unknown" } - # end - # - # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the - # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set - # by this method. - def default_url_options(options = nil) - end - - def rewrite_options(options) #:nodoc: - if defaults = default_url_options(options) - defaults.merge(options) - else - options - end - end + protected - def url_for(options = {}) - options ||= {} - case options - when String - options - when Hash - @url ||= UrlRewriter.new(request, params) - @url.rewrite(rewrite_options(options)) - else - polymorphic_url(options) - end + def _url_rewriter + return ActionController::UrlRewriter unless request + @_url_rewriter ||= ActionController::UrlRewriter.new(request, params) end end end diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb index 80285b07f5..933a1fa8f9 100644 --- a/actionpack/lib/action_controller/url_rewriter.rb +++ b/actionpack/lib/action_controller/url_rewriter.rb @@ -1,153 +1,21 @@ -module ActionController - # In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse - # is also possible: an URL can be generated from one of your routing definitions. - # URL generation functionality is centralized in this module. - # - # See ActionController::Routing and ActionController::Resources for general - # information about routing and routes.rb. - # - # <b>Tip:</b> If you need to generate URLs from your models or some other place, - # then ActionController::UrlWriter is what you're looking for. Read on for - # an introduction. - # - # == URL generation from parameters - # - # As you may know, some functions - such as ActionController::Base#url_for - # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set - # of parameters. For example, you've probably had the chance to write code - # like this in one of your views: - # - # <%= link_to('Click here', :controller => 'users', - # :action => 'new', :message => 'Welcome!') %> - # - # #=> Generates a link to: /users/new?message=Welcome%21 - # - # link_to, and all other functions that require URL generation functionality, - # actually use ActionController::UrlWriter under the hood. And in particular, - # they use the ActionController::UrlWriter#url_for method. One can generate - # the same path as the above example by using the following code: - # - # include UrlWriter - # url_for(:controller => 'users', - # :action => 'new', - # :message => 'Welcome!', - # :only_path => true) - # # => "/users/new?message=Welcome%21" - # - # Notice the <tt>:only_path => true</tt> part. This is because UrlWriter has no - # information about the website hostname that your Rails app is serving. So if you - # want to include the hostname as well, then you must also pass the <tt>:host</tt> - # argument: - # - # include UrlWriter - # url_for(:controller => 'users', - # :action => 'new', - # :message => 'Welcome!', - # :host => 'www.example.com') # Changed this. - # # => "http://www.example.com/users/new?message=Welcome%21" - # - # By default, all controllers and views have access to a special version of url_for, - # that already knows what the current hostname is. So if you use url_for in your - # controllers or your views, then you don't need to explicitly pass the <tt>:host</tt> - # argument. - # - # For convenience reasons, mailers provide a shortcut for ActionController::UrlWriter#url_for. - # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#url_for' - # in full. However, mailers don't have hostname information, and what's why you'll still - # have to specify the <tt>:host</tt> argument when generating URLs in mailers. - # - # - # == URL generation for named routes - # - # UrlWriter also allows one to access methods that have been auto-generated from - # named routes. For example, suppose that you have a 'users' resource in your - # <b>routes.rb</b>: - # - # map.resources :users - # - # This generates, among other things, the method <tt>users_path</tt>. By default, - # this method is accessible from your controllers, views and mailers. If you need - # to access this auto-generated method from other places (such as a model), then - # you can do that by including ActionController::UrlWriter in your class: - # - # class User < ActiveRecord::Base - # include ActionController::UrlWriter - # - # def base_uri - # user_path(self) - # end - # end - # - # User.find(1).base_uri # => "/users/1" - module UrlWriter - def self.included(base) #:nodoc: - ActionController::Routing::Routes.install_helpers(base) - base.mattr_accessor :default_url_options - - # The default options for urls written by this writer. Typically a <tt>:host</tt> pair is provided. - base.default_url_options ||= {} - end - - # Generate a url based on the options provided, default_url_options and the - # routes defined in routes.rb. The following options are supported: - # - # * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+. - # * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'. - # * <tt>:host</tt> - Specifies the host the link should be targeted at. - # If <tt>:only_path</tt> is false, this option must be - # provided either explicitly, or via +default_url_options+. - # * <tt>:port</tt> - Optionally specify the port to connect to. - # * <tt>:anchor</tt> - An anchor name to be appended to the path. - # * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the - # +relative_url_root+ set in ActionController::Base.relative_url_root. - # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/" - # - # Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to - # +url_for+ is forwarded to the Routes module. - # - # Examples: - # - # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing' - # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok' - # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/' - # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33' - def url_for(options) - options = self.class.default_url_options.merge(options) - - url = '' - - unless options.delete(:only_path) - url << (options.delete(:protocol) || 'http') - url << '://' unless url.match("://") - - raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host] - - url << options.delete(:host) - url << ":#{options.delete(:port)}" if options.key?(:port) - else - # Delete the unused options to prevent their appearance in the query string. - [:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) } - end - trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash) - url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root] - anchor = "##{Rack::Utils.escape options.delete(:anchor).to_param.to_s}" if options[:anchor] - generated = Routing::Routes.generate(options, {}) - url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated) - url << anchor if anchor - - url - end - end +require 'active_support/core_ext/hash/except' +module ActionController # Rewrites URLs for Base.redirect_to and Base.url_for in the controller. class UrlRewriter #:nodoc: RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root] + def initialize(request, parameters) @request, @parameters = request, parameters end def rewrite(options = {}) - rewrite_url(options) + options[:host] ||= @request.host_with_port + options[:protocol] ||= @request.protocol + + self.class.rewrite(options, @request.symbolized_path_parameters) do |options| + process_path_options(options) + end end def to_str @@ -156,49 +24,53 @@ module ActionController alias_method :to_s, :to_str - private - # Given a path and options, returns a rewritten URL string - def rewrite_url(options) - rewritten_url = "" - - unless options[:only_path] - rewritten_url << (options[:protocol] || @request.protocol) - rewritten_url << "://" unless rewritten_url.match("://") - rewritten_url << rewrite_authentication(options) - rewritten_url << (options[:host] || @request.host_with_port) - rewritten_url << ":#{options.delete(:port)}" if options.key?(:port) - end - - path = rewrite_path(options) - rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root] - rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) - rewritten_url << "##{Rack::Utils.escape(options[:anchor].to_param.to_s)}" if options[:anchor] - - rewritten_url + def self.rewrite(options, path_segments=nil) + rewritten_url = "" + + unless options[:only_path] + rewritten_url << (options[:protocol] || "http") + rewritten_url << "://" unless rewritten_url.match("://") + rewritten_url << rewrite_authentication(options) + + raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host] + + rewritten_url << options[:host] + rewritten_url << ":#{options.delete(:port)}" if options.key?(:port) end - # Given a Hash of options, generates a route - def rewrite_path(options) - options = options.symbolize_keys - options.update(options[:params].symbolize_keys) if options[:params] + path_options = options.except(*RESERVED_OPTIONS) + path_options = yield(path_options) if block_given? + path = Routing::Routes.generate(path_options, path_segments || {}) + + rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root] + rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) + rewritten_url << "##{Rack::Utils.escape(options[:anchor].to_param.to_s)}" if options[:anchor] - if (overwrite = options.delete(:overwrite_params)) - options.update(@parameters.symbolize_keys) - options.update(overwrite.symbolize_keys) - end + rewritten_url + end - RESERVED_OPTIONS.each { |k| options.delete(k) } + protected - # Generates the query string, too - Routing::Routes.generate(options, @request.symbolized_path_parameters) + def self.rewrite_authentication(options) + if options[:user] && options[:password] + "#{Rack::Utils.escape(options.delete(:user))}:#{Rack::Utils.escape(options.delete(:password))}@" + else + "" end + end + + # Given a Hash of options, generates a route + def process_path_options(options) + options = options.symbolize_keys + options.update(options[:params].symbolize_keys) if options[:params] - def rewrite_authentication(options) - if options[:user] && options[:password] - "#{Rack::Utils.escape(options.delete(:user))}:#{Rack::Utils.escape(options.delete(:password))}@" - else - "" - end + if (overwrite = options.delete(:overwrite_params)) + options.update(@parameters.symbolize_keys) + options.update(overwrite.symbolize_keys) end + + options + end + end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 0d2ffc6d69..90893aa0e6 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -74,9 +74,8 @@ module ActionDispatch @routes = {} @helpers = [] - @module ||= Module.new - @module.instance_methods.each do |selector| - @module.class_eval { remove_method selector } + @module ||= Module.new do + instance_methods.each { |selector| remove_method(selector) } end end @@ -168,25 +167,56 @@ module ActionDispatch selector = url_helper_name(name, kind) hash_access_method = hash_access_name(name, kind) - # We use module_eval to avoid leaks + # We use module_eval to avoid leaks. + # + # def users_url(*args) + # if args.empty? || Hash === args.first + # options = hash_for_users_url(args.first || {}) + # else + # options = hash_for_users_url(args.extract_options!) + # default = default_url_options(options) if self.respond_to?(:default_url_options, true) + # options = (default ||= {}).merge(options) + # + # keys = [] + # keys -= options.keys if args.size < keys.size - 1 + # + # args = args.zip(keys).inject({}) do |h, (v, k)| + # h[k] = v + # h + # end + # + # # Tell url_for to skip default_url_options + # options[:use_defaults] = false + # options.merge!(args) + # end + # + # url_for(options) + # end @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - def #{selector}(*args) # def users_url(*args) - # - opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first - args.first || {} # args.first || {} - else # else - options = args.extract_options! # options = args.extract_options! - args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)| - h[k] = v # h[k] = v - h # h - end # end - options.merge(args) # options.merge(args) - end # end - # - url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts)) - # - end # end - protected :#{selector} # protected :users_url + def #{selector}(*args) + if args.empty? || Hash === args.first + options = #{hash_access_method}(args.first || {}) + else + options = #{hash_access_method}(args.extract_options!) + default = default_url_options(options) if self.respond_to?(:default_url_options, true) + options = (default ||= {}).merge(options) + + keys = #{route.segment_keys.inspect} + keys -= options.keys if args.size < keys.size - 1 # take format into account + + args = args.zip(keys).inject({}) do |h, (v, k)| + h[k] = v + h + end + + # Tell url_for to skip default_url_options + options[:use_defaults] = false + options.merge!(args) + end + + url_for(options) + end + protected :#{selector} END_EVAL helpers << selector end diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index c70f29f098..87b7adf6c4 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -216,7 +216,7 @@ module ActionView end options[:object_name] ||= params.first - I18n.with_options :locale => options[:locale], :scope => [:activemodel, :errors, :template] do |locale| + I18n.with_options :locale => options[:locale], :scope => [:errors, :template] do |locale| header_message = if options.include?(:header_message) options[:header_message] else diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 81c9c88820..c1f342bcdd 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -505,7 +505,7 @@ module ActionView # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation - # is found in the current I18n locale (through views.labels.<modelname>.<attribute>) or you specify it explicitly. + # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly. # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to # target labels for radio_button tags (where the value is used in the ID of the input tag). @@ -517,8 +517,8 @@ module ActionView # You can localize your labels based on model and attribute names. # For example you can define the following in your locale (e.g. en.yml) # - # views: - # labels: + # helpers: + # label: # post: # body: "Write your entire text here" # @@ -777,7 +777,7 @@ module ActionView options["for"] ||= name_and_id["id"] content = if text.blank? - I18n.t("views.labels.#{object_name}.#{method_name}", :default => "").presence + I18n.t("helpers.label.#{object_name}.#{method_name}", :default => "").presence else text.to_s end diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 935ab5f3e8..02ad637509 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -571,7 +571,7 @@ module ActionView option_tags = "<option value=\"\">#{options[:include_blank] if options[:include_blank].kind_of?(String)}</option>\n" + option_tags end if value.blank? && options[:prompt] - prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('support.select.prompt', :default => 'Please select') + prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select') "<option value=\"\">#{prompt}</option>\n" + option_tags else option_tags diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 710178905a..14628c5404 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -11,6 +11,11 @@ module ActionView module UrlHelper include JavaScriptHelper + # Need to map default url options to controller one. + def default_url_options(*args) #:nodoc: + @controller.send(:default_url_options, *args) + end + # Returns the URL for the set of +options+ provided. This takes the # same options as +url_for+ in Action Controller (see the # documentation for <tt>ActionController::Base#url_for</tt>). Note that by default diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml index 5e2a92b89a..9918034020 100644 --- a/actionpack/lib/action_view/locale/en.yml +++ b/actionpack/lib/action_view/locale/en.yml @@ -102,16 +102,15 @@ minute: "Minute" second: "Seconds" - activemodel: - errors: - template: - header: - one: "1 error prohibited this {{model}} from being saved" - other: "{{count}} errors prohibited this {{model}} from being saved" - # The variable :count is also available - body: "There were problems with the following fields:" + errors: + template: + header: + one: "1 error prohibited this {{model}} from being saved" + other: "{{count}} errors prohibited this {{model}} from being saved" + # The variable :count is also available + body: "There were problems with the following fields:" - support: + helpers: select: # default value for :prompt => true in FormOptionsHelper prompt: "Please select" diff --git a/actionpack/test/abstract/url_for_test.rb b/actionpack/test/abstract/url_for_test.rb new file mode 100644 index 0000000000..e5570349b8 --- /dev/null +++ b/actionpack/test/abstract/url_for_test.rb @@ -0,0 +1,272 @@ +require 'abstract_unit' + +module AbstractController + module Testing + + class UrlForTests < ActionController::TestCase + class W + include AbstractController::UrlFor + end + + def teardown + W.default_url_options.clear + end + + def add_host! + W.default_url_options[:host] = 'www.basecamphq.com' + end + + def test_exception_is_thrown_without_host + assert_raise RuntimeError do + W.new.url_for :controller => 'c', :action => 'a', :id => 'i' + end + end + + def test_anchor + assert_equal('/c/a#anchor', + W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor') + ) + end + + def test_anchor_should_call_to_param + assert_equal('/c/a#anchor', + W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor')) + ) + end + + def test_anchor_should_be_cgi_escaped + assert_equal('/c/a#anc%2Fhor', + W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anc/hor')) + ) + end + + def test_default_host + add_host! + assert_equal('http://www.basecamphq.com/c/a/i', + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i') + ) + end + + def test_host_may_be_overridden + add_host! + assert_equal('http://37signals.basecamphq.com/c/a/i', + W.new.url_for(:host => '37signals.basecamphq.com', :controller => 'c', :action => 'a', :id => 'i') + ) + end + + def test_port + add_host! + assert_equal('http://www.basecamphq.com:3000/c/a/i', + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :port => 3000) + ) + end + + def test_protocol + add_host! + assert_equal('https://www.basecamphq.com/c/a/i', + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') + ) + end + + def test_protocol_with_and_without_separator + add_host! + assert_equal('https://www.basecamphq.com/c/a/i', + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') + ) + assert_equal('https://www.basecamphq.com/c/a/i', + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https://') + ) + end + + def test_trailing_slash + add_host! + options = {:controller => 'foo', :trailing_slash => true, :action => 'bar', :id => '33'} + assert_equal('http://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) ) + end + + def test_trailing_slash_with_protocol + add_host! + options = { :trailing_slash => true,:protocol => 'https', :controller => 'foo', :action => 'bar', :id => '33'} + assert_equal('https://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) ) + assert_equal 'https://www.basecamphq.com/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string'})) + end + + def test_trailing_slash_with_only_path + options = {:controller => 'foo', :trailing_slash => true} + assert_equal '/foo/', W.new.url_for(options.merge({:only_path => true})) + options.update({:action => 'bar', :id => '33'}) + assert_equal '/foo/bar/33/', W.new.url_for(options.merge({:only_path => true})) + assert_equal '/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string',:only_path => true})) + end + + def test_trailing_slash_with_anchor + options = {:trailing_slash => true, :controller => 'foo', :action => 'bar', :id => '33', :only_path => true, :anchor=> 'chapter7'} + assert_equal '/foo/bar/33/#chapter7', W.new.url_for(options) + assert_equal '/foo/bar/33/?query=string#chapter7', W.new.url_for(options.merge({:query => 'string'})) + end + + def test_trailing_slash_with_params + url = W.new.url_for(:trailing_slash => true, :only_path => true, :controller => 'cont', :action => 'act', :p1 => 'cafe', :p2 => 'link') + params = extract_params(url) + assert_equal params[0], { :p1 => 'cafe' }.to_query + assert_equal params[1], { :p2 => 'link' }.to_query + end + + def test_relative_url_root_is_respected + orig_relative_url_root = ActionController::Base.relative_url_root + ActionController::Base.relative_url_root = '/subdir' + + add_host! + assert_equal('https://www.basecamphq.com/subdir/c/a/i', + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') + ) + ensure + ActionController::Base.relative_url_root = orig_relative_url_root + end + + def test_named_routes + with_routing do |set| + set.draw do |map| + match 'this/is/verbose', :to => 'home#index', :as => :no_args + match 'home/sweet/home/:user', :to => 'home#index', :as => :home + end + + # We need to create a new class in order to install the new named route. + kls = Class.new { include AbstractController::UrlFor } + controller = kls.new + assert controller.respond_to?(:home_url) + assert_equal 'http://www.basecamphq.com/home/sweet/home/again', + controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again') + + assert_equal("/home/sweet/home/alabama", controller.send(:home_path, :user => 'alabama', :host => 'unused')) + assert_equal("http://www.basecamphq.com/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'www.basecamphq.com')) + assert_equal("http://www.basecamphq.com/this/is/verbose", controller.send(:no_args_url, :host=>'www.basecamphq.com')) + end + end + + def test_relative_url_root_is_respected_for_named_routes + orig_relative_url_root = ActionController::Base.relative_url_root + ActionController::Base.relative_url_root = '/subdir' + + with_routing do |set| + set.draw do |map| + match '/home/sweet/home/:user', :to => 'home#index', :as => :home + end + + kls = Class.new { include AbstractController::UrlFor } + controller = kls.new + + assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again', + controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again') + end + ensure + ActionController::Base.relative_url_root = orig_relative_url_root + end + + def test_only_path + with_routing do |set| + set.draw do |map| + match 'home/sweet/home/:user', :to => 'home#index', :as => :home + match ':controller/:action/:id' + end + + # We need to create a new class in order to install the new named route. + kls = Class.new { include AbstractController::UrlFor } + controller = kls.new + assert controller.respond_to?(:home_url) + assert_equal '/brave/new/world', + controller.send(:url_for, :controller => 'brave', :action => 'new', :id => 'world', :only_path => true) + + assert_equal("/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'unused', :only_path => true)) + assert_equal("/home/sweet/home/alabama", controller.send(:home_path, 'alabama')) + end + end + + def test_one_parameter + assert_equal('/c/a?param=val', + W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :param => 'val') + ) + end + + def test_two_parameters + url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2') + params = extract_params(url) + assert_equal params[0], { :p1 => 'X1' }.to_query + assert_equal params[1], { :p2 => 'Y2' }.to_query + end + + def test_hash_parameter + url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'}) + params = extract_params(url) + assert_equal params[0], { 'query[category]' => 'prof' }.to_query + assert_equal params[1], { 'query[name]' => 'Bob' }.to_query + end + + def test_array_parameter + url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof']) + params = extract_params(url) + assert_equal params[0], { 'query[]' => 'Bob' }.to_query + assert_equal params[1], { 'query[]' => 'prof' }.to_query + end + + def test_hash_recursive_parameters + url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'}) + params = extract_params(url) + assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query + assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query + assert_equal params[2], { 'query[person][position]' => 'prof' }.to_query + end + + def test_hash_recursive_and_array_parameters + url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'}) + assert_match %r(^/c/a/101), url + params = extract_params(url) + assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query + assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query + assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query + assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query + end + + def test_path_generation_for_symbol_parameter_keys + assert_generates("/image", :controller=> :image) + end + + def test_named_routes_with_nil_keys + with_routing do |set| + set.draw do |map| + match 'posts.:format', :to => 'posts#index', :as => :posts + match '/', :to => 'posts#index', :as => :main + end + + # We need to create a new class in order to install the new named route. + kls = Class.new { include AbstractController::UrlFor } + kls.default_url_options[:host] = 'www.basecamphq.com' + + controller = kls.new + params = {:action => :index, :controller => :posts, :format => :xml} + assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params)) + params[:format] = nil + assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params)) + end + end + + def test_multiple_includes_maintain_distinct_options + first_class = Class.new { include AbstractController::UrlFor } + second_class = Class.new { include AbstractController::UrlFor } + + first_host, second_host = 'firsthost.com', 'secondhost.com' + + first_class.default_url_options[:host] = first_host + second_class.default_url_options[:host] = second_host + + assert_equal first_class.default_url_options[:host], first_host + assert_equal second_class.default_url_options[:host], second_host + end + + private + def extract_params(url) + url.split('?', 2).last.split('&').sort + end + end + end +end
\ No newline at end of file diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index ad744421db..ea82758cf5 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -26,7 +26,7 @@ class Series < ActiveRecord::Base end class PolymorphicRoutesTest < ActionController::TestCase - include ActionController::UrlWriter + include ActionController::UrlFor self.default_url_options[:host] = 'example.com' def setup diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 65118f9bc9..1510a6a7e0 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -6,6 +6,7 @@ require 'pp' # require 'pp' early to prevent hidden_methods from not picking up module Submodule class ContainedEmptyController < ActionController::Base end + class ContainedNonEmptyController < ActionController::Base def public_action render :nothing => true @@ -20,12 +21,15 @@ module Submodule end hide_action :another_hidden_action end + class SubclassedController < ContainedNonEmptyController hide_action :public_action # Hiding it here should not affect the superclass. end end + class EmptyController < ActionController::Base end + class NonEmptyController < ActionController::Base def public_action render :nothing => true @@ -37,7 +41,6 @@ class NonEmptyController < ActionController::Base end class MethodMissingController < ActionController::Base - hide_action :shouldnt_be_called def shouldnt_be_called raise "NO WAY!" @@ -48,16 +51,15 @@ protected def method_missing(selector) render :text => selector.to_s end - end class DefaultUrlOptionsController < ActionController::Base - def default_url_options_action - render :nothing => true + def from_view + render :inline => "<%= #{params[:route]} %>" end def default_url_options(options = nil) - { :host => 'www.override.com', :action => 'new', :bacon => 'chunky' } + { :host => 'www.override.com', :action => 'new', :locale => 'en' } end end @@ -68,6 +70,7 @@ class ControllerClassTests < Test::Unit::TestCase assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path end + def test_controller_name assert_equal 'empty', EmptyController.controller_name assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name @@ -86,41 +89,16 @@ class ControllerInstanceTests < Test::Unit::TestCase def test_action_methods @empty_controllers.each do |c| - hide_mocha_methods_from_controller(c) assert_equal Set.new, c.class.__send__(:action_methods), "#{c.controller_path} should be empty!" end + @non_empty_controllers.each do |c| - hide_mocha_methods_from_controller(c) assert_equal Set.new(%w(public_action)), c.class.__send__(:action_methods), "#{c.controller_path} should not be empty!" end end - - protected - # Mocha adds some public instance methods to Object that would be - # considered actions, so explicitly hide_action them. - def hide_mocha_methods_from_controller(controller) - mocha_methods = [ - :expects, :mocha, :mocha_inspect, :reset_mocha, :stubba_object, - :stubba_method, :stubs, :verify, :__metaclass__, :__is_a__, :to_matcher, - ] - controller.class.__send__(:hide_action, *mocha_methods) - end end - class PerformActionTest < ActionController::TestCase - class MockLogger - attr_reader :logged - - def initialize - @logged = [] - end - - def method_missing(method, *args) - @logged << args.first.to_s - end - end - def use_controller(controller_class) @controller = controller_class.new @@ -128,9 +106,8 @@ class PerformActionTest < ActionController::TestCase # a more accurate simulation of what happens in "real life". @controller.logger = Logger.new(nil) - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new @request.host = "www.nextangle.com" rescue_action_in_public! @@ -145,8 +122,7 @@ class PerformActionTest < ActionController::TestCase def test_method_missing_is_not_an_action_name use_controller MethodMissingController - - assert ! @controller.__send__(:action_method?, 'method_missing') + assert !@controller.__send__(:action_method?, 'method_missing') get :method_missing assert_response :success @@ -172,16 +148,43 @@ class DefaultUrlOptionsTest < ActionController::TestCase def test_default_url_options_are_used_if_set with_routing do |set| set.draw do |map| - match 'default_url_options', :to => 'default_url_options#default_url_options_action', :as => :default_url_options + match 'from_view', :to => 'default_url_options#from_view', :as => :from_view match ':controller/:action' end - get :default_url_options_action # Make a dummy request so that the controller is initialized properly. + get :from_view, :route => "from_view_url" - assert_equal 'http://www.override.com/default_url_options/new?bacon=chunky', @controller.url_for(:controller => 'default_url_options') - assert_equal 'http://www.override.com/default_url_options?bacon=chunky', @controller.send(:default_url_options_url) + assert_equal 'http://www.override.com/from_view?locale=en', @response.body + assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url) + assert_equal 'http://www.override.com/default_url_options/new?locale=en', @controller.url_for(:controller => 'default_url_options') end end + + def test_default_url_options_are_used_in_non_positional_parameters + with_routing do |set| + set.draw do |map| + scope("/:locale") do + resources :descriptions + end + match ':controller/:action' + end + + get :from_view, :route => "description_path(1)" + + assert_equal '/en/descriptions/1', @response.body + assert_equal '/en/descriptions', @controller.send(:descriptions_path) + assert_equal '/pl/descriptions', @controller.send(:descriptions_path, "pl") + assert_equal '/pl/descriptions', @controller.send(:descriptions_path, :locale => "pl") + assert_equal '/pl/descriptions.xml', @controller.send(:descriptions_path, "pl", "xml") + assert_equal '/en/descriptions.xml', @controller.send(:descriptions_path, :format => "xml") + assert_equal '/en/descriptions/1', @controller.send(:description_path, 1) + assert_equal '/pl/descriptions/1', @controller.send(:description_path, "pl", 1) + assert_equal '/pl/descriptions/1', @controller.send(:description_path, 1, :locale => "pl") + assert_equal '/pl/descriptions/1.xml', @controller.send(:description_path, "pl", 1, "xml") + assert_equal '/en/descriptions/1.xml', @controller.send(:description_path, 1, :format => "xml") + end + end + end class EmptyUrlOptionsTest < ActionController::TestCase @@ -197,15 +200,12 @@ class EmptyUrlOptionsTest < ActionController::TestCase get :public_action assert_equal "http://www.example.com/non_empty/public_action", @controller.url_for end -end -class EnsureNamedRoutesWorksTicket22BugTest < ActionController::TestCase - def test_named_routes_still_work + def test_named_routes_with_path_without_doing_a_request_first with_routing do |set| set.draw do |map| resources :things end - EmptyController.send :include, ActionController::UrlWriter assert_equal '/things', EmptyController.new.send(:things_path) end diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index 139d91f8ac..c2b8cd85d8 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -100,268 +100,3 @@ class UrlRewriterTests < ActionController::TestCase end end -class UrlWriterTests < ActionController::TestCase - class W - include ActionController::UrlWriter - end - - def teardown - W.default_url_options.clear - end - - def add_host! - W.default_url_options[:host] = 'www.basecamphq.com' - end - - def test_exception_is_thrown_without_host - assert_raise RuntimeError do - W.new.url_for :controller => 'c', :action => 'a', :id => 'i' - end - end - - def test_anchor - assert_equal('/c/a#anchor', - W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor') - ) - end - - def test_anchor_should_call_to_param - assert_equal('/c/a#anchor', - W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor')) - ) - end - - def test_anchor_should_be_cgi_escaped - assert_equal('/c/a#anc%2Fhor', - W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anc/hor')) - ) - end - - def test_default_host - add_host! - assert_equal('http://www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i') - ) - end - - def test_host_may_be_overridden - add_host! - assert_equal('http://37signals.basecamphq.com/c/a/i', - W.new.url_for(:host => '37signals.basecamphq.com', :controller => 'c', :action => 'a', :id => 'i') - ) - end - - def test_port - add_host! - assert_equal('http://www.basecamphq.com:3000/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :port => 3000) - ) - end - - def test_protocol - add_host! - assert_equal('https://www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') - ) - end - - def test_protocol_with_and_without_separator - add_host! - assert_equal('https://www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') - ) - assert_equal('https://www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https://') - ) - end - - def test_trailing_slash - add_host! - options = {:controller => 'foo', :trailing_slash => true, :action => 'bar', :id => '33'} - assert_equal('http://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) ) - end - - def test_trailing_slash_with_protocol - add_host! - options = { :trailing_slash => true,:protocol => 'https', :controller => 'foo', :action => 'bar', :id => '33'} - assert_equal('https://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) ) - assert_equal 'https://www.basecamphq.com/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string'})) - end - - def test_trailing_slash_with_only_path - options = {:controller => 'foo', :trailing_slash => true} - assert_equal '/foo/', W.new.url_for(options.merge({:only_path => true})) - options.update({:action => 'bar', :id => '33'}) - assert_equal '/foo/bar/33/', W.new.url_for(options.merge({:only_path => true})) - assert_equal '/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string',:only_path => true})) - end - - def test_trailing_slash_with_anchor - options = {:trailing_slash => true, :controller => 'foo', :action => 'bar', :id => '33', :only_path => true, :anchor=> 'chapter7'} - assert_equal '/foo/bar/33/#chapter7', W.new.url_for(options) - assert_equal '/foo/bar/33/?query=string#chapter7', W.new.url_for(options.merge({:query => 'string'})) - end - - def test_trailing_slash_with_params - url = W.new.url_for(:trailing_slash => true, :only_path => true, :controller => 'cont', :action => 'act', :p1 => 'cafe', :p2 => 'link') - params = extract_params(url) - assert_equal params[0], { :p1 => 'cafe' }.to_query - assert_equal params[1], { :p2 => 'link' }.to_query - end - - def test_relative_url_root_is_respected - orig_relative_url_root = ActionController::Base.relative_url_root - ActionController::Base.relative_url_root = '/subdir' - - add_host! - assert_equal('https://www.basecamphq.com/subdir/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') - ) - ensure - ActionController::Base.relative_url_root = orig_relative_url_root - end - - def test_named_routes - with_routing do |set| - set.draw do |map| - match 'this/is/verbose', :to => 'home#index', :as => :no_args - match 'home/sweet/home/:user', :to => 'home#index', :as => :home - end - - # We need to create a new class in order to install the new named route. - kls = Class.new { include ActionController::UrlWriter } - controller = kls.new - assert controller.respond_to?(:home_url) - assert_equal 'http://www.basecamphq.com/home/sweet/home/again', - controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again') - - assert_equal("/home/sweet/home/alabama", controller.send(:home_path, :user => 'alabama', :host => 'unused')) - assert_equal("http://www.basecamphq.com/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'www.basecamphq.com')) - assert_equal("http://www.basecamphq.com/this/is/verbose", controller.send(:no_args_url, :host=>'www.basecamphq.com')) - end - end - - def test_relative_url_root_is_respected_for_named_routes - orig_relative_url_root = ActionController::Base.relative_url_root - ActionController::Base.relative_url_root = '/subdir' - - with_routing do |set| - set.draw do |map| - match '/home/sweet/home/:user', :to => 'home#index', :as => :home - end - - kls = Class.new { include ActionController::UrlWriter } - controller = kls.new - - assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again', - controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again') - end - ensure - ActionController::Base.relative_url_root = orig_relative_url_root - end - - def test_only_path - with_routing do |set| - set.draw do |map| - match 'home/sweet/home/:user', :to => 'home#index', :as => :home - match ':controller/:action/:id' - end - - # We need to create a new class in order to install the new named route. - kls = Class.new { include ActionController::UrlWriter } - controller = kls.new - assert controller.respond_to?(:home_url) - assert_equal '/brave/new/world', - controller.send(:url_for, :controller => 'brave', :action => 'new', :id => 'world', :only_path => true) - - assert_equal("/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'unused', :only_path => true)) - assert_equal("/home/sweet/home/alabama", controller.send(:home_path, 'alabama')) - end - end - - def test_one_parameter - assert_equal('/c/a?param=val', - W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :param => 'val') - ) - end - - def test_two_parameters - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2') - params = extract_params(url) - assert_equal params[0], { :p1 => 'X1' }.to_query - assert_equal params[1], { :p2 => 'Y2' }.to_query - end - - def test_hash_parameter - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'}) - params = extract_params(url) - assert_equal params[0], { 'query[category]' => 'prof' }.to_query - assert_equal params[1], { 'query[name]' => 'Bob' }.to_query - end - - def test_array_parameter - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof']) - params = extract_params(url) - assert_equal params[0], { 'query[]' => 'Bob' }.to_query - assert_equal params[1], { 'query[]' => 'prof' }.to_query - end - - def test_hash_recursive_parameters - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'}) - params = extract_params(url) - assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query - assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query - assert_equal params[2], { 'query[person][position]' => 'prof' }.to_query - end - - def test_hash_recursive_and_array_parameters - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'}) - assert_match %r(^/c/a/101), url - params = extract_params(url) - assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query - assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query - assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query - assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query - end - - def test_path_generation_for_symbol_parameter_keys - assert_generates("/image", :controller=> :image) - end - - def test_named_routes_with_nil_keys - with_routing do |set| - set.draw do |map| - match 'posts.:format', :to => 'posts#index', :as => :posts - match '/', :to => 'posts#index', :as => :main - end - - # We need to create a new class in order to install the new named route. - kls = Class.new { include ActionController::UrlWriter } - kls.default_url_options[:host] = 'www.basecamphq.com' - - controller = kls.new - params = {:action => :index, :controller => :posts, :format => :xml} - assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params)) - params[:format] = nil - assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params)) - end - end - - def test_multiple_includes_maintain_distinct_options - first_class = Class.new { include ActionController::UrlWriter } - second_class = Class.new { include ActionController::UrlWriter } - - first_host, second_host = 'firsthost.com', 'secondhost.com' - - first_class.default_url_options[:host] = first_host - second_class.default_url_options[:host] = second_host - - assert_equal first_class.default_url_options[:host], first_host - assert_equal second_class.default_url_options[:host], second_host - end - - private - def extract_params(url) - url.split('?', 2).last.split('&').sort - end -end diff --git a/actionpack/test/template/active_model_helper_i18n_test.rb b/actionpack/test/template/active_model_helper_i18n_test.rb index 2465444fc5..4eb2f262bd 100644 --- a/actionpack/test/template/active_model_helper_i18n_test.rb +++ b/actionpack/test/template/active_model_helper_i18n_test.rb @@ -16,27 +16,27 @@ class ActiveModelHelperI18nTest < Test::Unit::TestCase stubs(:content_tag).returns 'content_tag' - I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved" - I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:' + I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved" + I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).returns 'There were problems with the following fields:' end def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message - I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').never + I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').never error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en') end def test_error_messages_for_given_no_header_option_it_translates_header_message - I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns 'header message' + I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').returns 'header message' error_messages_for(:object => @object, :locale => 'en') end def test_error_messages_for_given_a_message_option_it_does_not_translate_message - I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).never + I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).never error_messages_for(:object => @object, :message => 'message', :locale => 'en') end def test_error_messages_for_given_no_message_option_it_translates_message - I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:' + I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).returns 'There were problems with the following fields:' error_messages_for(:object => @object, :locale => 'en') end end diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index b1e9fe99a2..acadbd0cd0 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -16,8 +16,8 @@ class FormHelperTest < ActionView::TestCase } } }, - :views => { - :labels => { + :helpers => { + :label => { :post => { :body => "Write entire text here" } diff --git a/actionpack/test/template/form_options_helper_i18n_test.rb b/actionpack/test/template/form_options_helper_i18n_test.rb index 91e370efa7..4972ea6511 100644 --- a/actionpack/test/template/form_options_helper_i18n_test.rb +++ b/actionpack/test/template/form_options_helper_i18n_test.rb @@ -6,7 +6,7 @@ class FormOptionsHelperI18nTests < ActionView::TestCase def setup @prompt_message = 'Select!' I18n.backend.send(:init_translations) - I18n.backend.store_translations :en, :support => { :select => { :prompt => @prompt_message } } + I18n.backend.store_translations :en, :helpers => { :select => { :prompt => @prompt_message } } end def teardown @@ -14,7 +14,7 @@ class FormOptionsHelperI18nTests < ActionView::TestCase end def test_select_with_prompt_true_translates_prompt_message - I18n.expects(:translate).with('support.select.prompt', { :default => 'Please select' }) + I18n.expects(:translate).with('helpers.select.prompt', { :default => 'Please select' }) select('post', 'category', [], :prompt => true) end @@ -24,4 +24,4 @@ class FormOptionsHelperI18nTests < ActionView::TestCase select('post', 'category', [], :prompt => true) ) end -end
\ No newline at end of file +end diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index abc084a74b..0b6c75c46e 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -105,8 +105,7 @@ module ActiveModel else attr_name = attribute.to_s.gsub('.', '_').humanize attr_name = @base.class.human_attribute_name(attribute, :default => attr_name) - options = { :default => "{{attribute}} {{message}}", :attribute => attr_name, - :scope => @base.class.i18n_scope } + options = { :default => "{{attribute}} {{message}}", :attribute => attr_name } messages.each do |m| full_messages << I18n.t(:"errors.format", options.merge(:message => m)) @@ -153,7 +152,7 @@ module ActiveModel :model => @base.class.model_name.human, :attribute => @base.class.human_attribute_name(attribute), :value => value, - :scope => [@base.class.i18n_scope, :errors] + :scope => [:errors] }.merge(options) I18n.translate(key, options) diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml index 1cdb897f13..ea58021767 100644 --- a/activemodel/lib/active_model/locale/en.yml +++ b/activemodel/lib/active_model/locale/en.yml @@ -1,27 +1,26 @@ en: - activemodel: - errors: - # model.errors.full_messages format. - format: "{{attribute}} {{message}}" + errors: + # The default format use in full error messages. + format: "{{attribute}} {{message}}" - # The values :model, :attribute and :value are always available for interpolation - # The value :count is available when applicable. Can be used for pluralization. - messages: - inclusion: "is not included in the list" - exclusion: "is reserved" - invalid: "is invalid" - confirmation: "doesn't match confirmation" - accepted: "must be accepted" - empty: "can't be empty" - blank: "can't be blank" - too_long: "is too long (maximum is {{count}} characters)" - too_short: "is too short (minimum is {{count}} characters)" - wrong_length: "is the wrong length (should be {{count}} characters)" - not_a_number: "is not a number" - greater_than: "must be greater than {{count}}" - greater_than_or_equal_to: "must be greater than or equal to {{count}}" - equal_to: "must be equal to {{count}}" - less_than: "must be less than {{count}}" - less_than_or_equal_to: "must be less than or equal to {{count}}" - odd: "must be odd" - even: "must be even" + # The values :model, :attribute and :value are always available for interpolation + # The value :count is available when applicable. Can be used for pluralization. + messages: + inclusion: "is not included in the list" + exclusion: "is reserved" + invalid: "is invalid" + confirmation: "doesn't match confirmation" + accepted: "must be accepted" + empty: "can't be empty" + blank: "can't be blank" + too_long: "is too long (maximum is {{count}} characters)" + too_short: "is too short (minimum is {{count}} characters)" + wrong_length: "is the wrong length (should be {{count}} characters)" + not_a_number: "is not a number" + greater_than: "must be greater than {{count}}" + greater_than_or_equal_to: "must be greater than or equal to {{count}}" + equal_to: "must be equal to {{count}}" + less_than: "must be less than {{count}}" + less_than_or_equal_to: "must be less than or equal to {{count}}" + odd: "must be odd" + even: "must be even" diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb index e5ef1e6114..2d2df269d0 100644 --- a/activemodel/lib/active_model/translation.rb +++ b/activemodel/lib/active_model/translation.rb @@ -25,13 +25,14 @@ module ActiveModel # Specify +options+ with additional translating options. def human_attribute_name(attribute, options = {}) defaults = lookup_ancestors.map do |klass| - :"#{klass.model_name.underscore}.#{attribute}" + :"#{self.i18n_scope}.attributes.#{klass.model_name.underscore}.#{attribute}" end + defaults << :"attributes.#{attribute}" defaults << options.delete(:default) if options[:default] defaults << attribute.to_s.humanize - options.reverse_merge! :scope => [self.i18n_scope, :attributes], :count => 1, :default => defaults + options.reverse_merge! :count => 1, :default => defaults I18n.translate(defaults.shift, options) end diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index d5460a58bd..276472ea46 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -15,21 +15,26 @@ module ActiveModel module ClassMethods # Validates each attribute against a block. # - # class Person < ActiveRecord::Base + # class Person + # include ActiveModel::Validations + # # validates_each :first_name, :last_name do |record, attr, value| # record.errors.add attr, 'starts with z.' if value[0] == ?z # end # end # # Options: - # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>). + # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, + # other options <tt>:create</tt>, <tt>:update</tt>). # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+. # * <tt>:allow_blank</tt> - Skip validation if attribute is blank. # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should - # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The + # occur (e.g. <tt>:if => :allow_validation</tt>, or + # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should - # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The + # not occur (e.g. <tt>:unless => :skip_validation</tt>, or + # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. def validates_each(*attr_names, &block) options = attr_names.extract_options!.symbolize_keys @@ -42,7 +47,9 @@ module ActiveModel # # This can be done with a symbol pointing to a method: # - # class Comment < ActiveRecord::Base + # class Comment + # include ActiveModel::Validations + # # validate :must_be_friends # # def must_be_friends @@ -52,7 +59,9 @@ module ActiveModel # # Or with a block which is passed the current record to be validated: # - # class Comment < ActiveRecord::Base + # class Comment + # include ActiveModel::Validations + # # validate do |comment| # comment.must_be_friends # end @@ -71,6 +80,13 @@ module ActiveModel end set_callback(:validate, *args, &block) end + + private + + def _merge_attributes(attr_names) + options = attr_names.extract_options! + options.merge(:attributes => attr_names) + end end # Returns the Errors object that holds all information about attribute error messages. @@ -90,27 +106,22 @@ module ActiveModel !valid? end - protected - # Hook method defining how an attribute value should be retieved. By default this is assumed - # to be an instance named after the attribute. Override this method in subclasses should you - # need to retrieve the value for a given attribute differently e.g. - # class MyClass - # include ActiveModel::Validations - # - # def initialize(data = {}) - # @data = data - # end - # - # private - # - # def read_attribute_for_validation(key) - # @data[key] - # end - # end - # - def read_attribute_for_validation(key) - send(key) - end + # Hook method defining how an attribute value should be retieved. By default this is assumed + # to be an instance named after the attribute. Override this method in subclasses should you + # need to retrieve the value for a given attribute differently e.g. + # class MyClass + # include ActiveModel::Validations + # + # def initialize(data = {}) + # @data = data + # end + # + # def read_attribute_for_validation(key) + # @data[key] + # end + # end + # + alias :read_attribute_for_validation :send end end diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index bd9463ed27..0423fcd17f 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -10,6 +10,13 @@ module ActiveModel record.errors.add(attribute, :accepted, :default => options[:message]) end end + + def setup(klass) + # Note: instance_methods.map(&:to_s) is important for 1.9 compatibility + # as instance_methods returns symbols unlike 1.8 which returns strings. + new_attributes = attributes.reject { |name| klass.instance_methods.map(&:to_s).include?("#{name}=") } + klass.send(:attr_accessor, *new_attributes) + end end module ClassMethods @@ -37,18 +44,7 @@ module ActiveModel # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. def validates_acceptance_of(*attr_names) - options = attr_names.extract_options! - - db_cols = begin - column_names - rescue Exception # To ignore both statement and connection errors - [] - end - - names = attr_names.reject { |name| db_cols.include?(name.to_s) } - attr_accessor(*names) - - validates_with AcceptanceValidator, options.merge(:attributes => attr_names) + validates_with AcceptanceValidator, _merge_attributes(attr_names) end end end diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index b06effdceb..8041d4b61f 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -6,6 +6,10 @@ module ActiveModel return if confirmed.nil? || value == confirmed record.errors.add(attribute, :confirmation, :default => options[:message]) end + + def setup(klass) + klass.send(:attr_accessor, *attributes.map { |attribute| :"#{attribute}_confirmation" }) + end end module ClassMethods @@ -38,9 +42,7 @@ module ActiveModel # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. def validates_confirmation_of(*attr_names) - options = attr_names.extract_options! - attr_accessor(*(attr_names.map { |n| :"#{n}_confirmation" })) - validates_with ConfirmationValidator, options.merge(:attributes => attr_names) + validates_with ConfirmationValidator, _merge_attributes(attr_names) end end end diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb index f8759f253b..0a44c6d54f 100644 --- a/activemodel/lib/active_model/validations/exclusion.rb +++ b/activemodel/lib/active_model/validations/exclusion.rb @@ -2,6 +2,7 @@ module ActiveModel module Validations class ExclusionValidator < EachValidator def check_validity! + options[:in] ||= options.delete(:within) raise ArgumentError, "An object with the method include? is required must be supplied as the " << ":in option of the configuration hash" unless options[:in].respond_to?(:include?) end @@ -33,9 +34,7 @@ module ActiveModel # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. def validates_exclusion_of(*attr_names) - options = attr_names.extract_options! - options[:in] ||= options.delete(:within) - validates_with ExclusionValidator, options.merge(:attributes => attr_names) + validates_with ExclusionValidator, _merge_attributes(attr_names) end end end diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb index d5427c2b03..9a9e7eca4d 100644 --- a/activemodel/lib/active_model/validations/format.rb +++ b/activemodel/lib/active_model/validations/format.rb @@ -8,6 +8,20 @@ module ActiveModel record.errors.add(attribute, :invalid, :default => options[:message], :value => value) end end + + def check_validity! + unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or" + raise ArgumentError, "Either :with or :without must be supplied (but not both)" + end + + if options[:with] && !options[:with].is_a?(Regexp) + raise ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash" + end + + if options[:without] && !options[:without].is_a?(Regexp) + raise ArgumentError, "A regular expression must be supplied as the :without option of the configuration hash" + end + end end module ClassMethods @@ -43,21 +57,7 @@ module ActiveModel # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. def validates_format_of(*attr_names) - options = attr_names.extract_options! - - unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or" - raise ArgumentError, "Either :with or :without must be supplied (but not both)" - end - - if options[:with] && !options[:with].is_a?(Regexp) - raise ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash" - end - - if options[:without] && !options[:without].is_a?(Regexp) - raise ArgumentError, "A regular expression must be supplied as the :without option of the configuration hash" - end - - validates_with FormatValidator, options.merge(:attributes => attr_names) + validates_with FormatValidator, _merge_attributes(attr_names) end end end diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index a122e9e737..25b8c7866d 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -2,6 +2,7 @@ module ActiveModel module Validations class InclusionValidator < EachValidator def check_validity! + options[:in] ||= options.delete(:within) raise ArgumentError, "An object with the method include? is required must be supplied as the " << ":in option of the configuration hash" unless options[:in].respond_to?(:include?) end @@ -33,9 +34,7 @@ module ActiveModel # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. def validates_inclusion_of(*attr_names) - options = attr_names.extract_options! - options[:in] ||= options.delete(:within) - validates_with InclusionValidator, options.merge(:attributes => attr_names) + validates_with InclusionValidator, _merge_attributes(attr_names) end end end diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index 6e90a75c17..f41ce34328 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -107,8 +107,7 @@ module ActiveModel # count words as in above example.) # Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters. def validates_length_of(*attr_names) - options = attr_names.extract_options! - validates_with LengthValidator, options.merge(:attributes => attr_names) + validates_with LengthValidator, _merge_attributes(attr_names) end alias_method :validates_size_of, :validates_length_of diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index f2aab8c5b8..9dfc5125cd 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -103,8 +103,7 @@ module ActiveModel # end # def validates_numericality_of(*attr_names) - options = attr_names.extract_options! - validates_with NumericalityValidator, options.merge(:attributes => attr_names) + validates_with NumericalityValidator, _merge_attributes(attr_names) end end end diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb index a4c6f866a7..4a71cf79b5 100644 --- a/activemodel/lib/active_model/validations/presence.rb +++ b/activemodel/lib/active_model/validations/presence.rb @@ -34,8 +34,7 @@ module ActiveModel # The method, proc or string should return or evaluate to a true or false value. # def validates_presence_of(*attr_names) - options = attr_names.extract_options! - validates_with PresenceValidator, options.merge(:attributes => attr_names) + validates_with PresenceValidator, _merge_attributes(attr_names) end end end diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb new file mode 100644 index 0000000000..61caf32c06 --- /dev/null +++ b/activemodel/lib/active_model/validations/validates.rb @@ -0,0 +1,86 @@ +module ActiveModel + module Validations + module ClassMethods + # This method is a shortcut to all default validators and any custom + # validator classes ending in 'Validator'. Note that Rails default + # validators can be overridden inside specific classes by creating + # custom validator classes in their place such as PresenceValidator. + # + # Examples of using the default rails validators: + # validates :terms, :acceptance => true + # validates :password, :confirmation => true + # validates :username, :exclusion => { :in => %w(admin superuser) } + # validates :email, :format => { :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create } + # validates :age, :inclusion => { :in => 0..9 } + # validates :first_name, :length => { :maximum => 30 } + # validates :age, :numericality => true + # validates :username, :presence => true + # validates :username, :uniqueness => true + # + # The power of the +validates+ method comes when using cusom validators + # and default validators in one call for a given attribute e.g. + # class EmailValidator < ActiveModel::EachValidator + # def validate_each(record, attribute, value) + # record.errors[attribute] << (options[:message] || "is not an email") unless + # value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i + # end + # end + # + # class Person + # include ActiveModel::Validations + # attr_accessor :name, :email + # + # validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 100 } + # validates :email, :presence => true, :format => { :with => /@/ } + # end + # + # Validator classes my also exist within the class being validated + # allowing custom modules of validators to be included as needed e.g. + # + # module MyValidators + # class TitleValidator < ActiveModel::EachValidator + # def validate_each(record, attribute, value) + # record.errors[attribute] << "must start with 'the'" unless =~ /^the/i + # end + # end + # end + # + # class Film + # include ActiveModel::Validations + # include MyValidators + # + # validates :name, :title => true + # end + # + # The options :if, :unless, :on, :allow_blank and :allow_nil can be given to one specific + # validator: + # + # validates :password, :presence => { :if => :password_required? }, :confirmation => true + # + # Or to all at the same time: + # + # validates :password, :presence => true, :confirmation => true, :if => :password_required? + # + def validates(*attributes) + defaults = attributes.extract_options! + validations = defaults.slice!(:if, :unless, :on, :allow_blank, :allow_nil) + + raise ArgumentError, "You need to supply at least one attribute" if attributes.empty? + raise ArgumentError, "Attribute names must be symbols" if attributes.any?{ |attribute| !attribute.is_a?(Symbol) } + raise ArgumentError, "You need to supply at least one validation" if validations.empty? + + defaults.merge!(:attributes => attributes) + + validations.each do |key, options| + begin + validator = const_get("#{key.to_s.camelize}Validator") + rescue NameError + raise ArgumentError, "Unknown validator: '#{key}'" + end + + validates_with(validator, defaults.merge(options == true ? {} : options)) + end + end + end + end +end
\ No newline at end of file diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 8d521173c6..db563876af 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -2,14 +2,16 @@ module ActiveModel module Validations module ClassMethods - # Passes the record off to the class or classes specified and allows them to add errors based on more complex conditions. + # Passes the record off to the class or classes specified and allows them + # to add errors based on more complex conditions. # - # class Person < ActiveRecord::Base + # class Person + # include ActiveModel::Validations # validates_with MyValidator # end # - # class MyValidator < ActiveRecord::Validator - # def validate + # class MyValidator < ActiveModel::Validator + # def validate(record) # if some_complex_logic # record.errors[:base] << "This record is invalid" # end @@ -23,37 +25,46 @@ module ActiveModel # # You may also pass it multiple classes, like so: # - # class Person < ActiveRecord::Base + # class Person + # include ActiveModel::Validations # validates_with MyValidator, MyOtherValidator, :on => :create # end # # Configuration options: - # * <tt>on</tt> - Specifies when this validation is active (<tt>:create</tt> or <tt>:update</tt> - # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should - # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). + # * <tt>on</tt> - Specifies when this validation is active + # (<tt>:create</tt> or <tt>:update</tt> + # * <tt>if</tt> - Specifies a method, proc or string to call to determine + # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>, + # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). # The method, proc or string should return or evaluate to a true or false value. - # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should - # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). + # * <tt>unless</tt> - Specifies a method, proc or string to call to + # determine if the validation should not occur + # (e.g. <tt>:unless => :skip_validation</tt>, or + # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). # The method, proc or string should return or evaluate to a true or false value. # - # If you pass any additional configuration options, they will be passed to the class and available as <tt>options</tt>: + # If you pass any additional configuration options, they will be passed + # to the class and available as <tt>options</tt>: # - # class Person < ActiveRecord::Base + # class Person + # include ActiveModel::Validations # validates_with MyValidator, :my_custom_key => "my custom value" # end # - # class MyValidator < ActiveRecord::Validator - # def validate + # class MyValidator < ActiveModel::Validator + # def validate(record) # options[:my_custom_key] # => "my custom value" # end # end # def validates_with(*args, &block) options = args.extract_options! - args.each { |klass| validate(klass.new(options, &block), options) } + args.each do |klass| + validator = klass.new(options, &block) + validator.setup(self) if validator.respond_to?(:setup) + validate(validator, options) + end end end end -end - - +end
\ No newline at end of file diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 01695cb73a..382a4cc98d 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -1,12 +1,13 @@ module ActiveModel #:nodoc: - # A simple base class that can be used along with ActiveModel::Base.validates_with + # A simple base class that can be used along with ActiveModel::Validations::ClassMethods.validates_with # - # class Person < ActiveModel::Base + # class Person + # include ActiveModel::Validations # validates_with MyValidator # end # # class MyValidator < ActiveModel::Validator - # def validate + # def validate(record) # if some_complex_logic # record.errors[:base] = "This record is invalid" # end @@ -18,10 +19,11 @@ module ActiveModel #:nodoc: # end # end # - # Any class that inherits from ActiveModel::Validator will have access to <tt>record</tt>, - # which is an instance of the record being validated, and must implement a method called <tt>validate</tt>. + # Any class that inherits from ActiveModel::Validator must implement a method + # called <tt>validate</tt> which accepts a <tt>record</tt>. # - # class Person < ActiveModel::Base + # class Person + # include ActiveModel::Validations # validates_with MyValidator # end # @@ -36,7 +38,7 @@ module ActiveModel #:nodoc: # from within the validators message # # class MyValidator < ActiveModel::Validator - # def validate + # def validate(record) # record.errors[:base] << "This is some custom error message" # record.errors[:first_name] << "This is some complex validation" # # etc... @@ -51,13 +53,47 @@ module ActiveModel #:nodoc: # @my_custom_field = options[:field_name] || :first_name # end # end + # + # The easiest way to add custom validators for validating individual attributes + # is with the convenient ActiveModel::EachValidator for example: + # + # class TitleValidator < ActiveModel::EachValidator + # def validate_each(record, attribute, value) + # record.errors[attribute] << 'must be Mr. Mrs. or Dr.' unless ['Mr.', 'Mrs.', 'Dr.'].include?(value) + # end + # end + # + # This can now be used in combination with the +validates+ method + # (see ActiveModel::Validations::ClassMethods.validates for more on this) + # + # class Person + # include ActiveModel::Validations + # attr_accessor :title + # + # validates :title, :presence => true, :title => true + # end + # + # Validator may also define a +setup+ instance method which will get called + # with the class that using that validator as it's argument. This can be + # useful when there are prerequisites such as an attr_accessor being present + # for example: + # + # class MyValidator < ActiveModel::Validator + # def setup(klass) + # klass.send :attr_accessor, :custom_attribute + # end + # end + # class Validator attr_reader :options + # Accepts options that will be made availible through the +options+ reader. def initialize(options) @options = options end + # Override this method in subclasses with validation logic, adding errors + # to the records +errors+ array where necessary. def validate(record) raise NotImplementedError end @@ -70,7 +106,10 @@ module ActiveModel #:nodoc: # All ActiveModel validations are built on top of this Validator. class EachValidator < Validator attr_reader :attributes - + + # Returns a new validator instance. All options will be available via the + # +options+ reader, however the <tt>:attributes</tt> option will be removed + # and instead be made available through the +attributes+ reader. def initialize(options) @attributes = Array(options.delete(:attributes)) raise ":attributes cannot be blank" if @attributes.empty? @@ -78,18 +117,26 @@ module ActiveModel #:nodoc: check_validity! end + # Performs validation on the supplied record. By default this will call + # +validates_each+ to determine validity therefore subclasses should + # override +validates_each+ with validation logic. def validate(record) attributes.each do |attribute| - value = record.send(:read_attribute_for_validation, attribute) + value = record.read_attribute_for_validation(attribute) next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank]) validate_each(record, attribute, value) end end + #Â Override this method in subclasses with the validation logic, adding + # errors to the records +errors+ array where necessary. def validate_each(record, attribute, value) raise NotImplementedError end + # Hook method that gets called by the initializer allowing verification + # that the arguments supplied are valid. You could for example raise an + # ArgumentError when invalid options are supplied. def check_validity! end end @@ -103,6 +150,8 @@ module ActiveModel #:nodoc: super end + private + def validate_each(record, attribute, value) @block.call(record, attribute, value) end diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb index bfc1ca12e6..e25d308ca1 100644 --- a/activemodel/test/cases/translation_test.rb +++ b/activemodel/test/cases/translation_test.rb @@ -11,6 +11,11 @@ class ActiveModelI18nTests < ActiveModel::TestCase I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:name => 'person name attribute'} } } assert_equal 'person name attribute', Person.human_attribute_name('name') end + + def test_translated_model_attributes_with_default + I18n.backend.store_translations 'en', :attributes => { :name => 'name default attribute' } + assert_equal 'name default attribute', Person.human_attribute_name('name') + end def test_translated_model_attributes_with_symbols I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:name => 'person name attribute'} } } diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb index 54b2405c92..6116ef71d4 100644 --- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb @@ -7,42 +7,6 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase def setup Person.reset_callbacks(:validate) @person = Person.new - - @old_load_path, @old_backend = I18n.load_path, I18n.backend - I18n.load_path.clear - I18n.backend = I18n::Backend::Simple.new - - I18n.backend.store_translations :'en', { - :activemodel => { - :errors => { - :messages => { - :inclusion => "is not included in the list", - :exclusion => "is reserved", - :invalid => "is invalid", - :confirmation => "doesn't match confirmation", - :accepted => "must be accepted", - :empty => "can't be empty", - :blank => "can't be blank", - :too_long => "is too long (maximum is {{count}} characters)", - :too_short => "is too short (minimum is {{count}} characters)", - :wrong_length => "is the wrong length (should be {{count}} characters)", - :not_a_number => "is not a number", - :greater_than => "must be greater than {{count}}", - :greater_than_or_equal_to => "must be greater than or equal to {{count}}", - :equal_to => "must be equal to {{count}}", - :less_than => "must be less than {{count}}", - :less_than_or_equal_to => "must be less than or equal to {{count}}", - :odd => "must be odd", - :even => "must be even" - } - } - } - } - end - - def teardown - I18n.load_path.replace @old_load_path - I18n.backend = @old_backend end # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value) diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index a7656fe219..7d33fcea98 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -9,10 +9,10 @@ class I18nValidationTest < ActiveModel::TestCase Person.reset_callbacks(:validate) @person = Person.new - @old_load_path, @old_backend = I18n.load_path, I18n.backend + @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend I18n.load_path.clear I18n.backend = I18n::Backend::Simple.new - I18n.backend.store_translations('en', :activemodel => {:errors => {:messages => {:custom => nil}}}) + I18n.backend.store_translations('en', :errors => {:messages => {:custom => nil}}) end def teardown @@ -42,13 +42,13 @@ class I18nValidationTest < ActiveModel::TestCase end def test_errors_full_messages_translates_human_attribute_name_for_model_attributes - @person.errors.add('name', 'empty') - I18n.expects(:translate).with(:"person.name", :default => ['Name', 'Name'], :scope => [:activemodel, :attributes], :count => 1).returns('Name') - @person.errors.full_messages + @person.errors.add(:name, 'not found') + Person.expects(:human_attribute_name).with(:name, :default => 'Name').returns("Person's name") + assert_equal ["Person's name not found"], @person.errors.full_messages end def test_errors_full_messages_uses_format - I18n.backend.store_translations('en', :activemodel => {:errors => {:format => "Field {{attribute}} {{message}}"}}) + I18n.backend.store_translations('en', :errors => {:format => "Field {{attribute}} {{message}}"}) @person.errors.add('name', 'empty') assert_equal ["Field Name empty"], @person.errors.full_messages end @@ -254,8 +254,8 @@ class I18nValidationTest < ActiveModel::TestCase # validates_confirmation_of w/o mocha def test_validates_confirmation_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:confirmation => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:confirmation => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:confirmation => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:confirmation => 'global message'}} Person.validates_confirmation_of :title @person.title_confirmation = 'foo' @@ -264,7 +264,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_confirmation_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:confirmation => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:confirmation => 'global message'}} Person.validates_confirmation_of :title @person.title_confirmation = 'foo' @@ -275,8 +275,8 @@ class I18nValidationTest < ActiveModel::TestCase # validates_acceptance_of w/o mocha def test_validates_acceptance_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:accepted => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:accepted => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:accepted => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:accepted => 'global message'}} Person.validates_acceptance_of :title, :allow_nil => false @person.valid? @@ -284,7 +284,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_acceptance_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:accepted => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:accepted => 'global message'}} Person.validates_acceptance_of :title, :allow_nil => false @person.valid? @@ -294,8 +294,8 @@ class I18nValidationTest < ActiveModel::TestCase # validates_presence_of w/o mocha def test_validates_presence_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:blank => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:blank => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:blank => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:blank => 'global message'}} Person.validates_presence_of :title @person.valid? @@ -303,7 +303,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_presence_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:blank => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:blank => 'global message'}} Person.validates_presence_of :title @person.valid? @@ -313,8 +313,8 @@ class I18nValidationTest < ActiveModel::TestCase # validates_length_of :within w/o mocha def test_validates_length_of_within_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:too_short => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:too_short => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:too_short => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:too_short => 'global message'}} Person.validates_length_of :title, :within => 3..5 @person.valid? @@ -322,7 +322,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_length_of_within_finds_global_default_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:too_short => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:too_short => 'global message'}} Person.validates_length_of :title, :within => 3..5 @person.valid? @@ -332,8 +332,8 @@ class I18nValidationTest < ActiveModel::TestCase # validates_length_of :is w/o mocha def test_validates_length_of_is_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:wrong_length => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:wrong_length => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:wrong_length => 'global message'}} Person.validates_length_of :title, :is => 5 @person.valid? @@ -341,7 +341,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_length_of_is_finds_global_default_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:wrong_length => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:wrong_length => 'global message'}} Person.validates_length_of :title, :is => 5 @person.valid? @@ -351,8 +351,8 @@ class I18nValidationTest < ActiveModel::TestCase # validates_format_of w/o mocha def test_validates_format_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:invalid => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:invalid => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:invalid => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}} Person.validates_format_of :title, :with => /^[1-9][0-9]*$/ @person.valid? @@ -360,7 +360,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_format_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:invalid => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}} Person.validates_format_of :title, :with => /^[1-9][0-9]*$/ @person.valid? @@ -370,8 +370,8 @@ class I18nValidationTest < ActiveModel::TestCase # validates_inclusion_of w/o mocha def test_validates_inclusion_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:inclusion => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:inclusion => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:inclusion => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:inclusion => 'global message'}} Person.validates_inclusion_of :title, :in => %w(a b c) @person.valid? @@ -379,7 +379,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_inclusion_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:inclusion => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:inclusion => 'global message'}} Person.validates_inclusion_of :title, :in => %w(a b c) @person.valid? @@ -389,8 +389,8 @@ class I18nValidationTest < ActiveModel::TestCase # validates_exclusion_of w/o mocha def test_validates_exclusion_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:exclusion => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:exclusion => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:exclusion => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:exclusion => 'global message'}} Person.validates_exclusion_of :title, :in => %w(a b c) @person.title = 'a' @@ -399,7 +399,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_exclusion_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:exclusion => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:exclusion => 'global message'}} Person.validates_exclusion_of :title, :in => %w(a b c) @person.title = 'a' @@ -410,8 +410,8 @@ class I18nValidationTest < ActiveModel::TestCase # validates_numericality_of without :only_integer w/o mocha def test_validates_numericality_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:not_a_number => 'global message'}} Person.validates_numericality_of :title @person.title = 'a' @@ -420,7 +420,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_numericality_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:not_a_number => 'global message'}} Person.validates_numericality_of :title, :only_integer => true @person.title = 'a' @@ -431,8 +431,8 @@ class I18nValidationTest < ActiveModel::TestCase # validates_numericality_of with :only_integer w/o mocha def test_validates_numericality_of_only_integer_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:not_a_number => 'global message'}} Person.validates_numericality_of :title, :only_integer => true @person.title = 'a' @@ -441,7 +441,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_numericality_of_only_integer_finds_global_default_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:not_a_number => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:not_a_number => 'global message'}} Person.validates_numericality_of :title, :only_integer => true @person.title = 'a' @@ -452,8 +452,8 @@ class I18nValidationTest < ActiveModel::TestCase # validates_numericality_of :odd w/o mocha def test_validates_numericality_of_odd_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:odd => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:odd => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:odd => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:odd => 'global message'}} Person.validates_numericality_of :title, :only_integer => true, :odd => true @person.title = 0 @@ -462,7 +462,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_numericality_of_odd_finds_global_default_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:odd => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:odd => 'global message'}} Person.validates_numericality_of :title, :only_integer => true, :odd => true @person.title = 0 @@ -473,8 +473,8 @@ class I18nValidationTest < ActiveModel::TestCase # validates_numericality_of :less_than w/o mocha def test_validates_numericality_of_less_than_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:less_than => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:less_than => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:less_than => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:less_than => 'global message'}} Person.validates_numericality_of :title, :only_integer => true, :less_than => 0 @person.title = 1 @@ -483,7 +483,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_numericality_of_less_than_finds_global_default_translation - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:less_than => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:less_than => 'global message'}} Person.validates_numericality_of :title, :only_integer => true, :less_than => 0 @person.title = 1 @@ -494,7 +494,7 @@ class I18nValidationTest < ActiveModel::TestCase # test with validates_with def test_validations_with_message_symbol_must_translate - I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:custom_error => "I am a custom error"}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:custom_error => "I am a custom error"}} Person.validates_presence_of :title, :message => :custom_error @person.title = nil @person.valid? @@ -502,7 +502,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_with_message_symbol_must_translate_per_attribute - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}} + I18n.backend.store_translations 'en', :errors => {:models => {:person => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}} Person.validates_presence_of :title, :message => :custom_error @person.title = nil @person.valid? @@ -510,7 +510,7 @@ class I18nValidationTest < ActiveModel::TestCase end def test_validates_with_message_symbol_must_translate_per_model - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:custom_error => "I am a custom error"}}}} + I18n.backend.store_translations 'en', :errors => {:models => {:person => {:custom_error => "I am a custom error"}}} Person.validates_presence_of :title, :message => :custom_error @person.title = nil @person.valid? diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb new file mode 100644 index 0000000000..181ff38b64 --- /dev/null +++ b/activemodel/test/cases/validations/validates_test.rb @@ -0,0 +1,88 @@ +# encoding: utf-8 +require 'cases/helper' +require 'models/person' +require 'models/person_with_validator' +require 'validators/email_validator' + +class ValidatesTest < ActiveModel::TestCase + setup :reset_callbacks + teardown :reset_callbacks + + def reset_callbacks + Person.reset_callbacks(:validate) + end + + def test_validates_with_built_in_validation + Person.validates :title, :numericality => true + person = Person.new + person.valid? + assert person.errors[:title].include?('is not a number') + end + + def test_validates_with_built_in_validation_and_options + Person.validates :title, :numericality => { :message => 'my custom message' } + person = Person.new + person.valid? + assert person.errors[:title].include?('my custom message') + end + + def test_validates_with_validator_class + Person.validates :karma, :email => true + person = Person.new + person.valid? + assert person.errors[:karma].include?('is not an email') + end + + def test_validates_with_if_as_local_conditions + Person.validates :karma, :presence => true, :email => { :unless => :condition_is_true } + person = Person.new + person.valid? + assert !person.errors[:karma].include?('is not an email') + assert person.errors[:karma].include?('can\'t be blank') + end + + def test_validates_with_if_as_shared_conditions + Person.validates :karma, :presence => true, :email => true, :if => :condition_is_true + person = Person.new + person.valid? + assert person.errors[:karma].include?('is not an email') + assert person.errors[:karma].include?('can\'t be blank') + end + + def test_validates_with_unless_shared_conditions + Person.validates :karma, :presence => true, :email => true, :unless => :condition_is_true + person = Person.new + assert person.valid? + end + + def test_validates_with_allow_nil_shared_conditions + Person.validates :karma, :length => { :minimum => 20 }, :email => true, :allow_nil => true + person = Person.new + assert person.valid? + end + + def test_validates_with_validator_class_and_options + Person.validates :karma, :email => { :message => 'my custom message' } + person = Person.new + person.valid? + assert person.errors[:karma].include?('my custom message') + end + + def test_validates_with_unknown_validator + assert_raise(ArgumentError) { Person.validates :karma, :unknown => true } + end + + def test_validates_with_included_validator + PersonWithValidator.validates :title, :presence => true + person = PersonWithValidator.new + person.valid? + assert person.errors[:title].include?('Local validator') + end + + def test_validates_with_included_validator_and_options + PersonWithValidator.validates :title, :presence => { :custom => ' please' } + person = PersonWithValidator.new + person.valid? + assert person.errors[:title].include?('Local validator please') + end +end
\ No newline at end of file diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb index 7540ccb580..66b072ea38 100644 --- a/activemodel/test/cases/validations/with_validation_test.rb +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -120,6 +120,28 @@ class ValidatesWithTest < ActiveRecord::TestCase Topic.validates_with(validator, :if => "1 == 1", :foo => :bar) assert topic.valid? end + + test "calls setup method of validator passing in self when validator has setup method" do + topic = Topic.new + validator = stub_everything + validator.stubs(:new).returns(validator) + validator.stubs(:validate) + validator.stubs(:respond_to?).with(:setup).returns(true) + validator.expects(:setup).with(Topic).once + Topic.validates_with(validator) + assert topic.valid? + end + + test "doesn't call setup method of validator when validator has no setup method" do + topic = Topic.new + validator = stub_everything + validator.stubs(:new).returns(validator) + validator.stubs(:validate) + validator.stubs(:respond_to?).with(:setup).returns(false) + validator.expects(:setup).with(Topic).never + Topic.validates_with(validator) + assert topic.valid? + end test "validates_with with options" do Topic.validates_with(ValidatorThatValidatesOptions, :field => :first_name) diff --git a/activemodel/test/models/custom_reader.rb b/activemodel/test/models/custom_reader.rb index 7ac70e6167..14a8be9ebc 100644 --- a/activemodel/test/models/custom_reader.rb +++ b/activemodel/test/models/custom_reader.rb @@ -8,8 +8,6 @@ class CustomReader def []=(key, value) @data[key] = value end - - private def read_attribute_for_validation(key) @data[key] diff --git a/activemodel/test/models/person.rb b/activemodel/test/models/person.rb index c83d768379..53ac68055f 100644 --- a/activemodel/test/models/person.rb +++ b/activemodel/test/models/person.rb @@ -3,6 +3,10 @@ class Person extend ActiveModel::Translation attr_accessor :title, :karma, :salary + + def condition_is_true + true + end end class Child < Person diff --git a/activemodel/test/models/person_with_validator.rb b/activemodel/test/models/person_with_validator.rb new file mode 100644 index 0000000000..f9763ea853 --- /dev/null +++ b/activemodel/test/models/person_with_validator.rb @@ -0,0 +1,11 @@ +class PersonWithValidator + include ActiveModel::Validations + + class PresenceValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + record.errors[attribute] << "Local validator#{options[:custom]}" if value.blank? + end + end + + attr_accessor :title, :karma +end diff --git a/activemodel/test/validators/email_validator.rb b/activemodel/test/validators/email_validator.rb new file mode 100644 index 0000000000..cff47ac230 --- /dev/null +++ b/activemodel/test/validators/email_validator.rb @@ -0,0 +1,6 @@ +class EmailValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + record.errors[attribute] << (options[:message] || "is not an email") unless + value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i + end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml index e33d389f8c..4115cc8e17 100644 --- a/activerecord/lib/active_record/locale/en.yml +++ b/activerecord/lib/active_record/locale/en.yml @@ -1,33 +1,9 @@ en: - activerecord: - errors: - # model.errors.full_messages format. - format: "{{attribute}} {{message}}" - - # The values :model, :attribute and :value are always available for interpolation - # The value :count is available when applicable. Can be used for pluralization. - messages: - inclusion: "is not included in the list" - exclusion: "is reserved" - invalid: "is invalid" - confirmation: "doesn't match confirmation" - accepted: "must be accepted" - empty: "can't be empty" - blank: "can't be blank" - too_long: "is too long (maximum is {{count}} characters)" - too_short: "is too short (minimum is {{count}} characters)" - wrong_length: "is the wrong length (should be {{count}} characters)" - taken: "has already been taken" - not_a_number: "is not a number" - greater_than: "must be greater than {{count}}" - greater_than_or_equal_to: "must be greater than or equal to {{count}}" - equal_to: "must be equal to {{count}}" - less_than: "must be less than {{count}}" - less_than_or_equal_to: "must be less than or equal to {{count}}" - odd: "must be odd" - even: "must be even" - record_invalid: "Validation failed: {{errors}}" - # Append your own errors here or at the model/attributes scope. + errors: + messages: + taken: "has already been taken" + record_invalid: "Validation failed: {{errors}}" + # Append your own errors here or at the model/attributes scope. # You can define own errors for models or model attributes. # The values :model, :attribute and :value are always available for interpolation. @@ -42,7 +18,14 @@ en: # Will define custom blank validation message for User model and # custom blank validation message for login attribute of User model. #models: - + + # Attributes names common to most models + #attributes: + #created_at: "Created at" + #updated_at: "Updated at" + + # ActiveRecord models configuration + #activerecord: # Translate model names. Used in Model.human_name(). #models: # For example, @@ -55,4 +38,3 @@ en: # user: # login: "Handle" # will translate User attribute "login" as "Handle" - diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index a35edace19..55008271b7 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -67,5 +67,16 @@ module ActiveRecord end end + initializer "active_record.i18n_deprecation" do + require 'active_support/i18n' + + begin + I18n.t(:"activerecord.errors", :raise => true) + warn "[DEPRECATION] \"activerecord.errors\" namespace is deprecated in I18n " << + "yml files, please use just \"errors\" instead." + rescue Exception => e + # No message then. + end + end end end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 12c1f23763..d5adcba3ba 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -11,7 +11,7 @@ module ActiveRecord def initialize(record) @record = record errors = @record.errors.full_messages.join(I18n.t('support.array.words_connector', :default => ', ')) - super(I18n.t('activerecord.errors.messages.record_invalid', :errors => errors)) + super(I18n.t('errors.messages.record_invalid', :errors => errors)) end end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index cf763d730a..116c8fc509 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -974,9 +974,9 @@ module AutosaveAssociationOnACollectionAssociationTests end def test_should_default_invalid_error_from_i18n - I18n.backend.store_translations(:en, :activerecord => { :errors => { :models => + I18n.backend.store_translations(:en, :errors => { :models => { @association_name.to_s.singularize.to_sym => { :blank => "cannot be blank" } } - }}) + }) @pirate.send(@association_name).build(:name => '') @@ -985,9 +985,7 @@ module AutosaveAssociationOnACollectionAssociationTests assert_equal ["#{@association_name.to_s.titleize} name cannot be blank"], @pirate.errors.full_messages assert @pirate.errors[@association_name].empty? ensure - I18n.backend.store_translations(:en, :activerecord => { :errors => { :models => - { @association_name.to_s.singularize.to_sym => nil } - }}) + I18n.backend = I18n::Backend::Simple.new end def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb index 3f96d7973b..15730c2a87 100644 --- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb @@ -6,15 +6,7 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase def setup Topic.reset_callbacks(:validate) @topic = Topic.new - I18n.backend.store_translations :'en', { - :activerecord => { - :errors => { - :messages => { - :taken => "has already been taken", - } - } - } - } + I18n.backend = I18n::Backend::Simple.new end # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index f017f24048..5dfbb1516f 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -4,13 +4,14 @@ require 'models/reply' class I18nValidationTest < ActiveRecord::TestCase repair_validations(Topic, Reply) + def setup Reply.validates_presence_of(:title) @topic = Topic.new - @old_load_path, @old_backend = I18n.load_path, I18n.backend + @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend I18n.load_path.clear I18n.backend = I18n::Backend::Simple.new - I18n.backend.store_translations('en', :activerecord => {:errors => {:messages => {:custom => nil}}}) + I18n.backend.store_translations('en', :errors => {:messages => {:custom => nil}}) end def teardown @@ -30,75 +31,6 @@ class I18nValidationTest < ActiveRecord::TestCase end end - # ActiveRecord::Errors - def test_errors_generate_message_translates_custom_model_attribute_key - I18n.expects(:translate).with( - :topic, - { :count => 1, - :default => ['Topic'], - :scope => [:activerecord, :models] - } - ).returns('Topic') - - I18n.expects(:translate).with( - :"topic.title", - { :count => 1, - :default => ['Title'], - :scope => [:activerecord, :attributes] - } - ).returns('Title') - - I18n.expects(:translate).with( - :"models.topic.attributes.title.invalid", - :value => nil, - :scope => [:activerecord, :errors], - :default => [ - :"models.topic.invalid", - 'default from class def error 1', - :"messages.invalid"], - :attribute => "Title", - :model => "Topic" - ).returns('default from class def error 1') - - @topic.errors.generate_message :title, :invalid, :default => 'default from class def error 1' - end - - def test_errors_generate_message_translates_custom_model_attribute_keys_with_sti - - I18n.expects(:translate).with( - :reply, - { :count => 1, - :default => [:topic, 'Reply'], - :scope => [:activerecord, :models] - } - ).returns('Reply') - - I18n.expects(:translate).with( - :"reply.title", - { :count => 1, - :default => [:'topic.title', 'Title'], - :scope => [:activerecord, :attributes] - } - ).returns('Title') - - I18n.expects(:translate).with( - :"models.reply.attributes.title.invalid", - :value => nil, - :scope => [:activerecord, :errors], - :default => [ - :"models.reply.invalid", - :"models.topic.attributes.title.invalid", - :"models.topic.invalid", - 'default from class def', - :"messages.invalid"], - :model => 'Reply', - :attribute => 'Title' - ).returns("default from class def") - - Reply.new.errors.generate_message :title, :invalid, :default => 'default from class def' - - end - # validates_uniqueness_of w/ mocha def test_validates_uniqueness_of_generates_message @@ -115,6 +47,25 @@ class I18nValidationTest < ActiveRecord::TestCase @topic.valid? end + # validates_uniqueness_of w/o mocha + + def test_validates_associated_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :errors => {:models => {:topic => {:attributes => {:title => {:taken => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:taken => 'global message'}} + + Topic.validates_uniqueness_of :title + unique_topic.valid? + assert_equal ['custom message'], unique_topic.errors[:replies] + end + + def test_validates_associated_finds_global_default_translation + I18n.backend.store_translations 'en', :errors => {:messages => {:taken => 'global message'}} + + Topic.validates_uniqueness_of :title + unique_topic.valid? + assert_equal ['global message'], unique_topic.errors[:replies] + end + # validates_associated w/ mocha def test_validates_associated_generates_message @@ -132,8 +83,8 @@ class I18nValidationTest < ActiveRecord::TestCase # validates_associated w/o mocha def test_validates_associated_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}} Topic.validates_associated :replies replied_topic.valid? @@ -141,7 +92,7 @@ class I18nValidationTest < ActiveRecord::TestCase end def test_validates_associated_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} + I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}} Topic.validates_associated :replies replied_topic.valid? diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt index 9f05cd5a31..451dbe1d1c 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt @@ -4,4 +4,4 @@ # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. -ActionController::Base.cookie_verifier_secret = '<%= app_secret %>'; +ActionController::Base.cookie_verifier_secret = '<%= app_secret %>' diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt index 4499ab84b6..baff704d3e 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt @@ -5,8 +5,8 @@ # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. ActionController::Base.session = { - :key => '_<%= app_name %>_session', - :secret => '<%= app_secret %>' + :key => '_<%= app_name %>_session', + :secret => '<%= app_secret %>' } # Use the database for sessions instead of the cookie-based default, |