aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/aggregations.rb36
-rw-r--r--activerecord/lib/active_record/associations.rb140
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb2
-rw-r--r--activerecord/lib/active_record/associations/association.rb11
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb16
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb36
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb30
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb5
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb188
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb43
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb46
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb26
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb35
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb50
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb36
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb9
-rw-r--r--activerecord/lib/active_record/autosave_association.rb23
-rw-r--r--activerecord/lib/active_record/base.rb84
-rw-r--r--activerecord/lib/active_record/callbacks.rb36
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb67
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb59
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb65
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb44
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb47
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb14
-rw-r--r--activerecord/lib/active_record/connection_handling.rb17
-rw-r--r--activerecord/lib/active_record/core.rb144
-rw-r--r--activerecord/lib/active_record/counter_cache.rb8
-rw-r--r--activerecord/lib/active_record/errors.rb16
-rw-r--r--activerecord/lib/active_record/explain.rb8
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb (renamed from activerecord/lib/active_record/fixtures/file.rb)4
-rw-r--r--activerecord/lib/active_record/fixtures.rb59
-rw-r--r--activerecord/lib/active_record/inheritance.rb43
-rw-r--r--activerecord/lib/active_record/locale/en.yml6
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb8
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb6
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb10
-rw-r--r--activerecord/lib/active_record/migration.rb50
-rw-r--r--activerecord/lib/active_record/model.rb170
-rw-r--r--activerecord/lib/active_record/model_schema.rb49
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb84
-rw-r--r--activerecord/lib/active_record/null_relation.rb6
-rw-r--r--activerecord/lib/active_record/persistence.rb69
-rw-r--r--activerecord/lib/active_record/query_cache.rb12
-rw-r--r--activerecord/lib/active_record/querying.rb1
-rw-r--r--activerecord/lib/active_record/railtie.rb49
-rw-r--r--activerecord/lib/active_record/railties/controller_runtime.rb4
-rw-r--r--activerecord/lib/active_record/railties/databases.rake8
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb3
-rw-r--r--activerecord/lib/active_record/reflection.rb24
-rw-r--r--activerecord/lib/active_record/relation.rb65
-rw-r--r--activerecord/lib/active_record/relation/batches.rb13
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb33
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb24
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb10
-rw-r--r--activerecord/lib/active_record/relation/merger.rb22
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb10
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb96
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb4
-rw-r--r--activerecord/lib/active_record/result.rb15
-rw-r--r--activerecord/lib/active_record/sanitization.rb41
-rw-r--r--activerecord/lib/active_record/scoping/default.rb55
-rw-r--r--activerecord/lib/active_record/scoping/named.rb113
-rw-r--r--activerecord/lib/active_record/serialization.rb10
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb14
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb14
-rw-r--r--activerecord/lib/active_record/timestamp.rb25
-rw-r--r--activerecord/lib/active_record/transactions.rb33
-rw-r--r--activerecord/lib/active_record/validations.rb23
-rw-r--r--activerecord/lib/active_record/validations/associated.rb2
-rw-r--r--activerecord/lib/active_record/validations/presence.rb23
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb23
-rw-r--r--activerecord/lib/rails/generators/active_record.rb6
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb6
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record/observer/observer_generator.rb4
95 files changed, 1609 insertions, 1260 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 1675127ab0..45122539f1 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -43,7 +43,6 @@ module ActiveRecord
autoload :Integration
autoload :Migration
autoload :Migrator, 'active_record/migration'
- autoload :Model
autoload :ModelSchema
autoload :NestedAttributes
autoload :Observer
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 3db8e0716b..8101f7a45e 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -16,8 +16,8 @@ module ActiveRecord
# the database).
#
# class Customer < ActiveRecord::Base
- # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
- # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
+ # composed_of :balance, class_name: "Money", mapping: %w(balance amount)
+ # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
# end
#
# The customer class now has the following methods to manipulate the value objects:
@@ -138,15 +138,15 @@ module ActiveRecord
#
# class NetworkResource < ActiveRecord::Base
# composed_of :cidr,
- # :class_name => 'NetAddr::CIDR',
- # :mapping => [ %w(network_address network), %w(cidr_range bits) ],
- # :allow_nil => true,
- # :constructor => Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
- # :converter => Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
+ # class_name: 'NetAddr::CIDR',
+ # mapping: [ %w(network_address network), %w(cidr_range bits) ],
+ # allow_nil: true,
+ # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
+ # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
# end
#
# # This calls the :constructor
- # network_resource = NetworkResource.new(:network_address => '192.168.0.1', :cidr_range => 24)
+ # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24)
#
# # These assignments will both use the :converter
# network_resource.cidr = [ '192.168.2.1', 8 ]
@@ -165,7 +165,7 @@ module ActiveRecord
# by specifying an instance of the value object in the conditions hash. The following example
# finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
#
- # Customer.where(:balance => Money.new(20, "USD")).all
+ # Customer.where(balance: Money.new(20, "USD")).all
#
module ClassMethods
# Adds reader and writer methods for manipulating a value object:
@@ -197,17 +197,17 @@ module ActiveRecord
# can return nil to skip the assignment.
#
# Option examples:
- # composed_of :temperature, :mapping => %w(reading celsius)
- # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount),
- # :converter => Proc.new { |balance| balance.to_money }
- # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
+ # composed_of :temperature, mapping: %w(reading celsius)
+ # composed_of :balance, class_name: "Money", mapping: %w(balance amount),
+ # converter: Proc.new { |balance| balance.to_money }
+ # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
# composed_of :gps_location
- # composed_of :gps_location, :allow_nil => true
+ # composed_of :gps_location, allow_nil: true
# composed_of :ip_address,
- # :class_name => 'IPAddr',
- # :mapping => %w(ip to_i),
- # :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
- # :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
+ # class_name: 'IPAddr',
+ # mapping: %w(ip to_i),
+ # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
+ # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
#
def composed_of(part_id, options = {})
options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 60b7118d7e..651b17920c 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1,6 +1,9 @@
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/string/conversions'
require 'active_support/core_ext/module/remove_method'
+require 'active_support/dependencies/autoload'
+require 'active_support/concern'
+require 'active_record/errors'
module ActiveRecord
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
@@ -190,10 +193,10 @@ module ActiveRecord
# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
# * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
# * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
- # <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.all(options),</tt>
- # <tt>Project#milestones.build, Project#milestones.create</tt>
+ # <tt>Project#milestones.delete(milestone), Project#milestones.destroy(mileston), Project#milestones.find(milestone_id),</tt>
+ # <tt>Project#milestones.all(options), Project#milestones.build, Project#milestones.create</tt>
# * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
- # <tt>Project#categories.delete(category1)</tt>
+ # <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt>
#
# === A word of warning
#
@@ -236,6 +239,7 @@ module ActiveRecord
# others.clear | X | X | X
# others.delete(other,other,...) | X | X | X
# others.delete_all | X | X | X
+ # others.destroy(other,other,...) | X | X | X
# others.destroy_all | X | X | X
# others.find(*args) | X | X | X
# others.exists? | X | X | X
@@ -304,11 +308,11 @@ module ActiveRecord
# end
# class Programmer < ActiveRecord::Base
# has_many :assignments
- # has_many :projects, :through => :assignments
+ # has_many :projects, through: :assignments
# end
# class Project < ActiveRecord::Base
# has_many :assignments
- # has_many :programmers, :through => :assignments
+ # has_many :programmers, through: :assignments
# end
#
# For the second way, use +has_and_belongs_to_many+ in both models. This requires a join table
@@ -425,7 +429,7 @@ module ActiveRecord
# object from an association collection.
#
# class Project
- # has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
+ # has_and_belongs_to_many :developers, after_add: :evaluate_velocity
#
# def evaluate_velocity(developer)
# ...
@@ -436,7 +440,7 @@ module ActiveRecord
#
# class Project
# has_and_belongs_to_many :developers,
- # :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
+ # after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
# end
#
# Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
@@ -506,7 +510,7 @@ module ActiveRecord
#
# class Author < ActiveRecord::Base
# has_many :authorships
- # has_many :books, :through => :authorships
+ # has_many :books, through: :authorships
# end
#
# class Authorship < ActiveRecord::Base
@@ -522,7 +526,7 @@ module ActiveRecord
#
# class Firm < ActiveRecord::Base
# has_many :clients
- # has_many :invoices, :through => :clients
+ # has_many :invoices, through: :clients
# end
#
# class Client < ActiveRecord::Base
@@ -542,7 +546,7 @@ module ActiveRecord
#
# class Group < ActiveRecord::Base
# has_many :users
- # has_many :avatars, :through => :users
+ # has_many :avatars, through: :users
# end
#
# class User < ActiveRecord::Base
@@ -570,7 +574,7 @@ module ActiveRecord
# works correctly (where <tt>tags</tt> is a +has_many+ <tt>:through</tt> association):
#
# @post = Post.first
- # @tag = @post.tags.build :name => "ruby"
+ # @tag = @post.tags.build name: "ruby"
# @tag.save
#
# The last line ought to save the through record (a <tt>Taggable</tt>). This will only work if the
@@ -578,7 +582,7 @@ module ActiveRecord
#
# class Taggable < ActiveRecord::Base
# belongs_to :post
- # belongs_to :tag, :inverse_of => :taggings
+ # belongs_to :tag, inverse_of: :taggings
# end
#
# == Nested Associations
@@ -588,8 +592,8 @@ module ActiveRecord
#
# class Author < ActiveRecord::Base
# has_many :posts
- # has_many :comments, :through => :posts
- # has_many :commenters, :through => :comments
+ # has_many :comments, through: :posts
+ # has_many :commenters, through: :comments
# end
#
# class Post < ActiveRecord::Base
@@ -607,12 +611,12 @@ module ActiveRecord
#
# class Author < ActiveRecord::Base
# has_many :posts
- # has_many :commenters, :through => :posts
+ # has_many :commenters, through: :posts
# end
#
# class Post < ActiveRecord::Base
# has_many :comments
- # has_many :commenters, :through => :comments
+ # has_many :commenters, through: :comments
# end
#
# class Comment < ActiveRecord::Base
@@ -631,11 +635,11 @@ module ActiveRecord
# must adhere to.
#
# class Asset < ActiveRecord::Base
- # belongs_to :attachable, :polymorphic => true
+ # belongs_to :attachable, polymorphic: true
# end
#
# class Post < ActiveRecord::Base
- # has_many :assets, :as => :attachable # The :as option specifies the polymorphic interface to use.
+ # has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use.
# end
#
# @asset.attachable = @post
@@ -652,7 +656,7 @@ module ActiveRecord
# column in the posts table.
#
# class Asset < ActiveRecord::Base
- # belongs_to :attachable, :polymorphic => true
+ # belongs_to :attachable, polymorphic: true
#
# def attachable_type=(sType)
# super(sType.to_s.classify.constantize.base_class.to_s)
@@ -660,8 +664,8 @@ module ActiveRecord
# end
#
# class Post < ActiveRecord::Base
- # # because we store "Post" in attachable_type now :dependent => :destroy will work
- # has_many :assets, :as => :attachable, :dependent => :destroy
+ # # because we store "Post" in attachable_type now dependent: :destroy will work
+ # has_many :assets, as: :attachable, dependent: :destroy
# end
#
# class GuestPost < Post
@@ -723,7 +727,7 @@ module ActiveRecord
#
# To include a deep hierarchy of associations, use a hash:
#
- # Post.includes(:author, {:comments => {:author => :gravatar}}).each do |post|
+ # Post.includes(:author, {comments: {author: :gravatar}}).each do |post|
#
# That'll grab not only all the comments but all their authors and gravatar pictures.
# You can mix and match symbols, arrays and hashes in any combination to describe the
@@ -748,13 +752,13 @@ module ActiveRecord
# In the above example posts with no approved comments are not returned at all, because
# the conditions apply to the SQL statement as a whole and not just to the association.
# You must disambiguate column references for this fallback to happen, for example
- # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
+ # <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not.
#
# If you do want eager load only some members of an association it is usually more natural
# to include an association which has conditions defined on it:
#
# class Post < ActiveRecord::Base
- # has_many :approved_comments, -> { where approved: true }, :class_name => 'Comment'
+ # has_many :approved_comments, -> { where approved: true }, class_name: 'Comment'
# end
#
# Post.includes(:approved_comments)
@@ -766,7 +770,7 @@ module ActiveRecord
# returning all the associated objects:
#
# class Picture < ActiveRecord::Base
- # has_many :most_recent_comments, -> { order('id DESC').limit(10) }, :class_name => 'Comment'
+ # has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment'
# end
#
# Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
@@ -774,7 +778,7 @@ module ActiveRecord
# Eager loading is supported with polymorphic associations.
#
# class Address < ActiveRecord::Base
- # belongs_to :addressable, :polymorphic => true
+ # belongs_to :addressable, polymorphic: true
# end
#
# A call that tries to eager load the addressable model
@@ -808,10 +812,10 @@ module ActiveRecord
#
# TreeMixin.joins(:children)
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
- # TreeMixin.joins(:children => :parent)
+ # TreeMixin.joins(children: :parent)
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
# INNER JOIN parents_mixins ...
- # TreeMixin.joins(:children => {:parent => :children})
+ # TreeMixin.joins(children: {parent: :children})
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
# INNER JOIN parents_mixins ...
# INNER JOIN mixins childrens_mixins_2
@@ -820,10 +824,10 @@ module ActiveRecord
#
# Post.joins(:categories)
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
- # Post.joins(:categories => :posts)
+ # Post.joins(categories: :posts)
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
- # Post.joins(:categories => {:posts => :categories})
+ # Post.joins(categories: {posts: :categories})
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
# INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
@@ -867,7 +871,7 @@ module ActiveRecord
#
# module Billing
# class Account < ActiveRecord::Base
- # belongs_to :firm, :class_name => "MyApplication::Business::Firm"
+ # belongs_to :firm, class_name: "MyApplication::Business::Firm"
# end
# end
# end
@@ -909,16 +913,16 @@ module ActiveRecord
# example, if we changed our model definitions to:
#
# class Dungeon < ActiveRecord::Base
- # has_many :traps, :inverse_of => :dungeon
- # has_one :evil_wizard, :inverse_of => :dungeon
+ # has_many :traps, inverse_of: :dungeon
+ # has_one :evil_wizard, inverse_of: :dungeon
# end
#
# class Trap < ActiveRecord::Base
- # belongs_to :dungeon, :inverse_of => :traps
+ # belongs_to :dungeon, inverse_of: :traps
# end
#
# class EvilWizard < ActiveRecord::Base
- # belongs_to :dungeon, :inverse_of => :evil_wizard
+ # belongs_to :dungeon, inverse_of: :evil_wizard
# end
#
# Then, from our code snippet above, +d+ and <tt>t.dungeon</tt> are actually the same
@@ -941,7 +945,7 @@ module ActiveRecord
# For example:
#
# class Author
- # has_many :posts, :dependent => :destroy
+ # has_many :posts, dependent: :destroy
# end
# Author.find(1).destroy # => Will destroy all of the author's posts, too
#
@@ -950,6 +954,11 @@ module ActiveRecord
# specific association types. When no option is given, the behaviour is to do nothing
# with the associated records when destroying a record.
#
+ # Note that <tt>:dependent</tt> is implemented using Rails' callback
+ # system, which works by processing callbacks in order. Therefore, other
+ # callbacks declared either before or after the <tt>:dependent</tt> option
+ # can affect what it does.
+ #
# === Delete or destroy?
#
# +has_many+ and +has_and_belongs_to_many+ associations have the methods <tt>destroy</tt>,
@@ -1020,12 +1029,18 @@ module ActiveRecord
# parent object.
# [collection.delete(object, ...)]
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
- # Objects will be in addition destroyed if they're associated with <tt>:dependent => :destroy</tt>,
- # and deleted if they're associated with <tt>:dependent => :delete_all</tt>.
+ # Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>,
+ # and deleted if they're associated with <tt>dependent: :delete_all</tt>.
#
# If the <tt>:through</tt> option is used, then the join records are deleted (rather than
- # nullified) by default, but you can specify <tt>:dependent => :destroy</tt> or
- # <tt>:dependent => :nullify</tt> to override this.
+ # nullified) by default, but you can specify <tt>dependent: :destroy</tt> or
+ # <tt>dependent: :nullify</tt> to override this.
+ # [collection.destroy(object, ...)]
+ # Removes one or more objects from the collection by running <tt>destroy</tt> on
+ # each record, regardless of any dependent option, ensuring callbacks are run.
+ #
+ # If the <tt>:through</tt> option is used, then the join records are destroyed
+ # instead, not the objects themselves.
# [collection=objects]
# Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt>
# option is true callbacks in the join models are triggered except destroy callbacks, since deletion is
@@ -1037,8 +1052,8 @@ module ActiveRecord
# method loads the models and calls <tt>collection=</tt>. See above.
# [collection.clear]
# Removes every object from the collection. This destroys the associated objects if they
- # are associated with <tt>:dependent => :destroy</tt>, deletes them directly from the
- # database if <tt>:dependent => :delete_all</tt>, otherwise sets their foreign keys to +NULL+.
+ # are associated with <tt>dependent: :destroy</tt>, deletes them directly from the
+ # database if <tt>dependent: :delete_all</tt>, otherwise sets their foreign keys to +NULL+.
# If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models.
# Join models are directly deleted.
# [collection.empty?]
@@ -1066,17 +1081,18 @@ module ActiveRecord
# === Example
#
# Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
- # * <tt>Firm#clients</tt> (similar to <tt>Clients.all :conditions => ["firm_id = ?", id]</tt>)
+ # * <tt>Firm#clients</tt> (similar to <tt>Clients.all conditions: ["firm_id = ?", id]</tt>)
# * <tt>Firm#clients<<</tt>
# * <tt>Firm#clients.delete</tt>
+ # * <tt>Firm#clients.destroy</tt>
# * <tt>Firm#clients=</tt>
# * <tt>Firm#client_ids</tt>
# * <tt>Firm#client_ids=</tt>
# * <tt>Firm#clients.clear</tt>
# * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
# * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
- # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
- # * <tt>Firm#clients.exists?(:name => 'ACME')</tt> (similar to <tt>Client.exists?(:name => 'ACME', :firm_id => firm.id)</tt>)
+ # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, conditions: "firm_id = #{id}")</tt>)
+ # * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>)
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
# The declaration can also include an options hash to specialize the behavior of the association.
@@ -1095,7 +1111,10 @@ module ActiveRecord
# Specify the method that returns the primary key used for the association. By default this is +id+.
# [:dependent]
# Controls what happens to the associated objects when
- # their owner is destroyed:
+ # their owner is destroyed. Note that these are implemented as
+ # callbacks, and Rails executes callbacks in order. Therefore, other
+ # similar callbacks may affect the :dependent behavior, and the
+ # :dependent behavior may affect other callbacks.
#
# * <tt>:destroy</tt> causes all the associated objects to also be destroyed
# * <tt>:delete_all</tt> causes all the asssociated objects to be deleted directly from the database (so callbacks will not execute)
@@ -1106,6 +1125,9 @@ module ActiveRecord
# If using with the <tt>:through</tt> option, the association on the join model must be
# a +belongs_to+, and the records which get deleted are the join records, rather than
# the associated records.
+ # [:counter_cache]
+ # This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option,
+ # when you customized the name of your <tt>:counter_cache</tt> on the <tt>belongs_to</tt> association.
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# [:through]
@@ -1127,7 +1149,7 @@ module ActiveRecord
# [:source]
# Specifies the source association name used by <tt>has_many :through</tt> queries.
# Only use it if the name cannot be inferred from the association.
- # <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
+ # <tt>has_many :subscribers, through: :subscriptions</tt> will look for either <tt>:subscribers</tt> or
# <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given.
# [:source_type]
# Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
@@ -1191,7 +1213,7 @@ module ActiveRecord
# === Example
#
# An Account class declares <tt>has_one :beneficiary</tt>, which will add:
- # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.first(:conditions => "account_id = #{id}")</tt>)
+ # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.first(conditions: "account_id = #{id}")</tt>)
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
# * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
@@ -1231,7 +1253,7 @@ module ActiveRecord
# [:source]
# Specifies the source association name used by <tt>has_one :through</tt> queries.
# Only use it if the name cannot be inferred from the association.
- # <tt>has_one :favorite, :through => :favorites</tt> will look for a
+ # <tt>has_one :favorite, through: :favorites</tt> will look for a
# <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
# [:source_type]
# Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
@@ -1251,11 +1273,11 @@ module ActiveRecord
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
#
# Option examples:
- # has_one :credit_card, :dependent => :destroy # destroys the associated credit card
- # has_one :credit_card, :dependent => :nullify # updates the associated records foreign
+ # has_one :credit_card, dependent: :destroy # destroys the associated credit card
+ # has_one :credit_card, dependent: :nullify # updates the associated records foreign
# # key value to NULL rather than destroying it
- # has_one :last_comment, -> { order 'posted_on' }, :class_name => "Comment"
- # has_one :project_manager, -> { where role: 'project_manager' }, :class_name => "Person"
+ # has_one :last_comment, -> { order 'posted_on' }, class_name: "Comment"
+ # has_one :project_manager, -> { where role: 'project_manager' }, class_name: "Person"
# has_one :attachment, as: :attachable
# has_one :boss, readonly: :true
# has_one :club, through: :membership
@@ -1310,12 +1332,12 @@ module ActiveRecord
# Specify the foreign key used for the association. By default this is guessed to be the name
# of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt>
# association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
- # <tt>belongs_to :favorite_person, :class_name => "Person"</tt> will use a foreign key
+ # <tt>belongs_to :favorite_person, class_name: "Person"</tt> will use a foreign key
# of "favorite_person_id".
# [:foreign_type]
# Specify the column used to store the associated object's type, if this is a polymorphic
# association. By default this is guessed to be the name of the association with a "_type"
- # suffix. So a class that defines a <tt>belongs_to :taggable, :polymorphic => true</tt>
+ # suffix. So a class that defines a <tt>belongs_to :taggable, polymorphic: true</tt>
# association will use "taggable_type" as the default <tt>:foreign_type</tt>.
# [:primary_key]
# Specify the method that returns the primary key of associated object used for the association.
@@ -1335,7 +1357,7 @@ module ActiveRecord
# <tt>#{table_name}_count</tt> is created on the associate class (such that Post.comments_count will
# return the count cached, see note below). You can also specify a custom counter
# cache column by providing a column name instead of a +true+/+false+ value to this
- # option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
+ # option (e.g., <tt>counter_cache: :my_custom_counter</tt>.)
# Note: Specifying a counter cache will add it to that model's list of readonly attributes
# using +attr_readonly+.
# [:polymorphic]
@@ -1393,7 +1415,7 @@ module ActiveRecord
#
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration
# def change
- # create_table :developers_projects, :id => false do |t|
+ # create_table :developers_projects, id: false do |t|
# t.integer :developer_id
# t.integer :project_id
# end
@@ -1417,6 +1439,9 @@ module ActiveRecord
# [collection.delete(object, ...)]
# Removes one or more objects from the collection by removing their associations from the join table.
# This does not destroy the objects.
+ # [collection.destroy(object, ...)]
+ # Removes one or more objects from the collection by running destroy on each association in the join table, overriding any dependent option.
+ # This does not destroy the objects.
# [collection=objects]
# Replaces the collection's content by deleting and adding objects as appropriate.
# [collection_singular_ids]
@@ -1453,6 +1478,7 @@ module ActiveRecord
# * <tt>Developer#projects</tt>
# * <tt>Developer#projects<<</tt>
# * <tt>Developer#projects.delete</tt>
+ # * <tt>Developer#projects.destroy</tt>
# * <tt>Developer#projects=</tt>
# * <tt>Developer#project_ids</tt>
# * <tt>Developer#project_ids=</tt>
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 84540a7000..0c23029981 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -8,7 +8,7 @@ module ActiveRecord
attr_reader :aliases, :table_joins, :connection
# table_joins is an array of arel joins which might conflict with the aliases we assign here
- def initialize(connection = ActiveRecord::Model.connection, table_joins = [])
+ def initialize(connection = Base.connection, table_joins = [])
@aliases = Hash.new { |h,k| h[k] = initial_count_for(k) }
@table_joins = table_joins
@connection = connection
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 495f0cde59..99e7383d42 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -85,7 +85,7 @@ module ActiveRecord
end
def scoped
- ActiveSupport::Deprecation.warn("#scoped is deprecated. use #scope instead.")
+ ActiveSupport::Deprecation.warn "#scoped is deprecated. use #scope instead."
scope
end
@@ -154,11 +154,8 @@ module ActiveRecord
# We can't dump @reflection since it contains the scope proc
def marshal_dump
- reflection = @reflection
- @reflection = nil
-
- ivars = instance_variables.map { |name| [name, instance_variable_get(name)] }
- [reflection.name, ivars]
+ ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] }
+ [@reflection.name, ivars]
end
def marshal_load(data)
@@ -226,7 +223,7 @@ module ActiveRecord
end
# This should be implemented to return the values of the relevant key(s) on the owner,
- # so that when state_state is different from the value stored on the last find_target,
+ # so that when stale_state is different from the value stored on the last find_target,
# the target is stale.
#
# This is only relevant to certain associations, which is why it returns nil by default.
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index 1b382f7285..fcdfc1e150 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -1,5 +1,8 @@
+require 'active_record/associations'
+
module ActiveRecord::Associations::Builder
class CollectionAssociation < Association #:nodoc:
+
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
def valid_options
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index ab8225460a..0d1bdd21ee 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
end
def valid_options
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of]
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
end
def valid_dependent_options
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index fe3e5b00f7..862ff201de 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -364,6 +364,16 @@ module ActiveRecord
record
end
+ def scope(opts = {})
+ scope = super()
+ scope.none! if opts.fetch(:nullify, true) && null_scope?
+ scope
+ end
+
+ def null_scope?
+ owner.new_record? && !foreign_key_present?
+ end
+
private
def custom_counter_sql
@@ -412,7 +422,7 @@ module ActiveRecord
persisted.map! do |record|
if mem_record = memory.delete(record)
- (record.attribute_names - mem_record.changes.keys).each do |name|
+ ((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
mem_record[name] = record[name]
end
@@ -574,7 +584,9 @@ module ActiveRecord
args.shift if args.first.is_a?(Hash) && args.first.empty?
collection = fetch_first_or_last_using_find?(args) ? scope : load_target
- collection.send(type, *args).tap {|it| set_inverse_instance it }
+ collection.send(type, *args).tap do |record|
+ set_inverse_instance record if record.is_a? ActiveRecord::Base
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index c113957faa..e444b0ed83 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -28,10 +28,12 @@ module ActiveRecord
# is computed directly through SQL and does not trigger by itself the
# instantiation of the actual post records.
class CollectionProxy < Relation
+ delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
+
def initialize(association) #:nodoc:
@association = association
super association.klass, association.klass.arel_table
- merge! association.scope
+ merge! association.scope(nullify: false)
end
def target
@@ -42,11 +44,15 @@ module ActiveRecord
@association.load_target
end
+ # Returns +true+ if the association has been loaded, otherwise +false+.
+ #
+ # person.pets.loaded? # => false
+ # person.pets
+ # person.pets.loaded? # => true
def loaded?
@association.loaded?
end
- ##
# Works in two ways.
#
# *First:* Specify a subset of fields to be selected from the result set.
@@ -104,9 +110,8 @@ module ActiveRecord
@association.select(select, &block)
end
- ##
# Finds an object in the collection responding to the +id+. Uses the same
- # rules as +ActiveRecord::Base.find+. Returns +ActiveRecord::RecordNotFound++
+ # rules as <tt>ActiveRecord::Base.find</tt>. Returns <tt>ActiveRecord::RecordNotFound</tt>
# error if the object can not be found.
#
# class Person < ActiveRecord::Base
@@ -135,7 +140,6 @@ module ActiveRecord
@association.find(*args, &block)
end
- ##
# Returns the first record, or the first +n+ records, from the collection.
# If the collection is empty, the first form returns +nil+, and the second
# form returns an empty array.
@@ -166,7 +170,6 @@ module ActiveRecord
@association.first(*args)
end
- ##
# Returns the last record, or the last +n+ records, from the collection.
# If the collection is empty, the first form returns +nil+, and the second
# form returns an empty array.
@@ -197,7 +200,6 @@ module ActiveRecord
@association.last(*args)
end
- ##
# Returns a new object of the collection type that has been instantiated
# with +attributes+ and linked to this object, but have not yet been saved.
# You can pass an array of attributes hashes, this will return an array
@@ -226,7 +228,6 @@ module ActiveRecord
@association.build(attributes, &block)
end
- ##
# Returns a new object of the collection type that has been instantiated with
# attributes, linked to this object and that has already been saved (if it
# passes the validations).
@@ -257,7 +258,6 @@ module ActiveRecord
@association.create(attributes, &block)
end
- ##
# Like +create+, except that if the record is invalid, raises an exception.
#
# class Person
@@ -274,7 +274,6 @@ module ActiveRecord
@association.create!(attributes, &block)
end
- ##
# Add one or more records to the collection by setting their foreign keys
# to the association's primary key. Since << flattens its argument list and
# inserts each record, +push+ and +concat+ behave identically. Returns +self+
@@ -303,7 +302,6 @@ module ActiveRecord
@association.concat(*records)
end
- ##
# Replace this collection with +other_array+. This will perform a diff
# and delete/add only records that have changed.
#
@@ -330,7 +328,6 @@ module ActiveRecord
@association.replace(other_array)
end
- ##
# Deletes all the records from the collection. For +has_many+ associations,
# the deletion is done according to the strategy specified by the <tt>:dependent</tt>
# option. Returns an array with the deleted records.
@@ -423,7 +420,6 @@ module ActiveRecord
@association.delete_all
end
- ##
# Deletes the records of the collection directly from the database.
# This will _always_ remove the records ignoring the +:dependent+
# option.
@@ -450,7 +446,6 @@ module ActiveRecord
@association.destroy_all
end
- ##
# Deletes the +records+ supplied and removes them from the collection. For
# +has_many+ associations, the deletion is done according to the strategy
# specified by the <tt>:dependent</tt> option. Returns an array with the
@@ -514,7 +509,7 @@ module ActiveRecord
# Pet.find(1, 3)
# # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 3)
#
- # If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
+ # If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
# *without* calling their +destroy+ method.
#
# class Person < ActiveRecord::Base
@@ -569,7 +564,6 @@ module ActiveRecord
@association.delete(*records)
end
- ##
# Destroys the +records+ supplied and removes them from the collection.
# This method will _always_ remove record from the database ignoring
# the +:dependent+ option. Returns an array with the removed records.
@@ -642,7 +636,6 @@ module ActiveRecord
@association.destroy(*records)
end
- ##
# Specifies whether the records should be unique or not.
#
# class Person < ActiveRecord::Base
@@ -661,7 +654,6 @@ module ActiveRecord
@association.uniq
end
- ##
# Count all records using SQL.
#
# class Person < ActiveRecord::Base
@@ -679,7 +671,6 @@ module ActiveRecord
@association.count(column_name, options)
end
- ##
# Returns the size of the collection. If the collection hasn't been loaded,
# it executes a <tt>SELECT COUNT(*)</tt> query.
#
@@ -704,7 +695,6 @@ module ActiveRecord
@association.size
end
- ##
# Returns the size of the collection calling +size+ on the target.
# If the collection has been already loaded, +length+ and +size+ are
# equivalent.
@@ -728,7 +718,6 @@ module ActiveRecord
@association.length
end
- ##
# Returns +true+ if the collection is empty.
#
# class Person < ActiveRecord::Base
@@ -746,7 +735,6 @@ module ActiveRecord
@association.empty?
end
- ##
# Returns +true+ if the collection is not empty.
#
# class Person < ActiveRecord::Base
@@ -780,7 +768,6 @@ module ActiveRecord
@association.any?(&block)
end
- ##
# Returns true if the collection has more than one record.
# Equivalent to <tt>collection.size > 1</tt>.
#
@@ -819,7 +806,6 @@ module ActiveRecord
@association.many?(&block)
end
- ##
# Returns +true+ if the given object is present in the collection.
#
# class Person < ActiveRecord::Base
@@ -889,7 +875,7 @@ module ActiveRecord
end
# Returns a new array of objects from the collection. If the collection
- # hasn't been loaded, it fetches the records from the database.
+ # hasn't been loaded, it fetches the records from the database.
#
# class Person < ActiveRecord::Base
# has_many :pets
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 74864d271f..f59565ae77 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -76,7 +76,7 @@ module ActiveRecord
end
def cached_counter_attribute_name(reflection = reflection)
- "#{reflection.name}_count"
+ options[:counter_cache] || "#{reflection.name}_count"
end
def update_counter(difference, reflection = reflection)
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 06bead41de..ee816d2392 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -28,7 +28,7 @@ module ActiveRecord
# If target and record are nil, or target is equal to record,
# we don't need to have transaction.
if (target || record) && target != record
- reflection.klass.transaction do
+ transaction_if(save) do
remove_target!(options[:dependent]) if target && !target.destroyed?
if record
@@ -90,6 +90,14 @@ module ActiveRecord
def nullify_owner_attributes(record)
record[reflection.foreign_key] = nil
end
+
+ def transaction_if(value)
+ if value
+ reflection.klass.transaction { yield }
+ else
+ yield
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index ce5bf15f10..0848e7afb3 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -30,17 +30,21 @@ module ActiveRecord
# option references an association's column), it will fallback to the table
# join strategy.
class Preloader #:nodoc:
- autoload :Association, 'active_record/associations/preloader/association'
- autoload :SingularAssociation, 'active_record/associations/preloader/singular_association'
- autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
- autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
+ extend ActiveSupport::Autoload
- autoload :HasMany, 'active_record/associations/preloader/has_many'
- autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
- autoload :HasOne, 'active_record/associations/preloader/has_one'
- autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
- autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many'
- autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
+ eager_autoload do
+ autoload :Association, 'active_record/associations/preloader/association'
+ autoload :SingularAssociation, 'active_record/associations/preloader/singular_association'
+ autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
+ autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
+
+ autoload :HasMany, 'active_record/associations/preloader/has_many'
+ autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
+ autoload :HasOne, 'active_record/associations/preloader/has_one'
+ autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
+ autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many'
+ autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
+ end
attr_reader :records, :associations, :preload_scope, :model
@@ -68,7 +72,7 @@ module ActiveRecord
# books.
# - a Hash which specifies multiple association names, as well as
# association names for the to-be-preloaded association objects. For
- # example, specifying <tt>{ :author => :avatar }</tt> will preload a
+ # example, specifying <tt>{ author: :avatar }</tt> will preload a
# book's author, as well as that author's avatar.
#
# +:associations+ has the same format as the +:include+ option for
@@ -76,8 +80,8 @@ module ActiveRecord
#
# :books
# [ :books, :author ]
- # { :author => :avatar }
- # [ :books, { :author => :avatar } ]
+ # { author: :avatar }
+ # [ :books, { author: :avatar } ]
def initialize(records, associations, preload_scope = nil)
@records = Array.wrap(records).compact.uniq
@associations = Array.wrap(associations)
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 32f4557c28..10238555f0 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -12,7 +12,7 @@ module ActiveRecord
target
end
- # Implements the writer method, e.g. foo.items= for Foo.has_many :items
+ # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
def writer(record)
replace(record)
end
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index b9e014735b..43520142bf 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -28,7 +28,7 @@ module ActiveRecord
# methods which create and delete records on the association.
#
# We only support indirectly modifying through associations which has a belongs_to source.
- # This is the "has_many :tags, :through => :taggings" situation, where the join model
+ # This is the "has_many :tags, through: :taggings" situation, where the join model
# typically has a belongs_to on both side. In other words, associations which could also
# be represented as has_and_belongs_to_many associations.
#
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index af13b75a9d..6c5e2ac05d 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -57,9 +57,8 @@ module ActiveRecord
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
- # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
- # f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the
- # attribute will be set to +nil+.
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
+ # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
def assign_multiparameter_attributes(pairs)
execute_callstack_for_multiparameter_attributes(
extract_callstack_for_multiparameter_attributes(pairs)
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index ced15bc330..e0bfdb8f3e 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -2,7 +2,7 @@ require 'active_support/core_ext/enumerable'
module ActiveRecord
# = Active Record Attribute Methods
- module AttributeMethods #:nodoc:
+ module AttributeMethods
extend ActiveSupport::Concern
include ActiveModel::AttributeMethods
@@ -20,7 +20,7 @@ module ActiveRecord
module ClassMethods
# Generates all the attribute related methods for columns in the database
# accessors, mutators and query methods.
- def define_attribute_methods
+ def define_attribute_methods # :nodoc:
# Use a mutex; we don't want two thread simaltaneously trying to define
# attribute methods.
@attribute_methods_mutex.synchronize do
@@ -31,21 +31,35 @@ module ActiveRecord
end
end
- def attribute_methods_generated?
+ def attribute_methods_generated? # :nodoc:
@attribute_methods_generated ||= false
end
- def undefine_attribute_methods
+ def undefine_attribute_methods # :nodoc:
super if attribute_methods_generated?
@attribute_methods_generated = false
end
+ # Raises a <tt>ActiveRecord::DangerousAttributeError</tt> exception when an
+ # \Active \Record method is defined in the model, otherwise +false+.
+ #
+ # class Person < ActiveRecord::Base
+ # def save
+ # 'already defined by Active Record'
+ # end
+ # end
+ #
+ # Person.instance_method_already_implemented?(:save)
+ # # => ActiveRecord::DangerousAttributeError: save is defined by ActiveRecord
+ #
+ # Person.instance_method_already_implemented?(:name)
+ # # => false
def instance_method_already_implemented?(method_name)
if dangerous_attribute_method?(method_name)
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
end
- if [Base, Model].include?(active_record_super)
+ if superclass == Base
super
else
# If B < A and A defines its own attribute method, then we don't want to overwrite that.
@@ -56,11 +70,11 @@ module ActiveRecord
# A method name is 'dangerous' if it is already defined by Active Record, but
# not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
- def dangerous_attribute_method?(name)
+ def dangerous_attribute_method?(name) # :nodoc:
method_defined_within?(name, Base)
end
- def method_defined_within?(name, klass, sup = klass.superclass)
+ def method_defined_within?(name, klass, sup = klass.superclass) # :nodoc:
if klass.method_defined?(name) || klass.private_method_defined?(name)
if sup.method_defined?(name) || sup.private_method_defined?(name)
klass.instance_method(name).owner != sup.instance_method(name).owner
@@ -72,13 +86,27 @@ module ActiveRecord
end
end
+ # Returns +true+ if +attribute+ is an attribute method and table exists,
+ # +false+ otherwise.
+ #
+ # class Person < ActiveRecord::Base
+ # end
+ #
+ # Person.attribute_method?('name') # => true
+ # Person.attribute_method?(:age=) # => true
+ # Person.attribute_method?(:nothing) # => false
def attribute_method?(attribute)
super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
end
- # Returns an array of column names as strings if it's not
- # an abstract class and table exists.
- # Otherwise it returns an empty array.
+ # Returns an array of column names as strings if it's not an abstract class and
+ # table exists. Otherwise it returns an empty array.
+ #
+ # class Person < ActiveRecord::Base
+ # end
+ #
+ # Person.attribute_names
+ # # => ["id", "created_at", "updated_at", "name", "age"]
def attribute_names
@attribute_names ||= if !abstract_class? && table_exists?
column_names
@@ -90,7 +118,7 @@ module ActiveRecord
# If we haven't generated any methods yet, generate them, then
# see if we've created the method we're looking for.
- def method_missing(method, *args, &block)
+ def method_missing(method, *args, &block) # :nodoc:
unless self.class.attribute_methods_generated?
self.class.define_attribute_methods
@@ -104,7 +132,7 @@ module ActiveRecord
end
end
- def attribute_missing(match, *args, &block)
+ def attribute_missing(match, *args, &block) # :nodoc:
if self.class.columns_hash[match.attr_name]
ActiveSupport::Deprecation.warn(
"The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
@@ -118,22 +146,60 @@ module ActiveRecord
super
end
+ # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
+ # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
+ # which will all return +true+. It also define the attribute methods if they have
+ # not been generated.
+ #
+ # class Person < ActiveRecord::Base
+ # end
+ #
+ # person = Person.new
+ # person.respond_to(:name) # => true
+ # person.respond_to(:name=) # => true
+ # person.respond_to(:name?) # => true
+ # person.respond_to('age') # => true
+ # person.respond_to('age=') # => true
+ # person.respond_to('age?') # => true
+ # person.respond_to(:nothing) # => false
def respond_to?(name, include_private = false)
self.class.define_attribute_methods unless self.class.attribute_methods_generated?
super
end
- # Returns true if the given attribute is in the attributes hash
+ # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
+ #
+ # class Person < ActiveRecord::Base
+ # end
+ #
+ # person = Person.new
+ # person.has_attribute?(:name) # => true
+ # person.has_attribute?('age') # => true
+ # person.has_attribute?(:nothing) # => false
def has_attribute?(attr_name)
@attributes.has_key?(attr_name.to_s)
end
# Returns an array of names for the attributes available on this object.
+ #
+ # class Person < ActiveRecord::Base
+ # end
+ #
+ # person = Person.new
+ # person.attribute_names
+ # # => ["id", "created_at", "updated_at", "name", "age"]
def attribute_names
@attributes.keys
end
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
+ #
+ # class Person < ActiveRecord::Base
+ # end
+ #
+ # person = Person.create(name: 'Francesco', age: 22)
+ # person.attributes
+ # # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
def attributes
attribute_names.each_with_object({}) { |name, attrs|
attrs[name] = read_attribute(name)
@@ -146,13 +212,13 @@ module ActiveRecord
# <tt>:db</tt> format. Other attributes return the value of
# <tt>#inspect</tt> without modification.
#
- # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
+ # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
#
# person.attribute_for_inspect(:name)
- # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
+ # # => "\"David Heinemeier Hansson David Heinemeier Hansson D...\""
#
# person.attribute_for_inspect(:created_at)
- # # => '"2009-01-12 04:48:57"'
+ # # => "\"2012-10-22 00:15:07\""
def attribute_for_inspect(attr_name)
value = read_attribute(attr_name)
@@ -165,57 +231,103 @@ module ActiveRecord
end
end
- # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
- # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
+ # Returns +true+ if the specified +attribute+ has been set by the user or by a
+ # database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
+ # to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
+ # Note that it always returns +true+ with boolean attributes.
+ #
+ # class Task < ActiveRecord::Base
+ # end
+ #
+ # person = Task.new(title: '', is_done: false)
+ # person.attribute_present?(:title) # => false
+ # person.attribute_present?(:is_done) # => true
+ # person.name = 'Francesco'
+ # person.is_done = true
+ # person.attribute_present?(:title) # => true
+ # person.attribute_present?(:is_done) # => true
def attribute_present?(attribute)
value = read_attribute(attribute)
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
end
- # Returns the column object for the named attribute.
+ # Returns the column object for the named attribute. Returns +nil+ if the
+ # named attribute not exists.
+ #
+ # class Person < ActiveRecord::Base
+ # end
+ #
+ # person = Person.new
+ # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
+ # # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
+ #
+ # person.column_for_attribute(:nothing)
+ # # => nil
def column_for_attribute(name)
# FIXME: should this return a null object for columns that don't exist?
self.class.columns_hash[name.to_s]
end
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
- # (Alias for the protected read_attribute method).
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). It raises
+ # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
+ #
+ # Alias for the <tt>read_attribute</tt> method.
+ #
+ # class Person < ActiveRecord::Base
+ # belongs_to :organization
+ # end
+ #
+ # person = Person.new(name: 'Francesco', age: '22')
+ # person[:name] # => "Francesco"
+ # person[:age] # => 22
+ #
+ # person = Person.select('id').first
+ # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
+ # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
def [](attr_name)
- read_attribute(attr_name)
+ read_attribute(attr_name) { |n| missing_attribute(n, caller) }
end
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
- # (Alias for the protected write_attribute method).
+ # (Alias for the protected <tt>write_attribute</tt> method).
+ #
+ # class Person < ActiveRecord::Base
+ # end
+ #
+ # person = Person.new
+ # person[:age] = '22'
+ # person[:age] # => 22
+ # person[:age] # => Fixnum
def []=(attr_name, value)
write_attribute(attr_name, value)
end
protected
- def clone_attributes(reader_method = :read_attribute, attributes = {})
+ def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
attribute_names.each do |name|
attributes[name] = clone_attribute_value(reader_method, name)
end
attributes
end
- def clone_attribute_value(reader_method, attribute_name)
+ def clone_attribute_value(reader_method, attribute_name) # :nodoc:
value = send(reader_method, attribute_name)
value.duplicable? ? value.clone : value
rescue TypeError, NoMethodError
value
end
- def arel_attributes_with_values_for_create(pk_attribute_allowed)
- arel_attributes_with_values(attributes_for_create(pk_attribute_allowed))
+ def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
+ arel_attributes_with_values(attributes_for_create(attribute_names))
end
- def arel_attributes_with_values_for_update(attribute_names)
+ def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
arel_attributes_with_values(attributes_for_update(attribute_names))
end
- def attribute_method?(attr_name)
+ def attribute_method?(attr_name) # :nodoc:
defined?(@attributes) && @attributes.include?(attr_name)
end
@@ -242,9 +354,9 @@ module ActiveRecord
# Filters out the primary keys, from the attribute names, when the primary
# key is to be generated (e.g. the id attribute has no value).
- def attributes_for_create(pk_attribute_allowed)
- @attributes.keys.select do |name|
- column_for_attribute(name) && (pk_attribute_allowed || !pk_attribute?(name))
+ def attributes_for_create(attribute_names)
+ attribute_names.select do |name|
+ column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
end
end
@@ -257,14 +369,10 @@ module ActiveRecord
end
def typecasted_attribute_value(name)
- if self.class.serialized_attributes.include?(name)
- @attributes[name].serialized_value
- else
- # FIXME: we need @attributes to be used consistently.
- # If the values stored in @attributes were already typecasted, this code
- # could be simplified
- read_attribute(name)
- end
+ # FIXME: we need @attributes to be used consistently.
+ # If the values stored in @attributes were already typecasted, this code
+ # could be simplified
+ read_attribute(name)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index d4f529acbf..a23baeaced 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -1,5 +1,28 @@
module ActiveRecord
module AttributeMethods
+ # = Active Record Attribute Methods Before Type Cast
+ #
+ # <tt>ActiveRecord::AttributeMethods::BeforeTypeCast</tt> provides a way to
+ # read the value of the attributes before typecasting and deserialization.
+ #
+ # class Task < ActiveRecord::Base
+ # end
+ #
+ # task = Task.new(id: '1', completed_on: '2012-10-21')
+ # task.id # => 1
+ # task.completed_on # => Sun, 21 Oct 2012
+ #
+ # task.attributes_before_type_cast
+ # # => {"id"=>"1", "completed_on"=>"2012-10-21", ... }
+ # task.read_attribute_before_type_cast('id') # => "1"
+ # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
+ #
+ # In addition to #read_attribute_before_type_cast and #attributes_before_type_cast,
+ # it declares a method for all attributes with the <tt>*_before_type_cast</tt>
+ # suffix.
+ #
+ # task.id_before_type_cast # => "1"
+ # task.completed_on_before_type_cast # => "2012-10-21"
module BeforeTypeCast
extend ActiveSupport::Concern
@@ -7,11 +30,31 @@ module ActiveRecord
attribute_method_suffix "_before_type_cast"
end
+ # Returns the value of the attribute identified by +attr_name+ before
+ # typecasting and deserialization.
+ #
+ # class Task < ActiveRecord::Base
+ # end
+ #
+ # task = Task.new(id: '1', completed_on: '2012-10-21')
+ # task.read_attribute('id') # => 1
+ # task.read_attribute_before_type_cast('id') # => '1'
+ # task.read_attribute('completed_on') # => Sun, 21 Oct 2012
+ # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
def read_attribute_before_type_cast(attr_name)
@attributes[attr_name]
end
# Returns a hash of attributes before typecasting and deserialization.
+ #
+ # class Task < ActiveRecord::Base
+ # end
+ #
+ # task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21')
+ # task.attributes
+ # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil}
+ # task.attributes_before_type_cast
+ # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
def attributes_before_type_cast
@attributes
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 60e5b0e2bb..0333605eac 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,13 +1,9 @@
require 'active_support/core_ext/module/attribute_accessors'
+require 'active_support/deprecation'
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- mattr_accessor :partial_updates, instance_accessor: false
- self.partial_updates = true
- end
-
module AttributeMethods
- module Dirty
+ module Dirty # :nodoc:
extend ActiveSupport::Concern
include ActiveModel::Dirty
@@ -17,11 +13,23 @@ module ActiveRecord
raise "You cannot include Dirty after Timestamp"
end
- config_attribute :partial_updates
+ class_attribute :partial_writes, instance_writer: false
+ self.partial_writes = true
+
+ def self.partial_updates=(v); self.partial_writes = v; end
+ def self.partial_updates?; partial_writes?; end
+ def self.partial_updates; partial_writes; end
+
+ ActiveSupport::Deprecation.deprecate_methods(
+ singleton_class,
+ :partial_updates= => :partial_writes=,
+ :partial_updates? => :partial_writes?,
+ :partial_updates => :partial_writes
+ )
end
# Attempts to +save+ the record and clears changed attributes if successful.
- def save(*) #:nodoc:
+ def save(*)
if status = super
@previously_changed = changes
@changed_attributes.clear
@@ -30,7 +38,7 @@ module ActiveRecord
end
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
- def save!(*) #:nodoc:
+ def save!(*)
super.tap do
@previously_changed = changes
@changed_attributes.clear
@@ -38,7 +46,7 @@ module ActiveRecord
end
# <tt>reload</tt> the record and clears changed attributes.
- def reload(*) #:nodoc:
+ def reload(*)
super.tap do
@previously_changed.clear
@changed_attributes.clear
@@ -64,13 +72,17 @@ module ActiveRecord
end
def update(*)
- if partial_updates?
- # Serialized attributes should always be written in case they've been
- # changed in place.
- super(changed | (attributes.keys & self.class.serialized_attributes.keys))
- else
- super
- end
+ partial_writes? ? super(keys_for_partial_write) : super
+ end
+
+ def create(*)
+ partial_writes? ? super(keys_for_partial_write) : super
+ end
+
+ # Serialized attributes should always be written in case they've been
+ # changed in place.
+ def keys_for_partial_write
+ changed | (attributes.keys & self.class.serialized_attributes.keys)
end
def _field_changed?(attr, old, value)
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index aa6704d5c9..0857b02c8e 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -5,28 +5,29 @@ module ActiveRecord
module PrimaryKey
extend ActiveSupport::Concern
- # Returns this record's primary key value wrapped in an Array if one is available
+ # Returns this record's primary key value wrapped in an Array if one is
+ # available.
def to_key
key = self.id
[key] if key
end
- # Returns the primary key value
+ # Returns the primary key value.
def id
read_attribute(self.class.primary_key)
end
- # Sets the primary key value
+ # Sets the primary key value.
def id=(value)
write_attribute(self.class.primary_key, value) if self.class.primary_key
end
- # Queries the primary key value
+ # Queries the primary key value.
def id?
query_attribute(self.class.primary_key)
end
- # Returns the primary key value before type cast
+ # Returns the primary key value before type cast.
def id_before_type_cast
read_attribute_before_type_cast(self.class.primary_key)
end
@@ -52,14 +53,16 @@ module ActiveRecord
super && !ID_ATTRIBUTE_METHODS.include?(method_name)
end
- # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
- # primary_key_prefix_type setting, though.
+ # Defines the primary key field -- can be overridden in subclasses.
+ # Overwriting will negate any effect of the +primary_key_prefix_type+
+ # setting, though.
def primary_key
@primary_key = reset_primary_key unless defined? @primary_key
@primary_key
end
- # Returns a quoted version of the primary key name, used to construct SQL statements.
+ # Returns a quoted version of the primary key name, used to construct
+ # SQL statements.
def quoted_primary_key
@quoted_primary_key ||= connection.quote_column_name(primary_key)
end
@@ -92,16 +95,17 @@ module ActiveRecord
# Sets the name of the primary key column.
#
# class Project < ActiveRecord::Base
- # self.primary_key = "sysid"
+ # self.primary_key = 'sysid'
# end
#
- # You can also define the primary_key method yourself:
+ # You can also define the +primary_key+ method yourself:
#
# class Project < ActiveRecord::Base
# def self.primary_key
- # "foo_" + super
+ # 'foo_' + super
# end
# end
+ #
# Project.primary_key # => "foo_id"
def primary_key=(value)
@primary_key = value && value.to_s
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 1a4cb25dd7..90701938e5 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -1,8 +1,4 @@
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- mattr_accessor :attribute_types_cached_by_default, instance_accessor: false
- end
-
module AttributeMethods
module Read
extend ActiveSupport::Concern
@@ -10,13 +6,15 @@ module ActiveRecord
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
included do
- config_attribute :attribute_types_cached_by_default
+ class_attribute :attribute_types_cached_by_default, instance_writer: false
+ self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
end
module ClassMethods
- # +cache_attributes+ allows you to declare which converted attribute values should
- # be cached. Usually caching only pays off for attributes with expensive conversion
- # methods, like time related columns (e.g. +created_at+, +updated_at+).
+ # +cache_attributes+ allows you to declare which converted attribute
+ # values should be cached. Usually caching only pays off for attributes
+ # with expensive conversion methods, like time related columns (e.g.
+ # +created_at+, +updated_at+).
def cache_attributes(*attribute_names)
cached_attributes.merge attribute_names.map { |attr| attr.to_s }
end
@@ -45,7 +43,7 @@ module ActiveRecord
def define_method_attribute(attr_name)
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
def __temp__
- read_attribute(:'#{attr_name}') { |n| missing_attribute(n, caller) }
+ read_attribute('#{attr_name}') { |n| missing_attribute(n, caller) }
end
alias_method '#{attr_name}', :__temp__
undef_method :__temp__
@@ -63,22 +61,17 @@ module ActiveRecord
end
end
- ActiveRecord::Model.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
-
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after
+ # it has been typecast (for example, "2004-12-12" in a data column is cast
+ # to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name)
- return unless attr_name
- name_sym = attr_name.to_sym
-
# If it's cached, just return it
# We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/3552829.
- @attributes_cache[name_sym] || @attributes_cache.fetch(name_sym) {
- name = attr_name.to_s
-
+ name = attr_name.to_s
+ @attributes_cache[name] || @attributes_cache.fetch(name) {
column = @columns_hash.fetch(name) {
return @attributes.fetch(name) {
- if name_sym == :id && self.class.primary_key != name
+ if name == 'id' && self.class.primary_key != name
read_attribute(self.class.primary_key)
end
}
@@ -89,7 +82,7 @@ module ActiveRecord
}
if self.class.cache_attribute?(name)
- @attributes_cache[name_sym] = column.type_cast(value)
+ @attributes_cache[name] = column.type_cast(value)
else
column.type_cast value
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index bdda5bc009..47d4a938af 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -4,17 +4,19 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- # Returns a hash of all the attributes that have been specified for serialization as
- # keys and their class restriction as values.
+ # Returns a hash of all the attributes that have been specified for
+ # serialization as keys and their class restriction as values.
class_attribute :serialized_attributes, instance_accessor: false
self.serialized_attributes = {}
end
module ClassMethods
- # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
- # then specify the name of that attribute using this method and it will be handled automatically.
- # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
- # class on retrieval or SerializationTypeMismatch will be raised.
+ # If you have an attribute that needs to be saved to the database as an
+ # object, and retrieved as the same object, then specify the name of that
+ # attribute using this method and it will be handled automatically. The
+ # serialization is done through YAML. If +class_name+ is specified, the
+ # serialized object must be of that class on retrieval or
+ # <tt>SerializationTypeMismatch</tt> will be raised.
#
# ==== Parameters
#
@@ -22,7 +24,8 @@ module ActiveRecord
# * +class_name+ - Optional, class name that the object type should be equal to.
#
# ==== Example
- # # Serialize a preferences attribute
+ #
+ # # Serialize a preferences attribute.
# class User < ActiveRecord::Base
# serialize :preferences
# end
@@ -42,7 +45,8 @@ module ActiveRecord
end
def serialized_attributes
- ActiveSupport::Deprecation.warn("Instance level serialized_attributes method is deprecated, please use class level method.")
+ message = "Instance level serialized_attributes method is deprecated, please use class level method."
+ ActiveSupport::Deprecation.warn message
defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes
end
@@ -60,7 +64,7 @@ module ActiveRecord
end
end
- class Attribute < Struct.new(:coder, :value, :state)
+ class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
def unserialized_value
state == :serialized ? unserialize : value
end
@@ -98,16 +102,6 @@ module ActiveRecord
attributes
end
-
- private
-
- def attribute_cast_code(attr_name)
- if serialized_attributes.include?(attr_name)
- "v.unserialized_value"
- else
- super
- end
- end
end
def type_cast_attribute_for_write(column, value)
@@ -125,6 +119,24 @@ module ActiveRecord
super
end
end
+
+ def attributes_before_type_cast
+ super.dup.tap do |attributes|
+ self.class.serialized_attributes.each_key do |key|
+ if attributes.key?(key)
+ attributes[key] = attributes[key].unserialized_value
+ end
+ end
+ end
+ end
+
+ def typecasted_attribute_value(name)
+ if self.class.serialized_attributes.include?(name)
+ @attributes[name].serialized_value
+ else
+ super
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 9647d03be4..427c61079a 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -1,13 +1,4 @@
-
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- mattr_accessor :time_zone_aware_attributes, instance_accessor: false
- self.time_zone_aware_attributes = false
-
- mattr_accessor :skip_time_zone_conversion_for_attributes, instance_accessor: false
- self.skip_time_zone_conversion_for_attributes = []
- end
-
module AttributeMethods
module TimeZoneConversion
class Type # :nodoc:
@@ -28,27 +19,15 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :time_zone_aware_attributes, global: true
- config_attribute :skip_time_zone_conversion_for_attributes
+ mattr_accessor :time_zone_aware_attributes, instance_writer: false
+ self.time_zone_aware_attributes = false
+
+ class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
+ self.skip_time_zone_conversion_for_attributes = []
end
module ClassMethods
protected
- # The enhanced read method automatically converts the UTC time stored in the database to the time
- # zone stored in Time.zone.
- def attribute_cast_code(attr_name)
- column = columns_hash[attr_name]
-
- if create_time_zone_conversion_attribute?(attr_name, column)
- typecast = "v = #{super}"
- time_zone_conversion = "v.acts_like?(:time) ? v.in_time_zone : v"
-
- "((#{typecast}) && (#{time_zone_conversion}))"
- else
- super
- end
- end
-
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
# This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
def define_method_attribute=(attr_name)
@@ -65,7 +44,7 @@ module ActiveRecord
if (rounded_value != rounded_time) || (!rounded_value && original_time)
write_attribute("#{attr_name}", original_time)
#{attr_name}_will_change!
- @attributes_cache[:"#{attr_name}"] = zoned_time
+ @attributes_cache["#{attr_name}"] = zoned_time
end
end
EOV
@@ -85,8 +64,7 @@ module ActiveRecord
private
def round_usec(value)
- return unless value
- value.change(:usec => 0)
+ value.change(usec: 0) if value
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 5a39cb0125..fa9097db1f 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -20,18 +20,19 @@ module ActiveRecord
end
end
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings
- # for fixnum and float columns are turned into +nil+.
+ # Updates the attribute identified by <tt>attr_name</tt> with the
+ # specified +value+. Empty strings for fixnum and float columns are
+ # turned into +nil+.
def write_attribute(attr_name, value)
attr_name = attr_name.to_s
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
- @attributes_cache.delete(attr_name.to_sym)
+ @attributes_cache.delete(attr_name)
column = column_for_attribute(attr_name)
# If we're dealing with a binary column, write the data to the cache
# so we don't attempt to typecast multiple times.
if column && column.binary?
- @attributes_cache[attr_name.to_sym] = value
+ @attributes_cache[attr_name] = value
end
if column || @attributes.has_key?(attr_name)
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 290f57659d..907fe70522 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -16,7 +16,7 @@ module ActiveRecord
# Note that it also means that associations marked for destruction won't
# be destroyed directly. They will however still be marked for destruction.
#
- # Note that <tt>:autosave => false</tt> is not same as not declaring <tt>:autosave</tt>.
+ # Note that <tt>autosave: false</tt> is not same as not declaring <tt>:autosave</tt>.
# When the <tt>:autosave</tt> option is not present new associations are saved.
#
# == Validation
@@ -37,7 +37,7 @@ module ActiveRecord
# === One-to-one Example
#
# class Post
- # has_one :author, :autosave => true
+ # has_one :author, autosave: true
# end
#
# Saving changes to the parent and its associated model can now be performed
@@ -81,27 +81,27 @@ module ActiveRecord
# has_many :comments # :autosave option is not declared
# end
#
- # post = Post.new(:title => 'ruby rocks')
- # post.comments.build(:body => 'hello world')
+ # post = Post.new(title: 'ruby rocks')
+ # post.comments.build(body: 'hello world')
# post.save # => saves both post and comment
#
- # post = Post.create(:title => 'ruby rocks')
- # post.comments.build(:body => 'hello world')
+ # post = Post.create(title: 'ruby rocks')
+ # post.comments.build(body: 'hello world')
# post.save # => saves both post and comment
#
- # post = Post.create(:title => 'ruby rocks')
- # post.comments.create(:body => 'hello world')
+ # post = Post.create(title: 'ruby rocks')
+ # post.comments.create(body: 'hello world')
# post.save # => saves both post and comment
#
# When <tt>:autosave</tt> is true all children are saved, no matter whether they
# are new records or not:
#
# class Post
- # has_many :comments, :autosave => true
+ # has_many :comments, autosave: true
# end
#
- # post = Post.create(:title => 'ruby rocks')
- # post.comments.create(:body => 'hello world')
+ # post = Post.create(title: 'ruby rocks')
+ # post.comments.create(body: 'hello world')
# post.comments[0].body = 'hi everyone'
# post.save # => saves both post and comment, with 'hi everyone' as body
#
@@ -394,6 +394,7 @@ module ActiveRecord
autosave = reflection.options[:autosave]
if autosave && record.marked_for_destruction?
+ self[reflection.foreign_key] = nil
record.destroy
elsif autosave != false
saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index a4705b24ca..5eacb8f143 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -8,7 +8,6 @@ require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/deep_merge'
-require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/kernel/singleton_class'
@@ -36,7 +35,7 @@ module ActiveRecord #:nodoc:
# method is especially useful when you're receiving the data from somewhere else, like an
# HTTP request. It works like this:
#
- # user = User.new(:name => "David", :occupation => "Code Artist")
+ # user = User.new(name: "David", occupation: "Code Artist")
# user.name # => "David"
#
# You can also use block initialization:
@@ -69,7 +68,7 @@ module ActiveRecord #:nodoc:
# end
#
# def self.authenticate_safely_simply(user_name, password)
- # where(:user_name => user_name, :password => password).first
+ # where(user_name: user_name, password: password).first
# end
# end
#
@@ -87,27 +86,27 @@ module ActiveRecord #:nodoc:
#
# Company.where(
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
- # { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
+ # { id: 3, name: "37signals", division: "First", accounting_date: '2005-01-01' }
# ).first
#
# Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
# operator. For instance:
#
- # Student.where(:first_name => "Harvey", :status => 1)
+ # Student.where(first_name: "Harvey", status: 1)
# Student.where(params[:student])
#
# A range may be used in the hash to use the SQL BETWEEN operator:
#
- # Student.where(:grade => 9..12)
+ # Student.where(grade: 9..12)
#
# An array may be used in the hash to use the SQL IN operator:
#
- # Student.where(:grade => [9,11,12])
+ # Student.where(grade: [9,11,12])
#
# When joining tables, nested hashes or keys written in the form 'table_name.column_name'
# can be used to qualify the table name of a particular condition. For instance:
#
- # Student.joins(:schools).where(:schools => { :category => 'public' })
+ # Student.joins(:schools).where(schools: { category: 'public' })
# Student.joins(:schools).where('schools.category' => 'public' )
#
# == Overwriting default accessors
@@ -141,10 +140,10 @@ module ActiveRecord #:nodoc:
# For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
# to determine whether the user has a name:
#
- # user = User.new(:name => "David")
+ # user = User.new(name: "David")
# user.name? # => true
#
- # anonymous = User.new(:name => "")
+ # anonymous = User.new(name: "")
# anonymous.name? # => false
#
# == Accessing attributes before they have been typecasted
@@ -165,8 +164,8 @@ module ActiveRecord #:nodoc:
# to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt> and thus produces finders
# like <tt>Person.find_by_user_name</tt>, <tt>Person.find_all_by_last_name</tt>, and
# <tt>Payment.find_by_transaction_id</tt>. Instead of writing
- # <tt>Person.where(:user_name => user_name).first</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
- # And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do
+ # <tt>Person.where(user_name: user_name).first</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
+ # And instead of writing <tt>Person.where(last_name: last_name).all</tt>, you just do
# <tt>Person.find_all_by_last_name(last_name)</tt>.
#
# It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
@@ -175,7 +174,7 @@ module ActiveRecord #:nodoc:
#
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
#
- # Person.where(:user_name => user_name, :password => password).first
+ # Person.where(user_name: user_name, password: password).first
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
#
# It's even possible to call these dynamic finder methods on relations and named scopes.
@@ -189,13 +188,13 @@ module ActiveRecord #:nodoc:
# unless they are given in a block.
#
# # No 'Summer' tag exists
- # Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
+ # Tag.find_or_create_by_name("Summer") # equal to Tag.create(name: "Summer")
#
# # Now the 'Summer' tag does exist
# Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
#
# # Now 'Bob' exist and is an 'admin'
- # User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
+ # User.find_or_create_by_name('Bob', age: 40) { |u| u.admin = true }
#
# Adding an exclamation point (!) on to the end of <tt>find_or_create_by_</tt> will
# raise an <tt>ActiveRecord::RecordInvalid</tt> error if the new record is invalid.
@@ -210,7 +209,7 @@ module ActiveRecord #:nodoc:
# To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of
# a list of parameters.
#
- # Tag.find_or_create_by_name(:name => "rails", :creator => current_user)
+ # Tag.find_or_create_by_name(name: "rails", creator: current_user)
#
# That will either find an existing tag named "rails", or create a new one while setting the
# user that created it.
@@ -232,7 +231,7 @@ module ActiveRecord #:nodoc:
# serialize :preferences
# end
#
- # user = User.create(:preferences => { "background" => "black", "display" => large })
+ # user = User.create(preferences: { "background" => "black", "display" => large })
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
#
# You can also specify a class option as the second parameter that'll raise an exception
@@ -242,7 +241,7 @@ module ActiveRecord #:nodoc:
# serialize :preferences, Hash
# end
#
- # user = User.create(:preferences => %w( one two three ))
+ # user = User.create(preferences: %w( one two three ))
# User.find(user.id).preferences # raises SerializationTypeMismatch
#
# When you specify a class option, the default value for that attribute will be a new
@@ -267,9 +266,9 @@ module ActiveRecord #:nodoc:
# class Client < Company; end
# class PriorityClient < Client; end
#
- # When you do <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in
+ # When you do <tt>Firm.create(name: "37signals")</tt>, this record will be saved in
# the companies table with type = "Firm". You can then fetch this row again using
- # <tt>Company.where(:name => '37signals').first</tt> and it will return a Firm object.
+ # <tt>Company.where(name: '37signals').first</tt> and it will return a Firm object.
#
# If you don't have a type column defined in your table, single-table inheritance won't
# be triggered. In that case, it'll work just like normal subclasses with no special magic
@@ -321,8 +320,47 @@ module ActiveRecord #:nodoc:
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
# instances in the current object space.
class Base
- include ActiveRecord::Model
+ extend ActiveModel::Observing::ClassMethods
+ extend ActiveModel::Naming
+
+ extend ActiveSupport::Benchmarkable
+ extend ActiveSupport::DescendantsTracker
+
+ extend ConnectionHandling
+ extend QueryCache::ClassMethods
+ extend Querying
+ extend Translation
+ extend DynamicMatchers
+ extend Explain
+
+ include Persistence
+ include ReadonlyAttributes
+ include ModelSchema
+ include Inheritance
+ include Scoping
+ include Sanitization
+ include AttributeAssignment
+ include ActiveModel::Conversion
+ include Integration
+ include Validations
+ include CounterCache
+ include Locking::Optimistic
+ include Locking::Pessimistic
+ include AttributeMethods
+ include Callbacks
+ include ActiveModel::Observing
+ include Timestamp
+ include Associations
+ include ActiveModel::SecurePassword
+ include AutosaveAssociation
+ include NestedAttributes
+ include Aggregations
+ include Transactions
+ include Reflection
+ include Serialization
+ include Store
+ include Core
end
-end
-ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Model::DeprecationProxy.new)
+ ActiveSupport.run_load_hooks(:active_record, Base)
+end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 111208d0b9..1c9c627090 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -35,7 +35,7 @@ module ActiveRecord
# class CreditCard < ActiveRecord::Base
# # Strip everything but digits, so the user can specify "555 234 34" or
# # "5552-3434" and both will mean "55523434"
- # before_validation(:on => :create) do
+ # before_validation(on: :create) do
# self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
# end
# end
@@ -200,6 +200,40 @@ module ActiveRecord
# Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
# methods on the model, which are called last.
#
+ # == Ordering callbacks
+ #
+ # Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+
+ # callback (+log_children+ in this case) should be executed before the children get destroyed by the +dependent: destroy+ option.
+ #
+ # Let's look at the code below:
+ #
+ # class Topic < ActiveRecord::Base
+ # has_many :children, dependent: destroy
+ #
+ # before_destroy :log_children
+ #
+ # private
+ # def log_children
+ # # Child processing
+ # end
+ # end
+ #
+ # In this case, the problem is that when the +before_destroy+ callback is executed, the children are not available
+ # because the +destroy+ callback gets executed first. You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
+ #
+ # class Topic < ActiveRecord::Base
+ # has_many :children, dependent: destroy
+ #
+ # before_destroy :log_children, prepend: true
+ #
+ # private
+ # def log_children
+ # # Child processing
+ # end
+ # end
+ #
+ # This way, the +before_destroy+ gets executed before the <tt>dependent: destroy</tt> is called, and the data is still available.
+ #
# == Transactions
#
# The entire callback chain of a +save+, <tt>save!</tt>, or +destroy+ call runs
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index f17e7158de..f6cdc67b4d 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -1,9 +1,8 @@
require 'yaml'
module ActiveRecord
- # :stopdoc:
- module Coders
- class YAMLColumn
+ module Coders # :nodoc:
+ class YAMLColumn # :nodoc:
RESCUE_ERRORS = [ ArgumentError, Psych::SyntaxError ]
attr_accessor :object_class
@@ -41,5 +40,4 @@ module ActiveRecord
end
end
end
- # :startdoc
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index f42a5df75f..db0db272a6 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1,12 +1,12 @@
require 'thread'
require 'monitor'
require 'set'
-require 'active_support/core_ext/module/deprecation'
+require 'active_support/deprecation'
module ActiveRecord
# Raised when a connection could not be obtained within the connection
# acquisition timeout period: because max connections in pool
- # are in use.
+ # are in use.
class ConnectionTimeoutError < ConnectionNotEstablished
end
@@ -417,7 +417,7 @@ module ActiveRecord
# queue for a connection to become available.
#
# Raises:
- # - ConnectionTimeoutError if a connection could not be acquired
+ # - ConnectionTimeoutError if a connection could not be acquired
def acquire_connection
if conn = @available.poll
conn
@@ -441,11 +441,11 @@ module ActiveRecord
end
def new_connection
- ActiveRecord::Model.send(spec.adapter_method, spec.config)
+ Base.send(spec.adapter_method, spec.config)
end
def current_connection_id #:nodoc:
- ActiveRecord::Model.connection_id ||= Thread.current.object_id
+ Base.connection_id ||= Thread.current.object_id
end
def checkout_new_connection
@@ -494,10 +494,18 @@ module ActiveRecord
@class_to_pool = Hash.new { |h,k| h[k] = {} }
end
- def connection_pools
+ def connection_pool_list
owner_to_pool.values.compact
end
+ def connection_pools
+ ActiveSupport::Deprecation.warn(
+ "In the next release, this will return the same as #connection_pool_list. " \
+ "(An array of pools, rather than a hash mapping specs to pools.)"
+ )
+ Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
+ end
+
def establish_connection(owner, spec)
@class_to_pool.clear
owner_to_pool[owner] = ConnectionAdapters::ConnectionPool.new(spec)
@@ -506,23 +514,23 @@ module ActiveRecord
# Returns true if there are any active connections among the connection
# pools that the ConnectionHandler is managing.
def active_connections?
- connection_pools.any?(&:active_connection?)
+ connection_pool_list.any?(&:active_connection?)
end
# Returns any connections in use by the current thread back to the pool,
# and also returns connections to the pool cached by threads that are no
# longer alive.
def clear_active_connections!
- connection_pools.each(&:release_connection)
+ connection_pool_list.each(&:release_connection)
end
# Clears the cache which maps classes.
def clear_reloadable_connections!
- connection_pools.each(&:clear_reloadable_connections!)
+ connection_pool_list.each(&:clear_reloadable_connections!)
end
def clear_all_connections!
- connection_pools.each(&:disconnect!)
+ connection_pool_list.each(&:disconnect!)
end
# Locate the connection of the nearest super class. This can be an
@@ -567,10 +575,10 @@ module ActiveRecord
class_to_pool[klass] ||= begin
until pool = pool_for(klass)
klass = klass.superclass
- break unless klass < Model::Tag
+ break unless klass <= Base
end
- class_to_pool[klass] = pool || pool_for(ActiveRecord::Model)
+ class_to_pool[klass] = pool
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 32e3c7f5d8..4f3eebce7d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -150,15 +150,52 @@ module ActiveRecord
# already-automatically-released savepoints:
#
# Model.connection.transaction do # BEGIN
- # Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
+ # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
# Model.connection.create_table(...)
# # active_record_1 now automatically released
# end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
# end
+ #
+ # == Transaction isolation
+ #
+ # If your database supports setting the isolation level for a transaction, you can set
+ # it like so:
+ #
+ # Post.transaction(isolation: :serializable) do
+ # # ...
+ # end
+ #
+ # Valid isolation levels are:
+ #
+ # * <tt>:read_uncommitted</tt>
+ # * <tt>:read_committed</tt>
+ # * <tt>:repeatable_read</tt>
+ # * <tt>:serializable</tt>
+ #
+ # You should consult the documentation for your database to understand the
+ # semantics of these different levels:
+ #
+ # * http://www.postgresql.org/docs/9.1/static/transaction-iso.html
+ # * https://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
+ #
+ # An <tt>ActiveRecord::TransactionIsolationError</tt> will be raised if:
+ #
+ # * The adapter does not support setting the isolation level
+ # * You are joining an existing open transaction
+ # * You are creating a nested (savepoint) transaction
+ #
+ # The mysql, mysql2 and postgresql adapters support setting the transaction
+ # isolation level. However, support is disabled for mysql versions below 5,
+ # because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
+ # which means the isolation level gets persisted outside the transaction.
def transaction(options = {})
- options.assert_valid_keys :requires_new, :joinable
+ options.assert_valid_keys :requires_new, :joinable, :isolation
if !options[:requires_new] && current_transaction.joinable?
+ if options[:isolation]
+ raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
+ end
+
yield
else
within_new_transaction(options) { yield }
@@ -168,10 +205,10 @@ module ActiveRecord
end
def within_new_transaction(options = {}) #:nodoc:
- begin_transaction(options)
+ transaction = begin_transaction(options)
yield
rescue Exception => error
- rollback_transaction
+ rollback_transaction if transaction
raise
ensure
begin
@@ -191,9 +228,7 @@ module ActiveRecord
end
def begin_transaction(options = {}) #:nodoc:
- @transaction = @transaction.begin
- @transaction.joinable = options.fetch(:joinable, true)
- @transaction
+ @transaction = @transaction.begin(options)
end
def commit_transaction #:nodoc:
@@ -217,6 +252,22 @@ module ActiveRecord
# Begins the transaction (and turns off auto-committing).
def begin_db_transaction() end
+ def transaction_isolation_levels
+ {
+ read_uncommitted: "READ UNCOMMITTED",
+ read_committed: "READ COMMITTED",
+ repeatable_read: "REPEATABLE READ",
+ serializable: "SERIALIZABLE"
+ }
+ end
+
+ # Begins the transaction with the isolation level set. Raises an error by
+ # default; adapters that support setting the isolation level should implement
+ # this method.
+ def begin_isolated_db_transaction(isolation)
+ raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
+ end
+
# Commits the transaction (and turns on auto-committing).
def commit_db_transaction() end
@@ -248,7 +299,7 @@ module ActiveRecord
end
def empty_insert_statement_value
- "VALUES(DEFAULT)"
+ "DEFAULT VALUES"
end
def case_sensitive_equality_operator
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index dca355aa93..7ec6abbc45 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -161,21 +161,21 @@ module ActiveRecord
# td.column(:granted, :boolean)
# # granted BOOLEAN
#
- # td.column(:picture, :binary, :limit => 2.megabytes)
+ # td.column(:picture, :binary, limit: 2.megabytes)
# # => picture BLOB(2097152)
#
- # td.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false)
+ # td.column(:sales_stage, :string, limit: 20, default: 'new', null: false)
# # => sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
#
- # td.column(:bill_gates_money, :decimal, :precision => 15, :scale => 2)
+ # td.column(:bill_gates_money, :decimal, precision: 15, scale: 2)
# # => bill_gates_money DECIMAL(15,2)
#
- # td.column(:sensor_reading, :decimal, :precision => 30, :scale => 20)
+ # td.column(:sensor_reading, :decimal, precision: 30, scale: 20)
# # => sensor_reading DECIMAL(30,20)
#
# # While <tt>:scale</tt> defaults to zero on most databases, it
# # probably wouldn't hurt to include it.
- # td.column(:huge_integer, :decimal, :precision => 30)
+ # td.column(:huge_integer, :decimal, precision: 30)
# # => huge_integer DECIMAL(30)
#
# # Defines a column with a database-specific type.
@@ -190,11 +190,11 @@ module ActiveRecord
#
# What can be written like this with the regular calls to column:
#
- # create_table "products", :force => true do |t|
+ # create_table "products", force: true do |t|
# t.column "shop_id", :integer
# t.column "creator_id", :integer
- # t.column "name", :string, :default => "Untitled"
- # t.column "value", :string, :default => "Untitled"
+ # t.column "name", :string, default: "Untitled"
+ # t.column "value", :string, default: "Untitled"
# t.column "created_at", :datetime
# t.column "updated_at", :datetime
# end
@@ -203,7 +203,7 @@ module ActiveRecord
#
# create_table :products do |t|
# t.integer :shop_id, :creator_id
- # t.string :name, :value, :default => "Untitled"
+ # t.string :name, :value, default: "Untitled"
# t.timestamps
# end
#
@@ -218,22 +218,26 @@ module ActiveRecord
# create_table :taggings do |t|
# t.integer :tag_id, :tagger_id, :taggable_id
# t.string :tagger_type
- # t.string :taggable_type, :default => 'Photo'
+ # t.string :taggable_type, default: 'Photo'
# end
- # add_index :taggings, :tag_id, :name => 'index_taggings_on_tag_id'
+ # add_index :taggings, :tag_id, name: 'index_taggings_on_tag_id'
# add_index :taggings, [:tagger_id, :tagger_type]
#
# Can also be written as follows using references:
#
# create_table :taggings do |t|
- # t.references :tag, :index => { :name => 'index_taggings_on_tag_id' }
- # t.references :tagger, :polymorphic => true, :index => true
- # t.references :taggable, :polymorphic => { :default => 'Photo' }
+ # t.references :tag, index: { name: 'index_taggings_on_tag_id' }
+ # t.references :tagger, polymorphic: true, index: true
+ # t.references :taggable, polymorphic: { default: 'Photo' }
# end
def column(name, type, options = {})
name = name.to_s
type = type.to_sym
+ if primary_key_column_name == name
+ raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
+ end
+
column = self[name] || new_column_definition(@base, name, type)
limit = options.fetch(:limit) do
@@ -262,7 +266,7 @@ module ActiveRecord
# Adds index options to the indexes hash, keyed by column name
# This is primarily used to track indexes that need to be created after the table
#
- # index(:account_id, :name => 'index_projects_on_account_id')
+ # index(:account_id, name: 'index_projects_on_account_id')
def index(column_name, options = {})
indexes[column_name] = options
end
@@ -302,6 +306,11 @@ module ActiveRecord
definition
end
+ def primary_key_column_name
+ primary_key_column = columns.detect { |c| c.type == :primary_key }
+ primary_key_column && primary_key_column.name
+ end
+
def native
@base.native_database_types
end
@@ -315,6 +324,7 @@ module ActiveRecord
# change_table :table do |t|
# t.column
# t.index
+ # t.rename_index
# t.timestamps
# t.change
# t.change_default
@@ -365,9 +375,9 @@ module ActiveRecord
# ====== Creating a simple index
# t.index(:name)
# ====== Creating a unique index
- # t.index([:branch_id, :party_id], :unique => true)
+ # t.index([:branch_id, :party_id], unique: true)
# ====== Creating a named index
- # t.index([:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
+ # t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
def index(column_name, options = {})
@base.add_index(@table_name, column_name, options)
end
@@ -377,6 +387,13 @@ module ActiveRecord
@base.index_exists?(@table_name, column_name, options)
end
+ # Renames the given index on the table.
+ #
+ # t.rename_index(:user_id, :account_id)
+ def rename_index(index_name, new_index_name)
+ @base.rename_index(@table_name, index_name, new_index_name)
+ end
+
# Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
#
# t.timestamps
@@ -387,7 +404,7 @@ module ActiveRecord
# Changes the column's definition according to the new options.
# See TableDefinition#column for details of the options you can use.
#
- # t.change(:name, :string, :limit => 80)
+ # t.change(:name, :string, limit: 80)
# t.change(:description, :text)
def change(column_name, type, options = {})
@base.change_column(@table_name, column_name, type, options)
@@ -414,11 +431,11 @@ module ActiveRecord
# ====== Remove the index_table_name_on_column in the table_name table
# t.remove_index :column
# ====== Remove the index named index_table_name_on_branch_id in the table_name table
- # t.remove_index :column => :branch_id
+ # t.remove_index column: :branch_id
# ====== Remove the index named index_table_name_on_branch_id_and_party_id in the table_name table
- # t.remove_index :column => [:branch_id, :party_id]
+ # t.remove_index column: [:branch_id, :party_id]
# ====== Remove the index named by_branch_party in the table_name table
- # t.remove_index :name => :by_branch_party
+ # t.remove_index name: :by_branch_party
def remove_index(options = {})
@base.remove_index(@table_name, options)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 86d6266af9..f1e42dfbbe 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -36,10 +36,10 @@ module ActiveRecord
# index_exists?(:suppliers, [:company_id, :company_type])
#
# # Check a unique index exists
- # index_exists?(:suppliers, :company_id, :unique => true)
+ # index_exists?(:suppliers, :company_id, unique: true)
#
# # Check an index with a custom name exists
- # index_exists?(:suppliers, :company_id, :name => "idx_company_id"
+ # index_exists?(:suppliers, :company_id, name: "idx_company_id"
def index_exists?(table_name, column_name, options = {})
column_names = Array(column_name)
index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
@@ -89,14 +89,14 @@ module ActiveRecord
# # table.
#
# create_table(:suppliers) do |t|
- # t.column :name, :string, :limit => 60
+ # t.column :name, :string, limit: 60
# # Other fields here
# end
#
# === Block form, with shorthand
# # You can also use the column types as method calls, rather than calling the column method.
# create_table(:suppliers) do |t|
- # t.string :name, :limit => 60
+ # t.string :name, limit: 60
# # Other fields here
# end
#
@@ -104,7 +104,7 @@ module ActiveRecord
# # Creates a table called 'suppliers' with no columns.
# create_table(:suppliers)
# # Add a column to 'suppliers'.
- # add_column(:suppliers, :name, :string, {:limit => 60})
+ # add_column(:suppliers, :name, :string, {limit: 60})
#
# The +options+ hash can include the following keys:
# [<tt>:id</tt>]
@@ -127,15 +127,15 @@ module ActiveRecord
# Defaults to false.
#
# ====== Add a backend specific option to the generated SQL (MySQL)
- # create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
+ # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
# generates:
# CREATE TABLE suppliers (
# id int(11) DEFAULT NULL auto_increment PRIMARY KEY
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8
#
# ====== Rename the primary key column
- # create_table(:objects, :primary_key => 'guid') do |t|
- # t.column :name, :string, :limit => 80
+ # create_table(:objects, primary_key: 'guid') do |t|
+ # t.column :name, :string, limit: 80
# end
# generates:
# CREATE TABLE objects (
@@ -144,7 +144,7 @@ module ActiveRecord
# )
#
# ====== Do not add a primary key column
- # create_table(:categories_suppliers, :id => false) do |t|
+ # create_table(:categories_suppliers, id: false) do |t|
# t.column :category_id, :integer
# t.column :supplier_id, :integer
# end
@@ -193,7 +193,7 @@ module ActiveRecord
# Defaults to false.
#
# ====== Add a backend specific option to the generated SQL (MySQL)
- # create_join_table(:assemblies, :parts, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
+ # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
# generates:
# CREATE TABLE assemblies_parts (
# assembly_id int NOT NULL,
@@ -218,7 +218,7 @@ module ActiveRecord
#
# # change_table() yields a Table instance
# change_table(:suppliers) do |t|
- # t.column :name, :string, :limit => 60
+ # t.column :name, :string, limit: 60
# # Other column alterations here
# end
#
@@ -231,12 +231,12 @@ module ActiveRecord
#
# ====== Add a column
# change_table(:suppliers) do |t|
- # t.column :name, :string, :limit => 60
+ # t.column :name, :string, limit: 60
# end
#
# ====== Add 2 integer columns
# change_table(:suppliers) do |t|
- # t.integer :width, :height, :null => false, :default => 0
+ # t.integer :width, :height, null: false, default: 0
# end
#
# ====== Add created_at/updated_at columns
@@ -253,7 +253,7 @@ module ActiveRecord
#
# ====== Add a polymorphic foreign key column
# change_table(:suppliers) do |t|
- # t.belongs_to :company, :polymorphic => true
+ # t.belongs_to :company, polymorphic: true
# end
#
# Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns
@@ -318,7 +318,7 @@ module ActiveRecord
# Changes the column's definition according to the new options.
# See TableDefinition#column for details of the options you can use.
#
- # change_column(:suppliers, :name, :string, :limit => 80)
+ # change_column(:suppliers, :name, :string, limit: 80)
# change_column(:accounts, :description, :text)
def change_column(table_name, column_name, type, options = {})
raise NotImplementedError, "change_column is not implemented"
@@ -352,35 +352,35 @@ module ActiveRecord
# CREATE INDEX suppliers_name_index ON suppliers(name)
#
# ====== Creating a unique index
- # add_index(:accounts, [:branch_id, :party_id], :unique => true)
+ # add_index(:accounts, [:branch_id, :party_id], unique: true)
# generates
# CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
#
# ====== Creating a named index
- # add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
+ # add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
# generates
# CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
#
# ====== Creating an index with specific key length
- # add_index(:accounts, :name, :name => 'by_name', :length => 10)
+ # add_index(:accounts, :name, name: 'by_name', length: 10)
# generates
# CREATE INDEX by_name ON accounts(name(10))
#
- # add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15})
+ # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
# generates
# CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
#
# Note: SQLite doesn't support index length
#
# ====== Creating an index with a sort order (desc or asc, asc is the default)
- # add_index(:accounts, [:branch_id, :party_id, :surname], :order => {:branch_id => :desc, :party_id => :asc})
+ # add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
# generates
# CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
#
# Note: mysql doesn't yet support index order (it accepts the syntax but ignores it)
#
# ====== Creating a partial index
- # add_index(:accounts, [:branch_id, :party_id], :unique => true, :where => "active")
+ # add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
# generates
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
#
@@ -396,11 +396,11 @@ module ActiveRecord
# Remove the index_accounts_on_column in the accounts table.
# remove_index :accounts, :column
# Remove the index named index_accounts_on_branch_id in the accounts table.
- # remove_index :accounts, :column => :branch_id
+ # remove_index :accounts, column: :branch_id
# Remove the index named index_accounts_on_branch_id_and_party_id in the accounts table.
- # remove_index :accounts, :column => [:branch_id, :party_id]
+ # remove_index :accounts, column: [:branch_id, :party_id]
# Remove the index named by_branch_party in the accounts table.
- # remove_index :accounts, :name => :by_branch_party
+ # remove_index :accounts, name: :by_branch_party
def remove_index(table_name, options = {})
remove_index!(table_name, index_name_for_remove(table_name, options))
end
@@ -422,7 +422,7 @@ module ActiveRecord
end
def index_name(table_name, options) #:nodoc:
- if Hash === options # legacy support
+ if Hash === options
if options[:column]
"index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
elsif options[:name]
@@ -540,7 +540,7 @@ module ActiveRecord
column_type_sql << "(#{precision})"
end
elsif scale
- raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified"
+ raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
end
elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
@@ -617,15 +617,26 @@ module ActiveRecord
def add_index_options(table_name, column_name, options = {})
column_names = Array(column_name)
- index_name = index_name(table_name, :column => column_names)
+ index_name = index_name(table_name, column: column_names)
if Hash === options # legacy support, since this param was a string
+ options.assert_valid_keys(:unique, :order, :name, :where, :length)
+
index_type = options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
+
if supports_partial_index?
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
end
else
+ if options
+ message = "Passing a string as third argument of `add_index` is deprecated and will" +
+ " be removed in Rails 4.1." +
+ " Use add_index(#{table_name.inspect}, #{column_name.inspect}, unique: true) instead"
+
+ ActiveSupport::Deprecation.warn message
+ end
+
index_type = options
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 2117eae5cb..4cca94e40b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -13,8 +13,8 @@ module ActiveRecord
0
end
- def begin
- RealTransaction.new(connection, self)
+ def begin(options = {})
+ RealTransaction.new(connection, self, options)
end
def closed?
@@ -38,13 +38,13 @@ module ActiveRecord
attr_reader :parent, :records
attr_writer :joinable
- def initialize(connection, parent)
+ def initialize(connection, parent, options = {})
super connection
@parent = parent
@records = []
@finishing = false
- @joinable = true
+ @joinable = options.fetch(:joinable, true)
end
# This state is necesarry so that we correctly handle stuff that might
@@ -66,11 +66,11 @@ module ActiveRecord
end
end
- def begin
+ def begin(options = {})
if finishing?
parent.begin
else
- SavepointTransaction.new(connection, self)
+ SavepointTransaction.new(connection, self, options)
end
end
@@ -120,9 +120,14 @@ module ActiveRecord
end
class RealTransaction < OpenTransaction #:nodoc:
- def initialize(connection, parent)
+ def initialize(connection, parent, options = {})
super
- connection.begin_db_transaction
+
+ if options[:isolation]
+ connection.begin_isolated_db_transaction(options[:isolation])
+ else
+ connection.begin_db_transaction
+ end
end
def perform_rollback
@@ -137,7 +142,11 @@ module ActiveRecord
end
class SavepointTransaction < OpenTransaction #:nodoc:
- def initialize(connection, parent)
+ def initialize(connection, parent, options = {})
+ if options[:isolation]
+ raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
+ end
+
super
connection.create_savepoint
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 3a8fbcf93f..8517ce5fc5 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -3,7 +3,7 @@ require 'bigdecimal'
require 'bigdecimal/util'
require 'active_support/core_ext/benchmark'
require 'active_record/connection_adapters/schema_cache'
-require 'active_record/connection_adapters/abstract/schema_dumper'
+require 'active_record/connection_adapters/abstract/schema_dumper'
require 'monitor'
require 'active_support/deprecation'
@@ -167,6 +167,11 @@ module ActiveRecord
false
end
+ # Does this adapter support setting the isolation level for a transaction?
+ def supports_transaction_isolation?
+ false
+ end
+
# QUOTING ==================================================
# Returns a bind substitution value given a +column+ and list of current
@@ -258,7 +263,8 @@ module ActiveRecord
end
def transaction_joinable=(joinable)
- ActiveSupport::Deprecation.warn "#transaction_joinable= is deprecated. Please pass the :joinable option to #begin_transaction instead."
+ message = "#transaction_joinable= is deprecated. Please pass the :joinable option to #begin_transaction instead."
+ ActiveSupport::Deprecation.warn message
@transaction.joinable = joinable
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 1126fe7fce..84e73e6f0f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -4,17 +4,19 @@ module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter < AbstractAdapter
class Column < ConnectionAdapters::Column # :nodoc:
- attr_reader :collation
+ attr_reader :collation, :strict
- def initialize(name, default, sql_type = nil, null = true, collation = nil)
- super(name, default, sql_type, null)
+ def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false)
+ @strict = strict
@collation = collation
+
+ super(name, default, sql_type, null)
end
def extract_default(default)
if sql_type =~ /blob/i || type == :text
if default.blank?
- return null ? nil : ''
+ null || strict ? nil : ''
else
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
end
@@ -169,6 +171,14 @@ module ActiveRecord
true
end
+ # MySQL 4 technically support transaction isolation, but it is affected by a bug
+ # where the transaction level gets persisted for the whole session:
+ #
+ # http://bugs.mysql.com/bug.php?id=39170
+ def supports_transaction_isolation?
+ version[0] >= 5
+ end
+
def native_database_types
NATIVE_DATABASE_TYPES
end
@@ -269,6 +279,13 @@ module ActiveRecord
# Transactions aren't supported
end
+ def begin_isolated_db_transaction(isolation)
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
+ begin_db_transaction
+ rescue
+ # Transactions aren't supported
+ end
+
def commit_db_transaction #:nodoc:
execute "COMMIT"
rescue
@@ -305,6 +322,10 @@ module ActiveRecord
end
end
+ def empty_insert_statement_value
+ "VALUES ()"
+ end
+
# SCHEMA STATEMENTS ========================================
def structure_dump #:nodoc:
@@ -332,9 +353,9 @@ module ActiveRecord
# Charset defaults to utf8.
#
# Example:
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
+ # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
# create_database 'matt_development'
- # create_database 'matt_development', :charset => :big5
+ # create_database 'matt_development', charset: :big5
def create_database(name, options = {})
if options[:collation]
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
@@ -477,6 +498,13 @@ module ActiveRecord
# Maps logical Rails types to MySQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
case type.to_s
+ when 'binary'
+ case limit
+ when 0..0xfff; "varbinary(#{limit})"
+ when nil; "blob"
+ when 0x1000..0xffffffff; "blob(#{limit})"
+ else raise(ActiveRecordError, "No binary type has character length #{limit}")
+ end
when 'integer'
case limit
when 1; 'tinyint'
@@ -548,6 +576,10 @@ module ActiveRecord
where_sql
end
+ def strict_mode?
+ @config.fetch(:strict, true)
+ end
+
protected
# MySQL is too stupid to create a temporary table for use subquery, so we have
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 816b5e17c1..80984f39c9 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -94,7 +94,7 @@ module ActiveRecord
case type
when :string, :text then value
- when :integer then value.to_i
+ when :integer then klass.value_to_integer(value)
when :float then value.to_f
when :decimal then klass.value_to_decimal(value)
when :datetime, :timestamp then klass.string_to_time(value)
@@ -107,14 +107,15 @@ module ActiveRecord
end
def type_cast_code(var_name)
- ActiveSupport::Deprecation.warn("Column#type_cast_code is deprecated in favor of" \
- "using Column#type_cast only, and it is going to be removed in future Rails versions.")
+ message = "Column#type_cast_code is deprecated in favor of using Column#type_cast only, " \
+ "and it is going to be removed in future Rails versions."
+ ActiveSupport::Deprecation.warn message
klass = self.class.name
case type
when :string, :text then var_name
- when :integer then "(#{var_name}.to_i)"
+ when :integer then "#{klass}.value_to_integer(#{var_name})"
when :float then "#{var_name}.to_f"
when :decimal then "#{klass}.value_to_decimal(#{var_name})"
when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})"
@@ -197,6 +198,17 @@ module ActiveRecord
end
end
+ # Used to convert values to integer.
+ # handle the case when an integer column is used to store boolean values
+ def value_to_integer(value)
+ case value
+ when TrueClass, FalseClass
+ value ? 1 : 0
+ else
+ value.to_i
+ end
+ end
+
# convert something to a BigDecimal
def value_to_decimal(value)
# Using .class is faster than .is_a? and
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index b9a61f7d91..09250d3c01 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -73,6 +73,8 @@ module ActiveRecord
:database => config.path.sub(%r{^/},""),
:host => config.host }
spec.reject!{ |_,value| value.blank? }
+ uri_parser = URI::Parser.new
+ spec.map { |key,value| spec[key] = uri_parser.unescape(value) if value.is_a?(String) }
if config.query
options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
spec.merge!(options)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 328d080687..f55d19393c 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -53,7 +53,7 @@ module ActiveRecord
end
def new_column(field, default, type, null, collation) # :nodoc:
- Column.new(field, default, type, null, collation)
+ Column.new(field, default, type, null, collation, strict_mode?)
end
def error_number(exception)
@@ -175,7 +175,7 @@ module ActiveRecord
# # as values.
# def select_one(sql, name = nil)
# result = execute(sql, name)
- # result.each(:as => :hash) do |r|
+ # result.each(as: :hash) do |r|
# return r
# end
# end
@@ -259,9 +259,7 @@ module ActiveRecord
# Make MySQL reject illegal values rather than truncating or
# blanking them. See
# http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
- if @config.fetch(:strict, true)
- variable_assignments << "SQL_MODE='STRICT_ALL_TABLES'"
- end
+ variable_assignments << "SQL_MODE='STRICT_ALL_TABLES'" if strict_mode?
encoding = @config[:encoding]
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 0b936bbf39..e9677415cc 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -2,7 +2,7 @@ require 'active_record/connection_adapters/abstract_mysql_adapter'
require 'active_record/connection_adapters/statement_pool'
require 'active_support/core_ext/hash/keys'
-gem 'mysql', '~> 2.8.1'
+gem 'mysql', '~> 2.9'
require 'mysql'
class Mysql
@@ -150,7 +150,7 @@ module ActiveRecord
end
def new_column(field, default, type, null, collation) # :nodoc:
- Column.new(field, default, type, null, collation)
+ Column.new(field, default, type, null, collation, strict_mode?)
end
def error_number(exception) # :nodoc:
@@ -546,9 +546,7 @@ module ActiveRecord
# Make MySQL reject illegal values rather than truncating or
# blanking them. See
# http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
- if @config.fetch(:strict, true)
- execute("SET SQL_MODE='STRICT_ALL_TABLES'", :skip_logging)
- end
+ execute("SET SQL_MODE='STRICT_ALL_TABLES'", :skip_logging) if strict_mode?
end
def select(sql, name = nil, binds = [])
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index 62d091357d..c04a799b8d 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -8,6 +8,8 @@ module ActiveRecord
case string
when 'infinity'; 1.0 / 0.0
when '-infinity'; -1.0 / 0.0
+ when / BC$/
+ super("-" + string.sub(/ BC$/, ""))
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index c8437c18cc..34d7a246b2 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -205,6 +205,11 @@ module ActiveRecord
execute "BEGIN"
end
+ def begin_isolated_db_transaction(isolation)
+ begin_db_transaction
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
+ end
+
# Commits a transaction.
def commit_db_transaction
execute "COMMIT"
@@ -216,10 +221,9 @@ module ActiveRecord
end
def outside_transaction?
- ActiveSupport::Deprecation.warn(
- "#outside_transaction? is deprecated. This method was only really used " \
- "internally, but you can use #transaction_open? instead."
- )
+ message = "#outside_transaction? is deprecated. This method was only really used " \
+ "internally, but you can use #transaction_open? instead."
+ ActiveSupport::Deprecation.warn message
@connection.transaction_status == PGconn::PQTRANS_IDLE
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 37d43d891d..62a4d76928 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -90,7 +90,7 @@ module ActiveRecord
else super(value, column)
end
when IPAddr
- return super(value, column) unless ['inet','cidr'].includes? column.sql_type
+ return super(value, column) unless ['inet','cidr'].include? column.sql_type
PostgreSQLColumn.cidr_to_string(value)
else
super(value, column)
@@ -129,11 +129,15 @@ module ActiveRecord
# Quote date/time values for use in SQL input. Includes microseconds
# if the value is a Time responding to usec.
def quoted_date(value) #:nodoc:
+ result = super
if value.acts_like?(:time) && value.respond_to?(:usec)
- "#{super}.#{sprintf("%06d", value.usec)}"
- else
- super
+ result = "#{result}.#{sprintf("%06d", value.usec)}"
+ end
+
+ if value.year < 0
+ result = result.sub(/^-/, "") + " BC"
end
+ result
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 8a073bf878..7c561b6f82 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -16,7 +16,7 @@ module ActiveRecord
#
# Example:
# create_database config[:database], config
- # create_database 'foo_development', :encoding => 'unicode'
+ # create_database 'foo_development', encoding: 'unicode'
def create_database(name, options = {})
options = options.reverse_merge(:encoding => "utf8")
@@ -280,16 +280,13 @@ module ActiveRecord
end_sql
if result.nil? or result.empty?
- # If that fails, try parsing the primary key's default value.
- # Support the 7.x and 8.0 nextval('foo'::text) as well as
- # the 8.1+ nextval('foo'::regclass).
result = query(<<-end_sql, 'SCHEMA')[0]
SELECT attr.attname,
CASE
- WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
- substr(split_part(def.adsrc, '''', 2),
- strpos(split_part(def.adsrc, '''', 2), '.')+1)
- ELSE split_part(def.adsrc, '''', 2)
+ WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
+ substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
+ strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
+ ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
END
FROM pg_class t
JOIN pg_attribute attr ON (t.oid = attrelid)
@@ -297,7 +294,7 @@ module ActiveRecord
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
WHERE t.oid = '#{quote_table_name(table)}'::regclass
AND cons.contype = 'p'
- AND def.adsrc ~* 'nextval'
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval'
end_sql
end
@@ -314,7 +311,7 @@ module ActiveRecord
INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
WHERE cons.contype = 'p'
- AND dep.refobjid = '#{table}'::regclass
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
end_sql
row && row.first
@@ -399,6 +396,13 @@ module ActiveRecord
when nil, 0..0x3fffffff; super(type)
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
end
+ when 'text'
+ # PostgreSQL doesn't support limits on text columns.
+ # The hard limit is 1Gb, according to section 8.3 in the manual.
+ case limit
+ when nil, 0..0x3fffffff; super(type)
+ else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
+ end
when 'integer'
return 'integer' unless limit
@@ -425,20 +429,17 @@ module ActiveRecord
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
# requires that the ORDER BY include the distinct column.
#
- # distinct("posts.id", "posts.created_at desc")
+ # distinct("posts.id", ["posts.created_at desc"])
+ # # => "DISTINCT posts.id, posts.created_at AS alias_0"
def distinct(columns, orders) #:nodoc:
- return "DISTINCT #{columns}" if orders.empty?
-
- # Construct a clean list of column names from the ORDER BY clause, removing
- # any ASC/DESC modifiers
- order_columns = orders.collect do |s|
- s = s.to_sql unless s.is_a?(String)
- s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
- end
- order_columns.delete_if { |c| c.blank? }
- order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
-
- "DISTINCT #{columns}, #{order_columns * ', '}"
+ order_columns = orders.map{ |s|
+ # Convert Arel node to string
+ s = s.to_sql unless s.is_a?(String)
+ # Remove any ASC/DESC modifiers
+ s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
+
+ [super].concat(order_columns).join(', ')
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 761052a788..e18464fa35 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -78,11 +78,8 @@ module ActiveRecord
when /\A\(?(-?\d+(\.\d*)?\)?)\z/
$1
# Character types
- when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
+ when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
$1
- # Character types (8.1 formatting)
- when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
- $1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
# Binary data types
when /\A'(.*)'::bytea\z/m
$1
@@ -241,7 +238,7 @@ module ActiveRecord
# <encoding></tt> call on the connection.
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
- # * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT<tt> statements
+ # * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT</tt> statements
# defaults to true.
#
# Any further options are used as connection parameters to libpq. See
@@ -370,6 +367,10 @@ module ActiveRecord
true
end
+ def supports_transaction_isolation?
+ true
+ end
+
class StatementPool < ConnectionAdapters::StatementPool
def initialize(connection, max)
super
@@ -759,7 +760,8 @@ module ActiveRecord
# - ::regclass is a function that gives the id for a table name
def column_definitions(table_name) #:nodoc:
exec_query(<<-end_sql, 'SCHEMA').rows
- SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull, a.atttypid, a.atttypmod
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 4a48812807..b89e9a01a8 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -251,7 +251,7 @@ module ActiveRecord
value = super
if column.type == :string && value.encoding == Encoding::ASCII_8BIT
logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger
- value.encode! 'utf-8'
+ value = value.encode Encoding::UTF_8
end
value
end
@@ -490,10 +490,6 @@ module ActiveRecord
alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
end
- def empty_insert_statement_value
- "VALUES(NULL)"
- end
-
protected
def select(sql, name = nil, binds = []) #:nodoc:
exec_query(sql, name, binds)
@@ -523,24 +519,22 @@ module ActiveRecord
def copy_table(from, to, options = {}) #:nodoc:
from_primary_key = primary_key(from)
- options[:primary_key] = from_primary_key if from_primary_key != 'id'
- unless options[:primary_key]
- options[:id] = columns(from).detect{|c| c.name == 'id'}.present? && from_primary_key == 'id'
- end
+ options[:id] = false
create_table(to, options) do |definition|
@definition = definition
+ @definition.primary_key(from_primary_key) if from_primary_key.present?
columns(from).each do |column|
column_name = options[:rename] ?
(options[:rename][column.name] ||
options[:rename][column.name.to_sym] ||
column.name) : column.name
+ next if column_name == from_primary_key
@definition.column(column_name, column.type,
:limit => column.limit, :default => column.default,
:precision => column.precision, :scale => column.scale,
:null => column.null)
end
- @definition.primary_key(from_primary_key) if from_primary_key
yield @definition if block_given?
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 3531be05bf..d6d998c7be 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
module ConnectionHandling
# Establishes the connection to the database. Accepts a hash as input where
@@ -6,18 +5,18 @@ module ActiveRecord
# example for regular databases (MySQL, Postgresql, etc):
#
# ActiveRecord::Base.establish_connection(
- # :adapter => "mysql",
- # :host => "localhost",
- # :username => "myuser",
- # :password => "mypass",
- # :database => "somedatabase"
+ # adapter: "mysql",
+ # host: "localhost",
+ # username: "myuser",
+ # password: "mypass",
+ # database: "somedatabase"
# )
#
# Example for SQLite database:
#
# ActiveRecord::Base.establish_connection(
- # :adapter => "sqlite",
- # :database => "path/to/dbfile"
+ # adapter: "sqlite",
+ # database: "path/to/dbfile"
# )
#
# Also accepts keys as strings (for parsing from YAML for example):
@@ -65,7 +64,7 @@ module ActiveRecord
# Returns the configuration of the associated connection as a hash:
#
# ActiveRecord::Base.connection_config
- # # => {:pool=>5, :timeout=>5000, :database=>"db/development.sqlite3", :adapter=>"sqlite3"}
+ # # => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}
#
# Please use only for reading.
def connection_config
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index f97c363871..957027c1ee 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -3,88 +3,74 @@ require 'active_support/core_ext/object/duplicable'
require 'thread'
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- ##
- # :singleton-method:
- #
- # Accepts a logger conforming to the interface of Log4r which is then
- # passed on to any new database connections made and which can be
- # retrieved on both a class and instance level by calling +logger+.
- mattr_accessor :logger, instance_accessor: false
-
- ##
- # :singleton-method:
- # Contains the database configuration - as is typically stored in config/database.yml -
- # as a Hash.
- #
- # For example, the following database.yml...
- #
- # development:
- # adapter: sqlite3
- # database: db/development.sqlite3
- #
- # production:
- # adapter: sqlite3
- # database: db/production.sqlite3
- #
- # ...would result in ActiveRecord::Base.configurations to look like this:
- #
- # {
- # 'development' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/development.sqlite3'
- # },
- # 'production' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/production.sqlite3'
- # }
- # }
- mattr_accessor :configurations, instance_accessor: false
- self.configurations = {}
-
- ##
- # :singleton-method:
- # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
- # dates and times from the database. This is set to :utc by default.
- mattr_accessor :default_timezone, instance_accessor: false
- self.default_timezone = :utc
-
- ##
- # :singleton-method:
- # Specifies the format to use when dumping the database schema with Rails'
- # Rakefile. If :sql, the schema is dumped as (potentially database-
- # specific) SQL statements. If :ruby, the schema is dumped as an
- # ActiveRecord::Schema file which can be loaded into any database that
- # supports migrations. Use :ruby if you want to have different database
- # adapters for, e.g., your development and test environments.
- mattr_accessor :schema_format, instance_accessor: false
- self.schema_format = :ruby
+ module Core
+ extend ActiveSupport::Concern
- ##
- # :singleton-method:
- # Specify whether or not to use timestamps for migration versions
- mattr_accessor :timestamped_migrations, instance_accessor: false
- self.timestamped_migrations = true
+ included do
+ ##
+ # :singleton-method:
+ #
+ # Accepts a logger conforming to the interface of Log4r which is then
+ # passed on to any new database connections made and which can be
+ # retrieved on both a class and instance level by calling +logger+.
+ mattr_accessor :logger, instance_writer: false
- mattr_accessor :connection_handler, instance_accessor: false
- self.connection_handler = ConnectionAdapters::ConnectionHandler.new
+ ##
+ # :singleton-method:
+ # Contains the database configuration - as is typically stored in config/database.yml -
+ # as a Hash.
+ #
+ # For example, the following database.yml...
+ #
+ # development:
+ # adapter: sqlite3
+ # database: db/development.sqlite3
+ #
+ # production:
+ # adapter: sqlite3
+ # database: db/production.sqlite3
+ #
+ # ...would result in ActiveRecord::Base.configurations to look like this:
+ #
+ # {
+ # 'development' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/development.sqlite3'
+ # },
+ # 'production' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/production.sqlite3'
+ # }
+ # }
+ mattr_accessor :configurations, instance_writer: false
+ self.configurations = {}
- mattr_accessor :dependent_restrict_raises, instance_accessor: false
- self.dependent_restrict_raises = true
- end
+ ##
+ # :singleton-method:
+ # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
+ # dates and times from the database. This is set to :utc by default.
+ mattr_accessor :default_timezone, instance_writer: false
+ self.default_timezone = :utc
- module Core
- extend ActiveSupport::Concern
+ ##
+ # :singleton-method:
+ # Specifies the format to use when dumping the database schema with Rails'
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
+ # specific) SQL statements. If :ruby, the schema is dumped as an
+ # ActiveRecord::Schema file which can be loaded into any database that
+ # supports migrations. Use :ruby if you want to have different database
+ # adapters for, e.g., your development and test environments.
+ mattr_accessor :schema_format, instance_writer: false
+ self.schema_format = :ruby
- included do
##
# :singleton-method:
- # The connection handler
- config_attribute :connection_handler
+ # Specify whether or not to use timestamps for migration versions
+ mattr_accessor :timestamped_migrations, instance_writer: false
+ self.timestamped_migrations = true
- %w(logger configurations default_timezone schema_format timestamped_migrations).each do |name|
- config_attribute name, global: true
- end
+ class_attribute :connection_handler, instance_writer: false
+ self.connection_handler = ConnectionAdapters::ConnectionHandler.new
end
module ClassMethods
@@ -139,7 +125,13 @@ module ActiveRecord
# Returns the Arel engine.
def arel_engine
- @arel_engine ||= connection_handler.retrieve_connection_pool(self) ? self : active_record_super.arel_engine
+ @arel_engine ||= begin
+ if Base == self || connection_handler.retrieve_connection_pool(self)
+ self
+ else
+ superclass.arel_engine
+ end
+ end
end
private
@@ -162,7 +154,7 @@ module ActiveRecord
#
# ==== Example:
# # Instantiates a single new object
- # User.new(:first_name => 'Jamie')
+ # User.new(first_name: 'Jamie')
def initialize(attributes = nil)
defaults = self.class.column_defaults.dup
defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index c877079b25..c53b7b3e78 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -22,6 +22,10 @@ module ActiveRecord
counters.each do |association|
has_many_association = reflect_on_association(association.to_sym)
+ if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
+ has_many_association = has_many_association.through_reflection
+ end
+
foreign_key = has_many_association.foreign_key.to_s
child_class = has_many_association.klass
belongs_to = child_class.reflect_on_all_associations(:belongs_to)
@@ -52,7 +56,7 @@ module ActiveRecord
#
# # For the Post with id of 5, decrement the comment_count by 1, and
# # increment the action_count by 1
- # Post.update_counters 5, :comment_count => -1, :action_count => 1
+ # Post.update_counters 5, comment_count: -1, action_count: 1
# # Executes the following SQL:
# # UPDATE posts
# # SET comment_count = COALESCE(comment_count, 0) - 1,
@@ -60,7 +64,7 @@ module ActiveRecord
# # WHERE id = 5
#
# # For the Posts with id of 10 and 15, increment the comment_count by 1
- # Post.update_counters [10, 15], :comment_count => 1
+ # Post.update_counters [10, 15], comment_count: 1
# # Executes the following SQL:
# # UPDATE posts
# # SET comment_count = COALESCE(comment_count, 0) + 1
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 5f157fde6d..c615d59725 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -22,7 +22,7 @@ module ActiveRecord
# end
#
# # Comments are not patches, this assignment raises AssociationTypeMismatch.
- # @ticket.patches << Comment.new(:content => "Please attach tests to your patch.")
+ # @ticket.patches << Comment.new(content: "Please attach tests to your patch.")
class AssociationTypeMismatch < ActiveRecordError
end
@@ -193,6 +193,20 @@ module ActiveRecord
end
+ # Raised when a relation cannot be mutated because it's already loaded.
+ #
+ # class Task < ActiveRecord::Base
+ # end
+ #
+ # relation = Task.all
+ # relation.loaded? # => true
+ #
+ # # Methods which try to mutate a loaded relation fail.
+ # relation.where!(title: 'TODO') # => ActiveRecord::ImmutableRelation
+ # relation.limit!(5) # => ActiveRecord::ImmutableRelation
class ImmutableRelation < ActiveRecordError
end
+
+ class TransactionIsolationError < ActiveRecordError
+ end
end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 9e0390bed1..af772996f1 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -1,12 +1,10 @@
require 'active_support/lazy_load_hooks'
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- mattr_accessor :auto_explain_threshold_in_seconds, instance_accessor: false
- end
-
module Explain
- delegate :auto_explain_threshold_in_seconds, :auto_explain_threshold_in_seconds=, to: 'ActiveRecord::Model'
+ def self.extended(base)
+ base.mattr_accessor :auto_explain_threshold_in_seconds, instance_accessor: false
+ end
# If auto explain is enabled, this method triggers EXPLAIN logging for the
# queries triggered by the block if it takes more than the threshold as a
diff --git a/activerecord/lib/active_record/fixtures/file.rb b/activerecord/lib/active_record/fixture_set/file.rb
index a9cabf5a7b..11b53275e1 100644
--- a/activerecord/lib/active_record/fixtures/file.rb
+++ b/activerecord/lib/active_record/fixture_set/file.rb
@@ -2,8 +2,8 @@ require 'erb'
require 'yaml'
module ActiveRecord
- class Fixtures
- class File
+ class FixtureSet
+ class File # :nodoc:
include Enumerable
##
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 60fc653735..7922bbcfa0 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -2,9 +2,11 @@ require 'erb'
require 'yaml'
require 'zlib'
require 'active_support/dependencies'
-require 'active_record/fixtures/file'
+require 'active_record/fixture_set/file'
require 'active_record/errors'
+require 'active_support/deprecation' # temporary
+
module ActiveRecord
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
end
@@ -248,7 +250,7 @@ module ActiveRecord
#
# ### in fruit.rb
#
- # belongs_to :eater, :polymorphic => true
+ # belongs_to :eater, polymorphic: true
#
# ### in fruits.yml
#
@@ -350,8 +352,8 @@ module ActiveRecord
# to the rescue:
#
# george_reginald:
- # monkey_id: <%= ActiveRecord::Fixtures.identify(:reginald) %>
- # pirate_id: <%= ActiveRecord::Fixtures.identify(:george) %>
+ # monkey_id: <%= ActiveRecord::FixtureSet.identify(:reginald) %>
+ # pirate_id: <%= ActiveRecord::FixtureSet.identify(:george) %>
#
# == Support for YAML defaults
#
@@ -370,14 +372,21 @@ module ActiveRecord
# *DEFAULTS
#
# Any fixture labeled "DEFAULTS" is safely ignored.
- class Fixtures
+ class FixtureSet
#--
- # NOTE: an instance of Fixtures can be called fixture_set, it is normally stored in a single YAML file and possibly in a folder with the same name.
+ # An instance of FixtureSet is normally stored in a single YAML file and possibly in a folder with the same name.
#++
+
MAX_ID = 2 ** 30 - 1
@@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
+ def self.find_table_name(fixture_set_name) # :nodoc:
+ ActiveSupport::Deprecation.warn(
+ "ActiveRecord::Fixtures.find_table_name is deprecated and shall be removed from future releases. Use ActiveRecord::Fixtures.default_fixture_model_name instead.")
+ default_fixture_model_name(fixture_set_name)
+ end
+
def self.default_fixture_model_name(fixture_set_name) # :nodoc:
ActiveRecord::Base.pluralize_table_names ?
fixture_set_name.singularize.camelize :
@@ -451,7 +460,7 @@ module ActiveRecord
fixtures_map = {}
fixture_sets = files_to_read.map do |fs_name|
- fixtures_map[fs_name] = new( # ActiveRecord::Fixtures.new
+ fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new
connection,
fs_name,
class_names[fs_name] || default_fixture_model_name(fs_name),
@@ -550,7 +559,7 @@ module ActiveRecord
rows[table_name] = fixtures.map do |label, fixture|
row = fixture.to_hash
- if model_class && model_class < ActiveRecord::Model
+ if model_class && model_class < ActiveRecord::Base
# fill in timestamp columns if they aren't specified and the model is set to record_timestamps
if model_class.record_timestamps
timestamp_column_names.each do |c_name|
@@ -565,7 +574,7 @@ module ActiveRecord
# generate a primary key if necessary
if has_primary_key_column? && !row.include?(primary_key_name)
- row[primary_key_name] = ActiveRecord::Fixtures.identify(label)
+ row[primary_key_name] = ActiveRecord::FixtureSet.identify(label)
end
# If STI is used, find the correct subclass for association reflection
@@ -588,7 +597,7 @@ module ActiveRecord
row[association.foreign_type] = $1
end
- row[fk_name] = ActiveRecord::Fixtures.identify(value)
+ row[fk_name] = ActiveRecord::FixtureSet.identify(value)
end
when :has_and_belongs_to_many
if (targets = row.delete(association.name.to_s))
@@ -596,7 +605,7 @@ module ActiveRecord
table_name = association.join_table
rows[table_name].concat targets.map { |target|
{ association.foreign_key => row[primary_key_name],
- association.association_foreign_key => ActiveRecord::Fixtures.identify(target) }
+ association.association_foreign_key => ActiveRecord::FixtureSet.identify(target) }
}
end
end
@@ -637,7 +646,7 @@ module ActiveRecord
} + [yaml_file_path]
yaml_files.each do |file|
- Fixtures::File.open(file) do |fh|
+ FixtureSet::File.open(file) do |fh|
fh.each do |fixture_name, row|
fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class)
end
@@ -651,6 +660,12 @@ module ActiveRecord
end
+ #--
+ # Deprecate 'Fixtures' in favor of 'FixtureSet'.
+ #++
+ # :nodoc:
+ Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::Fixtures', 'ActiveRecord::FixtureSet')
+
class Fixture #:nodoc:
include Enumerable
@@ -712,7 +727,7 @@ module ActiveRecord
self.pre_loaded_fixtures = false
self.fixture_class_names = Hash.new do |h, fixture_set_name|
- h[fixture_set_name] = ActiveRecord::Fixtures.default_fixture_model_name(fixture_set_name)
+ h[fixture_set_name] = ActiveRecord::FixtureSet.default_fixture_model_name(fixture_set_name)
end
end
@@ -721,7 +736,7 @@ module ActiveRecord
#
# Examples:
#
- # set_fixture_class :some_fixture => SomeModel,
+ # set_fixture_class some_fixture: SomeModel,
# 'namespaced/fixture' => Another::Model
#
# The keys must be the fixture names, that coincide with the short paths to the fixture files.
@@ -847,7 +862,7 @@ module ActiveRecord
end
# Load fixtures for every test.
else
- ActiveRecord::Fixtures.reset_cache
+ ActiveRecord::FixtureSet.reset_cache
@@already_loaded_fixtures[self.class] = nil
@loaded_fixtures = load_fixtures
end
@@ -860,7 +875,7 @@ module ActiveRecord
return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
unless run_in_transaction?
- ActiveRecord::Fixtures.reset_cache
+ ActiveRecord::FixtureSet.reset_cache
end
# Rollback changes if a transaction is active.
@@ -874,12 +889,12 @@ module ActiveRecord
end
def enlist_fixture_connections
- ActiveRecord::Base.connection_handler.connection_pools.map(&:connection)
+ ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
end
private
def load_fixtures
- fixtures = ActiveRecord::Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
+ fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
Hash[fixtures.map { |f| [f.name, f] }]
end
@@ -888,16 +903,16 @@ module ActiveRecord
def instantiate_fixtures
if pre_loaded_fixtures
- raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::Fixtures.all_loaded_fixtures.empty?
+ raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::FixtureSet.all_loaded_fixtures.empty?
unless @@required_fixture_classes
- self.class.require_fixture_classes ActiveRecord::Fixtures.all_loaded_fixtures.keys
+ self.class.require_fixture_classes ActiveRecord::FixtureSet.all_loaded_fixtures.keys
@@required_fixture_classes = true
end
- ActiveRecord::Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
+ ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?)
else
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
@loaded_fixtures.each_value do |fixture_set|
- ActiveRecord::Fixtures.instantiate_fixtures(self, fixture_set, load_instances?)
+ ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?)
end
end
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 04fff99a6e..a448fa1f5c 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -1,29 +1,22 @@
-
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- # Determine whether to store the full constant name including namespace when using STI
- mattr_accessor :store_full_sti_class, instance_accessor: false
- self.store_full_sti_class = true
- end
-
module Inheritance
extend ActiveSupport::Concern
included do
- config_attribute :store_full_sti_class
+ # Determine whether to store the full constant name including namespace when using STI
+ class_attribute :store_full_sti_class, instance_writer: false
+ self.store_full_sti_class = true
end
module ClassMethods
# True if this isn't a concrete subclass needing a STI type condition.
def descends_from_active_record?
- sup = active_record_super
-
- if sup.abstract_class?
- sup.descends_from_active_record?
- elsif self == Base
+ if self == Base
false
+ elsif superclass.abstract_class?
+ superclass.descends_from_active_record?
else
- [Base, Model].include?(sup) || !columns_hash.include?(inheritance_column)
+ superclass == Base || !columns_hash.include?(inheritance_column)
end
end
@@ -40,9 +33,8 @@ module ActiveRecord
@symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
end
- # Returns the class descending directly from ActiveRecord::Base (or
- # that includes ActiveRecord::Model), or an abstract class, if any, in
- # the inheritance hierarchy.
+ # Returns the class descending directly from ActiveRecord::Base, or
+ # an abstract class, if any, in the inheritance hierarchy.
#
# If A extends AR::Base, A.base_class will return A. If B descends from A
# through some arbitrarily deep hierarchy, B.base_class will return A.
@@ -50,15 +42,14 @@ module ActiveRecord
# If B < A and C < B and if A is an abstract_class then both B.base_class
# and C.base_class would return B as the answer since A is an abstract_class.
def base_class
- unless self < Model::Tag
+ unless self < Base
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
end
- sup = active_record_super
- if sup == Base || sup == Model || sup.abstract_class?
+ if superclass == Base || superclass.abstract_class?
self
else
- sup.base_class
+ superclass.base_class
end
end
@@ -73,7 +64,7 @@ module ActiveRecord
# class Child < SuperClass
# self.table_name = 'the_table_i_really_want'
# end
- #
+ #
#
# <tt>self.abstract_class = true</tt> is required to make <tt>Child<.find,.create, or any Arel method></tt> use <tt>the_table_i_really_want</tt> instead of a table called <tt>super_classes</tt>
#
@@ -97,14 +88,6 @@ module ActiveRecord
sti_class.allocate.init_with('attributes' => record, 'column_types' => column_types)
end
- # For internal use.
- #
- # If this class includes ActiveRecord::Model then it won't have a
- # superclass. So this provides a way to get to the 'root' (ActiveRecord::Model).
- def active_record_super #:nodoc:
- superclass < Model ? superclass : Model
- end
-
protected
# Returns the class type of the record using the current module as a prefix. So descendants of
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index 896132d566..b1fbd38622 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -4,11 +4,15 @@ en:
#created_at: "Created at"
#updated_at: "Updated at"
+ # Default error messages
+ errors:
+ messages:
+ taken: "has already been taken"
+
# Active Record models configuration
activerecord:
errors:
messages:
- taken: "has already been taken"
record_invalid: "Validation failed: %{errors}"
restrict_dependent_destroy:
one: "Cannot delete record because a dependent %{record} exists"
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index e96ed00f9c..035c77c424 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -1,9 +1,4 @@
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- mattr_accessor :lock_optimistically, instance_accessor: false
- self.lock_optimistically = true
- end
-
module Locking
# == What is Optimistic Locking
#
@@ -56,7 +51,8 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :lock_optimistically
+ class_attribute :lock_optimistically, instance_writer: false
+ self.lock_optimistically = true
end
def locking_enabled? #:nodoc:
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index 58af92f0b1..b4bb95a392 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -3,12 +3,12 @@ module ActiveRecord
# Locking::Pessimistic provides support for row-level locking using
# SELECT ... FOR UPDATE and other lock types.
#
- # Pass <tt>:lock => true</tt> to <tt>ActiveRecord::Base.find</tt> to obtain an exclusive
+ # Pass <tt>lock: true</tt> to <tt>ActiveRecord::Base.find</tt> to obtain an exclusive
# lock on the selected rows:
# # select * from accounts where id=1 for update
- # Account.find(1, :lock => true)
+ # Account.find(1, lock: true)
#
- # Pass <tt>:lock => 'some locking clause'</tt> to give a database-specific locking clause
+ # Pass <tt>lock: 'some locking clause'</tt> to give a database-specific locking clause
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
#
# Account.transaction do
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index a25f2c7bca..ca79950049 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -1,11 +1,13 @@
module ActiveRecord
class LogSubscriber < ActiveSupport::LogSubscriber
+ IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
+
def self.runtime=(value)
- Thread.current["active_record_sql_runtime"] = value
+ Thread.current[:active_record_sql_runtime] = value
end
def self.runtime
- Thread.current["active_record_sql_runtime"] ||= 0
+ Thread.current[:active_record_sql_runtime] ||= 0
end
def self.reset_runtime
@@ -24,9 +26,9 @@ module ActiveRecord
payload = event.payload
- return if 'SCHEMA' == payload[:name]
+ return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
- name = '%s (%.1fms)' % [payload[:name], event.duration]
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
sql = payload[:sql].squeeze(' ')
binds = nil
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index c1d57855a9..22347fcaef 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -32,7 +32,7 @@ module ActiveRecord
class PendingMigrationError < ActiveRecordError#:nodoc:
def initialize
- super("Migrations are pending run 'rake db:migrate RAILS_ENV=#{ENV['RAILS_ENV']}' to resolve the issue")
+ super("Migrations are pending; run 'rake db:migrate RAILS_ENV=#{ENV['RAILS_ENV']}' to resolve this issue.")
end
end
@@ -50,7 +50,7 @@ module ActiveRecord
#
# class AddSsl < ActiveRecord::Migration
# def up
- # add_column :accounts, :ssl_enabled, :boolean, :default => true
+ # add_column :accounts, :ssl_enabled, :boolean, default: true
# end
#
# def down
@@ -62,7 +62,7 @@ module ActiveRecord
# if you're backing out of the migration. It shows how all migrations have
# two methods +up+ and +down+ that describes the transformations
# required to implement or remove the migration. These methods can consist
- # of both the migration specific methods like add_column and remove_column,
+ # of both the migration specific methods like +add_column+ and +remove_column+,
# but may also contain regular Ruby code for generating data needed for the
# transformations.
#
@@ -78,9 +78,9 @@ module ActiveRecord
# t.integer :position
# end
#
- # SystemSetting.create :name => "notice",
- # :label => "Use notice?",
- # :value => 1
+ # SystemSetting.create name: 'notice',
+ # label: 'Use notice?',
+ # value: 1
# end
#
# def down
@@ -88,19 +88,22 @@ module ActiveRecord
# end
# end
#
- # This migration first adds the system_settings table, then creates the very
+ # This migration first adds the +system_settings+ table, then creates the very
# first row in it using the Active Record model that relies on the table. It
- # also uses the more advanced create_table syntax where you can specify a
+ # also uses the more advanced +create_table+ syntax where you can specify a
# complete table schema in one block call.
#
# == Available transformations
#
- # * <tt>create_table(name, options)</tt> Creates a table called +name+ and
+ # * <tt>create_table(name, options)</tt>: Creates a table called +name+ and
# makes the table object available to a block that can then add columns to it,
- # following the same format as add_column. See example above. The options hash
+ # following the same format as +add_column+. See example above. The options hash
# is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create
# table definition.
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
+ # * <tt>change_table(name, options)</tt>: Allows to make column alterations to
+ # the table called +name+. It makes the table object availabe to a block that
+ # can then add/remove columns, indexes or foreign keys to it.
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
# to +new_name+.
# * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column
@@ -109,9 +112,9 @@ module ActiveRecord
# <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>,
# <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
# <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. A default value can be
- # specified by passing an +options+ hash like <tt>{ :default => 11 }</tt>.
+ # specified by passing an +options+ hash like <tt>{ default: 11 }</tt>.
# Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g.
- # <tt>{ :limit => 50, :null => false }</tt>) -- see
+ # <tt>{ limit: 50, null: false }</tt>) -- see
# ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
# * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames
# a column but keeps the type and content.
@@ -122,11 +125,11 @@ module ActiveRecord
# * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
# with the name of the column. Other options include
# <tt>:name</tt>, <tt>:unique</tt> (e.g.
- # <tt>{ :name => "users_name_index", :unique => true }</tt>) and <tt>:order</tt>
- # (e.g. { :order => {:name => :desc} }</tt>).
- # * <tt>remove_index(table_name, :column => column_name)</tt>: Removes the index
+ # <tt>{ name: 'users_name_index', unique: true }</tt>) and <tt>:order</tt>
+ # (e.g. <tt>{ order: { name: :desc } }</tt>).
+ # * <tt>remove_index(table_name, column: column_name)</tt>: Removes the index
# specified by +column_name+.
- # * <tt>remove_index(table_name, :name => index_name)</tt>: Removes the index
+ # * <tt>remove_index(table_name, name: index_name)</tt>: Removes the index
# specified by +index_name+.
#
# == Irreversible transformations
@@ -339,7 +342,9 @@ module ActiveRecord
end
def call(env)
- ActiveRecord::Migration.check_pending!
+ ActiveRecord::Base.logger.quietly do
+ ActiveRecord::Migration.check_pending!
+ end
@app.call(env)
end
end
@@ -637,7 +642,11 @@ module ActiveRecord
def proper_table_name(name)
# Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
- name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
+ if name.respond_to? :table_name
+ name.table_name
+ else
+ "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
+ end
end
def migrations_paths
@@ -727,9 +736,8 @@ module ActiveRecord
running = runnable
if block_given?
- ActiveSupport::Deprecation.warn(<<-eomsg)
-block argument to migrate is deprecated, please filter migrations before constructing the migrator
- eomsg
+ message = "block argument to migrate is deprecated, please filter migrations before constructing the migrator"
+ ActiveSupport::Deprecation.warn message
running.select! { |m| yield m }
end
diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb
deleted file mode 100644
index 44cde49bd5..0000000000
--- a/activerecord/lib/active_record/model.rb
+++ /dev/null
@@ -1,170 +0,0 @@
-require 'active_support/core_ext/module/attribute_accessors'
-
-module ActiveRecord
- module Configuration # :nodoc:
- # This just abstracts out how we define configuration options in AR. Essentially we
- # have mattr_accessors on the ActiveRecord:Model constant that define global defaults.
- # Classes that then use AR get class_attributes defined, which means that when they
- # are assigned the default will be overridden for that class and subclasses. (Except
- # when options[:global] == true, in which case there is one global value always.)
- def config_attribute(name, options = {})
- if options[:global]
- class_eval <<-CODE, __FILE__, __LINE__ + 1
- def self.#{name}; ActiveRecord::Model.#{name}; end
- def #{name}; ActiveRecord::Model.#{name}; end
- def self.#{name}=(val); ActiveRecord::Model.#{name} = val; end
- CODE
- else
- options[:instance_writer] ||= false
- class_attribute name, options
-
- singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
- remove_method :#{name}
- def #{name}; ActiveRecord::Model.#{name}; end
- CODE
- end
- end
- end
-
- # <tt>ActiveRecord::Model</tt> can be included into a class to add Active Record persistence.
- # This is an alternative to inheriting from <tt>ActiveRecord::Base</tt>. Example:
- #
- # class Post
- # include ActiveRecord::Model
- # end
- #
- module Model
- extend ActiveSupport::Concern
- extend ConnectionHandling
- extend ActiveModel::Observing::ClassMethods
-
- # This allows us to detect an ActiveRecord::Model while it's in the process of being included.
- module Tag; end
-
- def self.append_features(base)
- base.class_eval do
- include Tag
- extend Configuration
- end
-
- super
- end
-
- included do
- extend ActiveModel::Naming
- extend ActiveSupport::Benchmarkable
- extend ActiveSupport::DescendantsTracker
-
- extend QueryCache::ClassMethods
- extend Querying
- extend Translation
- extend DynamicMatchers
- extend Explain
- extend ConnectionHandling
-
- initialize_generated_modules unless self == Base
- end
-
- include Persistence
- include ReadonlyAttributes
- include ModelSchema
- include Inheritance
- include Scoping
- include Sanitization
- include AttributeAssignment
- include ActiveModel::Conversion
- include Integration
- include Validations
- include CounterCache
- include Locking::Optimistic
- include Locking::Pessimistic
- include AttributeMethods
- include Callbacks
- include ActiveModel::Observing
- include Timestamp
- include Associations
- include ActiveModel::SecurePassword
- include AutosaveAssociation
- include NestedAttributes
- include Aggregations
- include Transactions
- include Reflection
- include Serialization
- include Store
- include Core
-
- class << self
- def arel_engine
- self
- end
-
- def abstract_class?
- false
- end
-
- # Defines the name of the table column which will store the class name on single-table
- # inheritance situations.
- #
- # The default inheritance column name is +type+, which means it's a
- # reserved word inside Active Record. To be able to use single-table
- # inheritance with another column name, or to use the column +type+ in
- # your own model for something else, you can override this method to
- # return a different name:
- #
- # def self.inheritance_column
- # 'zoink'
- # end
- def inheritance_column
- 'type'
- end
- end
-
- class DeprecationProxy < BasicObject #:nodoc:
- def initialize(model = Model, base = Base)
- @model = model
- @base = base
- end
-
- def method_missing(name, *args, &block)
- if @model.respond_to?(name, true)
- @model.send(name, *args, &block)
- else
- ::ActiveSupport::Deprecation.warn(
- "The object passed to the active_record load hook was previously ActiveRecord::Base " \
- "(a Class). Now it is ActiveRecord::Model (a Module). You have called `#{name}' which " \
- "is only defined on ActiveRecord::Base. Please change your code so that it works with " \
- "a module rather than a class. (Model is included in Base, so anything added to Model " \
- "will be available on Base as well.)"
- )
- @base.send(name, *args, &block)
- end
- end
-
- alias send method_missing
-
- def extend(*mods)
- ::ActiveSupport::Deprecation.warn(
- "The object passed to the active_record load hook was previously ActiveRecord::Base " \
- "(a Class). Now it is ActiveRecord::Model (a Module). You have called `extend' which " \
- "would add singleton methods to Model. This is presumably not what you want, since the " \
- "methods would not be inherited down to Base. Rather than using extend, please use " \
- "ActiveSupport::Concern + include, which will ensure that your class methods are " \
- "inherited."
- )
- @base.extend(*mods)
- end
- end
- end
-
- # This hook is where config accessors on Model get defined.
- #
- # We don't want to just open the Model module and add stuff to it in other files, because
- # that would cause Model to load, which causes all sorts of loading order issues.
- #
- # We need this hook rather than just using the :active_record one, because users of the
- # :active_record hook may need to use config options.
- ActiveSupport.run_load_hooks(:active_record_config, Model)
-
- # Load Base at this point, because the active_record load hook is run in that file.
- Base
-end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 99de16cd33..628ab0f566 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -1,18 +1,4 @@
-
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- mattr_accessor :primary_key_prefix_type, instance_accessor: false
-
- mattr_accessor :table_name_prefix, instance_accessor: false
- self.table_name_prefix = ""
-
- mattr_accessor :table_name_suffix, instance_accessor: false
- self.table_name_suffix = ""
-
- mattr_accessor :pluralize_table_names, instance_accessor: false
- self.pluralize_table_names = true
- end
-
module ModelSchema
extend ActiveSupport::Concern
@@ -24,7 +10,7 @@ module ActiveRecord
# the Product class will look for "productid" instead of "id" as the primary column. If the
# latter is specified, the Product class will look for "product_id" instead of "id". Remember
# that this is a global setting for all Active Records.
- config_attribute :primary_key_prefix_type, global: true
+ mattr_accessor :primary_key_prefix_type, instance_writer: false
##
# :singleton-method:
@@ -36,20 +22,25 @@ module ActiveRecord
# If you are organising your models within modules you can add a prefix to the models within
# a namespace by defining a singleton method in the parent module called table_name_prefix which
# returns your chosen prefix.
- config_attribute :table_name_prefix
+ class_attribute :table_name_prefix, instance_writer: false
+ self.table_name_prefix = ""
##
# :singleton-method:
# Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
# "people_basecamp"). By default, the suffix is the empty string.
- config_attribute :table_name_suffix
+ class_attribute :table_name_suffix, instance_writer: false
+ self.table_name_suffix = ""
##
# :singleton-method:
# Indicates whether table names should be the pluralized versions of the corresponding class names.
# If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
# See table_name for the full rules on table/class naming. This is true, by default.
- config_attribute :pluralize_table_names
+ class_attribute :pluralize_table_names, instance_writer: false
+ self.pluralize_table_names = true
+
+ self.inheritance_column = 'type'
end
module ClassMethods
@@ -144,9 +135,9 @@ module ActiveRecord
# Computes the table name, (re)sets it internally, and returns it.
def reset_table_name #:nodoc:
self.table_name = if abstract_class?
- active_record_super == Base ? nil : active_record_super.table_name
- elsif active_record_super.abstract_class?
- active_record_super.table_name || compute_table_name
+ superclass == Base ? nil : superclass.table_name
+ elsif superclass.abstract_class?
+ superclass.table_name || compute_table_name
else
compute_table_name
end
@@ -156,9 +147,17 @@ module ActiveRecord
(parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
end
- # The name of the column containing the object's class when Single Table Inheritance is used
+ # Defines the name of the table column which will store the class name on single-table
+ # inheritance situations.
+ #
+ # The default inheritance column name is +type+, which means it's a
+ # reserved word inside Active Record. To be able to use single-table
+ # inheritance with another column name, or to use the column +type+ in
+ # your own model for something else, you can set +inheritance_column+:
+ #
+ # self.inheritance_column = 'zoink'
def inheritance_column
- (@inheritance_column ||= nil) || active_record_super.inheritance_column
+ (@inheritance_column ||= nil) || superclass.inheritance_column
end
# Sets the value of inheritance_column
@@ -286,7 +285,7 @@ module ActiveRecord
#
# JobLevel.reset_column_information
# %w{assistant executive manager director}.each do |type|
- # JobLevel.create(:name => type)
+ # JobLevel.create(name: type)
# end
# end
#
@@ -331,7 +330,7 @@ module ActiveRecord
base = base_class
if self == base
# Nested classes are prefixed with singular parent table name.
- if parent < ActiveRecord::Model && !parent.abstract_class?
+ if parent < Base && !parent.abstract_class?
contained = parent.table_name
contained = contained.singularize if parent.pluralize_table_names
contained += '_'
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 2e7fb3bbb3..4c9bd76d7c 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -3,11 +3,6 @@ require 'active_support/core_ext/object/try'
require 'active_support/core_ext/hash/indifferent_access'
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- mattr_accessor :nested_attributes_options, instance_accessor: false
- self.nested_attributes_options = {}
- end
-
module NestedAttributes #:nodoc:
class TooManyRecords < ActiveRecordError
end
@@ -15,7 +10,8 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :nested_attributes_options
+ class_attribute :nested_attributes_options, instance_writer: false
+ self.nested_attributes_options = {}
end
# = Active Record Nested Attributes
@@ -54,14 +50,14 @@ module ActiveRecord
# Enabling nested attributes on a one-to-one association allows you to
# create the member and avatar in one go:
#
- # params = { :member => { :name => 'Jack', :avatar_attributes => { :icon => 'smiling' } } }
+ # params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
# member = Member.create(params[:member])
# member.avatar.id # => 2
# member.avatar.icon # => 'smiling'
#
# It also allows you to update the avatar through the member:
#
- # params = { :member => { :avatar_attributes => { :id => '2', :icon => 'sad' } } }
+ # params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
# member.update_attributes params[:member]
# member.avatar.icon # => 'sad'
#
@@ -72,13 +68,13 @@ module ActiveRecord
#
# class Member < ActiveRecord::Base
# has_one :avatar
- # accepts_nested_attributes_for :avatar, :allow_destroy => true
+ # accepts_nested_attributes_for :avatar, allow_destroy: true
# end
#
# Now, when you add the <tt>_destroy</tt> key to the attributes hash, with a
# value that evaluates to +true+, you will destroy the associated model:
#
- # member.avatar_attributes = { :id => '2', :_destroy => '1' }
+ # member.avatar_attributes = { id: '2', _destroy: '1' }
# member.avatar.marked_for_destruction? # => true
# member.save
# member.reload.avatar # => nil
@@ -101,15 +97,15 @@ module ActiveRecord
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
# that evaluates to +true+.
#
- # params = { :member => {
- # :name => 'joe', :posts_attributes => [
- # { :title => 'Kari, the awesome Ruby documentation browser!' },
- # { :title => 'The egalitarian assumption of the modern citizen' },
- # { :title => '', :_destroy => '1' } # this will be ignored
+ # params = { member: {
+ # name: 'joe', posts_attributes: [
+ # { title: 'Kari, the awesome Ruby documentation browser!' },
+ # { title: 'The egalitarian assumption of the modern citizen' },
+ # { title: '', _destroy: '1' } # this will be ignored
# ]
# }}
#
- # member = Member.create(params['member'])
+ # member = Member.create(params[:member])
# member.posts.length # => 2
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
@@ -120,18 +116,18 @@ module ActiveRecord
#
# class Member < ActiveRecord::Base
# has_many :posts
- # accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? }
+ # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
# end
#
- # params = { :member => {
- # :name => 'joe', :posts_attributes => [
- # { :title => 'Kari, the awesome Ruby documentation browser!' },
- # { :title => 'The egalitarian assumption of the modern citizen' },
- # { :title => '' } # this will be ignored because of the :reject_if proc
+ # params = { member: {
+ # name: 'joe', posts_attributes: [
+ # { title: 'Kari, the awesome Ruby documentation browser!' },
+ # { title: 'The egalitarian assumption of the modern citizen' },
+ # { title: '' } # this will be ignored because of the :reject_if proc
# ]
# }}
#
- # member = Member.create(params['member'])
+ # member = Member.create(params[:member])
# member.posts.length # => 2
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
@@ -140,12 +136,12 @@ module ActiveRecord
#
# class Member < ActiveRecord::Base
# has_many :posts
- # accepts_nested_attributes_for :posts, :reject_if => :new_record?
+ # accepts_nested_attributes_for :posts, reject_if: :new_record?
# end
#
# class Member < ActiveRecord::Base
# has_many :posts
- # accepts_nested_attributes_for :posts, :reject_if => :reject_posts
+ # accepts_nested_attributes_for :posts, reject_if: :reject_posts
#
# def reject_posts(attributed)
# attributed['title'].blank?
@@ -156,10 +152,10 @@ module ActiveRecord
# associated record, the matching record will be modified:
#
# member.attributes = {
- # :name => 'Joe',
- # :posts_attributes => [
- # { :id => 1, :title => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
- # { :id => 2, :title => '[UPDATED] other post' }
+ # name: 'Joe',
+ # posts_attributes: [
+ # { id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
+ # { id: 2, title: '[UPDATED] other post' }
# ]
# }
#
@@ -174,14 +170,14 @@ module ActiveRecord
#
# class Member < ActiveRecord::Base
# has_many :posts
- # accepts_nested_attributes_for :posts, :allow_destroy => true
+ # accepts_nested_attributes_for :posts, allow_destroy: true
# end
#
- # params = { :member => {
- # :posts_attributes => [{ :id => '2', :_destroy => '1' }]
+ # params = { member: {
+ # posts_attributes: [{ id: '2', _destroy: '1' }]
# }}
#
- # member.attributes = params['member']
+ # member.attributes = params[:member]
# member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
# member.posts.length # => 2
# member.save
@@ -201,12 +197,12 @@ module ActiveRecord
# <tt>inverse_of</tt> as this example illustrates:
#
# class Member < ActiveRecord::Base
- # has_many :posts, :inverse_of => :member
+ # has_many :posts, inverse_of: :member
# accepts_nested_attributes_for :posts
# end
#
# class Post < ActiveRecord::Base
- # belongs_to :member, :inverse_of => :posts
+ # belongs_to :member, inverse_of: :posts
# validates_presence_of :member
# end
module ClassMethods
@@ -252,11 +248,11 @@ module ActiveRecord
#
# Examples:
# # creates avatar_attributes=
- # accepts_nested_attributes_for :avatar, :reject_if => proc { |attributes| attributes['name'].blank? }
+ # accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? }
# # creates avatar_attributes=
- # accepts_nested_attributes_for :avatar, :reject_if => :all_blank
+ # accepts_nested_attributes_for :avatar, reject_if: :all_blank
# # creates avatar_attributes= and posts_attributes=
- # accepts_nested_attributes_for :avatar, :posts, :allow_destroy => true
+ # accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
def accepts_nested_attributes_for(*attr_names)
options = { :allow_destroy => false, :update_only => false }
options.update(attr_names.extract_options!)
@@ -352,9 +348,9 @@ module ActiveRecord
# For example:
#
# assign_nested_attributes_for_collection_association(:people, {
- # '1' => { :id => '1', :name => 'Peter' },
- # '2' => { :name => 'John' },
- # '3' => { :id => '2', :_destroy => true }
+ # '1' => { id: '1', name: 'Peter' },
+ # '2' => { name: 'John' },
+ # '3' => { id: '2', _destroy: true }
# })
#
# Will update the name of the Person with ID 1, build a new associated
@@ -364,9 +360,9 @@ module ActiveRecord
# Also accepts an Array of attribute hashes:
#
# assign_nested_attributes_for_collection_association(:people, [
- # { :id => '1', :name => 'Peter' },
- # { :name => 'John' },
- # { :id => '2', :_destroy => true }
+ # { id: '1', name: 'Peter' },
+ # { name: 'John' },
+ # { id: '2', _destroy: true }
# ])
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
options = self.nested_attributes_options[association_name]
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 4c1c91e3df..711fc8b883 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -46,7 +46,11 @@ module ActiveRecord
{}
end
- def count
+ def count(*)
+ 0
+ end
+
+ def sum(*)
0
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 2eaad1d469..eed49e17b1 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -15,18 +15,18 @@ module ActiveRecord
#
# ==== Examples
# # Create a single new object
- # User.create(:first_name => 'Jamie')
+ # User.create(first_name: 'Jamie')
#
# # Create an Array of new objects
- # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
+ # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
#
# # Create a single object and pass it into a block to set other attributes.
- # User.create(:first_name => 'Jamie') do |u|
+ # User.create(first_name: 'Jamie') do |u|
# u.is_admin = false
# end
#
# # Creating an Array of new objects using a block, where the block is executed for each object:
- # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
+ # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
# u.is_admin = false
# end
def create(attributes = nil, &block)
@@ -64,7 +64,7 @@ module ActiveRecord
#
# By default, save always run validations. If any of them fail the action
# is cancelled and +save+ returns +false+. However, if you supply
- # :validate => false, validations are bypassed altogether. See
+ # validate: false, validations are bypassed altogether. See
# ActiveRecord::Validations for more information.
#
# There's a series of callbacks associated with +save+. If any of the
@@ -72,11 +72,9 @@ module ActiveRecord
# +save+ returns +false+. See ActiveRecord::Callbacks for further
# details.
def save(*)
- begin
- create_or_update
- rescue ActiveRecord::RecordInvalid
- false
- end
+ create_or_update
+ rescue ActiveRecord::RecordInvalid
+ false
end
# Saves the model.
@@ -143,7 +141,7 @@ module ActiveRecord
# inheritance structures where you want a subclass to appear as the
# superclass. This can be used along with record identification in
# Action Pack to allow, say, <tt>Client < Company</tt> to do something
- # like render <tt>:partial => @client.becomes(Company)</tt> to render that
+ # like render <tt>partial: @client.becomes(Company)</tt> to render that
# instance using the companies/company partial instead of clients/client.
#
# Note: The new instance will share a link to the same attributes as the original class.
@@ -155,7 +153,18 @@ module ActiveRecord
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
became.instance_variable_set("@errors", errors)
- became.public_send("#{klass.inheritance_column}=", klass.name) unless self.class.descends_from_active_record?
+ became
+ end
+
+ # Wrapper around +becomes+ that also changes the instance's sti column value.
+ # This is especially useful if you want to persist the changed class in your
+ # database.
+ #
+ # Note: The old instance's sti column value will be changed too, as both objects
+ # share the same set of attributes.
+ def becomes!(klass)
+ became = becomes(klass)
+ became.public_send("#{klass.inheritance_column}=", klass.sti_name) unless self.class.descends_from_active_record?
became
end
@@ -171,7 +180,7 @@ module ActiveRecord
name = name.to_s
verify_readonly_attribute(name)
send("#{name}=", value)
- save(:validate => false)
+ save(validate: false)
end
# Updates the attributes of the model from the passed-in hash and saves the
@@ -197,7 +206,7 @@ module ActiveRecord
end
end
- # Updates a single attribute of an object, without calling save.
+ # Updates a single attribute of an object, without having to explicitly call save on that object.
#
# * Validation is skipped.
# * Callbacks are skipped.
@@ -209,7 +218,7 @@ module ActiveRecord
update_columns(name => value)
end
- # Updates the attributes from the passed-in hash, without calling save.
+ # Updates the attributes from the passed-in hash, without having to explicitly call save on that object.
#
# * Validation is skipped.
# * Callbacks are skipped.
@@ -224,11 +233,13 @@ module ActiveRecord
verify_readonly_attribute(key.to_s)
end
- attributes.each do |k,v|
- raw_write_attribute(k,v)
+ updated_count = self.class.where(self.class.primary_key => id).update_all(attributes)
+
+ attributes.each do |k, v|
+ raw_write_attribute(k, v)
end
- self.class.where(self.class.primary_key => id).update_all(attributes) == 1
+ updated_count == 1
end
# Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
@@ -284,7 +295,7 @@ module ActiveRecord
# Reloads the attributes of this object from the database.
# The optional options argument is passed to find when reloading so you
- # may do e.g. record.reload(:lock => true) to reload the same record with
+ # may do e.g. record.reload(lock: true) to reload the same record with
# an exclusive row lock.
def reload(options = nil)
clear_aggregation_cache
@@ -315,11 +326,11 @@ module ActiveRecord
# If used along with +belongs_to+ then +touch+ will invoke +touch+ method on associated object.
#
# class Brake < ActiveRecord::Base
- # belongs_to :car, :touch => true
+ # belongs_to :car, touch: true
# end
#
# class Car < ActiveRecord::Base
- # belongs_to :corporation, :touch => true
+ # belongs_to :corporation, touch: true
# end
#
# # triggers @brake.car.touch and @brake.car.corporation.touch
@@ -377,16 +388,20 @@ module ActiveRecord
# Returns the number of affected rows.
def update(attribute_names = @attributes.keys)
attributes_with_values = arel_attributes_with_values_for_update(attribute_names)
- return 0 if attributes_with_values.empty?
- klass = self.class
- stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
- klass.connection.update stmt
+
+ if attributes_with_values.empty?
+ 0
+ else
+ klass = self.class
+ stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
+ klass.connection.update stmt
+ end
end
# Creates a record with values matching those of the instance attributes
# and returns its id.
- def create
- attributes_values = arel_attributes_with_values_for_create(!id.nil?)
+ def create(attribute_names = @attributes.keys)
+ attributes_values = arel_attributes_with_values_for_create(attribute_names)
new_id = self.class.unscoped.insert attributes_values
self.id ||= new_id if self.class.primary_key
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 2bd8ecda20..38e18b32a4 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -5,19 +5,19 @@ module ActiveRecord
module ClassMethods
# Enable the query cache within the block if Active Record is configured.
def cache(&block)
- if ActiveRecord::Base.configurations.blank?
- yield
- else
+ if ActiveRecord::Base.connected?
connection.cache(&block)
+ else
+ yield
end
end
# Disable the query cache within the block if Active Record is configured.
def uncached(&block)
- if ActiveRecord::Base.configurations.blank?
- yield
- else
+ if ActiveRecord::Base.connected?
connection.uncached(&block)
+ else
+ yield
end
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 13e09eda53..45f6a78428 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -3,6 +3,7 @@ module ActiveRecord
module Querying
delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :to => :all
delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :all
+ delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, :to => :all
delegate :find_by, :find_by!, :to => :all
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :all
delegate :find_each, :find_in_batches, :to => :all
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index a9f80ccd5f..5464ca6066 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -10,7 +10,7 @@ require "action_controller/railtie"
module ActiveRecord
# = Active Record Railtie
- class Railtie < Rails::Railtie
+ class Railtie < Rails::Railtie # :nodoc:
config.active_record = ActiveSupport::OrderedOptions.new
config.app_generators.orm :active_record, :migration => true,
@@ -76,13 +76,13 @@ module ActiveRecord
config.after_initialize do |app|
ActiveSupport.on_load(:active_record) do
filename = File.join(app.config.paths["db"].first, "schema_cache.dump")
-
+
if File.file?(filename)
cache = Marshal.load File.binread filename
if cache.version == ActiveRecord::Migrator.current_version
- ActiveRecord::Model.connection.schema_cache = cache
+ self.connection.schema_cache = cache
else
- warn "schema_cache.dump is expired. Current version is #{ActiveRecord::Migrator.current_version}, but cache version is #{cache.version}."
+ warn "Ignoring db/schema_cache.dump because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}."
end
end
end
@@ -92,6 +92,33 @@ module ActiveRecord
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
+ begin
+ old_behavior, ActiveSupport::Deprecation.behavior = ActiveSupport::Deprecation.behavior, :stderr
+ whitelist_attributes = app.config.active_record.delete(:whitelist_attributes)
+
+ if respond_to?(:mass_assignment_sanitizer=)
+ mass_assignment_sanitizer = nil
+ else
+ mass_assignment_sanitizer = app.config.active_record.delete(:mass_assignment_sanitizer)
+ end
+
+ unless whitelist_attributes.nil? && mass_assignment_sanitizer.nil?
+ ActiveSupport::Deprecation.warn <<-EOF.strip_heredoc, []
+ Model based mass assignment security has been extracted
+ out of Rails into a gem. Please use the new recommended protection model for
+ params or add `protected_attributes` to your Gemfile to use the old one.
+
+ To disable this message remove the `whitelist_attributes` option from your
+ `config/application.rb` file and any `mass_assignment_sanitizer` options
+ from your `config/environments/*.rb` files.
+
+ See http://edgeguides.rubyonrails.org/security.html#mass-assignment for more information
+ EOF
+ end
+ ensure
+ ActiveSupport::Deprecation.behavior = old_behavior
+ end
+
app.config.active_record.each do |k,v|
send "#{k}=", v
end
@@ -122,25 +149,27 @@ module ActiveRecord
ActiveSupport.on_load(:active_record) do
ActionDispatch::Reloader.send(hook) do
- ActiveRecord::Model.clear_reloadable_connections!
- ActiveRecord::Model.clear_cache!
+ if ActiveRecord::Base.connected?
+ ActiveRecord::Base.clear_reloadable_connections!
+ ActiveRecord::Base.clear_cache!
+ end
end
end
end
initializer "active_record.add_watchable_files" do |app|
- config.watchable_files.concat ["#{app.root}/db/schema.rb", "#{app.root}/db/structure.sql"]
+ path = app.paths["db"].first
+ config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"]
end
config.after_initialize do |app|
ActiveSupport.on_load(:active_record) do
- ActiveRecord::Model.instantiate_observers
+ instantiate_observers
ActionDispatch::Reloader.to_prepare do
- ActiveRecord::Model.instantiate_observers
+ ActiveRecord::Base.instantiate_observers
end
end
-
end
end
end
diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb
index c5db9b4625..7695eacbff 100644
--- a/activerecord/lib/active_record/railties/controller_runtime.rb
+++ b/activerecord/lib/active_record/railties/controller_runtime.rb
@@ -2,7 +2,7 @@ require 'active_support/core_ext/module/attr_internal'
require 'active_record/log_subscriber'
module ActiveRecord
- module Railties
+ module Railties # :nodoc:
module ControllerRuntime #:nodoc:
extend ActiveSupport::Concern
@@ -37,7 +37,7 @@ module ActiveRecord
end
end
- module ClassMethods
+ module ClassMethods # :nodoc:
def log_process_action(payload)
messages, db_runtime = super, payload[:db_runtime]
messages << ("ActiveRecord: %.1fms" % db_runtime.to_f) if db_runtime
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d134978128..0a9caa25b2 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -196,7 +196,7 @@ db_namespace = namespace :db do
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
- ActiveRecord::Fixtures.create_fixtures(fixtures_dir, fixture_file)
+ ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_file)
end
end
@@ -207,13 +207,13 @@ db_namespace = namespace :db do
label, id = ENV['LABEL'], ENV['ID']
raise 'LABEL or ID required' if label.blank? && id.blank?
- puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::Fixtures.identify(label)}.) if label
+ puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::FixtureSet.identify(label)}.) if label
base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
Dir["#{base_dir}/**/*.yml"].each do |file|
if data = YAML::load(ERB.new(IO.read(file)).result)
data.keys.each do |key|
- key_id = ActiveRecord::Fixtures.identify(key)
+ key_id = ActiveRecord::FixtureSet.identify(key)
if key == label || key_id == id.to_i
puts "#{file}: #{key} (#{key_id})"
@@ -307,7 +307,7 @@ db_namespace = namespace :db do
# desc "Recreate the databases from the structure.sql file"
task :load => [:environment, :load_config] do
- current_config = ActiveRecord::Tasks::DatabaseTasks.current_config(:env => (ENV['RAILS_ENV'] || 'test'))
+ current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
case current_config['adapter']
when /mysql/, /postgresql/, /sqlite/
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index b3c20c4aff..8499bb16e7 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -22,7 +22,8 @@ module ActiveRecord
end
def _attr_readonly
- ActiveSupport::Deprecation.warn("Instance level _attr_readonly method is deprecated, please use class level method.")
+ message = "Instance level _attr_readonly method is deprecated, please use class level method."
+ ActiveSupport::Deprecation.warn message
defined?(@_attr_readonly) ? @_attr_readonly : self.class._attr_readonly
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index f322b96f79..0103de4cbd 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -81,13 +81,13 @@ module ActiveRecord
class MacroReflection
# Returns the name of the macro.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
attr_reader :name
# Returns the macro type.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:composed_of</tt>
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
attr_reader :macro
@@ -95,7 +95,7 @@ module ActiveRecord
# Returns the hash of options used for the macro.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
# <tt>has_many :clients</tt> returns +{}+
attr_reader :options
@@ -115,7 +115,7 @@ module ActiveRecord
# Returns the class for the macro.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
# <tt>has_many :clients</tt> returns the Client class
def klass
@klass ||= class_name.constantize
@@ -123,7 +123,7 @@ module ActiveRecord
# Returns the class name for the macro.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
def class_name
@class_name ||= (options[:class_name] || derive_class_name).to_s
@@ -315,10 +315,10 @@ module ActiveRecord
# the parent's validation.
#
# Unless you explicitly disable validation with
- # <tt>:validate => false</tt>, validation will take place when:
+ # <tt>validate: false</tt>, validation will take place when:
#
- # * you explicitly enable validation; <tt>:validate => true</tt>
- # * you use autosave; <tt>:autosave => true</tt>
+ # * you explicitly enable validation; <tt>validate: true</tt>
+ # * you use autosave; <tt>autosave: true</tt>
# * the association is a +has_many+ association
def validate?
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
@@ -399,7 +399,7 @@ module ActiveRecord
#
# class Post < ActiveRecord::Base
# has_many :taggings
- # has_many :tags, :through => :taggings
+ # has_many :tags, through: :taggings
# end
#
def source_reflection
@@ -411,7 +411,7 @@ module ActiveRecord
#
# class Post < ActiveRecord::Base
# has_many :taggings
- # has_many :tags, :through => :taggings
+ # has_many :tags, through: :taggings
# end
#
# tags_reflection = Post.reflect_on_association(:tags)
@@ -439,12 +439,12 @@ module ActiveRecord
#
# class Person
# has_many :articles
- # has_many :comment_tags, :through => :articles
+ # has_many :comment_tags, through: :articles
# end
#
# class Article
# has_many :comments
- # has_many :comment_tags, :through => :comments, :source => :tags
+ # has_many :comment_tags, through: :comments, source: :tags
# end
#
# class Comment
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index ed80422336..3ee55c580e 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -91,8 +91,10 @@ module ActiveRecord
end
def initialize_copy(other)
- @values = @values.dup
- @values[:bind] = @values[:bind].dup if @values[:bind]
+ # This method is a hot spot, so for now, use Hash[] to dup the hash.
+ # https://bugs.ruby-lang.org/issues/7166
+ @values = Hash[@values]
+ @values[:bind] = @values[:bind].dup if @values.key? :bind
reset
end
@@ -127,46 +129,53 @@ module ActiveRecord
scoping { @klass.create!(*args, &block) }
end
- # Tries to load the first record; if it fails, then <tt>create</tt> is called with the same arguments as this method.
- #
- # Expects arguments in the same format as +Base.create+.
+ def first_or_create(attributes = nil, &block) # :nodoc:
+ first || create(attributes, &block)
+ end
+
+ def first_or_create!(attributes = nil, &block) # :nodoc:
+ first || create!(attributes, &block)
+ end
+
+ def first_or_initialize(attributes = nil, &block) # :nodoc:
+ first || new(attributes, &block)
+ end
+
+ # Finds the first record with the given attributes, or creates a record with the attributes
+ # if one is not found.
#
# ==== Examples
# # Find the first user named Penélope or create a new one.
- # User.where(:first_name => 'Penélope').first_or_create
+ # User.find_or_create_by(first_name: 'Penélope')
# # => <User id: 1, first_name: 'Penélope', last_name: nil>
#
# # Find the first user named Penélope or create a new one.
# # We already have one so the existing record will be returned.
- # User.where(:first_name => 'Penélope').first_or_create
+ # User.find_or_create_by(first_name: 'Penélope')
# # => <User id: 1, first_name: 'Penélope', last_name: nil>
#
# # Find the first user named Scarlett or create a new one with a particular last name.
- # User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson')
+ # User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett')
# # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
#
# # Find the first user named Scarlett or create a new one with a different last name.
# # We already have one so the existing record will be returned.
- # User.where(:first_name => 'Scarlett').first_or_create do |user|
+ # User.find_or_create_by(first_name: 'Scarlett') do |user|
# user.last_name = "O'Hara"
# end
# # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
- def first_or_create(attributes = nil, &block)
- first || create(attributes, &block)
+ def find_or_create_by(attributes, &block)
+ find_by(attributes) || create(attributes, &block)
end
- # Like <tt>first_or_create</tt> but calls <tt>create!</tt> so an exception is raised if the created record is invalid.
- #
- # Expects arguments in the same format as <tt>Base.create!</tt>.
- def first_or_create!(attributes = nil, &block)
- first || create!(attributes, &block)
+ # Like <tt>find_or_create_by</tt>, but calls <tt>create!</tt> so an exception is raised if the created record is invalid.
+ def find_or_create_by!(attributes, &block)
+ find_by(attributes) || create!(attributes, &block)
end
- # Like <tt>first_or_create</tt> but calls <tt>new</tt> instead of <tt>create</tt>.
- #
- # Expects arguments in the same format as <tt>Base.new</tt>.
- def first_or_initialize(attributes = nil, &block)
- first || new(attributes, &block)
+ # Like <tt>find_or_create_by</tt>, but calls <tt>new</tt> instead of <tt>create</tt>.
+ def find_or_initialize_by(attributes, &block)
+ find_by(attributes) || new(attributes, &block)
end
# Runs EXPLAIN on the query or queries triggered by this relation and
@@ -226,7 +235,7 @@ module ActiveRecord
# Scope all queries to the current scope.
#
- # Comment.where(:post_id => 1).scoping do
+ # Comment.where(post_id: 1).scoping do
# Comment.first # SELECT * FROM comments WHERE post_id = 1
# end
#
@@ -257,7 +266,7 @@ module ActiveRecord
# Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
#
# # Update all books that match conditions, but limit it to 5 ordered by date
- # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(:author => 'David')
+ # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
def update_all(updates)
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
@@ -330,7 +339,7 @@ module ActiveRecord
#
# Person.destroy_all("last_login < '2004-04-04'")
# Person.destroy_all(status: "inactive")
- # Person.where(:age => 0..18).destroy_all
+ # Person.where(age: 0..18).destroy_all
def destroy_all(conditions = nil)
if conditions
where(conditions).destroy_all
@@ -375,7 +384,7 @@ module ActiveRecord
#
# Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
# Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
- # Post.where(:person_id => 5).where(:category => ['Something', 'Else']).delete_all
+ # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
#
# Both calls delete the affected posts all at once with a single DELETE statement.
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
@@ -477,7 +486,7 @@ module ActiveRecord
# Returns a hash of where conditions
#
# Users.where(name: 'Oscar').where_values_hash
- # # => {:name=>"oscar"}
+ # # => {name: "oscar"}
def where_values_hash
equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
node.left.relation.name == table_name
@@ -505,7 +514,7 @@ module ActiveRecord
# Joins that are also marked for preloading. In which case we should just eager load them.
# Note that this is a naive implementation because we could have strings and symbols which
# represent the same association, but that aren't matched by this. Also, we could have
- # nested hashes which partially match, e.g. { :a => :b } & { :a => [:b, :c] }
+ # nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] }
def joined_includes_values
includes_values & joins_values
end
@@ -540,7 +549,7 @@ module ActiveRecord
end
def values
- @values.dup
+ Hash[@values]
end
def inspect
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index d32048cce1..b921f2eddb 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -36,12 +36,12 @@ module ActiveRecord
# want multiple workers dealing with the same processing queue. You can
# make worker 1 handle all the records between id 0 and 10,000 and
# worker 2 handle from 10,000 and beyond (by setting the +:start+
- # option on that worker). You can also use non-integer-based primary keys
- # if start point is set.
+ # option on that worker).
#
# It's not possible to set the order. That is automatically set to
- # ascending on the primary key (e.g. "id ASC") to make the batch ordering
- # work. You can't set the limit either, that's used to control
+ # ascending on the primary key ("id ASC") to make the batch ordering
+ # work. This also means that this method only works with integer-based
+ # primary keys. You can't set the limit either, that's used to control
# the batch sizes.
#
# Person.where("age > 21").find_in_batches do |group|
@@ -63,15 +63,14 @@ module ActiveRecord
end
start = options.delete(:start)
- start ||= 0
batch_size = options.delete(:batch_size) || 1000
relation = relation.reorder(batch_order).limit(batch_size)
- records = relation.where(table[primary_key].gteq(start)).to_a
+ records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
while records.any?
records_size = records.size
- primary_key_offset = records.last.send(primary_key)
+ primary_key_offset = records.last.id
yield records
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 7c43d844d0..741f94f777 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/object/try'
-
module ActiveRecord
module Calculations
# Count the records.
@@ -145,7 +143,7 @@ module ActiveRecord
# # SELECT DISTINCT role FROM people
# # => ['admin', 'member', 'guest']
#
- # Person.where(:age => 21).limit(5).pluck(:id)
+ # Person.where(age: 21).limit(5).pluck(:id)
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
# # => [2, 3]
#
@@ -165,7 +163,9 @@ module ActiveRecord
if has_include?(column_names.first)
construct_relation_for_association_calculations.pluck(*column_names)
else
- result = klass.connection.select_all(select(column_names).arel, nil, bind_values)
+ relation = spawn
+ relation.select_values = column_names
+ result = klass.connection.select_all(relation.arel, nil, bind_values)
columns = result.columns.map do |key|
klass.column_types.fetch(key) {
result.column_types.fetch(key) {
@@ -271,17 +271,19 @@ module ActiveRecord
group_fields = group_attrs
end
- group_aliases = group_fields.map { |field| column_alias_for(field) }
+ group_aliases = group_fields.map { |field|
+ column_alias_for(field)
+ }
group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
[aliaz, column_for(field)]
}
- group = @klass.connection.adapter_name == 'FrontBase' ? group_aliases : group_fields
+ group = group_fields
if operation == 'count' && column_name == :all
aggregate_alias = 'count_all'
else
- aggregate_alias = column_alias_for(operation, column_name)
+ aggregate_alias = column_alias_for([operation, column_name].join(' '))
end
select_values = [
@@ -300,7 +302,8 @@ module ActiveRecord
end
}
- relation = except(:group).group(group)
+ relation = except(:group)
+ relation.group_values = group
relation.select_values = select_values
calculated_data = @klass.connection.select_all(relation, nil, bind_values)
@@ -329,10 +332,12 @@ module ActiveRecord
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
# column_alias_for("count(*)") # => "count_all"
# column_alias_for("count", "id") # => "count_id"
- def column_alias_for(*keys)
- keys.map! {|k| k.respond_to?(:to_sql) ? k.to_sql : k}
- table_name = keys.join(' ')
- table_name.downcase!
+ def column_alias_for(keys)
+ if keys.respond_to? :name
+ keys = "#{keys.relation.name}.#{keys.name}"
+ end
+
+ table_name = keys.to_s.downcase
table_name.gsub!(/\*/, 'all')
table_name.gsub!(/\W+/, ' ')
table_name.strip!
@@ -343,13 +348,13 @@ module ActiveRecord
def column_for(field)
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
- @klass.columns.detect { |c| c.name.to_s == field_name }
+ @klass.columns_hash[field_name]
end
def type_cast_calculated_value(value, column, operation = nil)
case operation
when 'count' then value.to_i
- when 'sum' then type_cast_using_column(value || '0', column)
+ when 'sum' then type_cast_using_column(value || 0, column)
when 'average' then value.respond_to?(:to_d) ? value.to_d : value
else type_cast_using_column(value, column)
end
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index ab8b36c8ab..dbfa92bbbd 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,3 +1,4 @@
+require 'thread'
module ActiveRecord
module Delegation # :nodoc:
@@ -6,6 +7,8 @@ module ActiveRecord
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass
+ @@delegation_mutex = Mutex.new
+
def self.delegate_to_scoped_klass(method)
if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
module_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -32,13 +35,28 @@ module ActiveRecord
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
- ::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
+ @@delegation_mutex.synchronize do
+ unless ::ActiveRecord::Delegation.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
+ end
+ end
+
scoping { @klass.send(method, *args, &block) }
elsif Array.method_defined?(method)
- ::ActiveRecord::Delegation.delegate method, :to => :to_a
+ @@delegation_mutex.synchronize do
+ unless ::ActiveRecord::Delegation.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate method, :to => :to_a
+ end
+ end
+
to_a.send(method, *args, &block)
elsif arel.respond_to?(method)
- ::ActiveRecord::Delegation.delegate method, :to => :arel
+ @@delegation_mutex.synchronize do
+ unless ::ActiveRecord::Delegation.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate method, :to => :arel
+ end
+ end
+
arel.send(method, *args, &block)
else
super
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 84aaa39fed..eafe4a54c4 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/hash/indifferent_access'
-
module ActiveRecord
module FinderMethods
# Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
@@ -78,7 +76,7 @@ module ActiveRecord
#
# Person.first # returns the first object fetched by SELECT * FROM people
# Person.where(["user_name = ?", user_name]).first
- # Person.where(["user_name = :u", { :u => user_name }]).first
+ # Person.where(["user_name = :u", { u: user_name }]).first
# Person.order("created_on DESC").offset(5).first
# Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
def first(limit = nil)
@@ -159,7 +157,7 @@ module ActiveRecord
# Person.exists?(false)
# Person.exists?
def exists?(conditions = :none)
- conditions = conditions.id if ActiveRecord::Model === conditions
+ conditions = conditions.id if Base === conditions
return false if !conditions
join_dependency = construct_join_dependency_for_association_find
@@ -225,7 +223,7 @@ module ActiveRecord
def construct_limited_ids_condition(relation)
orders = relation.order_values.map { |val| val.presence }.compact
- values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
+ values = @klass.connection.distinct("#{quoted_table_name}.#{primary_key}", orders)
relation = relation.dup
@@ -234,8 +232,6 @@ module ActiveRecord
end
def find_with_ids(*ids)
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
-
expects_array = ids.first.kind_of?(Array)
return ids.first if expects_array && ids.first.empty?
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index e5b50673da..59226d316e 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -22,7 +22,17 @@ module ActiveRecord
# the values.
def other
other = Relation.new(relation.klass, relation.table)
- hash.each { |k, v| other.send("#{k}!", v) }
+ hash.each { |k, v|
+ if k == :joins
+ if Hash === v
+ other.joins!(v)
+ else
+ other.joins!(*v)
+ end
+ else
+ other.send("#{k}!", v)
+ end
+ }
other
end
end
@@ -39,16 +49,18 @@ module ActiveRecord
@values = other.values
end
+ NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
+ Relation::MULTI_VALUE_METHODS -
+ [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
+
def normal_values
- Relation::SINGLE_VALUE_METHODS +
- Relation::MULTI_VALUE_METHODS -
- [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from]
+ NORMAL_VALUES
end
def merge
normal_values.each do |name|
value = values[name]
- relation.send("#{name}!", value) unless value.blank?
+ relation.send("#{name}!", *value) unless value.blank?
end
merge_multi_values
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 263fdce250..83074e72c1 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -36,11 +36,11 @@ module ActiveRecord
queries = []
# Find the foreign key when using queries such as:
- # Post.where(:author => author)
+ # Post.where(author: author)
#
# For polymorphic relationships, find the foreign key and type:
- # PriceEstimate.where(:estimate_of => treasure)
- if klass && value.class < Model::Tag && reflection = klass.reflect_on_association(column.to_sym)
+ # PriceEstimate.where(estimate_of: treasure)
+ if klass && value.class < Base && reflection = klass.reflect_on_association(column.to_sym)
if reflection.polymorphic?
queries << build(table[reflection.foreign_type], value.class.base_class)
end
@@ -67,7 +67,7 @@ module ActiveRecord
def self.build(attribute, value)
case value
when Array, ActiveRecord::Associations::CollectionProxy
- values = value.to_a.map {|x| x.is_a?(ActiveRecord::Model) ? x.id : x}
+ values = value.to_a.map {|x| x.is_a?(Base) ? x.id : x}
ranges, values = values.partition {|v| v.is_a?(Range)}
values_predicate = if values.include?(nil)
@@ -93,7 +93,7 @@ module ActiveRecord
attribute.in(value.arel.ast)
when Range
attribute.in(value)
- when ActiveRecord::Model
+ when ActiveRecord::Base
attribute.eq(value.id)
when Class
# FIXME: I think we need to deprecate this behavior
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 3c59bd8a68..b3712b4ad6 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -202,6 +202,15 @@ module ActiveRecord
#
# User.order('name DESC, email')
# => SELECT "users".* FROM "users" ORDER BY name DESC, email
+ #
+ # User.order(:name)
+ # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
+ #
+ # User.order(email: :desc)
+ # => SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
+ #
+ # User.order(:name, email: :desc)
+ # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
def order(*args)
args.blank? ? self : spawn.order!(*args)
end
@@ -209,6 +218,7 @@ module ActiveRecord
# Like #order, but modifies relation in place.
def order!(*args)
args.flatten!
+ validate_order_args args
references = args.reject { |arg| Arel::Node === arg }
references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
@@ -234,6 +244,7 @@ module ActiveRecord
# Like #reorder, but modifies relation in place.
def reorder!(*args)
args.flatten!
+ validate_order_args args
self.reordering_value = true
self.order_values = args
@@ -245,13 +256,11 @@ module ActiveRecord
# User.joins(:posts)
# => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
def joins(*args)
- args.compact.blank? ? self : spawn.joins!(*args)
+ args.compact.blank? ? self : spawn.joins!(*args.flatten)
end
# Like #joins, but modifies relation in place.
def joins!(*args)
- args.flatten!
-
self.joins_values += args
self
end
@@ -346,17 +355,17 @@ module ActiveRecord
# author = Author.find(1)
#
# # The following queries will be equivalent:
- # Post.where(:author => author)
- # Post.where(:author_id => author)
+ # Post.where(author: author)
+ # Post.where(author_id: author)
#
# This also works with polymorphic belongs_to relationships:
#
- # treasure = Treasure.create(:name => 'gold coins')
- # treasure.price_estimates << PriceEstimate.create(:price => 125)
+ # treasure = Treasure.create(name: 'gold coins')
+ # treasure.price_estimates << PriceEstimate.create(price: 125)
#
# # The following queries will be equivalent:
- # PriceEstimate.where(:estimate_of => treasure)
- # PriceEstimate.where(:estimate_of_type => 'Treasure', :estimate_of_id => treasure)
+ # PriceEstimate.where(estimate_of: treasure)
+ # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
#
# === Joins
#
@@ -368,7 +377,7 @@ module ActiveRecord
# For hash conditions, you can either use the table name in the key, or use a sub-hash.
#
# User.joins(:posts).where({ "posts.published" => true })
- # User.joins(:posts).where({ :posts => { :published => true } })
+ # User.joins(:posts).where({ posts: { published: true } })
#
# === empty condition
#
@@ -467,13 +476,13 @@ module ActiveRecord
#
# For example:
#
- # @posts = current_user.visible_posts.where(:name => params[:name])
+ # @posts = current_user.visible_posts.where(name: params[:name])
# # => the visible_posts method is expected to return a chainable Relation
#
# def visible_posts
# case role
# when 'Country Manager'
- # Post.where(:country => country)
+ # Post.where(country: country)
# when 'Reviewer'
# Post.published
# when 'Bad User'
@@ -485,6 +494,11 @@ module ActiveRecord
extending(NullRelation)
end
+ # Like #none, but modifies relation in place.
+ def none!
+ extending!(NullRelation)
+ end
+
# Sets readonly attributes for the returned relation. If value is
# true (default), attempting to update a record will result in an error.
#
@@ -658,9 +672,7 @@ module ActiveRecord
arel.group(*group_values.uniq.reject{|g| g.blank?}) unless group_values.empty?
- order = order_values
- order = reverse_sql_order(order) if reverse_order_value
- arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
+ build_order(arel)
build_select(arel, select_values.uniq)
@@ -729,22 +741,22 @@ module ActiveRecord
buckets = joins.group_by do |join|
case join
when String
- 'string_join'
+ :string_join
when Hash, Symbol, Array
- 'association_join'
+ :association_join
when ActiveRecord::Associations::JoinDependency::JoinAssociation
- 'stashed_join'
+ :stashed_join
when Arel::Nodes::Join
- 'join_node'
+ :join_node
else
raise 'unknown class: %s' % join.class.name
end
end
- association_joins = buckets['association_join'] || []
- stashed_association_joins = buckets['stashed_join'] || []
- join_nodes = (buckets['join_node'] || []).uniq
- string_joins = (buckets['string_join'] || []).map { |x|
+ association_joins = buckets[:association_join] || []
+ stashed_association_joins = buckets[:stashed_join] || []
+ join_nodes = (buckets[:join_node] || []).uniq
+ string_joins = (buckets[:string_join] || []).map { |x|
x.strip
}.uniq
@@ -782,24 +794,56 @@ module ActiveRecord
def reverse_sql_order(order_query)
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
- order_query.map do |o|
+ order_query.flat_map do |o|
case o
when Arel::Nodes::Ordering
o.reverse
- when String, Symbol
+ when String
o.to_s.split(',').collect do |s|
s.strip!
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
end
+ when Symbol
+ { o => :desc }
+ when Hash
+ o.each_with_object({}) do |(field, dir), memo|
+ memo[field] = (dir == :asc ? :desc : :asc )
+ end
else
o
end
- end.flatten
+ end
end
def array_of_strings?(o)
o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
end
+ def build_order(arel)
+ orders = order_values
+ orders = reverse_sql_order(orders) if reverse_order_value
+
+ orders = orders.uniq.reject(&:blank?).flat_map do |order|
+ case order
+ when Symbol
+ table[order].asc
+ when Hash
+ order.map { |field, dir| table[field].send(dir) }
+ else
+ order
+ end
+ end
+
+ arel.order(*orders) unless orders.empty?
+ end
+
+ def validate_order_args(args)
+ args.select { |a| Hash === a }.each do |h|
+ unless (h.values - [:asc, :desc]).empty?
+ raise ArgumentError, 'Direction should be :asc or :desc'
+ end
+ end
+ end
+
end
end
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 5394c1b28b..62dda542ab 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -15,11 +15,11 @@ module ActiveRecord
#
# ==== Examples
#
- # Post.where(:published => true).joins(:comments).merge( Comment.where(:spam => false) )
+ # Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
# # Performs a single join query with both where conditions.
#
# recent_posts = Post.order('created_at DESC').first(5)
- # Post.where(:published => true).merge(recent_posts)
+ # Post.where(published: true).merge(recent_posts)
# # Returns the intersection of all published posts with the 5 most recently created posts.
# # (This is just an example. You'd probably want to do this with a single query!)
#
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 8905137142..bea195e9b8 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -53,12 +53,15 @@ module ActiveRecord
private
def hash_rows
- @hash_rows ||= @rows.map { |row|
- # We freeze the strings to prevent them getting duped when
- # used as keys in ActiveRecord::Model's @attributes hash
- columns = @columns.map { |c| c.freeze }
- Hash[columns.zip(row)]
- }
+ @hash_rows ||=
+ begin
+ # We freeze the strings to prevent them getting duped when
+ # used as keys in ActiveRecord::Base's @attributes hash
+ columns = @columns.map { |c| c.dup.freeze }
+ @rows.map { |row|
+ Hash[columns.zip(row)]
+ }
+ end
end
end
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 42b4cff4b8..2dad1dc177 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -17,7 +17,7 @@ module ActiveRecord
# Accepts an array, hash, or string of SQL conditions and sanitizes
# them into a valid SQL fragment for a WHERE clause.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
- # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
+ # { name: "foo'bar", group_id: 4 } returns "name='foo''bar' and group_id='4'"
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
def sanitize_sql_for_conditions(condition, table_name = self.table_name)
return nil if condition.blank?
@@ -32,7 +32,7 @@ module ActiveRecord
# Accepts an array, hash, or string of SQL conditions and sanitizes
# them into a valid SQL fragment for a SET clause.
- # { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
+ # { name: nil, group_id: 4 } returns "name = NULL , group_id='4'"
def sanitize_sql_for_assignment(assignments)
case assignments
when Array; sanitize_sql_array(assignments)
@@ -46,12 +46,12 @@ module ActiveRecord
# aggregate attribute values.
# Given:
# class Person < ActiveRecord::Base
- # composed_of :address, :class_name => "Address",
- # :mapping => [%w(address_street street), %w(address_city city)]
+ # composed_of :address, class_name: "Address",
+ # mapping: [%w(address_street street), %w(address_city city)]
# end
# Then:
- # { :address => Address.new("813 abc st.", "chicago") }
- # # => { :address_street => "813 abc st.", :address_city => "chicago" }
+ # { address: Address.new("813 abc st.", "chicago") }
+ # # => { address_street: "813 abc st.", address_city: "chicago" }
def expand_hash_conditions_for_aggregates(attrs)
expanded_attrs = {}
attrs.each do |attr, value|
@@ -72,18 +72,18 @@ module ActiveRecord
end
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
- # { :name => "foo'bar", :group_id => 4 }
+ # { name: "foo'bar", group_id: 4 }
# # => "name='foo''bar' and group_id= 4"
- # { :status => nil, :group_id => [1,2,3] }
+ # { status: nil, group_id: [1,2,3] }
# # => "status IS NULL and group_id IN (1,2,3)"
- # { :age => 13..18 }
+ # { age: 13..18 }
# # => "age BETWEEN 13 AND 18"
# { 'other_records.id' => 7 }
# # => "`other_records`.`id` = 7"
- # { :other_records => { :id => 7 } }
+ # { other_records: { id: 7 } }
# # => "`other_records`.`id` = 7"
# And for value objects on a composed_of relationship:
- # { :address => Address.new("123 abc st.", "chicago") }
+ # { address: Address.new("123 abc st.", "chicago") }
# # => "address_street='123 abc st.' and address_city='chicago'"
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
attrs = expand_hash_conditions_for_aggregates(attrs)
@@ -96,7 +96,7 @@ module ActiveRecord
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
- # { :status => nil, :group_id => 1 }
+ # { status: nil, group_id: 1 }
# # => "status = NULL , group_id = 1"
def sanitize_sql_hash_for_assignment(attrs)
attrs.map do |attr, value|
@@ -141,23 +141,6 @@ module ActiveRecord
end
end
- def expand_range_bind_variables(bind_vars) #:nodoc:
- expanded = []
-
- bind_vars.each do |var|
- next if var.is_a?(Hash)
-
- if var.is_a?(Range)
- expanded << var.first
- expanded << var.last
- else
- expanded << var
- end
- end
-
- expanded
- end
-
def quote_bound_value(value, c = connection) #:nodoc:
if value.respond_to?(:map) && !value.acts_like?(:string)
if value.respond_to?(:empty?) && value.empty?
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index a2a85d4b96..6835d0e01b 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -5,17 +5,17 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- # Stores the default scope for the class
+ # Stores the default scope for the class.
class_attribute :default_scopes, instance_writer: false
self.default_scopes = []
end
module ClassMethods
- # Returns a scope for the model without the default_scope.
+ # Returns a scope for the model without the +default_scope+.
#
# class Post < ActiveRecord::Base
# def self.default_scope
- # where :published => true
+ # where published: true
# end
# end
#
@@ -23,16 +23,16 @@ module ActiveRecord
# Post.unscoped.all # Fires "SELECT * FROM posts"
#
# This method also accepts a block. All queries inside the block will
- # not use the default_scope:
+ # not use the +default_scope+:
#
# Post.unscoped {
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
# }
#
# It is recommended that you use the block form of unscoped because
- # chaining unscoped with <tt>scope</tt> does not work. Assuming that
- # <tt>published</tt> is a <tt>scope</tt>, the following two statements
- # are equal: the <tt>default_scope</tt> is applied on both.
+ # chaining unscoped with +scope+ does not work. Assuming that
+ # +published+ is a +scope+, the following two statements
+ # are equal: the +default_scope+ is applied on both.
#
# Post.unscoped.published
# Post.published
@@ -50,35 +50,37 @@ module ActiveRecord
# the model.
#
# class Article < ActiveRecord::Base
- # default_scope { where(:published => true) }
+ # default_scope { where(published: true) }
# end
#
# Article.all # => SELECT * FROM articles WHERE published = true
#
- # The <tt>default_scope</tt> is also applied while creating/building a record. It is not
- # applied while updating a record.
+ # The +default_scope+ is also applied while creating/building a record.
+ # It is not applied while updating a record.
#
# Article.new.published # => true
# Article.create.published # => true
#
- # (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
- # macro, and it will be called when building the default scope.)
+ # (You can also pass any object which responds to +call+ to the
+ # +default_scope+ macro, and it will be called when building the
+ # default scope.)
#
- # If you use multiple <tt>default_scope</tt> declarations in your model then they will
- # be merged together:
+ # If you use multiple +default_scope+ declarations in your model then
+ # they will be merged together:
#
# class Article < ActiveRecord::Base
- # default_scope { where(:published => true) }
- # default_scope { where(:rating => 'G') }
+ # default_scope { where(published: true) }
+ # default_scope { where(rating: 'G') }
# end
#
# Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
#
- # This is also the case with inheritance and module includes where the parent or module
- # defines a <tt>default_scope</tt> and the child or including class defines a second one.
+ # This is also the case with inheritance and module includes where the
+ # parent or module defines a +default_scope+ and the child or including
+ # class defines a second one.
#
- # If you need to do more complex things with a default scope, you can alternatively
- # define it as a class method:
+ # If you need to do more complex things with a default scope, you can
+ # alternatively define it as a class method:
#
# class Article < ActiveRecord::Base
# def self.default_scope
@@ -100,7 +102,7 @@ module ActiveRecord
self.default_scopes = default_scopes + [scope]
end
- def build_default_scope #:nodoc:
+ def build_default_scope # :nodoc:
if !Base.is_a?(method(:default_scope).owner)
# The user has defined their own default scope method, so call that
evaluate_default_scope { default_scope }
@@ -117,17 +119,18 @@ module ActiveRecord
end
end
- def ignore_default_scope? #:nodoc:
+ def ignore_default_scope? # :nodoc:
Thread.current["#{self}_ignore_default_scope"]
end
- def ignore_default_scope=(ignore) #:nodoc:
+ def ignore_default_scope=(ignore) # :nodoc:
Thread.current["#{self}_ignore_default_scope"] = ignore
end
- # The ignore_default_scope flag is used to prevent an infinite recursion situation where
- # a default scope references a scope which has a default scope which references a scope...
- def evaluate_default_scope
+ # The ignore_default_scope flag is used to prevent an infinite recursion
+ # situation where a default scope references a scope which has a default
+ # scope which references a scope...
+ def evaluate_default_scope # :nodoc:
return if ignore_default_scope?
begin
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 75f31229b5..fb5f5b5be0 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/kernel/singleton_class'
module ActiveRecord
- # = Active Record Named \Scopes
+ # = Active Record \Named \Scopes
module Scoping
module Named
extend ActiveSupport::Concern
@@ -16,11 +16,11 @@ module ActiveRecord
# posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
#
# fruits = Fruit.all
- # fruits = fruits.where(:color => 'red') if options[:red_only]
+ # fruits = fruits.where(color: 'red') if options[:red_only]
# fruits = fruits.limit(10) if limited?
#
- # You can define a \scope that applies to all finders using
- # ActiveRecord::Base.default_scope.
+ # You can define a scope that applies to all finders using
+ # <tt>ActiveRecord::Base.default_scope</tt>.
def all
if current_scope
current_scope.clone
@@ -31,7 +31,6 @@ module ActiveRecord
end
end
- ##
# Collects attributes from scopes that should be applied when creating
# an AR instance for the particular class this is called on.
def scope_attributes # :nodoc:
@@ -44,86 +43,70 @@ module ActiveRecord
end
end
- ##
# Are there default attributes associated with this scope?
def scope_attributes? # :nodoc:
current_scope || default_scopes.any?
end
- # Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query,
- # such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>.
+ # Adds a class method for retrieving and querying objects. A \scope
+ # represents a narrowing of a database query, such as
+ # <tt>where(color: :red).select('shirts.*').includes(:washing_instructions)</tt>.
#
# class Shirt < ActiveRecord::Base
- # scope :red, where(:color => 'red')
- # scope :dry_clean_only, joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true)
+ # scope :red, -> { where(color: 'red') }
+ # scope :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) }
# end
#
- # The above calls to <tt>scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
- # in effect, represents the query <tt>Shirt.where(:color => 'red')</tt>.
+ # The above calls to +scope+ define class methods <tt>Shirt.red</tt> and
+ # <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>, in effect,
+ # represents the query <tt>Shirt.where(color: 'red')</tt>.
#
- # Note that this is simply 'syntactic sugar' for defining an actual class method:
+ # You should always pass a callable object to the scopes defined
+ # with +scope+. This ensures that the scope is re-evaluated each
+ # time it is called.
+ #
+ # Note that this is simply 'syntactic sugar' for defining an actual
+ # class method:
#
# class Shirt < ActiveRecord::Base
# def self.red
- # where(:color => 'red')
+ # where(color: 'red')
# end
# end
#
- # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it
- # resembles the association object constructed by a <tt>has_many</tt> declaration. For instance,
- # you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>, <tt>Shirt.red.where(:size => 'small')</tt>.
- # Also, just as with the association objects, named \scopes act like an Array, implementing Enumerable;
- # <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt>
- # all behave as if Shirt.red really was an Array.
- #
- # These named \scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce
- # all shirts that are both red and dry clean only.
- # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
- # returns the number of garments for which these criteria obtain. Similarly with
- # <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
- #
- # All \scopes are available as class methods on the ActiveRecord::Base descendant upon which
- # the \scopes were defined. But they are also available to <tt>has_many</tt> associations. If,
+ # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by
+ # <tt>Shirt.red</tt> is not an Array; it resembles the association object
+ # constructed by a +has_many+ declaration. For instance, you can invoke
+ # <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>,
+ # <tt>Shirt.red.where(size: 'small')</tt>. Also, just as with the
+ # association objects, named \scopes act like an Array, implementing
+ # Enumerable; <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>,
+ # and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if
+ # <tt>Shirt.red</tt> really was an Array.
+ #
+ # These named \scopes are composable. For instance,
+ # <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are
+ # both red and dry clean only. Nested finds and calculations also work
+ # with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
+ # returns the number of garments for which these criteria obtain.
+ # Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
+ #
+ # All scopes are available as class methods on the ActiveRecord::Base
+ # descendant upon which the \scopes were defined. But they are also
+ # available to +has_many+ associations. If,
#
# class Person < ActiveRecord::Base
# has_many :shirts
# end
#
- # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
- # only shirts.
- #
- # Named \scopes can also be procedural:
- #
- # class Shirt < ActiveRecord::Base
- # scope :colored, lambda { |color| where(:color => color) }
- # end
- #
- # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
- #
- # On Ruby 1.9 you can use the 'stabby lambda' syntax:
- #
- # scope :colored, ->(color) { where(:color => color) }
- #
- # Note that scopes defined with \scope will be evaluated when they are defined, rather than
- # when they are used. For example, the following would be incorrect:
- #
- # class Post < ActiveRecord::Base
- # scope :recent, where('published_at >= ?', Time.current - 1.week)
- # end
- #
- # The example above would be 'frozen' to the <tt>Time.current</tt> value when the <tt>Post</tt>
- # class was defined, and so the resultant SQL query would always be the same. The correct
- # way to do this would be via a lambda, which will re-evaluate the scope each time
- # it is called:
- #
- # class Post < ActiveRecord::Base
- # scope :recent, lambda { where('published_at >= ?', Time.current - 1.week) }
- # end
+ # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of
+ # Elton's red, dry clean only shirts.
#
- # Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
+ # \Named scopes can also have extensions, just as with +has_many+
+ # declarations:
#
# class Shirt < ActiveRecord::Base
- # scope :red, where(:color => 'red') do
+ # scope :red, -> { where(color: 'red') } do
# def dom_id
# 'red_shirts'
# end
@@ -133,18 +116,18 @@ module ActiveRecord
# Scopes can also be used while creating/building a record.
#
# class Article < ActiveRecord::Base
- # scope :published, where(:published => true)
+ # scope :published, -> { where(published: true) }
# end
#
# Article.published.new.published # => true
# Article.published.create.published # => true
#
- # Class methods on your model are automatically available
+ # \Class methods on your model are automatically available
# on scopes. Assuming the following setup:
#
# class Article < ActiveRecord::Base
- # scope :published, where(:published => true)
- # scope :featured, where(:featured => true)
+ # scope :published, -> { where(published: true) }
+ # scope :featured, -> { where(featured: true) }
#
# def self.latest_article
# order('published_at desc').first
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index e8dd312a47..6b55af4205 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -1,19 +1,11 @@
module ActiveRecord #:nodoc:
- ActiveSupport.on_load(:active_record_config) do
- mattr_accessor :include_root_in_json, instance_accessor: false
- self.include_root_in_json = true
- end
-
# = Active Record Serialization
module Serialization
extend ActiveSupport::Concern
include ActiveModel::Serializers::JSON
included do
- singleton_class.class_eval do
- remove_method :include_root_in_json
- delegate :include_root_in_json, to: 'ActiveRecord::Model'
- end
+ self.include_root_in_json = true
end
def serializable_hash(options = nil)
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index 834d01a1e8..1a766093d0 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -36,7 +36,7 @@ module ActiveRecord #:nodoc:
#
# For instance:
#
- # topic.to_xml(:skip_instruct => true, :except => [ :id, :bonus_time, :written_on, :replies_count ])
+ # topic.to_xml(skip_instruct: true, except: [ :id, :bonus_time, :written_on, :replies_count ])
#
# <topic>
# <title>The First Topic</title>
@@ -50,7 +50,7 @@ module ActiveRecord #:nodoc:
#
# To include first level associations use <tt>:include</tt>:
#
- # firm.to_xml :include => [ :account, :clients ]
+ # firm.to_xml include: [ :account, :clients ]
#
# <?xml version="1.0" encoding="UTF-8"?>
# <firm>
@@ -81,7 +81,7 @@ module ActiveRecord #:nodoc:
# associated with models.
#
# proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
- # firm.to_xml :procs => [ proc ]
+ # firm.to_xml procs: [ proc ]
#
# <firm>
# # ... normal attributes as shown above ...
@@ -90,7 +90,7 @@ module ActiveRecord #:nodoc:
#
# To include deeper levels of associations pass a hash like this:
#
- # firm.to_xml :include => {:account => {}, :clients => {:include => :address}}
+ # firm.to_xml include: {account: {}, clients: {include: :address}}
# <?xml version="1.0" encoding="UTF-8"?>
# <firm>
# <id type="integer">1</id>
@@ -120,7 +120,7 @@ module ActiveRecord #:nodoc:
#
# To include any methods on the model being called use <tt>:methods</tt>:
#
- # firm.to_xml :methods => [ :calculated_earnings, :real_earnings ]
+ # firm.to_xml methods: [ :calculated_earnings, :real_earnings ]
#
# <firm>
# # ... normal attributes as shown above ...
@@ -132,7 +132,7 @@ module ActiveRecord #:nodoc:
# modified version of the options hash that was given to +to_xml+:
#
# proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
- # firm.to_xml :procs => [ proc ]
+ # firm.to_xml procs: [ proc ]
#
# <firm>
# # ... normal attributes as shown above ...
@@ -164,7 +164,7 @@ module ActiveRecord #:nodoc:
# def to_xml(options = {})
# require 'builder'
# options[:indent] ||= 2
- # xml = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
+ # xml = options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
# xml.instruct! unless options[:skip_instruct]
# xml.level_one do
# xml.tag!(:second_level, 'content')
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 2340f949b7..3d27c97254 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -74,10 +74,16 @@ module ActiveRecord
end
def creation_options
- {
- charset: (configuration['encoding'] || DEFAULT_CHARSET),
- collation: (configuration['collation'] || DEFAULT_COLLATION)
- }
+ Hash.new.tap do |options|
+ options[:charset] = configuration['encoding'] if configuration.include? 'encoding'
+ options[:collation] = configuration['collation'] if configuration.include? 'collation'
+
+ # Set default charset only when collation isn't set.
+ options[:charset] ||= DEFAULT_CHARSET unless options[:collation]
+
+ # Set default collation only when charset is also default.
+ options[:collation] ||= DEFAULT_COLLATION if options[:charset] == DEFAULT_CHARSET
+ end
end
def error_class
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index c32e0d6bf8..cf17b1d8a4 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -1,10 +1,5 @@
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- mattr_accessor :record_timestamps, instance_accessor: false
- self.record_timestamps = true
- end
-
# = Active Record Timestamp
#
# Active Record automatically timestamps create and update operations if the
@@ -37,16 +32,18 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :record_timestamps, instance_writer: true
+ class_attribute :record_timestamps
+ self.record_timestamps = true
end
- def initialize_dup(other)
+ def initialize_dup(other) # :nodoc:
clear_timestamp_attributes
+ super
end
private
- def create #:nodoc:
+ def create
if self.record_timestamps
current_time = current_time_from_proper_timezone
@@ -60,7 +57,7 @@ module ActiveRecord
super
end
- def update(*args) #:nodoc:
+ def update(*args)
if should_record_timestamps?
current_time = current_time_from_proper_timezone
@@ -74,7 +71,7 @@ module ActiveRecord
end
def should_record_timestamps?
- self.record_timestamps && (!partial_updates? || changed? || (attributes.keys & self.class.serialized_attributes.keys).present?)
+ self.record_timestamps && (!partial_writes? || changed? || (attributes.keys & self.class.serialized_attributes.keys).present?)
end
def timestamp_attributes_for_create_in_model
@@ -89,19 +86,19 @@ module ActiveRecord
timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
end
- def timestamp_attributes_for_update #:nodoc:
+ def timestamp_attributes_for_update
[:updated_at, :updated_on]
end
- def timestamp_attributes_for_create #:nodoc:
+ def timestamp_attributes_for_create
[:created_at, :created_on]
end
- def all_timestamp_attributes #:nodoc:
+ def all_timestamp_attributes
timestamp_attributes_for_create + timestamp_attributes_for_update
end
- def current_time_from_proper_timezone #:nodoc:
+ def current_time_from_proper_timezone
self.class.default_timezone == :utc ? Time.now.utc : Time.now
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 934393b4e7..ce6998530f 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -108,10 +108,10 @@ module ActiveRecord
#
# # Suppose that we have a Number model with a unique column called 'i'.
# Number.transaction do
- # Number.create(:i => 0)
+ # Number.create(i: 0)
# begin
# # This will raise a unique constraint error...
- # Number.create(:i => 0)
+ # Number.create(i: 0)
# rescue ActiveRecord::StatementInvalid
# # ...which we ignore.
# end
@@ -119,7 +119,7 @@ module ActiveRecord
# # On PostgreSQL, the transaction is now unusable. The following
# # statement will cause a PostgreSQL error, even though the unique
# # constraint is no longer violated:
- # Number.create(:i => 1)
+ # Number.create(i: 1)
# # => "PGError: ERROR: current transaction is aborted, commands
# # ignored until end of transaction block"
# end
@@ -134,9 +134,9 @@ module ActiveRecord
# transaction. For example, the following behavior may be surprising:
#
# User.transaction do
- # User.create(:username => 'Kotori')
+ # User.create(username: 'Kotori')
# User.transaction do
- # User.create(:username => 'Nemu')
+ # User.create(username: 'Nemu')
# raise ActiveRecord::Rollback
# end
# end
@@ -147,14 +147,14 @@ module ActiveRecord
# real transaction is committed.
#
# In order to get a ROLLBACK for the nested transaction you may ask for a real
- # sub-transaction by passing <tt>:requires_new => true</tt>. If anything goes wrong,
+ # sub-transaction by passing <tt>requires_new: true</tt>. If anything goes wrong,
# the database rolls back to the beginning of the sub-transaction without rolling
# back the parent transaction. If we add it to the previous example:
#
# User.transaction do
- # User.create(:username => 'Kotori')
- # User.transaction(:requires_new => true) do
- # User.create(:username => 'Nemu')
+ # User.create(username: 'Kotori')
+ # User.transaction(requires_new: true) do
+ # User.create(username: 'Nemu')
# raise ActiveRecord::Rollback
# end
# end
@@ -194,7 +194,7 @@ module ActiveRecord
# automatically released. The following example demonstrates the problem:
#
# Model.connection.transaction do # BEGIN
- # Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
+ # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
# Model.connection.create_table(...) # active_record_1 now automatically released
# end # RELEASE savepoint active_record_1
# # ^^^^ BOOM! database error!
@@ -213,13 +213,13 @@ module ActiveRecord
# You can specify that the callback should only be fired by a certain action with
# the +:on+ option:
#
- # after_commit :do_foo, :on => :create
- # after_commit :do_bar, :on => :update
- # after_commit :do_baz, :on => :destroy
+ # after_commit :do_foo, on: :create
+ # after_commit :do_bar, on: :update
+ # after_commit :do_baz, on: :destroy
#
# Also, to have the callback fired on create and update, but not on destroy:
#
- # after_commit :do_zoo, :if => :persisted?
+ # after_commit :do_zoo, if: :persisted?
#
# Note that transactional fixtures do not play well with this feature. Please
# use the +test_after_commit+ gem to have these hooks fired in tests.
@@ -328,6 +328,7 @@ module ActiveRecord
@_start_transaction_state[:new_record] = @new_record
@_start_transaction_state[:destroyed] = @destroyed
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
+ @_start_transaction_state[:frozen?] = @attributes.frozen?
end
# Clear the new record state and id of a record.
@@ -342,8 +343,8 @@ module ActiveRecord
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
if @_start_transaction_state[:level] < 1 || force
restore_state = @_start_transaction_state
- was_frozen = @attributes.frozen?
- @attributes = @attributes.dup if was_frozen
+ was_frozen = restore_state[:frozen?]
+ @attributes = @attributes.dup if @attributes.frozen?
@new_record = restore_state[:new_record]
@destroyed = restore_state[:destroyed]
if restore_state.has_key?(:id)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index ed561bfb3c..3706885881 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -10,8 +10,8 @@ module ActiveRecord
# puts invalid.record.errors
# end
class RecordInvalid < ActiveRecordError
- attr_reader :record
- def initialize(record)
+ attr_reader :record # :nodoc:
+ def initialize(record) # :nodoc:
@record = record
errors = @record.errors.full_messages.join(", ")
super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid"))
@@ -44,23 +44,24 @@ module ActiveRecord
end
end
- # The validation process on save can be skipped by passing <tt>:validate => false</tt>. The regular Base#save method is
- # replaced with this when the validations module is mixed in, which it is by default.
+ # The validation process on save can be skipped by passing <tt>validate: false</tt>.
+ # The regular Base#save method is replaced with this when the validations
+ # module is mixed in, which it is by default.
def save(options={})
perform_validations(options) ? super : false
end
- # Attempts to save the record just like Base#save but will raise a +RecordInvalid+ exception instead of returning false
- # if the record is not valid.
+ # Attempts to save the record just like Base#save but will raise a +RecordInvalid+
+ # exception instead of returning +false+ if the record is not valid.
def save!(options={})
perform_validations(options) ? super : raise(RecordInvalid.new(self))
end
- # Runs all the validations within the specified context. Returns true if no errors are found,
- # false otherwise.
+ # Runs all the validations within the specified context. Returns +true+ if
+ # no errors are found, +false+ otherwise.
#
- # If the argument is false (default is +nil+), the context is set to <tt>:create</tt> if
- # <tt>new_record?</tt> is true, and to <tt>:update</tt> if it is not.
+ # If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
+ # <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
#
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
# some <tt>:on</tt> option will only run in the specified context.
@@ -72,7 +73,7 @@ module ActiveRecord
protected
- def perform_validations(options={})
+ def perform_validations(options={}) # :nodoc:
perform_validation = options[:validate] != false
perform_validation ? valid?(options[:context]) : true
end
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index 1fa6629980..7f1972ccf9 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -38,7 +38,7 @@ module ActiveRecord
# 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
+ # 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)
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index 056527b512..6b14c39686 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -1,12 +1,14 @@
module ActiveRecord
module Validations
- class PresenceValidator < ActiveModel::Validations::PresenceValidator
+ class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
def validate(record)
super
attributes.each do |attribute|
next unless record.class.reflect_on_association(attribute)
- value = record.send(attribute)
- if Array(value).all? { |r| r.marked_for_destruction? }
+ associated_records = Array(record.send(attribute))
+
+ # Superclass validates presence. Ensure present records aren't about to be destroyed.
+ if associated_records.present? && associated_records.all? { |r| r.marked_for_destruction? }
record.errors.add(attribute, :blank, options)
end
end
@@ -29,7 +31,7 @@ module ActiveRecord
#
# If you want to validate the presence of a boolean field (where the real values
# are true and false), you will want to use
- # <tt>validates_inclusion_of :field_name, :in => [true, false]</tt>.
+ # <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
#
# This is due to the way Object#blank? handles boolean values:
# <tt>false.blank? # => true</tt>.
@@ -46,16 +48,15 @@ module ActiveRecord
# 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.
+ # 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 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>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
- #
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index c117872ac8..5fa6a0b892 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 #:nodoc:
+ class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
def initialize(options)
super(options.reverse_merge(:case_sensitive => true))
end
@@ -26,11 +26,12 @@ module ActiveRecord
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.send(:id))) if record.persisted?
Array(options[:scope]).each do |scope_item|
- scope_value = record.read_attribute(scope_item)
reflection = record.class.reflect_on_association(scope_item)
if reflection
scope_value = record.send(reflection.foreign_key)
scope_item = reflection.foreign_key
+ else
+ scope_value = record.read_attribute(scope_item)
end
relation = relation.and(table[scope_item].eq(scope_value))
end
@@ -64,14 +65,12 @@ module ActiveRecord
end
def build_relation(klass, table, attribute, value) #:nodoc:
- reflection = klass.reflect_on_association(attribute)
- if reflection
- column = klass.columns_hash[reflection.foreign_key]
+ if reflection = klass.reflect_on_association(attribute)
attribute = reflection.foreign_key
value = value.attributes[reflection.primary_key_column.name]
- else
- column = klass.columns_hash[attribute.to_s]
end
+
+ column = klass.columns_hash[attribute.to_s]
value = column.limit ? value.to_s[0, column.limit] : value.to_s if !value.nil? && column.text?
if !options[:case_sensitive] && value && column.text?
@@ -199,7 +198,7 @@ module ActiveRecord
# can catch it and restart the transaction (e.g. by telling the user
# that the title already exists, and asking him to re-enter the title).
# This technique is also known as optimistic concurrency control:
- # http://en.wikipedia.org/wiki/Optimistic_concurrency_control
+ # http://en.wikipedia.org/wiki/Optimistic_concurrency_control.
#
# The bundled ActiveRecord::ConnectionAdapters distinguish unique index
# constraint errors from other types of database errors by throwing an
@@ -209,10 +208,10 @@ module ActiveRecord
#
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
#
- # * ActiveRecord::ConnectionAdapters::MysqlAdapter
- # * ActiveRecord::ConnectionAdapters::Mysql2Adapter
- # * ActiveRecord::ConnectionAdapters::SQLite3Adapter
- # * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
+ # * 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/activerecord/lib/rails/generators/active_record.rb b/activerecord/lib/rails/generators/active_record.rb
index 297cd094c2..c8aa37f275 100644
--- a/activerecord/lib/rails/generators/active_record.rb
+++ b/activerecord/lib/rails/generators/active_record.rb
@@ -4,8 +4,8 @@ require 'rails/generators/active_model'
require 'active_record'
module ActiveRecord
- module Generators
- class Base < Rails::Generators::NamedBase #:nodoc:
+ module Generators # :nodoc:
+ class Base < Rails::Generators::NamedBase # :nodoc:
include Rails::Generators::Migration
# Set the current directory as base for the inherited generators.
@@ -14,7 +14,7 @@ module ActiveRecord
end
# Implement the required interface for Rails::Generators::Migration.
- def self.next_migration_number(dirname) #:nodoc:
+ def self.next_migration_number(dirname)
next_migration_number = current_migration_number(dirname) + 1
ActiveRecord::Migration.next_migration_number(next_migration_number)
end
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index a3c274d9b9..5f1dbe36d6 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -1,8 +1,8 @@
require 'rails/generators/active_record'
module ActiveRecord
- module Generators
- class MigrationGenerator < Base
+ module Generators # :nodoc:
+ class MigrationGenerator < Base # :nodoc:
argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]"
def create_migration_file
@@ -42,7 +42,7 @@ module ActiveRecord
attribute.name.singularize.foreign_key
end.to_sym
end
-
+
private
def validate_file_name!
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index 8e6ef20285..5f36181694 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -1,8 +1,8 @@
require 'rails/generators/active_record'
module ActiveRecord
- module Generators
- class ModelGenerator < Base
+ module Generators # :nodoc:
+ class ModelGenerator < Base # :nodoc:
argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]"
check_class_collision
diff --git a/activerecord/lib/rails/generators/active_record/observer/observer_generator.rb b/activerecord/lib/rails/generators/active_record/observer/observer_generator.rb
index c1c0e3f25b..e7445d03a2 100644
--- a/activerecord/lib/rails/generators/active_record/observer/observer_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/observer/observer_generator.rb
@@ -1,8 +1,8 @@
require 'rails/generators/active_record'
module ActiveRecord
- module Generators
- class ObserverGenerator < Base
+ module Generators # :nodoc:
+ class ObserverGenerator < Base # :nodoc:
check_class_collision :suffix => "Observer"
def create_observer_file