aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVijay Dev <vijaydev.cse@gmail.com>2012-08-04 15:18:35 +0530
committerVijay Dev <vijaydev.cse@gmail.com>2012-08-04 15:18:35 +0530
commit3d3fa165e40c8cdaaebdab9dd2985d0ca3a36236 (patch)
tree21ae9b984d34c9487628097f3933f8fa14a3558f
parentc963f883a41913624363bfd8203b5640318198c2 (diff)
parentb51201242aaf77c6db5a9b2f72e433c521df79c5 (diff)
downloadrails-3d3fa165e40c8cdaaebdab9dd2985d0ca3a36236.tar.gz
rails-3d3fa165e40c8cdaaebdab9dd2985d0ca3a36236.tar.bz2
rails-3d3fa165e40c8cdaaebdab9dd2985d0ca3a36236.zip
Merge branch 'master' of github.com:lifo/docrails
Conflicts: activemodel/lib/active_model/secure_password.rb activerecord/lib/active_record/associations/collection_proxy.rb
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb8
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb8
-rw-r--r--activemodel/lib/active_model/errors.rb13
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb244
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/sanitizer.rb8
-rw-r--r--activemodel/lib/active_model/naming.rb32
-rw-r--r--activemodel/lib/active_model/observer_array.rb27
-rw-r--r--activemodel/lib/active_model/observing.rb205
-rw-r--r--activemodel/lib/active_model/secure_password.rb54
-rw-r--r--activemodel/lib/active_model/serializers/json.rb38
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb36
-rw-r--r--activemodel/lib/active_model/validations.rb161
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb87
-rw-r--r--activemodel/lib/active_model/validations/validates.rb74
-rw-r--r--activemodel/lib/active_model/validations/with.rb34
-rw-r--r--activemodel/lib/active_model/validator.rb22
-rw-r--r--activerecord/lib/active_record/validations/associated.rb32
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb76
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_param.rb7
-rw-r--r--guides/source/action_view_overview.textile2
-rw-r--r--guides/source/ajax_on_rails.textile2
-rw-r--r--guides/source/asset_pipeline.textile12
-rw-r--r--guides/source/command_line.textile49
-rw-r--r--guides/source/configuring.textile4
-rw-r--r--guides/source/contributing_to_ruby_on_rails.textile2
-rw-r--r--guides/source/engines.textile78
-rw-r--r--guides/source/getting_started.textile18
-rw-r--r--guides/source/i18n.textile4
-rw-r--r--guides/source/performance_testing.textile230
-rw-r--r--guides/source/security.textile2
31 files changed, 1147 insertions, 424 deletions
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 70e7ec7e81..03b8d8db1a 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -371,7 +371,7 @@ module ActionController
# def test_access_granted_from_xml
# get(
# "/notes/1.xml", nil,
- # :authorization => ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
+ # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
# )
#
# assert_equal 200, status
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 020ad8015e..5e2f1ff1e0 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -403,6 +403,10 @@ module ActionDispatch
#
# # Matches any request starting with 'path'
# match 'path' => 'c#a', :anchor => false
+ #
+ # [:format]
+ # Allows you to specify the default value for optional +format+
+ # segment or disable it by supplying +false+.
def match(path, options=nil)
end
@@ -1185,6 +1189,10 @@ module ActionDispatch
# sekret_comment PATCH/PUT /comments/:id(.:format)
# sekret_comment DELETE /comments/:id(.:format)
#
+ # [:format]
+ # Allows you to specify the default value for optional +format+
+ # segment or disable it by supplying +false+.
+ #
# === Examples
#
# # routes call <tt>Admin::PostsController</tt>
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 21b1209875..ef04f1fa49 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -1,6 +1,14 @@
module ActiveModel
# Raised when an attribute is not defined.
+ #
+ # class User < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # user = User.first
+ # user.pets.select(:id).first.user_id
+ # # => ActiveModel::MissingAttributeError: missing attribute: user_id
class MissingAttributeError < NoMethodError
end
# == Active Model Attribute Methods
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index d68086584e..1026b0f4d3 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -436,6 +436,19 @@ module ActiveModel
# Raised when a validation cannot be corrected by end users and are considered
# exceptional.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ #
+ # validates_presence_of :name, strict: true
+ # end
+ #
+ # person = Person.new
+ # person.name = nil
+ # person.valid?
+ # # => ActiveModel::StrictValidationFailed: Name can't be blank
class StrictValidationFailed < StandardError
end
end
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
index 87c0bcf59b..f9841abcb0 100644
--- a/activemodel/lib/active_model/mass_assignment_security.rb
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -3,7 +3,48 @@ require 'active_model/mass_assignment_security/permission_set'
require 'active_model/mass_assignment_security/sanitizer'
module ActiveModel
- # = Active Model Mass-Assignment Security
+ # == Active Model Mass-Assignment Security
+ #
+ # Mass assignment security provides an interface for protecting attributes
+ # from end-user assignment. For more complex permissions, mass assignment
+ # security may be handled outside the model by extending a non-ActiveRecord
+ # class, such as a controller, with this behavior.
+ #
+ # For example, a logged in user may need to assign additional attributes
+ # depending on their role:
+ #
+ # class AccountsController < ApplicationController
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # attr_accessible :first_name, :last_name
+ # attr_accessible :first_name, :last_name, :plan_id, as: :admin
+ #
+ # def update
+ # ...
+ # @account.update_attributes(account_params)
+ # ...
+ # end
+ #
+ # protected
+ #
+ # def account_params
+ # role = admin ? :admin : :default
+ # sanitize_for_mass_assignment(params[:account], role)
+ # end
+ #
+ # end
+ #
+ # === Configuration options
+ #
+ # * <tt>mass_assignment_sanitizer</tt> - Defines sanitize method. Possible
+ # values are:
+ # * <tt>:logger</tt> (default) - writes filtered attributes to logger
+ # * <tt>:strict</tt> - raise <tt>ActiveModel::MassAssignmentSecurity::Error</tt>
+ # on any protected attribute update.
+ #
+ # You can specify your own sanitizer object eg. <tt>MySanitizer.new</tt>.
+ # See <tt>ActiveModel::MassAssignmentSecurity::LoggerSanitizer</tt> for
+ # example implementation.
module MassAssignmentSecurity
extend ActiveSupport::Concern
@@ -16,55 +57,17 @@ module ActiveModel
self.mass_assignment_sanitizer = :logger
end
- # Mass assignment security provides an interface for protecting attributes
- # from end-user assignment. For more complex permissions, mass assignment security
- # may be handled outside the model by extending a non-ActiveRecord class,
- # such as a controller, with this behavior.
- #
- # For example, a logged in user may need to assign additional attributes depending
- # on their role:
- #
- # class AccountsController < ApplicationController
- # include ActiveModel::MassAssignmentSecurity
- #
- # attr_accessible :first_name, :last_name
- # attr_accessible :first_name, :last_name, :plan_id, :as => :admin
- #
- # def update
- # ...
- # @account.update_attributes(account_params)
- # ...
- # end
- #
- # protected
- #
- # def account_params
- # role = admin ? :admin : :default
- # sanitize_for_mass_assignment(params[:account], role)
- # end
- #
- # end
- #
- # = Configuration options
- #
- # * <tt>mass_assignment_sanitizer</tt> - Defines sanitize method. Possible values are:
- # * <tt>:logger</tt> (default) - writes filtered attributes to logger
- # * <tt>:strict</tt> - raise <tt>ActiveModel::MassAssignmentSecurity::Error</tt> on any protected attribute update
- #
- # You can specify your own sanitizer object eg. MySanitizer.new.
- # See <tt>ActiveModel::MassAssignmentSecurity::LoggerSanitizer</tt> for example implementation.
- #
- #
module ClassMethods
# Attributes named in this macro are protected from mass-assignment
# whenever attributes are sanitized before assignment. A role for the
- # attributes is optional, if no role is provided then :default is used.
- # A role can be defined by using the :as option with a symbol or an array of symbols as the value.
+ # attributes is optional, if no role is provided then <tt>:default</tt>
+ # is used. A role can be defined by using the <tt>:as</tt> option with a
+ # symbol or an array of symbols as the value.
#
# Mass-assignment to these attributes will simply be ignored, to assign
# to them you can use direct writer methods. This is meant to protect
# sensitive attributes from being overwritten by malicious users
- # tampering with URLs or forms. Example:
+ # tampering with URLs or forms.
#
# class Customer
# include ActiveModel::MassAssignmentSecurity
@@ -73,7 +76,7 @@ module ActiveModel
#
# attr_protected :logins_count
# # Suppose that admin can not change email for customer
- # attr_protected :logins_count, :email, :as => :admin
+ # attr_protected :logins_count, :email, as: :admin
#
# def assign_attributes(values, options = {})
# sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
@@ -82,23 +85,23 @@ module ActiveModel
# end
# end
#
- # When using the :default role:
+ # When using the <tt>:default</tt> role:
#
# customer = Customer.new
- # customer.assign_attributes({ "name" => "David", "email" => "a@b.com", :logins_count => 5 }, :as => :default)
- # customer.name # => "David"
- # customer.email # => "a@b.com"
- # customer.logins_count # => nil
+ # customer.assign_attributes({ name: 'David', email: 'a@b.com', logins_count: 5 }, as: :default)
+ # customer.name # => "David"
+ # customer.email # => "a@b.com"
+ # customer.logins_count # => nil
#
- # And using the :admin role:
+ # And using the <tt>:admin</tt> role:
#
# customer = Customer.new
- # customer.assign_attributes({ "name" => "David", "email" => "a@b.com", :logins_count => 5}, :as => :admin)
- # customer.name # => "David"
- # customer.email # => nil
- # customer.logins_count # => nil
+ # customer.assign_attributes({ name: 'David', email: 'a@b.com', logins_count: 5}, as: :admin)
+ # customer.name # => "David"
+ # customer.email # => nil
+ # customer.logins_count # => nil
#
- # customer.email = "c@d.com"
+ # customer.email = 'c@d.com'
# customer.email # => "c@d.com"
#
# To start from an all-closed default and enable attributes as needed,
@@ -124,8 +127,9 @@ module ActiveModel
# mass-assignment.
#
# Like +attr_protected+, a role for the attributes is optional,
- # if no role is provided then :default is used. A role can be defined by
- # using the :as option with a symbol or an array of symbols as the value.
+ # if no role is provided then <tt>:default</tt> is used. A role can be
+ # defined by using the <tt>:as</tt> option with a symbol or an array of
+ # symbols as the value.
#
# This is the opposite of the +attr_protected+ macro: Mass-assignment
# will only set attributes in this list, to assign to the rest of
@@ -141,9 +145,9 @@ module ActiveModel
# attr_accessor :name, :credit_rating
#
# # Both admin and default user can change name of a customer
- # attr_accessible :name, :as => [:admin, :default]
+ # attr_accessible :name, as: [:admin, :default]
# # Only admin can change credit rating of a customer
- # attr_accessible :credit_rating, :as => :admin
+ # attr_accessible :credit_rating, as: :admin
#
# def assign_attributes(values, options = {})
# sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
@@ -152,20 +156,20 @@ module ActiveModel
# end
# end
#
- # When using the :default role:
+ # When using the <tt>:default</tt> role:
#
# customer = Customer.new
- # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default)
+ # customer.assign_attributes({ name: 'David', credit_rating: 'Excellent', last_login: 1.day.ago }, as: :default)
# customer.name # => "David"
# customer.credit_rating # => nil
#
- # customer.credit_rating = "Average"
+ # customer.credit_rating = 'Average'
# customer.credit_rating # => "Average"
#
- # And using the :admin role:
+ # And using the <tt>:admin</tt> role:
#
# customer = Customer.new
- # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin)
+ # customer.assign_attributes({ name: 'David', credit_rating: 'Excellent', last_login: 1.day.ago }, as: :admin)
# customer.name # => "David"
# customer.credit_rating # => "Excellent"
#
@@ -185,23 +189,131 @@ module ActiveModel
self._active_authorizer = self._accessible_attributes
end
+ # Returns an instance of <tt>ActiveModel::MassAssignmentSecurity::BlackList</tt>
+ # with the attributes protected by #attr_protected method. If no +role+
+ # is provided, then <tt>:default</tt> is used.
+ #
+ # class Customer
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # attr_accessor :name, :email, :logins_count
+ #
+ # attr_protected :logins_count
+ # attr_protected :logins_count, :email, as: :admin
+ # end
+ #
+ # Customer.protected_attributes
+ # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {"logins_count"}>
+ #
+ # Customer.protected_attributes(:default)
+ # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {"logins_count"}>
+ #
+ # Customer.protected_attributes(:admin)
+ # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {"logins_count", "email"}>
def protected_attributes(role = :default)
protected_attributes_configs[role]
end
+ # Returns an instance of <tt>ActiveModel::MassAssignmentSecurity::WhiteList</tt>
+ # with the attributes protected by #attr_accessible method. If no +role+
+ # is provided, then <tt>:default</tt> is used.
+ #
+ # class Customer
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # attr_accessor :name, :credit_rating
+ #
+ # attr_accessible :name, as: [:admin, :default]
+ # attr_accessible :credit_rating, as: :admin
+ # end
+ #
+ # Customer.accessible_attributes
+ # # => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name"}>
+ #
+ # Customer.accessible_attributes(:default)
+ # # => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name"}>
+ #
+ # Customer.accessible_attributes(:admin)
+ # # => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name", "credit_rating"}>
def accessible_attributes(role = :default)
accessible_attributes_configs[role]
end
+ # Returns a hash with the protected attributes (by #attr_accessible or
+ # #attr_protected) per role.
+ #
+ # class Customer
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # attr_accessor :name, :credit_rating
+ #
+ # attr_accessible :name, as: [:admin, :default]
+ # attr_accessible :credit_rating, as: :admin
+ # end
+ #
+ # Customer.active_authorizers
+ # # => {
+ # # :admin=> #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name", "credit_rating"}>,
+ # # :default=>#<ActiveModel::MassAssignmentSecurity::WhiteList: {"name"}>
+ # #  }
def active_authorizers
self._active_authorizer ||= protected_attributes_configs
end
alias active_authorizer active_authorizers
+ # Returns an empty array by default. You can still override this to define
+ # the default attributes protected by #attr_protected method.
+ #
+ # class Customer
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # def self.attributes_protected_by_default
+ # [:name]
+ # end
+ # end
+ #
+ # Customer.protected_attributes
+ # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {:name}>
def attributes_protected_by_default
[]
end
+ # Defines sanitize method.
+ #
+ # class Customer
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # attr_accessor :name
+ #
+ # attr_protected :name
+ #
+ # def assign_attributes(values)
+ # sanitize_for_mass_assignment(values).each do |k, v|
+ # send("#{k}=", v)
+ # end
+ # end
+ # end
+ #
+ # # See ActiveModel::MassAssignmentSecurity::StrictSanitizer for more information.
+ # Customer.mass_assignment_sanitizer = :strict
+ #
+ # customer = Customer.new
+ # customer.assign_attributes(name: 'David')
+ # # => ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes for Customer: name
+ #
+ # Also, you can specify your own sanitizer object.
+ #
+ # class CustomSanitizer < ActiveModel::MassAssignmentSecurity::Sanitizer
+ # def process_removed_attributes(klass, attrs)
+ # raise StandardError
+ # end
+ # end
+ #
+ # Customer.mass_assignment_sanitizer = CustomSanitizer.new
+ #
+ # customer = Customer.new
+ # customer.assign_attributes(name: 'David')
+ # # => StandardError: StandardError
def mass_assignment_sanitizer=(value)
self._mass_assignment_sanitizer = if value.is_a?(Symbol)
const_get(:"#{value.to_s.camelize}Sanitizer").new(self)
@@ -227,11 +339,11 @@ module ActiveModel
protected
- def sanitize_for_mass_assignment(attributes, role = nil)
+ def sanitize_for_mass_assignment(attributes, role = nil) #:nodoc:
_mass_assignment_sanitizer.sanitize(self.class, attributes, mass_assignment_authorizer(role))
end
- def mass_assignment_authorizer(role)
+ def mass_assignment_authorizer(role) #:nodoc:
self.class.active_authorizer[role || :default]
end
end
diff --git a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
index 44ce5a489d..dafb7cdff3 100644
--- a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
@@ -1,6 +1,6 @@
module ActiveModel
module MassAssignmentSecurity
- class Sanitizer
+ class Sanitizer #:nodoc:
# Returns all attributes not denied by the authorizer.
def sanitize(klass, attributes, authorizer)
rejected = []
@@ -18,7 +18,7 @@ module ActiveModel
end
end
- class LoggerSanitizer < Sanitizer
+ class LoggerSanitizer < Sanitizer #:nodoc:
def initialize(target)
@target = target
super()
@@ -50,7 +50,7 @@ module ActiveModel
end
end
- class StrictSanitizer < Sanitizer
+ class StrictSanitizer < Sanitizer #:nodoc:
def initialize(target = nil)
super()
end
@@ -65,7 +65,7 @@ module ActiveModel
end
end
- class Error < StandardError
+ class Error < StandardError #:nodoc:
def initialize(klass, attrs)
super("Can't mass-assign protected attributes for #{klass.name}: #{attrs.join(', ')}")
end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index af89391413..7ba439fb3e 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -218,6 +218,14 @@ module ActiveModel
# Returns an ActiveModel::Name object for module. It can be
# used to retrieve all kinds of naming-related information
# (See ActiveModel::Name for more information).
+ #
+ # class Person < ActiveModel::Model
+ # end
+ #
+ # Person.model_name # => Person
+ # Person.model_name.class # => ActiveModel::Name
+ # Person.model_name.singular # => "person"
+ # Person.model_name.plural # => "people"
def model_name
@_model_name ||= begin
namespace = self.parents.detect do |n|
@@ -254,11 +262,11 @@ module ActiveModel
# Returns string to use while generating route names. It differs for
# namespaced models regarding whether it's inside isolated engine.
#
- # For isolated engine:
- # ActiveModel::Naming.route_key(Blog::Post) #=> post
+ # # For isolated engine:
+ # ActiveModel::Naming.route_key(Blog::Post) #=> post
#
- # For shared engine:
- # ActiveModel::Naming.route_key(Blog::Post) #=> blog_post
+ # # For shared engine:
+ # ActiveModel::Naming.route_key(Blog::Post) #=> blog_post
def self.singular_route_key(record_or_class)
model_name_from_record_or_class(record_or_class).singular_route_key
end
@@ -266,11 +274,11 @@ module ActiveModel
# Returns string to use while generating route names. It differs for
# namespaced models regarding whether it's inside isolated engine.
#
- # For isolated engine:
- # ActiveModel::Naming.route_key(Blog::Post) #=> posts
+ # # For isolated engine:
+ # ActiveModel::Naming.route_key(Blog::Post) #=> posts
#
- # For shared engine:
- # ActiveModel::Naming.route_key(Blog::Post) #=> blog_posts
+ # # For shared engine:
+ # ActiveModel::Naming.route_key(Blog::Post) #=> blog_posts
#
# The route key also considers if the noun is uncountable and, in
# such cases, automatically appends _index.
@@ -281,11 +289,11 @@ module ActiveModel
# Returns string to use for params names. It differs for
# namespaced models regarding whether it's inside isolated engine.
#
- # For isolated engine:
- # ActiveModel::Naming.param_key(Blog::Post) #=> post
+ # # For isolated engine:
+ # ActiveModel::Naming.param_key(Blog::Post) #=> post
#
- # For shared engine:
- # ActiveModel::Naming.param_key(Blog::Post) #=> blog_post
+ # # For shared engine:
+ # ActiveModel::Naming.param_key(Blog::Post) #=> blog_post
def self.param_key(record_or_class)
model_name_from_record_or_class(record_or_class).param_key
end
diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb
index 8de6918d18..77bc0f71e3 100644
--- a/activemodel/lib/active_model/observer_array.rb
+++ b/activemodel/lib/active_model/observer_array.rb
@@ -5,20 +5,21 @@ module ActiveModel
# a particular model class.
class ObserverArray < Array
attr_reader :model_class
- def initialize(model_class, *args)
+ def initialize(model_class, *args) #:nodoc:
@model_class = model_class
super(*args)
end
- # Returns true if the given observer is disabled for the model class.
- def disabled_for?(observer)
+ # Returns +true+ if the given observer is disabled for the model class,
+ # +false+ otherwise.
+ def disabled_for?(observer) #:nodoc:
disabled_observers.include?(observer.class)
end
# Disables one or more observers. This supports multiple forms:
#
# ORM.observers.disable :all
- # # => disables all observers for all models subclassed from
+ # # => disables all observers for all models subclassed from
# # an ORM base class that includes ActiveModel::Observing
# # e.g. ActiveRecord::Base
#
@@ -43,7 +44,7 @@ module ActiveModel
# Enables one or more observers. This supports multiple forms:
#
# ORM.observers.enable :all
- # # => enables all observers for all models subclassed from
+ # # => enables all observers for all models subclassed from
# # an ORM base class that includes ActiveModel::Observing
# # e.g. ActiveRecord::Base
#
@@ -71,11 +72,11 @@ module ActiveModel
protected
- def disabled_observers
+ def disabled_observers #:nodoc:
@disabled_observers ||= Set.new
end
- def observer_class_for(observer)
+ def observer_class_for(observer) #:nodoc:
return observer if observer.is_a?(Class)
if observer.respond_to?(:to_sym) # string/symbol
@@ -86,25 +87,25 @@ module ActiveModel
end
end
- def start_transaction
+ def start_transaction #:nodoc:
disabled_observer_stack.push(disabled_observers.dup)
each_subclass_array do |array|
array.start_transaction
end
end
- def disabled_observer_stack
+ def disabled_observer_stack #:nodoc:
@disabled_observer_stack ||= []
end
- def end_transaction
+ def end_transaction #:nodoc:
@disabled_observers = disabled_observer_stack.pop
each_subclass_array do |array|
array.end_transaction
end
end
- def transaction
+ def transaction #:nodoc:
start_transaction
begin
@@ -114,13 +115,13 @@ module ActiveModel
end
end
- def each_subclass_array
+ def each_subclass_array #:nodoc:
model_class.descendants.each do |subclass|
yield subclass.observers
end
end
- def set_enablement(enabled, observers)
+ def set_enablement(enabled, observers) #:nodoc:
if block_given?
transaction do
set_enablement(enabled, observers)
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index acc96ddc9f..9db7639ea3 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -8,6 +8,7 @@ require 'active_support/core_ext/object/try'
require 'active_support/descendants_tracker'
module ActiveModel
+ # == Active Model Observers Activation
module Observing
extend ActiveSupport::Concern
@@ -16,9 +17,7 @@ module ActiveModel
end
module ClassMethods
- # == Active Model Observers Activation
- #
- # Activates the observers assigned. Examples:
+ # Activates the observers assigned.
#
# class ORM
# include ActiveModel::Observing
@@ -34,34 +33,95 @@ module ActiveModel
# ORM.observers = Cacher, GarbageCollector
#
# Note: Setting this does not instantiate the observers yet.
- # +instantiate_observers+ is called during startup, and before
+ # <tt>instantiate_observers</tt> is called during startup, and before
# each development request.
def observers=(*values)
observers.replace(values.flatten)
end
- # Gets an array of observers observing this model.
- # The array also provides +enable+ and +disable+ methods
- # that allow you to selectively enable and disable observers.
- # (see <tt>ActiveModel::ObserverArray.enable</tt> and
- # <tt>ActiveModel::ObserverArray.disable</tt> for more on this)
+ # Gets an array of observers observing this model. The array also provides
+ # +enable+ and +disable+ methods that allow you to selectively enable and
+ # disable observers (see ActiveModel::ObserverArray.enable and
+ # ActiveModel::ObserverArray.disable for more on this).
+ #
+ # class ORM
+ # include ActiveModel::Observing
+ # end
+ #
+ # ORM.observers = :cacher, :garbage_collector
+ # ORM.observers # => [:cacher, :garbage_collector]
+ # ORM.observers.class # => ActiveModel::ObserverArray
def observers
@observers ||= ObserverArray.new(self)
end
- # Gets the current observer instances.
+ # Returns the current observer instances.
+ #
+ # class Foo
+ # include ActiveModel::Observing
+ #
+ # attr_accessor :status
+ # end
+ #
+ # class FooObserver < ActiveModel::Observer
+ # def on_spec(record, *args)
+ # record.status = true
+ # end
+ # end
+ #
+ # Foo.observers = FooObserver
+ # Foo.instantiate_observers
+ #
+ # Foo.observer_instances # => [#<FooObserver:0x007fc212c40820>]
def observer_instances
@observer_instances ||= []
end
# Instantiate the global observers.
+ #
+ # class Foo
+ # include ActiveModel::Observing
+ #
+ # attr_accessor :status
+ # end
+ #
+ # class FooObserver < ActiveModel::Observer
+ # def on_spec(record, *args)
+ # record.status = true
+ # end
+ # end
+ #
+ # Foo.observers = FooObserver
+ #
+ # foo = Foo.new
+ # foo.status = false
+ # foo.notify_observers(:on_spec)
+ # foo.status # => false
+ #
+ # Foo.instantiate_observers # => [FooObserver]
+ #
+ # foo = Foo.new
+ # foo.status = false
+ # foo.notify_observers(:on_spec)
+ # foo.status # => true
def instantiate_observers
observers.each { |o| instantiate_observer(o) }
end
- # Add a new observer to the pool.
- # The new observer needs to respond to 'update', otherwise it
- # raises an +ArgumentError+ exception.
+ # Add a new observer to the pool. The new observer needs to respond to
+ # <tt>update</tt>, otherwise it raises an +ArgumentError+ exception.
+ #
+ # class Foo
+ # include ActiveModel::Observing
+ # end
+ #
+ # class FooObserver < ActiveModel::Observer
+ # end
+ #
+ # Foo.add_observer(FooObserver.instance)
+ #
+ # Foo.observers_instance
+ # # => [#<FooObserver:0x007fccf55d9390>]
def add_observer(observer)
unless observer.respond_to? :update
raise ArgumentError, "observer needs to respond to 'update'"
@@ -69,16 +129,47 @@ module ActiveModel
observer_instances << observer
end
- # Notify list of observers of a change.
+ # Fires notifications to model's observers.
+ #
+ # def save
+ # notify_observers(:before_save)
+ # ...
+ # notify_observers(:after_save)
+ # end
+ #
+ # Custom notifications can be sent in a similar fashion:
+ #
+ # notify_observers(:custom_notification, :foo)
+ #
+ # This will call <tt>custom_notification</tt>, passing as arguments
+ # the current object and <tt>:foo</tt>.
def notify_observers(*args)
observer_instances.each { |observer| observer.update(*args) }
end
- # Total number of observers.
+ # Returns the total number of instantiated observers.
+ #
+ # class Foo
+ # include ActiveModel::Observing
+ #
+ # attr_accessor :status
+ # end
+ #
+ # class FooObserver < ActiveModel::Observer
+ # def on_spec(record, *args)
+ # record.status = true
+ # end
+ # end
+ #
+ # Foo.observers = FooObserver
+ # Foo.observers_count # => 0
+ # Foo.instantiate_observers
+ # Foo.observers_count # => 1
def observers_count
observer_instances.size
end
+ # <tt>count_observers</tt> is deprecated. Use #observers_count.
def count_observers
msg = "count_observers is deprecated in favor of observers_count"
ActiveSupport::Deprecation.warn(msg)
@@ -103,27 +194,36 @@ module ActiveModel
end
# Notify observers when the observed class is subclassed.
- def inherited(subclass)
+ def inherited(subclass) #:nodoc:
super
notify_observers :observed_class_inherited, subclass
end
end
- # Fires notifications to model's observers
+ # Notify a change to the list of observers.
+ #
+ # class Foo
+ # include ActiveModel::Observing
#
- # def save
- # notify_observers(:before_save)
- # ...
- # notify_observers(:after_save)
+ # attr_accessor :status
# end
#
- # Custom notifications can be sent in a similar fashion:
+ # class FooObserver < ActiveModel::Observer
+ # def on_spec(record, *args)
+ # record.status = true
+ # end
+ # end
#
- # notify_observers(:custom_notification, :foo)
+ # Foo.observers = FooObserver
+ # Foo.instantiate_observers # => [FooObserver]
#
- # This will call +custom_notification+, passing as arguments
- # the current object and :foo.
+ # foo = Foo.new
+ # foo.status = false
+ # foo.notify_observers(:on_spec)
+ # foo.status # => true
#
+ # See ActiveModel::Observing::ClassMethods.notify_observers for more
+ # information.
def notify_observers(method, *extra_args)
self.class.notify_observers(method, self, *extra_args)
end
@@ -135,15 +235,15 @@ module ActiveModel
# behavior outside the original class. This is a great way to reduce the
# clutter that normally comes when the model class is burdened with
# functionality that doesn't pertain to the core responsibility of the
- # class. Example:
+ # class.
#
# class CommentObserver < ActiveModel::Observer
# def after_save(comment)
- # Notifications.comment("admin@do.com", "New comment was posted", comment).deliver
+ # Notifications.comment('admin@do.com', 'New comment was posted', comment).deliver
# end
# end
#
- # This Observer sends an email when a Comment#save is finished.
+ # This Observer sends an email when a <tt>Comment#save</tt> is finished.
#
# class ContactObserver < ActiveModel::Observer
# def after_create(contact)
@@ -160,44 +260,50 @@ module ActiveModel
# == Observing a class that can't be inferred
#
# Observers will by default be mapped to the class with which they share a
- # name. So CommentObserver will be tied to observing Comment, ProductManagerObserver
- # to ProductManager, and so on. If you want to name your observer differently than
- # the class you're interested in observing, you can use the <tt>Observer.observe</tt>
- # class method which takes either the concrete class (Product) or a symbol for that
- # class (:product):
+ # name. So <tt>CommentObserver</tt> will be tied to observing <tt>Comment</tt>,
+ # <tt>ProductManagerObserver</tt> to <tt>ProductManager</tt>, and so on. If
+ # you want to name your observer differently than the class you're interested
+ # in observing, you can use the <tt>Observer.observe</tt> class method which
+ # takes either the concrete class (<tt>Product</tt>) or a symbol for that
+ # class (<tt>:product</tt>):
#
# class AuditObserver < ActiveModel::Observer
# observe :account
#
# def after_update(account)
- # AuditTrail.new(account, "UPDATED")
+ # AuditTrail.new(account, 'UPDATED')
# end
# end
#
- # If the audit observer needs to watch more than one kind of object, this can be
- # specified with multiple arguments:
+ # If the audit observer needs to watch more than one kind of object, this can
+ # be specified with multiple arguments:
#
# class AuditObserver < ActiveModel::Observer
# observe :account, :balance
#
# def after_update(record)
- # AuditTrail.new(record, "UPDATED")
+ # AuditTrail.new(record, 'UPDATED')
# end
# end
#
- # The AuditObserver will now act on both updates to Account and Balance by treating
- # them both as records.
+ # The <tt>AuditObserver</tt> will now act on both updates to <tt>Account</tt>
+ # and <tt>Balance</tt> by treating them both as records.
#
- # If you're using an Observer in a Rails application with Active Record, be sure to
- # read about the necessary configuration in the documentation for
+ # If you're using an Observer in a Rails application with Active Record, be
+ # sure to read about the necessary configuration in the documentation for
# ActiveRecord::Observer.
- #
class Observer
include Singleton
extend ActiveSupport::DescendantsTracker
class << self
# Attaches the observer to the supplied model classes.
+ #
+ # class AuditObserver < ActiveModel::Observer
+ # observe :account, :balance
+ # end
+ #
+ # AuditObserver.observed_classes # => [Account, Balance]
def observe(*models)
models.flatten!
models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
@@ -206,6 +312,8 @@ module ActiveModel
# Returns an array of Classes to observe.
#
+ # AccountObserver.observed_classes # => [Account]
+ #
# You can override this instead of using the +observe+ helper.
#
# class AuditObserver < ActiveModel::Observer
@@ -217,8 +325,11 @@ module ActiveModel
Array(observed_class)
end
- # The class observed by default is inferred from the observer's class name:
- # assert_equal Person, PersonObserver.observed_class
+ # Returns the class observed by default. It's inferred from the observer's
+ # class name.
+ #
+ # PersonObserver.observed_class # => Person
+ # AccountObserver.observed_class # => Account
def observed_class
name[/(.*)Observer/, 1].try :constantize
end
@@ -226,7 +337,7 @@ module ActiveModel
# Start observing the declared classes and their subclasses.
# Called automatically by the instance method.
- def initialize
+ def initialize #:nodoc:
observed_classes.each { |klass| add_observer!(klass) }
end
@@ -237,7 +348,7 @@ module ActiveModel
# Send observed_method(object) if the method exists and
# the observer is enabled for the given object's class.
def update(observed_method, object, *extra_args, &block) #:nodoc:
- return if !respond_to?(observed_method) || disabled_for?(object)
+ return if !respond_to?(observed_method) || disabled_for?(object)
send(observed_method, object, *extra_args, &block)
end
@@ -254,7 +365,7 @@ module ActiveModel
end
# Returns true if notifications are disabled for this object.
- def disabled_for?(object)
+ def disabled_for?(object) #:nodoc:
klass = object.class
return false unless klass.respond_to?(:observers)
klass.observers.disabled_for?(self)
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index dc89efd2d1..d011402081 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -6,12 +6,12 @@ module ActiveModel
# Adds methods to set and authenticate against a BCrypt password.
# This mechanism requires you to have a password_digest attribute.
#
- # Validations for presence of password on create, confirmation of password (using
- # a "password_confirmation" attribute) are automatically added.
- # If you wish to turn off validations, pass 'validations: false' as an argument.
- # You can add more validations by hand if need be.
+ # Validations for presence of password on create, confirmation of password
+ # (using a +password_confirmation+ attribute) are automatically added. If
+ # you wish to turn off validations, pass <tt>validations: false</tt> as an
+ # argument. You can add more validations by hand if need be.
#
- # You need to add bcrypt-ruby (~> 3.0.0) to Gemfile to use has_secure_password:
+ # You need to add bcrypt-ruby (~> 3.0.0) to Gemfile to use #has_secure_password:
#
# gem 'bcrypt-ruby', '~> 3.0.0'
#
@@ -22,24 +22,25 @@ module ActiveModel
# has_secure_password
# end
#
- # user = User.new(:name => "david", :password => "", :password_confirmation => "nomatch")
+ # user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
# user.save # => false, password required
- # user.password = "mUc3m00RsqyRe"
+ # user.password = 'mUc3m00RsqyRe'
# user.save # => false, confirmation doesn't match
- # user.password_confirmation = "mUc3m00RsqyRe"
+ # user.password_confirmation = 'mUc3m00RsqyRe'
# user.save # => true
- # user.authenticate("notright") # => false
- # user.authenticate("mUc3m00RsqyRe") # => user
- # User.find_by_name("david").try(:authenticate, "notright") # => false
- # User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
+ # user.authenticate('notright') # => false
+ # user.authenticate('mUc3m00RsqyRe') # => user
+ # User.find_by_name('david').try(:authenticate, 'notright') # => false
+ # User.find_by_name('david').try(:authenticate, 'mUc3m00RsqyRe') # => user
def has_secure_password(options = {})
# Load bcrypt-ruby only when has_secure_password is used.
- # This is to avoid ActiveModel (and by extension the entire framework) being dependent on a binary library.
+ # This is to avoid ActiveModel (and by extension the entire framework)
+ # being dependent on a binary library.
gem 'bcrypt-ruby', '~> 3.0.0'
require 'bcrypt'
attr_reader :password
-
+
if options.fetch(:validations, true)
validates_confirmation_of :password
validates_presence_of :password, :on => :create
@@ -50,7 +51,7 @@ module ActiveModel
include InstanceMethodsOnActivation
if respond_to?(:attributes_protected_by_default)
- def self.attributes_protected_by_default
+ def self.attributes_protected_by_default #:nodoc:
super + ['password_digest']
end
end
@@ -58,13 +59,32 @@ module ActiveModel
end
module InstanceMethodsOnActivation
- # Returns self if the password is correct, otherwise false.
+ # Returns +self+ if the password is correct, otherwise +false+.
+ #
+ # class User < ActiveRecord::Base
+ # has_secure_password validations: false
+ # end
+ #
+ # user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
+ # user.save
+ # user.authenticate('notright') # => false
+ #  user.authenticate('mUc3m00RsqyRe') # => user
def authenticate(unencrypted_password)
BCrypt::Password.new(password_digest) == unencrypted_password && self
end
- # Encrypts the password into the password_digest attribute, only if the
+ # Encrypts the password into the +password_digest+ attribute, only if the
# new password is not blank.
+ #
+ # class User < ActiveRecord::Base
+ # has_secure_password validations: false
+ # end
+ #
+ # user = User.new
+ # user.password = nil
+ # user.password_digest # => nil
+ # user.password = 'mUc3m00RsqyRe'
+ # user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4."
def password=(unencrypted_password)
unless unencrypted_password.blank?
@password = unencrypted_password
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index eaea0475ad..a4252b995d 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -18,8 +18,8 @@ module ActiveModel
# passed through +options+.
#
# The option <tt>include_root_in_json</tt> controls the top-level behavior
- # of +as_json+. If true +as_json+ will emit a single root node named after
- # the object's type. The default value for <tt>include_root_in_json</tt>
+ # of +as_json+. If +true+, +as_json+ will emit a single root node named
+ # after the object's type. The default value for <tt>include_root_in_json</tt>
# option is +false+.
#
# user = User.find(1)
@@ -100,6 +100,40 @@ module ActiveModel
end
end
+ # Sets the model +attributes+ from a JSON string. Returns +self+.
+ #
+ # class Person
+ # include ActiveModel::Serializers::JSON
+ #
+ # attr_accessor :name, :age, :awesome
+ #
+ # def attributes=(hash)
+ # hash.each do |key, value|
+ # instance_variable_set("@#{key}", value)
+ # end
+ # end
+ #
+ # def attributes
+ # instance_values
+ # end
+ # end
+ #
+ # json = { name: 'bob', age: 22, awesome:true }.to_json
+ # person = Person.new
+ # person.from_json(json) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
+ # person.name # => "bob"
+ # person.age # => 22
+ # person.awesome # => true
+ #
+ # The default value for +include_root+ is +false+. You can change it to
+ # +true+ if the given JSON string includes a single root node.
+ #
+ # json = { person: { name: 'bob', age: 22, awesome:true } }.to_json
+ # person = Person.new
+ # person.from_json(json) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
+ # person.name # => "bob"
+ # person.age # => 22
+ # person.awesome # => true
def from_json(json, include_root=include_root_in_json)
hash = ActiveSupport::JSON.decode(json)
hash = hash.values.first if include_root
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 2b3e9ce134..016d821fdf 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -110,7 +110,7 @@ module ActiveModel
end
end
- # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
+ # TODO: This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
def add_associations(association, records, opts)
merged_options = opts.merge(options.slice(:builder, :indent))
merged_options[:skip_instruct] = true
@@ -161,8 +161,8 @@ module ActiveModel
# Returns XML representing the model. Configuration can be
# passed through +options+.
#
- # Without any +options+, the returned XML string will include all the model's
- # attributes. For example:
+ # Without any +options+, the returned XML string will include all the
+ # model's attributes.
#
# user = User.find(1)
# user.to_xml
@@ -175,18 +175,42 @@ module ActiveModel
# <created-at type="dateTime">2011-01-30T22:29:23Z</created-at>
# </user>
#
- # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
- # included, and work similar to the +attributes+ method.
+ # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the
+ # attributes included, and work similar to the +attributes+ method.
#
# To include the result of some method calls on the model use <tt>:methods</tt>.
#
# To include associations use <tt>:include</tt>.
#
- # For further documentation see activerecord/lib/active_record/serializers/xml_serializer.xml.
+ # For further documentation, see <tt>ActiveRecord::Serialization#to_xml</tt>
def to_xml(options = {}, &block)
Serializer.new(self, options).serialize(&block)
end
+ # Sets the model +attributes+ from a JSON string. Returns +self+.
+ #
+ # class Person
+ # include ActiveModel::Serializers::Xml
+ #
+ # attr_accessor :name, :age, :awesome
+ #
+ # def attributes=(hash)
+ # hash.each do |key, value|
+ # instance_variable_set("@#{key}", value)
+ # end
+ # end
+ #
+ # def attributes
+ # instance_values
+ # end
+ # end
+ #
+ # xml = { name: 'bob', age: 22, awesome:true }.to_xml
+ # person = Person.new
+ # person.from_xml(xml) # => #<Person:0x007fec5e3b3c40 @age=22, @awesome=true, @name="bob">
+ # person.name # => "bob"
+ # person.age # => 22
+ # person.awesome # => true
def from_xml(xml)
self.attributes = Hash.from_xml(xml).values.first
self
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 5ae3a97c6b..4762f39044 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -32,11 +32,11 @@ module ActiveModel
# person.first_name = 'zoolander'
# person.valid? # => false
# person.invalid? # => true
- # person.errors # => #<Hash {:first_name=>["starts with z."]}>
+ # person.errors.messages # => {:first_name=>["starts with z."]}
#
- # Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+ method
- # to your instances initialized with a new <tt>ActiveModel::Errors</tt> object, so
- # there is no need for you to do this manually.
+ # Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+
+ # method to your instances initialized with a new <tt>ActiveModel::Errors</tt>
+ # object, so there is no need for you to do this manually.
module Validations
extend ActiveSupport::Concern
@@ -62,24 +62,25 @@ module ActiveModel
#
# attr_accessor :first_name, :last_name
#
- # validates_each :first_name, :last_name, :allow_blank => true do |record, attr, value|
+ # validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
# end
# end
#
# Options:
# * <tt>:on</tt> - Specifies the context where this validation is active
- # (e.g. <tt>:on => :create</tt> or <tt>:on => :custom_validation_context</tt>)
+ # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</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 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
- # method, proc or string should return or evaluate to a true or false value.
+ # 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>). The
+ # method, proc or string should return or evaluate to a +true+ or +false+
+ # value.
def validates_each(*attr_names, &block)
validates_with BlockValidator, _merge_attributes(attr_names), &block
end
@@ -96,7 +97,7 @@ module ActiveModel
# validate :must_be_friends
#
# def must_be_friends
- # errors.add(:base, "Must be friends to leave a comment") unless commenter.friend_of?(commentee)
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
# end
# end
#
@@ -110,7 +111,7 @@ module ActiveModel
# end
#
# def must_be_friends
- # errors.add(:base, "Must be friends to leave a comment") unless commenter.friend_of?(commentee)
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
# end
# end
#
@@ -120,23 +121,24 @@ module ActiveModel
# include ActiveModel::Validations
#
# validate do
- # errors.add(:base, "Must be friends to leave a comment") unless commenter.friend_of?(commentee)
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
# end
# end
#
# Options:
# * <tt>:on</tt> - Specifies the context where this validation is active
- # (e.g. <tt>:on => :create</tt> or <tt>:on => :custom_validation_context</tt>)
+ # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</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 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
- # method, proc or string should return or evaluate to a true or false value.
+ # 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>). The
+ # method, proc or string should return or evaluate to a +true+ or +false+
+ # value.
def validate(*args, &block)
options = args.extract_options!
if options.key?(:on)
@@ -170,39 +172,101 @@ module ActiveModel
end
# List all validators that are being used to validate a specific attribute.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name , :age
+ #
+ # validates_presence_of :name
+ # validates_inclusion_of :age, in: 0..99
+ # end
+ #
+ # Person.validators_on(:name)
+ # # => [
+ # # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>,
+ # # #<ActiveModel::Validations::InclusionValidator:0x007fe603bb8780 @attributes=[:age], @options={:in=>0..99}>
+ # # ]
def validators_on(*attributes)
attributes.map do |attribute|
_validators[attribute.to_sym]
end.flatten
end
- # Check if method is an attribute method or not.
+ # Returns +true+ if +attribute+ is an attribute method, +false+ otherwise.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # end
+ #
+ # User.attribute_method?(:name) # => true
+ # User.attribute_method?(:age) # => false
def attribute_method?(attribute)
method_defined?(attribute)
end
# Copy validators on inheritance.
- def inherited(base)
+ def inherited(base) #:nodoc:
dup = _validators.dup
base._validators = dup.each { |k, v| dup[k] = v.dup }
super
end
end
- # Clean the +Errors+ object if instance is duped
- def initialize_dup(other) # :nodoc:
+ # Clean the +Errors+ object if instance is duped.
+ def initialize_dup(other) #:nodoc:
@errors = nil
super
end
- # Returns the +Errors+ object that holds all information about attribute error messages.
+ # Returns the +Errors+ object that holds all information about attribute
+ # error messages.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # validates_presence_of :name
+ # end
+ #
+ # person = Person.new
+ # person.valid? # => false
+ # person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={:name=>["can't be blank"]}>
def errors
@errors ||= Errors.new(self)
end
- # Runs all the specified validations and returns true if no errors were added
- # otherwise false. Context can optionally be supplied to define which callbacks
- # to test against (the context is defined on the validations using :on).
+ # Runs all the specified validations and returns +true+ if no errors were
+ # added otherwise +false+.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # validates_presence_of :name
+ # end
+ #
+ # person = Person.new
+ # person.name = ''
+ # person.valid? # => false
+ # person.name = 'david'
+ # person.valid? # => true
+ #
+ # Context can optionally be supplied to define which callbacks to test
+ # against (the context is defined on the validations using <tt>:on</tt>).
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # validates_presence_of :name, on: :new
+ # end
+ #
+ # person = Person.new
+ # person.valid? # => true
+ # person.valid?(:new) # => false
def valid?(context = nil)
current_context, self.validation_context = validation_context, context
errors.clear
@@ -211,8 +275,35 @@ module ActiveModel
self.validation_context = current_context
end
- # Performs the opposite of <tt>valid?</tt>. Returns true if errors were added,
- # false otherwise.
+ # Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
+ # added, +false+ otherwise.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # validates_presence_of :name
+ # end
+ #
+ # person = Person.new
+ # person.name = ''
+ # person.invalid? # => true
+ # person.name = 'david'
+ # person.invalid? # => false
+ #
+ # Context can optionally be supplied to define which callbacks to test
+ # against (the context is defined on the validations using <tt>:on</tt>).
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # validates_presence_of :name, on: :new
+ # end
+ #
+ # person = Person.new
+ # person.invalid? # => false
+ # person.invalid?(:new) # => true
def invalid?(context = nil)
!valid?(context)
end
@@ -237,7 +328,7 @@ module ActiveModel
protected
- def run_validations!
+ def run_validations! #:nodoc:
run_callbacks :validate
errors.empty?
end
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index dbafd0bd1a..bf3fe7ff04 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -2,24 +2,24 @@ require 'active_support/callbacks'
module ActiveModel
module Validations
+ # == Active Model Validation callbacks
+ #
+ # Provides an interface for any class to have +before_validation+ and
+ # +after_validation+ callbacks.
+ #
+ # First, include ActiveModel::Validations::Callbacks from the class you are
+ # creating:
+ #
+ # class MyModel
+ # include ActiveModel::Validations::Callbacks
+ #
+ # before_validation :do_stuff_before_validation
+ # after_validation :do_stuff_after_validation
+ # end
+ #
+ # Like other <tt>before_*</tt> callbacks if +before_validation+ returns
+ # +false+ then <tt>valid?</tt> will not be called.
module Callbacks
- # == Active Model Validation callbacks
- #
- # Provides an interface for any class to have <tt>before_validation</tt> and
- # <tt>after_validation</tt> callbacks.
- #
- # First, include ActiveModel::Validations::Callbacks from the class you are
- # creating:
- #
- # class MyModel
- # include ActiveModel::Validations::Callbacks
- #
- # before_validation :do_stuff_before_validation
- # after_validation :do_stuff_after_validation
- # end
- #
- # Like other before_* callbacks if <tt>before_validation</tt> returns false
- # then <tt>valid?</tt> will not be called.
extend ActiveSupport::Concern
included do
@@ -28,6 +28,30 @@ module ActiveModel
end
module ClassMethods
+ # Defines a callback that will get called right before validation
+ # happens.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ # include ActiveModel::Validations::Callbacks
+ #
+ # attr_accessor :name
+ #
+ # validates_length_of :name, maximum: 6
+ #
+ # before_validation :remove_whitespaces
+ #
+ # private
+ #
+ # def remove_whitespaces
+ # name.strip!
+ # end
+ # end
+ #
+ # person = Person.new
+ # person.name = ' bob '
+ # person.valid? # => true
+ # person.name # => "bob"
def before_validation(*args, &block)
options = args.last
if options.is_a?(Hash) && options[:on]
@@ -37,6 +61,33 @@ module ActiveModel
set_callback(:validation, :before, *args, &block)
end
+ # Defines a callback that will get called right after validation
+ # happens.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ # include ActiveModel::Validations::Callbacks
+ #
+ # attr_accessor :name, :status
+ #
+ # validates_presence_of :name
+ #
+ # after_validation :set_status
+ #
+ # private
+ #
+ # def set_status
+ # self.status = errors.empty?
+ # end
+ # end
+ #
+ # person = Person.new
+ # person.name = ''
+ # person.valid? # => false
+ # person.status # => false
+ #  person.name = 'bob'
+ # person.valid? # => true
+ # person.status # => true
def after_validation(*args, &block)
options = args.extract_options!
options[:prepend] = true
@@ -49,7 +100,7 @@ module ActiveModel
protected
# Overwrite run validations to include callbacks.
- def run_validations!
+ def run_validations! #:nodoc:
run_callbacks(:validation) { super }
end
end
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index ecda9dffcf..5892ad29d1 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -11,18 +11,18 @@ module ActiveModel
#
# 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
+ # 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 custom validators
- # and default validators in one call for a given attribute e.g.
+ # and default validators in one call for a given attribute.
#
# class EmailValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
@@ -35,12 +35,12 @@ module ActiveModel
# include ActiveModel::Validations
# attr_accessor :name, :email
#
- # validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 100 }
- # validates :email, :presence => true, :email => true
+ # validates :name, presence: true, uniqueness: true, length: { maximum: 100 }
+ # validates :email, presence: true, email: true
# end
#
# Validator classes may also exist within the class being validated
- # allowing custom modules of validators to be included as needed e.g.
+ # allowing custom modules of validators to be included as needed.
#
# class Film
# include ActiveModel::Validations
@@ -51,25 +51,27 @@ module ActiveModel
# end
# end
#
- # validates :name, :title => true
+ # validates :name, title: true
# end
#
- # Additionally validator classes may be in another namespace and still used within any class.
+ # Additionally validator classes may be in another namespace and still
+ # used within any class.
#
# validates :name, :'film/title' => true
#
- # The validators hash can also handle regular expressions, ranges,
- # arrays and strings in shortcut form, e.g.
+ # The validators hash can also handle regular expressions, ranges, arrays
+ # and strings in shortcut form.
#
- # validates :email, :format => /@/
- # validates :gender, :inclusion => %w(male female)
- # validates :password, :length => 6..20
+ # validates :email, format: /@/
+ # validates :gender, inclusion: %w(male female)
+ # validates :password, length: 6..20
#
# When using shortcut form, ranges and arrays are passed to your
- # validator's initializer as +options[:in]+ while other types including
- # regular expressions and strings are passed as +options[:with]+
+ # validator's initializer as <tt>options[:in]</tt> while other types
+ # including regular expressions and strings are passed as <tt>options[:with]</tt>.
#
# There is also a list of options that could be used along with validators:
+ #
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
# validation contexts by default (+nil+), other options are <tt>:create</tt>
# and <tt>:update</tt>.
@@ -87,14 +89,12 @@ module ActiveModel
#
# Example:
#
- # validates :password, :presence => true, :confirmation => true, :if => :password_required?
- #
- # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+ and +:strict+
- # can be given to one specific validator, as a hash:
- #
- # validates :password, :presence => { :if => :password_required? }, :confirmation => true
+ # validates :password, presence: true, confirmation: true, if: :password_required?
#
+ # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+
+ # and +:strict+ can be given to one specific validator, as a hash:
#
+ # validates :password, presence: { if: :password_required? }, confirmation: true
def validates(*attributes)
defaults = attributes.extract_options!.dup
validations = defaults.slice!(*_validates_default_keys)
@@ -122,8 +122,20 @@ module ActiveModel
# users and are considered exceptional. So each validator defined with bang
# or <tt>:strict</tt> option set to <tt>true</tt> will always raise
# <tt>ActiveModel::StrictValidationFailed</tt> instead of adding error
- # when validation fails.
- # See <tt>validates</tt> for more information about the validation itself.
+ # when validation fails. See <tt>validates</tt> for more information about
+ # the validation itself.
+ #
+ # class Person
+ #  include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # validates! :name, presence: true
+ # end
+ #
+ # person = Person.new
+ #  person.name = ''
+ #  person.valid?
+ # # => ActiveModel::StrictValidationFailed: Name can't be blank
def validates!(*attributes)
options = attributes.extract_options!
options[:strict] = true
@@ -134,7 +146,7 @@ module ActiveModel
# When creating custom validators, it might be useful to be able to specify
# additional default keys. This can be done by overwriting this method.
- def _validates_default_keys
+ def _validates_default_keys #:nodoc:
[:if, :unless, :on, :allow_blank, :allow_nil , :strict]
end
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 3c516f8b22..869591cd9e 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -34,7 +34,7 @@ module ActiveModel
# class MyValidator < ActiveModel::Validator
# def validate(record)
# if some_complex_logic
- # record.errors.add :base, "This record is invalid"
+ # record.errors.add :base, 'This record is invalid'
# end
# end
#
@@ -48,30 +48,32 @@ module ActiveModel
#
# class Person
# include ActiveModel::Validations
- # validates_with MyValidator, MyOtherValidator, :on => :create
+ # 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>: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.
+ # 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>).
- # The method, proc or string should return or evaluate to a true or false value.
+ # (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.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
#
# If you pass any additional configuration options, they will be passed
- # to the class and available as <tt>options</tt>:
+ # to the class and available as +options+:
#
# class Person
# include ActiveModel::Validations
- # validates_with MyValidator, :my_custom_key => "my custom value"
+ # validates_with MyValidator, my_custom_key: 'my custom value'
# end
#
# class MyValidator < ActiveModel::Validator
@@ -119,17 +121,17 @@ module ActiveModel
# class Person
# include ActiveModel::Validations
#
- # validate :instance_validations, :on => :create
+ # validate :instance_validations, on: :create
#
# def instance_validations
# validates_with MyValidator, MyOtherValidator
# end
# end
#
- # Standard configuration options (:on, :if and :unless), which are
- # available on the class version of +validates_with+, should instead be
- # placed on the +validates+ method as these are applied and tested
- # in the callback.
+ # Standard configuration options (<tt>:on</tt>, <tt>:if</tt> and
+ # <tt>:unless</tt>), which are available on the class version of
+ # +validates_with+, should instead be placed on the +validates+ method
+ # as these are applied and tested in the callback.
#
# If you pass any additional configuration options, they will be passed
# to the class and available as +options+, please refer to the
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index cbbf58505b..87e4319421 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -1,6 +1,6 @@
require "active_support/core_ext/module/anonymous"
-module ActiveModel #:nodoc:
+module ActiveModel
# == Active Model Validator
#
@@ -26,7 +26,7 @@ module ActiveModel #:nodoc:
# end
#
# Any class that inherits from ActiveModel::Validator must implement a method
- # called <tt>validate</tt> which accepts a <tt>record</tt>.
+ # called +validate+ which accepts a +record+.
#
# class Person
# include ActiveModel::Validations
@@ -40,8 +40,8 @@ module ActiveModel #:nodoc:
# end
# end
#
- # To cause a validation error, you must add to the <tt>record</tt>'s errors directly
- # from within the validators message
+ # To cause a validation error, you must add to the +record+'s errors directly
+ # from within the validators message.
#
# class MyValidator < ActiveModel::Validator
# def validate(record)
@@ -61,7 +61,7 @@ module ActiveModel #:nodoc:
# end
#
# The easiest way to add custom validators for validating individual attributes
- # is with the convenient <tt>ActiveModel::EachValidator</tt>. For example:
+ # is with the convenient <tt>ActiveModel::EachValidator</tt>.
#
# class TitleValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
@@ -70,7 +70,7 @@ module ActiveModel #:nodoc:
# end
#
# This can now be used in combination with the +validates+ method
- # (see <tt>ActiveModel::Validations::ClassMethods.validates</tt> for more on this)
+ # (see <tt>ActiveModel::Validations::ClassMethods.validates</tt> for more on this).
#
# class Person
# include ActiveModel::Validations
@@ -81,8 +81,7 @@ module ActiveModel #:nodoc:
#
# Validator may also define a +setup+ instance method which will get called
# with the class that using that validator as its argument. This can be
- # useful when there are prerequisites such as an +attr_accessor+ being present
- # for example:
+ # useful when there are prerequisites such as an +attr_accessor+ being present.
#
# class MyValidator < ActiveModel::Validator
# def setup(klass)
@@ -92,15 +91,13 @@ module ActiveModel #:nodoc:
#
# This setup method is only called when used with validation macros or the
# class level <tt>validates_with</tt> method.
- #
class Validator
attr_reader :options
- # Returns the kind of the validator. Examples:
+ # Returns the kind of the validator.
#
# PresenceValidator.kind # => :presence
# UniquenessValidator.kind # => :uniqueness
- #
def self.kind
@kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous?
end
@@ -111,6 +108,9 @@ module ActiveModel #:nodoc:
end
# Return the kind for this validator.
+ #
+ # PresenceValidator.new.kind # => :presence
+ # UniquenessValidator.new.kind # => :uniqueness 
def kind
self.class.kind
end
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index afce149da9..1fa6629980 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module Validations
- class AssociatedValidator < ActiveModel::EachValidator
+ class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
def validate_each(record, attribute, value)
if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?(record.validation_context) }.any?
record.errors.add(attribute, :invalid, options.merge(:value => value))
@@ -9,7 +9,8 @@ module ActiveRecord
end
module ClassMethods
- # Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
+ # Validates whether the associated object or objects are all valid
+ # themselves. Works with any kind of association.
#
# class Book < ActiveRecord::Base
# has_many :pages
@@ -18,23 +19,28 @@ module ActiveRecord
# validates_associated :pages, :library
# end
#
- # WARNING: This validation must not be used on both ends of an association. Doing so will lead to a circular dependency and cause infinite recursion.
+ # WARNING: This validation must not be used on both ends of an association.
+ # Doing so will lead to a circular dependency and cause infinite recursion.
#
- # NOTE: This validation will not fail if the association hasn't been assigned. If you want to
- # ensure that the association is both present and guaranteed to be valid, you also need to
- # use +validates_presence_of+.
+ # NOTE: This validation will not fail if the association hasn't been
+ # assigned. If you want to ensure that the association is both present and
+ # guaranteed to be valid, you also need to use +validates_presence_of+.
#
# Configuration options:
- # * <tt>:message</tt> - A custom error message (default is: "is invalid")
+ #
+ # * <tt>:message</tt> - A custom error message (default is: "is invalid").
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
# validation contexts by default (+nil+), other options are <tt>:create</tt>
# and <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>). The
- # method, proc or string should return or evaluate to a true or false value.
+ # * <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>). The
+ # method, proc or string should return or evaluate to a +true+ or +false+
+ # value.
def validates_associated(*attr_names)
validates_with AssociatedValidator, _merge_attributes(attr_names)
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 5a24135f8e..c117872ac8 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -2,7 +2,7 @@ require 'active_support/core_ext/array/prepend_and_append'
module ActiveRecord
module Validations
- class UniquenessValidator < ActiveModel::EachValidator
+ class UniquenessValidator < ActiveModel::EachValidator #:nodoc:
def initialize(options)
super(options.reverse_merge(:case_sensitive => true))
end
@@ -87,54 +87,67 @@ module ActiveRecord
end
module ClassMethods
- # Validates whether the value of the specified attributes are unique across the system.
- # Useful for making sure that only one user
+ # Validates whether the value of the specified attributes are unique
+ # across the system. Useful for making sure that only one user
# can be named "davidhh".
#
# class Person < ActiveRecord::Base
# validates_uniqueness_of :user_name
# end
#
- # It can also validate whether the value of the specified attributes are unique based on a scope parameter:
+ # It can also validate whether the value of the specified attributes are
+ # unique based on a <tt>:scope</tt> parameter:
#
# class Person < ActiveRecord::Base
- # validates_uniqueness_of :user_name, :scope => :account_id
+ # validates_uniqueness_of :user_name, scope: :account_id
# end
#
- # Or even multiple scope parameters. For example, making sure that a teacher can only be on the schedule once
- # per semester for a particular class.
+ # Or even multiple scope parameters. For example, making sure that a
+ # teacher can only be on the schedule once per semester for a particular
+ # class.
#
# class TeacherSchedule < ActiveRecord::Base
- # validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
+ # validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id]
# end
#
- # It is also possible to limit the uniqueness constraint to a set of records matching certain conditions.
- # In this example archived articles are not being taken into consideration when validating uniqueness
+ # It is also possible to limit the uniqueness constraint to a set of
+ # records matching certain conditions. In this example archived articles
+ # are not being taken into consideration when validating uniqueness
# of the title attribute:
#
# class Article < ActiveRecord::Base
- # validates_uniqueness_of :title, :conditions => where('status != ?', 'archived')
+ # validates_uniqueness_of :title, conditions: where('status != ?', 'archived')
# end
#
- # When the record is created, a check is performed to make sure that no record exists in the database
- # with the given value for the specified attribute (that maps to a column). When the record is updated,
+ # When the record is created, a check is performed to make sure that no
+ # record exists in the database with the given value for the specified
+ # attribute (that maps to a column). When the record is updated,
# the same check is made but disregarding the record itself.
#
# Configuration options:
- # * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
- # * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
- # * <tt>:conditions</tt> - Specify the conditions to be included as a <tt>WHERE</tt> SQL fragment to limit
- # the uniqueness constraint lookup. (e.g. <tt>:conditions => where('status = ?', 'active')</tt>)
- # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
- # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
- # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
- # * <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>). The method, proc or string should
- # return or evaluate to a true or false value.
+ #
+ # * <tt>:message</tt> - Specifies a custom error message (default is:
+ # "has already been taken").
+ # * <tt>:scope</tt> - One or more columns by which to limit the scope of
+ # the uniqueness constraint.
+ # * <tt>:conditions</tt> - Specify the conditions to be included as a
+ # <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup
+ # (e.g. <tt>conditions: where('status = ?', 'active')</tt>).
+ # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
+ # non-text columns (+true+ by default).
+ # * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
+ # attribute is +nil+ (default is +false+).
+ # * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
+ # attribute is blank (default is +false+).
+ # * <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 ot 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.
#
# === Concurrency and integrity
#
@@ -190,15 +203,16 @@ module ActiveRecord
#
# The bundled ActiveRecord::ConnectionAdapters distinguish unique index
# constraint errors from other types of database errors by throwing an
- # ActiveRecord::RecordNotUnique exception.
- # For other adapters you will have to parse the (database-specific) exception
- # message to detect such a case.
+ # ActiveRecord::RecordNotUnique exception. For other adapters you will
+ # have to parse the (database-specific) exception message to detect such
+ # a case.
+ #
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
+ #
# * ActiveRecord::ConnectionAdapters::MysqlAdapter
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
- #
def validates_uniqueness_of(*attr_names)
validates_with UniquenessValidator, _merge_attributes(attr_names)
end
diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb
index e5f81078ee..0d5f3501e5 100644
--- a/activesupport/lib/active_support/core_ext/object/to_param.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_param.rb
@@ -6,18 +6,21 @@ class Object
end
class NilClass
+ # Returns +self+.
def to_param
self
end
end
class TrueClass
+ # Returns +self+.
def to_param
self
end
end
class FalseClass
+ # Returns +self+.
def to_param
self
end
@@ -35,12 +38,12 @@ class Hash
# Returns a string representation of the receiver suitable for use as a URL
# query string:
#
- # {:name => 'David', :nationality => 'Danish'}.to_param
+ # {name: 'David', nationality: 'Danish'}.to_param
# # => "name=David&nationality=Danish"
#
# An optional namespace can be passed to enclose the param names:
#
- # {:name => 'David', :nationality => 'Danish'}.to_param('user')
+ # {name: 'David', nationality: 'Danish'}.to_param('user')
# # => "user[name]=David&user[nationality]=Danish"
#
# The string pairs "key=value" that conform the query string
diff --git a/guides/source/action_view_overview.textile b/guides/source/action_view_overview.textile
index fdfa97effa..1fd98a5bbe 100644
--- a/guides/source/action_view_overview.textile
+++ b/guides/source/action_view_overview.textile
@@ -785,7 +785,7 @@ h5. content_for
Calling +content_for+ stores a block of markup in an identifier for later use. You can make subsequent calls to the stored content in other templates or the layout by passing the identifier as an argument to +yield+.
-For example, let's say we have a standard application layout, but also a special page that requires certain Javascript that the rest of the site doesn't need. We can use +content_for+ to include this Javascript on our special page without fattening up the rest of the site.
+For example, let's say we have a standard application layout, but also a special page that requires certain JavaScript that the rest of the site doesn't need. We can use +content_for+ to include this JavaScript on our special page without fattening up the rest of the site.
*app/views/layouts/application.html.erb*
diff --git a/guides/source/ajax_on_rails.textile b/guides/source/ajax_on_rails.textile
index 26e0270a31..67b0c9f0d3 100644
--- a/guides/source/ajax_on_rails.textile
+++ b/guides/source/ajax_on_rails.textile
@@ -28,7 +28,7 @@ the +ul+ node.
h4. Asynchronous JavaScript + XML
AJAX means Asynchronous JavaScript + XML. Asynchronous means that the page is not
-reloaded, the request made is separate from the regular page request. Javascript
+reloaded, the request made is separate from the regular page request. JavaScript
is used to evaluate the response and the XML part is a bit misleading as XML is
not required, you respond to the HTTP request with JSON or regular HTML as well.
diff --git a/guides/source/asset_pipeline.textile b/guides/source/asset_pipeline.textile
index d0952fcf29..42b5d102e7 100644
--- a/guides/source/asset_pipeline.textile
+++ b/guides/source/asset_pipeline.textile
@@ -121,7 +121,7 @@ h5. Search paths
When a file is referenced from a manifest or a helper, Sprockets searches the three default asset locations for it.
-The default locations are: +app/assets/images+ and the subdirectories +javascripts+ and +stylesheets+ in all three asset locations.
+The default locations are: +app/assets/images+ and the subdirectories +javascripts+ and +stylesheets+ in all three asset locations, but these subdirectories are not special. Any path under +assets/*+ will be searched.
For example, these files:
@@ -129,6 +129,7 @@ For example, these files:
app/assets/javascripts/home.js
lib/assets/javascripts/moovinator.js
vendor/assets/javascripts/slider.js
+vendor/assets/somepackage/phonebox.js
</plain>
would be referenced in a manifest like this:
@@ -137,6 +138,7 @@ would be referenced in a manifest like this:
//= require home
//= require moovinator
//= require slider
+//= require phonebox
</plain>
Assets inside subdirectories can also be accessed.
@@ -153,13 +155,13 @@ is referenced as:
You can view the search path by inspecting +Rails.application.config.assets.paths+ in the Rails console.
-Additional (fully qualified) paths can be added to the pipeline in +config/application.rb+. For example:
+Besides the standard +assets/*+ paths, additional (fully qualified) paths can be added to the pipeline in +config/application.rb+. For example:
<ruby>
-config.assets.paths << Rails.root.join("app", "assets", "flash")
+config.assets.paths << Rails.root.join("lib", "videoplayer", "flash")
</ruby>
-Paths are traversed in the order that they occur in the search path.
+Paths are traversed in the order that they occur in the search path. By default, this means the files in +app/assets+ take precedence, and will mask corresponding paths in +lib+ and +vendor+.
It is important to note that files you want to reference outside a manifest must be added to the precompile array or they will not be available in the production environment.
@@ -651,7 +653,7 @@ WARNING: If you are upgrading an existing application and intend to use this opt
h3. Assets Cache Store
-Sprockets uses the default Rails cache store will be used to cache assets in development and production. This can be changed by setting +config.assets.cache_store+.
+The default Rails cache store will be used by Sprockets to cache assets in development and production. This can be changed by setting +config.assets.cache_store+.
<ruby>
config.assets.cache_store = :memory_store
diff --git a/guides/source/command_line.textile b/guides/source/command_line.textile
index 19e42cea93..39b75c2781 100644
--- a/guides/source/command_line.textile
+++ b/guides/source/command_line.textile
@@ -160,7 +160,7 @@ $ rails generate controller Greetings hello
create app/assets/stylesheets/greetings.css.scss
</shell>
-What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a view file, a functional test file, a helper for the view, a javascript file and a stylesheet file.
+What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a view file, a functional test file, a helper for the view, a JavaScript file and a stylesheet file.
Check out the controller and modify it a little (in +app/controllers/greetings_controller.rb+):
@@ -477,6 +477,53 @@ h4. Miscellaneous
* +rake secret+ will give you a pseudo-random key to use for your session secret.
* <tt>rake time:zones:all</tt> lists all the timezones Rails knows about.
+h4. Writing Rake Tasks
+
+If you have (or want to write) any automation scripts outside your app (data import, checks, etc), you can make them as rake tasks. It's easy.
+
+INFO: "Complete guide about how to write tasks":http://rake.rubyforge.org/files/doc/rakefile_rdoc.html is available in the official documentation.
+
+Tasks should be placed in <tt>Rails.root/lib/tasks</tt> and should have a +.rake+ extension.
+
+Each task should be defined in next format (dependencies are optional):
+
+<ruby>
+desc "I am short, but comprehensive description for my cool task"
+task :task_name => [:prerequisite_task, :another_task_we_depend_on] do
+ # All your magick here
+ # Any valid Ruby code is allowed
+end
+</ruby>
+
+If you need to pass parameters, you can use next format (both arguments and dependencies are optional):
+
+<ruby>
+task :task_name, [:arg_1] => [:pre_1, :pre_2] do |t, args|
+ # You can use args from here
+end
+</ruby>
+
+You can group tasks by placing them in namespaces:
+
+<ruby>
+namespace :do
+ desc "This task does nothing"
+ task :nothing do
+ # Seriously, nothing
+ end
+end
+</ruby>
+
+You can see your tasks to be listed by <tt>rake -T</tt> command. And, according to the examples above, you can invoke them as follows:
+
+<shell>
+rake task_name
+rake "task_name[value 1]" # entire argument string should be quoted
+rake do:nothing
+</shell>
+
+NOTE: If your need to interact with your application models, perform database queries and so on, your task should depend on the +environment+ task, which will load your application code.
+
h3. The Rails Advanced Command Line
More advanced use of the command line is focused around finding useful (even surprising at times) options in the utilities, and fitting those to your needs and specific work flow. Listed here are some tricks up Rails' sleeve.
diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile
index cd9aab4892..cc5c352df4 100644
--- a/guides/source/configuring.textile
+++ b/guides/source/configuring.textile
@@ -157,7 +157,7 @@ Rails 3.1, by default, is set up to use the +sprockets+ gem to manage assets wit
* +config.assets.digest+ enables the use of MD5 fingerprints in asset names. Set to +true+ by default in +production.rb+.
-* +config.assets.debug+ disables the concatenation and compression of assets. Set to +false+ by default in +development.rb+.
+* +config.assets.debug+ disables the concatenation and compression of assets. Set to +true+ by default in +development.rb+.
* +config.assets.manifest+ defines the full path to be used for the asset precompiler's manifest file. Defaults to using +config.assets.prefix+.
@@ -186,7 +186,7 @@ The full set of methods that can be used in this block are as follows:
* +force_plural+ allows pluralized model names. Defaults to +false+.
* +helper+ defines whether or not to generate helpers. Defaults to +true+.
* +integration_tool+ defines which integration tool to use. Defaults to +nil+.
-* +javascripts+ turns on the hook for javascripts in generators. Used in Rails for when the +scaffold+ generator is run. Defaults to +true+.
+* +javascripts+ turns on the hook for JavaScript files in generators. Used in Rails for when the +scaffold+ generator is run. Defaults to +true+.
* +javascript_engine+ configures the engine to be used (for eg. coffee) when generating assets. Defaults to +nil+.
* +orm+ defines which orm to use. Defaults to +false+ and will use Active Record by default.
* +performance_tool+ defines which performance tool to use. Defaults to +nil+.
diff --git a/guides/source/contributing_to_ruby_on_rails.textile b/guides/source/contributing_to_ruby_on_rails.textile
index a8a097d156..dd43ef795f 100644
--- a/guides/source/contributing_to_ruby_on_rails.textile
+++ b/guides/source/contributing_to_ruby_on_rails.textile
@@ -215,7 +215,7 @@ NOTE: Using the rake task to create the test databases ensures they have the cor
NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator".
-If you’re using another database, check the files under +activerecord/test/connections+ for default connection information. You can edit these files to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails.
+If you’re using another database, check the file +activerecord/test/config.yml+ or +activerecord/test/config.example.yml+ for default connection information. You can edit +activerecord/test/config.yml+ to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails.
You can now run the tests as you did for +sqlite3+. The tasks are respectively
diff --git a/guides/source/engines.textile b/guides/source/engines.textile
index 53c2845731..11c837be32 100644
--- a/guides/source/engines.textile
+++ b/guides/source/engines.textile
@@ -657,6 +657,84 @@ h3. Improving engine functionality
This section looks at overriding or adding functionality to the views, controllers and models provided by an engine.
+h4. Overriding Models
+
+Engine Models can be extended by (1) implementing decorators, or (2) including modules.
+
+h5. Decorators
+
+Decorators extends Engine's model classes in the main application by open classing Engine models at run time execution.
+
+<ruby>
+# MyApp/app/decorators/models/blorgh/post_decorator.rb
+
+Blorgh::Post.class_eval do
+ def time_since_created
+ Time.current - created_at
+ end
+end
+</ruby>
+
+h5. Modules
+
+The other strategy is to create modules within the Engine holding all the models' code and include these in the respective Engine's model classes. Thus the Engine's model classes contain a mere include line referencing the respective module.
+
+Engine models can be overriden in the main application by creating a file with the Engine's same namespace and including the module originally referenced in the Engine's model class. ["**ActiveSupport::Concern**":http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html] helps manage the correct ordering of module dependencies at run time (it's worth it to reach the linked documentation).
+
+<ruby>
+# MyApp/app/models/blorgh/post.rb
+# overrides Blorgh's original Post model
+
+class Blorgh::Post < ActiveRecord::Base
+ include Blorgh::Concerns::Models::Post
+
+ def time_since_created
+ Time.current - created_at
+ end
+end
+
+
+# Blorgh/app/models/post.rb
+# this class is overriden by the MyApp Post model
+
+class Post < ActiveRecord::Base
+ include Blorgh::Concerns::Models::Post
+end
+
+
+# Blorgh/app/concerns/models/post
+
+module Blorg::Concerns::Models::Post
+ extend ActiveSupport::Concern
+
+ # 'included do' causes the code within to be evaluated in the conext
+ # where it is included, rather be executed in the module's context.
+ included do
+ attr_accessor :author_name
+ belongs_to :author, :class_name => "User"
+
+ before_save :set_author
+
+ private
+
+ def set_author
+ self.author = User.find_or_create_by_name(author_name)
+ end
+ end
+
+ # methods defined here will be instance methods by default
+ def some_method
+ 'some method string'
+ end
+
+ module ClassMethods
+ def some_class_method
+ 'some class method string'
+ end
+ end
+end
+</ruby>
+
h4. Overriding views
When Rails looks for a view to render, it will first look in the +app/views+ directory of the application. If it cannot find the view there, then it will check in the +app/views+ directories of all engines which have this directory.
diff --git a/guides/source/getting_started.textile b/guides/source/getting_started.textile
index 8d7c0d4bea..22da369a2a 100644
--- a/guides/source/getting_started.textile
+++ b/guides/source/getting_started.textile
@@ -373,7 +373,7 @@ Edit the +form_for+ line inside +app/views/posts/new.html.erb+ to look like this
In this example, a +Hash+ object is passed to the +:url+ option. What Rails will do with this is that it will point the form to the +create+ action of the current controller, the +PostsController+, and will send a +POST+ request to that route. For this to work, you will need to add a route to +config/routes.rb+, right underneath the one for "posts/new":
<ruby>
-post "posts/create"
+post "posts" => "posts#create"
</ruby>
By using the +post+ method rather than the +get+ method, Rails will define a route that will only respond to POST methods. The POST method is the typical method used by forms all over the web.
@@ -1064,13 +1064,13 @@ received an error before.
<shell>
# rake routes
- posts GET /posts(.:format) posts#index
- posts_new GET /posts/new(.:format) posts#new
-posts_create POST /posts/create(.:format) posts#create
- GET /posts/:id(.:format) posts#show
- GET /posts/:id/edit(.:format) posts#edit
- PUT /posts/:id(.:format) posts#update
- root / welcome#index
+ posts GET /posts(.:format) posts#index
+posts_new GET /posts/new(.:format) posts#new
+ POST /posts(.:format) posts#create
+ GET /posts/:id(.:format) posts#show
+ GET /posts/:id/edit(.:format) posts#edit
+ PUT /posts/:id(.:format) posts#update
+ root / welcome#index
</shell>
To fix this, open +config/routes.rb+ and modify the +get "posts/:id"+
@@ -1175,7 +1175,7 @@ declaring separate routes with the appropriate verbs into
<ruby>
get "posts" => "posts#index"
get "posts/new"
-post "posts/create"
+post "posts" => "posts#create"
get "posts/:id" => "posts#show", :as => :post
get "posts/:id/edit" => "posts#edit"
put "posts/:id" => "posts#update"
diff --git a/guides/source/i18n.textile b/guides/source/i18n.textile
index 8ad6ee4b73..c782539399 100644
--- a/guides/source/i18n.textile
+++ b/guides/source/i18n.textile
@@ -405,6 +405,10 @@ So that would give you:
TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected (at least for the 'pirate' locale). Of course, there's a great chance that somebody already did all the work by *translating Rails' defaults for your locale*. See the "rails-i18n repository at Github":https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale for an archive of various locale files. When you put such file(s) in +config/locales/+ directory, they will automatically be ready for use.
+h4. Inflection Rules For Other Locales
+
+Rails 4.0 allows you to define inflection rules (such as rules for singularization and pluralization) for locales other than English. In +config/initializers/inflections.rb+, you can define these rules for multiple locales. The initializer contains a default example for specifying additional rules for English; follow that format for other locales as you see fit.
+
h4. Localized Views
Rails 2.3 introduces another convenient localization feature: localized views (templates). Let's say you have a _BooksController_ in your application. Your _index_ action renders content in +app/views/books/index.html.erb+ template. When you put a _localized variant_ of this template: *+index.es.html.erb+* in the same directory, Rails will render content in this template, when the locale is set to +:es+. When the locale is set to the default locale, the generic +index.html.erb+ view will be used. (Future Rails versions may well bring this _automagic_ localization to assets in +public+, etc.)
diff --git a/guides/source/performance_testing.textile b/guides/source/performance_testing.textile
index 982fd1b070..b7d4f1ef33 100644
--- a/guides/source/performance_testing.textile
+++ b/guides/source/performance_testing.textile
@@ -1,40 +1,55 @@
h2. Performance Testing Rails Applications
-This guide covers the various ways of performance testing a Ruby on Rails application. By referring to this guide, you will be able to:
-
-* Understand the various types of benchmarking and profiling metrics
-* Generate performance and benchmarking tests
-* Install and use a GC-patched Ruby binary to measure memory usage and object allocation
-* Understand the benchmarking information provided by Rails inside the log files
-* Learn about various tools facilitating benchmarking and profiling
-
-Performance testing is an integral part of the development cycle. It is very important that you don't make your end users wait for too long before the page is completely loaded. Ensuring a pleasant browsing experience for end users and cutting the cost of unnecessary hardware is important for any non-trivial web application.
+This guide covers the various ways of performance testing a Ruby on Rails
+application. By referring to this guide, you will be able to:
+
+* Understand the various types of benchmarking and profiling metrics.
+* Generate performance and benchmarking tests.
+* Install and use a GC-patched Ruby binary to measure memory usage and object
+ allocation.
+* Understand the benchmarking information provided by Rails inside the log files.
+* Learn about various tools facilitating benchmarking and profiling.
+
+Performance testing is an integral part of the development cycle. It is very
+important that you don't make your end users wait for too long before the page
+is completely loaded. Ensuring a pleasant browsing experience for end users and
+cutting the cost of unnecessary hardware is important for any non-trivial web
+application.
endprologue.
h3. Performance Test Cases
-Rails performance tests are a special type of integration tests, designed for benchmarking and profiling the test code. With performance tests, you can determine where your application's memory or speed problems are coming from, and get a more in-depth picture of those problems.
+Rails performance tests are a special type of integration tests, designed for
+benchmarking and profiling the test code. With performance tests, you can
+determine where your application's memory or speed problems are coming from,
+and get a more in-depth picture of those problems.
-In a freshly generated Rails application, +test/performance/browsing_test.rb+ contains an example of a performance test:
+In a freshly generated Rails application, +test/performance/browsing_test.rb+
+contains an example of a performance test:
<ruby>
require 'test_helper'
require 'rails/performance_test_help'
-# Profiling results for each test method are written to tmp/performance.
class BrowsingTest < ActionDispatch::PerformanceTest
- def test_homepage
+ # Refer to the documentation for all available options
+ # self.profile_options = { runs: 5, metrics: [:wall_time, :memory],
+ # output: 'tmp/performance', formats: [:flat] }
+
+ test "homepage" do
get '/'
end
end
</ruby>
-This example is a simple performance test case for profiling a GET request to the application's homepage.
+This example is a simple performance test case for profiling a GET request to
+the application's homepage.
h4. Generating Performance Tests
-Rails provides a generator called +performance_test+ for creating new performance tests:
+Rails provides a generator called +performance_test+ for creating new
+performance tests:
<shell>
$ rails generate performance_test homepage
@@ -47,8 +62,11 @@ require 'test_helper'
require 'rails/performance_test_help'
class HomepageTest < ActionDispatch::PerformanceTest
- # Replace this with your real tests.
- def test_homepage
+ # Refer to the documentation for all available options
+ # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory],
+ # :output => 'tmp/performance', :formats => [:flat] }
+
+ test "homepage" do
get '/'
end
end
@@ -60,7 +78,7 @@ Let's assume your application has the following controller and model:
<ruby>
# routes.rb
-root :to => 'home#index'
+root to: 'home#dashboard'
resources :posts
# home_controller.rb
@@ -97,9 +115,11 @@ end
h5. Controller Example
-Because performance tests are a special kind of integration test, you can use the +get+ and +post+ methods in them.
+Because performance tests are a special kind of integration test, you can use
+the +get+ and +post+ methods in them.
-Here's the performance test for +HomeController#dashboard+ and +PostsController#create+:
+Here's the performance test for +HomeController#dashboard+ and
++PostsController#create+:
<ruby>
require 'test_helper'
@@ -111,21 +131,24 @@ class PostPerformanceTest < ActionDispatch::PerformanceTest
login_as(:lifo)
end
- def test_homepage
+ test "homepage" do
get '/dashboard'
end
- def test_creating_new_post
- post '/posts', :post => { :body => 'lifo is fooling you' }
+ test "creating new post" do
+ post '/posts', post: { body: 'lifo is fooling you' }
end
end
</ruby>
-You can find more details about the +get+ and +post+ methods in the "Testing Rails Applications":testing.html guide.
+You can find more details about the +get+ and +post+ methods in the
+"Testing Rails Applications":testing.html guide.
h5. Model Example
-Even though the performance tests are integration tests and hence closer to the request/response cycle by nature, you can still performance test pure model code.
+Even though the performance tests are integration tests and hence closer to
+the request/response cycle by nature, you can still performance test pure model
+code.
Performance test for +Post+ model:
@@ -134,11 +157,11 @@ require 'test_helper'
require 'rails/performance_test_help'
class PostModelTest < ActionDispatch::PerformanceTest
- def test_creation
- Post.create :body => 'still fooling you', :cost => '100'
+ test "creation" do
+ Post.create body: 'still fooling you', cost: '100'
end
- def test_slow_method
+ test "slow method" do
# Using posts(:awesome) fixture
posts(:awesome).slow_method
end
@@ -151,7 +174,8 @@ Performance tests can be run in two modes: Benchmarking and Profiling.
h5. Benchmarking
-Benchmarking makes it easy to quickly gather a few metrics about each test run. By default, each test case is run *4 times* in benchmarking mode.
+Benchmarking makes it easy to quickly gather a few metrics about each test run.
+By default, each test case is run *4 times* in benchmarking mode.
To run performance tests in benchmarking mode:
@@ -161,7 +185,10 @@ $ rake test:benchmark
h5. Profiling
-Profiling allows you to make an in-depth analysis of each of your tests by using an external profiler. Depending on your Ruby interpreter, this profiler can be native (Rubinius, JRuby) or not (MRI, which uses RubyProf). By default, each test case is run *once* in profiling mode.
+Profiling allows you to make an in-depth analysis of each of your tests by using
+an external profiler. Depending on your Ruby interpreter, this profiler can be
+native (Rubinius, JRuby) or not (MRI, which uses RubyProf). By default, each
+test case is run *once* in profiling mode.
To run performance tests in profiling mode:
@@ -171,23 +198,33 @@ $ rake test:profile
h4. Metrics
-Benchmarking and profiling run performance tests and give you multiple metrics. The availability of each metric is determined by the interpreter being used—none of them support all metrics—and by the mode in use. A brief description of each metric and their availability across interpreters/modes is given below.
+Benchmarking and profiling run performance tests and give you multiple metrics.
+The availability of each metric is determined by the interpreter being used—none
+of them support all metrics—and by the mode in use. A brief description of each
+metric and their availability across interpreters/modes is given below.
h5. Wall Time
-Wall time measures the real world time elapsed during the test run. It is affected by any other processes concurrently running on the system.
+Wall time measures the real world time elapsed during the test run. It is
+affected by any other processes concurrently running on the system.
h5. Process Time
-Process time measures the time taken by the process. It is unaffected by any other processes running concurrently on the same system. Hence, process time is likely to be constant for any given performance test, irrespective of the machine load.
+Process time measures the time taken by the process. It is unaffected by any
+other processes running concurrently on the same system. Hence, process time
+is likely to be constant for any given performance test, irrespective of the
+machine load.
h5. CPU Time
-Similar to process time, but leverages the more accurate CPU clock counter available on the Pentium and PowerPC platforms.
+Similar to process time, but leverages the more accurate CPU clock counter
+available on the Pentium and PowerPC platforms.
h5. User Time
-User time measures the amount of time the CPU spent in user-mode, i.e. within the process. This is not affected by other processes and by the time it possibly spends blocked.
+User time measures the amount of time the CPU spent in user-mode, i.e. within
+the process. This is not affected by other processes and by the time it possibly
+spends blocked.
h5. Memory
@@ -223,11 +260,13 @@ h6(#profiling_1). Profiling
|_.Rubinius | yes | no | no | no | no | no | no | no |
|_.JRuby | yes | no | no | no | no | no | no | no |
-NOTE: To profile under JRuby you'll need to run +export JRUBY_OPTS="-Xlaunch.inproc=false --profile.api"+ *before* the performance tests.
+NOTE: To profile under JRuby you'll need to run +export JRUBY_OPTS="-Xlaunch.inproc=false --profile.api"+
+*before* the performance tests.
h4. Understanding the Output
-Performance tests generate different outputs inside +tmp/performance+ directory depending on their mode and metric.
+Performance tests generate different outputs inside +tmp/performance+ directory
+depending on their mode and metric.
h5(#output-benchmarking). Benchmarking
@@ -248,7 +287,9 @@ BrowsingTest#test_homepage (31 ms warmup)
h6. CSV Files
-Performance test results are also appended to +.csv+ files inside +tmp/performance+. For example, running the default +BrowsingTest#test_homepage+ will generate following five files:
+Performance test results are also appended to +.csv+ files inside +tmp/performance+.
+For example, running the default +BrowsingTest#test_homepage+ will generate
+following five files:
* BrowsingTest#test_homepage_gc_runs.csv
* BrowsingTest#test_homepage_gc_time.csv
@@ -256,7 +297,9 @@ Performance test results are also appended to +.csv+ files inside +tmp/performan
* BrowsingTest#test_homepage_objects.csv
* BrowsingTest#test_homepage_wall_time.csv
-As the results are appended to these files each time the performance tests are run in benchmarking mode, you can collect data over a period of time. This can be very helpful in analyzing the effects of code changes.
+As the results are appended to these files each time the performance tests are
+run in benchmarking mode, you can collect data over a period of time. This can
+be very helpful in analyzing the effects of code changes.
Sample output of +BrowsingTest#test_homepage_wall_time.csv+:
@@ -276,7 +319,10 @@ measurement,created_at,app,rails,ruby,platform
h5(#output-profiling). Profiling
-In profiling mode, performance tests can generate multiple types of outputs. The command line output is always presented but support for the others is dependent on the interpreter in use. A brief description of each type and their availability across interpreters is given below.
+In profiling mode, performance tests can generate multiple types of outputs.
+The command line output is always presented but support for the others is
+dependent on the interpreter in use. A brief description of each type and
+their availability across interpreters is given below.
h6. Command Line
@@ -291,15 +337,18 @@ BrowsingTest#test_homepage (58 ms warmup)
h6. Flat
-Flat output shows the metric—time, memory, etc—measure in each method. "Check Ruby-Prof documentation for a better explanation":http://ruby-prof.rubyforge.org/files/examples/flat_txt.html.
+Flat output shows the metric—time, memory, etc—measure in each method.
+"Check Ruby-Prof documentation for a better explanation":http://ruby-prof.rubyforge.org/files/examples/flat_txt.html.
h6. Graph
-Graph output shows the metric measure in each method, which methods call it and which methods it calls. "Check Ruby-Prof documentation for a better explanation":http://ruby-prof.rubyforge.org/files/examples/graph_txt.html.
+Graph output shows the metric measure in each method, which methods call it and
+which methods it calls. "Check Ruby-Prof documentation for a better explanation":http://ruby-prof.rubyforge.org/files/examples/graph_txt.html.
h6. Tree
-Tree output is profiling information in calltree format for use by "kcachegrind":http://kcachegrind.sourceforge.net/html/Home.html and similar tools.
+Tree output is profiling information in calltree format for use by "kcachegrind":http://kcachegrind.sourceforge.net/html/Home.html
+and similar tools.
h6. Output Availability
@@ -311,24 +360,24 @@ h6. Output Availability
h4. Tuning Test Runs
-Test runs can be tuned by setting the +profile_options+ class variable on your test class.
+Test runs can be tuned by setting the +profile_options+ class variable on your
+test class.
<ruby>
require 'test_helper'
require 'rails/performance_test_help'
-# Profiling results for each test method are written to tmp/performance.
class BrowsingTest < ActionDispatch::PerformanceTest
- self.profile_options = { :runs => 5,
- :metrics => [:wall_time, :memory] }
+ self.profile_options = { runs: 5, metrics: [:wall_time, :memory] }
- def test_homepage
+ test "homepage"
get '/'
end
end
</ruby>
-In this example, the test would run 5 times and measure wall time and memory. There are a few configurable options:
+In this example, the test would run 5 times and measure wall time and memory.
+There are a few configurable options:
|_.Option |_.Description|_.Default|_.Mode|
|+:runs+ |Number of runs.|Benchmarking: 4, Profiling: 1|Both|
@@ -346,11 +395,13 @@ Metrics and formats have different defaults depending on the interpreter in use.
|/2.JRuby |Benchmarking|+[:wall_time, :user_time, :memory, :gc_runs, :gc_time]+|N/A|
|Profiling |+[:wall_time]+|+[:flat, :graph]+|
-As you've probably noticed by now, metrics and formats are specified using a symbol array with each name "underscored.":http://api.rubyonrails.org/classes/String.html#method-i-underscore
+As you've probably noticed by now, metrics and formats are specified using a
+symbol array with each name "underscored.":http://api.rubyonrails.org/classes/String.html#method-i-underscore
h4. Performance Test Environment
-Performance tests are run in the +test+ environment. But running performance tests will set the following configuration parameters:
+Performance tests are run in the +test+ environment. But running performance
+tests will set the following configuration parameters:
<shell>
ActionController::Base.perform_caching = true
@@ -358,11 +409,13 @@ ActiveSupport::Dependencies.mechanism = :require
Rails.logger.level = ActiveSupport::BufferedLogger::INFO
</shell>
-As +ActionController::Base.perform_caching+ is set to +true+, performance tests will behave much as they do in the +production+ environment.
+As +ActionController::Base.perform_caching+ is set to +true+, performance tests
+will behave much as they do in the +production+ environment.
h4. Installing GC-Patched MRI
-To get the best from Rails' performance tests under MRI, you'll need to build a special Ruby binary with some super powers.
+To get the best from Rails' performance tests under MRI, you'll need to build
+a special Ruby binary with some super powers.
The recommended patches for each MRI version are:
@@ -371,13 +424,18 @@ The recommended patches for each MRI version are:
|1.8.7|ruby187gc|
|1.9.2 and above|gcdata|
-All of these can be found on "RVM's _patches_ directory":https://github.com/wayneeseguin/rvm/tree/master/patches/ruby under each specific interpreter version.
+All of these can be found on "RVM's _patches_ directory":https://github.com/wayneeseguin/rvm/tree/master/patches/ruby
+under each specific interpreter version.
-Concerning the installation itself, you can either do this easily by using "RVM":http://rvm.beginrescueend.com or you can build everything from source, which is a little bit harder.
+Concerning the installation itself, you can either do this easily by using
+"RVM":http://rvm.beginrescueend.com or you can build everything from source,
+which is a little bit harder.
h5. Install Using RVM
-The process of installing a patched Ruby interpreter is very easy if you let RVM do the hard work. All of the following RVM commands will provide you with a patched Ruby interpreter:
+The process of installing a patched Ruby interpreter is very easy if you let RVM
+do the hard work. All of the following RVM commands will provide you with a
+patched Ruby interpreter:
<shell>
$ rvm install 1.9.2-p180 --patch gcdata
@@ -385,7 +443,8 @@ $ rvm install 1.8.7 --patch ruby187gc
$ rvm install 1.9.2-p180 --patch ~/Downloads/downloaded_gcdata_patch.patch
</shell>
-You can even keep your regular interpreter by assigning a name to the patched one:
+You can even keep your regular interpreter by assigning a name to the patched
+one:
<shell>
$ rvm install 1.9.2-p180 --patch gcdata --name gcdata
@@ -397,7 +456,9 @@ And it's done! You have installed a patched Ruby interpreter.
h5. Install From Source
-This process is a bit more complicated, but straightforward nonetheless. If you've never compiled a Ruby binary before, follow these steps to build a Ruby binary inside your home directory.
+This process is a bit more complicated, but straightforward nonetheless. If
+you've never compiled a Ruby binary before, follow these steps to build a
+Ruby binary inside your home directory.
h6. Download and Extract
@@ -417,7 +478,9 @@ $ curl http://github.com/wayneeseguin/rvm/raw/master/patches/ruby/1.8.7/ruby187g
h6. Configure and Install
-The following will install Ruby in your home directory's +/rubygc+ directory. Make sure to replace +&lt;homedir&gt;+ with a full patch to your actual home directory.
+The following will install Ruby in your home directory's +/rubygc+ directory.
+Make sure to replace +&lt;homedir&gt;+ with a full patch to your actual home
+directory.
<shell>
$ ./configure --prefix=/<homedir>/rubygc
@@ -438,23 +501,22 @@ alias gcrails='~/rubygc/bin/rails'
Don't forget to use your aliases from now on.
-h6. Install RubyGems (1.8 only!)
-
-Download "RubyGems":http://rubyforge.org/projects/rubygems and install it from source. Rubygem's README file should have necessary installation instructions. Please note that this step isn't necessary if you've installed Ruby 1.9 and above.
-
h4. Using Ruby-Prof on MRI and REE
-Add Ruby-Prof to your applications' Gemfile if you want to benchmark/profile under MRI or REE:
+Add Ruby-Prof to your applications' Gemfile if you want to benchmark/profile
+under MRI or REE:
<ruby>
-gem 'ruby-prof', :git => 'git://github.com/wycats/ruby-prof.git'
+gem 'ruby-prof', git: 'git://github.com/wycats/ruby-prof.git'
</ruby>
Now run +bundle install+ and you're ready to go.
h3. Command Line Tools
-Writing performance test cases could be an overkill when you are looking for one time tests. Rails ships with two command line tools that enable quick and dirty performance testing:
+Writing performance test cases could be an overkill when you are looking for one
+time tests. Rails ships with two command line tools that enable quick and dirty
+performance testing:
h4. +benchmarker+
@@ -498,11 +560,14 @@ Example:
$ rails profiler 'Item.all' 'CouchItem.all' --runs 2 --metrics process_time --formats flat
</shell>
-NOTE: Metrics and formats vary from interpreter to interpreter. Pass +--help+ to each tool to see the defaults for your interpreter.
+NOTE: Metrics and formats vary from interpreter to interpreter. Pass +--help+ to
+each tool to see the defaults for your interpreter.
h3. Helper Methods
-Rails provides various helper methods inside Active Record, Action Controller and Action View to measure the time taken by a given piece of code. The method is called +benchmark()+ in all the three components.
+Rails provides various helper methods inside Active Record, Action Controller
+and Action View to measure the time taken by a given piece of code. The method
+is called +benchmark()+ in all the three components.
h4. Model
@@ -514,17 +579,19 @@ Project.benchmark("Creating project") do
end
</ruby>
-This benchmarks the code enclosed in the +Project.benchmark("Creating project") do...end+ block and prints the result to the log file:
+This benchmarks the code enclosed in the +Project.benchmark("Creating project") do...end+
+block and prints the result to the log file:
<ruby>
Creating project (185.3ms)
</ruby>
-Please refer to the "API docs":http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M001336 for additional options to +benchmark()+
+Please refer to the "API docs":http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.html#method-i-benchmark
+for additional options to +benchmark()+.
h4. Controller
-Similarly, you could use this helper method inside "controllers":http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.html
+Similarly, you could use this helper method inside "controllers.":http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.html
<ruby>
def process_projects
@@ -535,7 +602,7 @@ def process_projects
end
</ruby>
-NOTE: +benchmark+ is a class method inside controllers
+NOTE: +benchmark+ is a class method inside controllers.
h4. View
@@ -549,7 +616,8 @@ And in "views":http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.ht
h3. Request Logging
-Rails log files contain very useful information about the time taken to serve each request. Here's a typical log file entry:
+Rails log files contain very useful information about the time taken to serve
+each request. Here's a typical log file entry:
<shell>
Processing ItemsController#index (for 127.0.0.1 at 2009-01-08 03:06:39) [GET]
@@ -564,9 +632,14 @@ For this section, we're only interested in the last line:
Completed in 5ms (View: 2, DB: 0) | 200 OK [http://0.0.0.0/items]
</shell>
-This data is fairly straightforward to understand. Rails uses millisecond(ms) as the metric to measure the time taken. The complete request spent 5 ms inside Rails, out of which 2 ms were spent rendering views and none was spent communication with the database. It's safe to assume that the remaining 3 ms were spent inside the controller.
+This data is fairly straightforward to understand. Rails uses millisecond(ms) as
+the metric to measure the time taken. The complete request spent 5 ms inside
+Rails, out of which 2 ms were spent rendering views and none was spent
+communication with the database. It's safe to assume that the remaining 3 ms
+were spent inside the controller.
-Michael Koziarski has an "interesting blog post":http://www.therailsway.com/2009/1/6/requests-per-second explaining the importance of using milliseconds as the metric.
+Michael Koziarski has an "interesting blog post":http://www.therailsway.com/2009/1/6/requests-per-second
+explaining the importance of using milliseconds as the metric.
h3. Useful Links
@@ -587,11 +660,12 @@ h4. Generic Tools
h4. Tutorials and Documentation
* "ruby-prof API Documentation":http://ruby-prof.rubyforge.org
-* "Request Profiling Railscast":http://railscasts.com/episodes/98-request-profiling - Outdated, but useful for understanding call graphs
+* "Request Profiling Railscast":http://railscasts.com/episodes/98-request-profiling - Outdated, but useful for understanding call graphs.
h3. Commercial Products
-Rails has been lucky to have a few companies dedicated to Rails-specific performance tools. A couple of those are:
+Rails has been lucky to have a few companies dedicated to Rails-specific
+performance tools. A couple of those are:
* "New Relic":http://www.newrelic.com
-* "Scout":http://scoutapp.com
+* "Scout":http://scoutapp.com \ No newline at end of file
diff --git a/guides/source/security.textile b/guides/source/security.textile
index 8879122b66..49e5da6bb7 100644
--- a/guides/source/security.textile
+++ b/guides/source/security.textile
@@ -608,7 +608,7 @@ This URL passes the filter because the regular expression matches – the second
link_to "Homepage", @user.homepage
</ruby>
-The link looks innocent to visitors, but when it's clicked, it will execute the javascript function "exploit_code" or any other javascript the attacker provides.
+The link looks innocent to visitors, but when it's clicked, it will execute the JavaScript function "exploit_code" or any other JavaScript the attacker provides.
To fix the regular expression, \A and \z should be used instead of ^ and $, like so: