aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record')
-rw-r--r--activerecord/lib/active_record/associations.rb20
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb13
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb6
-rw-r--r--activerecord/lib/active_record/attribute.rb15
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb19
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb25
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb8
-rw-r--r--activerecord/lib/active_record/base.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb44
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb214
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb2
-rw-r--r--activerecord/lib/active_record/counter_cache.rb2
-rw-r--r--activerecord/lib/active_record/enum.rb4
-rw-r--r--activerecord/lib/active_record/migration.rb12
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb22
-rw-r--r--activerecord/lib/active_record/query_cache.rb1
-rw-r--r--activerecord/lib/active_record/railties/databases.rake19
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb1
-rw-r--r--activerecord/lib/active_record/reflection.rb86
-rw-r--r--activerecord/lib/active_record/relation/batches.rb1
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb18
-rw-r--r--activerecord/lib/active_record/schema.rb1
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb24
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb33
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb6
-rw-r--r--activerecord/lib/active_record/timestamp.rb8
-rw-r--r--activerecord/lib/active_record/type/decimal.rb21
-rw-r--r--activerecord/lib/active_record/type/serialized.rb6
-rw-r--r--activerecord/lib/active_record/type/value.rb11
-rw-r--r--activerecord/lib/active_record/validations.rb15
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
45 files changed, 417 insertions, 344 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index ec78d10124..d3b9b8251a 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1052,7 +1052,7 @@ module ActiveRecord
# Specifies a one-to-many association. The following methods for retrieval and query of
# collections of associated objects will be added:
#
- # +collection+ is a placeholder for the symbol passed as the first argument, so
+ # +collection+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
#
# [collection(force_reload = false)]
@@ -1131,7 +1131,7 @@ module ActiveRecord
# * <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>)
# * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>)
- # The declaration can also include an options hash to specialize the behavior of the association.
+ # The declaration can also include an +options+ hash to specialize the behavior of the association.
#
# === Options
# [:class_name]
@@ -1209,7 +1209,7 @@ module ActiveRecord
# Option examples:
# has_many :comments, -> { order "posted_on" }
# has_many :comments, -> { includes :author }
- # has_many :people, -> { where("deleted = 0").order("name") }, class_name: "Person"
+ # has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
# has_many :tracks, -> { order "position" }, dependent: :destroy
# has_many :comments, dependent: :nullify
# has_many :tags, as: :taggable
@@ -1227,7 +1227,7 @@ module ActiveRecord
#
# The following methods for retrieval and query of a single associated object will be added:
#
- # +association+ is a placeholder for the symbol passed as the first argument, so
+ # +association+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
#
# [association(force_reload = false)]
@@ -1259,7 +1259,7 @@ module ActiveRecord
#
# === Options
#
- # The declaration can also include an options hash to specialize the behavior of the association.
+ # The declaration can also include an +options+ hash to specialize the behavior of the association.
#
# Options are:
# [:class_name]
@@ -1338,7 +1338,7 @@ module ActiveRecord
# Methods will be added for retrieval and query for a single associated object, for which
# this object holds an id:
#
- # +association+ is a placeholder for the symbol passed as the first argument, so
+ # +association+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
#
# [association(force_reload = false)]
@@ -1364,7 +1364,7 @@ module ActiveRecord
# * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
# * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
# * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>)
- # The declaration can also include an options hash to specialize the behavior of the association.
+ # The declaration can also include an +options+ hash to specialize the behavior of the association.
#
# === Options
#
@@ -1435,7 +1435,7 @@ module ActiveRecord
# belongs_to :firm, foreign_key: "client_of"
# belongs_to :person, primary_key: "name", foreign_key: "person_name"
# belongs_to :author, class_name: "Person", foreign_key: "author_id"
- # belongs_to :valid_coupon, ->(o) { where "discounts > #{o.payments_count}" },
+ # belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count },
# class_name: "Coupon", foreign_key: "coupon_id"
# belongs_to :attachable, polymorphic: true
# belongs_to :project, readonly: true
@@ -1480,7 +1480,7 @@ module ActiveRecord
#
# Adds the following methods for retrieval and query:
#
- # +collection+ is a placeholder for the symbol passed as the first argument, so
+ # +collection+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
#
# [collection(force_reload = false)]
@@ -1541,7 +1541,7 @@ module ActiveRecord
# * <tt>Developer#projects.exists?(...)</tt>
# * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>)
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>)
- # The declaration may include an options hash to specialize the behavior of the association.
+ # The declaration may include an +options+ hash to specialize the behavior of the association.
#
# === Options
#
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 2a97d0ed31..1413efaf7f 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -103,7 +103,7 @@ module ActiveRecord
if has_cached_counter?(reflection)
counter = cached_counter_attribute_name(reflection)
owner[counter] += difference
- owner.changed_attributes.delete(counter) # eww
+ owner.send(:clear_attribute_changes, counter) # eww
end
end
@@ -124,7 +124,7 @@ module ActiveRecord
def inverse_updates_counter_named?(counter_name, reflection = reflection())
reflection.klass._reflections.values.any? { |inverse_reflection|
- :belongs_to == inverse_reflection.macro &&
+ inverse_reflection.belongs_to? &&
inverse_reflection.counter_cache_column == counter_name
}
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 007e3bc555..44c4436e95 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
# = Active Record Has Many Through Association
module Associations
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 944caacab6..e6095d84dc 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
# = Active Record Belongs To Has One Association
module Associations
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index a0e83c0a02..c3bbdccad8 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -37,14 +37,9 @@ module ActiveRecord
table = tables.shift
klass = reflection.klass
- case reflection.source_macro
- when :belongs_to
- key = reflection.association_primary_key
- foreign_key = reflection.foreign_key
- else
- key = reflection.foreign_key
- foreign_key = reflection.active_record_primary_key
- end
+ join_keys = reflection.join_keys(klass)
+ key = join_keys.key
+ foreign_key = join_keys.foreign_key
constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
@@ -95,7 +90,7 @@ module ActiveRecord
# end
#
# If I execute `Physician.joins(:appointments).to_a` then
- # reflection # => #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
+ # klass # => Physician
# table # => #<Arel::Table @name="appointments" ...>
# key # => physician_id
# foreign_table # => #<Arel::Table @name="physicians" ...>
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 1fed7f74e7..d57da366bd 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -63,7 +63,7 @@ module ActiveRecord
should_reset = (through_scope != through_reflection.klass.unscoped) ||
(reflection.options[:source_type] && through_reflection.collection?)
- # Dont cache the association - we would only be caching a subset
+ # Don't cache the association - we would only be caching a subset
if should_reset
owners.each { |owner|
owner.association(association_name).reset
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index f00fef8b9e..611d471e62 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -3,7 +3,7 @@ module ActiveRecord
module Associations
module ThroughAssociation #:nodoc:
- delegate :source_reflection, :through_reflection, :chain, :to => :reflection
+ delegate :source_reflection, :through_reflection, :to => :reflection
protected
@@ -13,7 +13,7 @@ module ActiveRecord
# 2. To get the type conditions for any STI models in the chain
def target_scope
scope = super
- chain.drop(1).each do |reflection|
+ reflection.chain.drop(1).each do |reflection|
relation = reflection.klass.all
relation.merge!(reflection.scope) if reflection.scope
@@ -77,7 +77,7 @@ module ActiveRecord
end
def ensure_mutable
- if source_reflection.macro != :belongs_to
+ unless source_reflection.belongs_to?
raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
end
end
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 6d38224830..8cc1904575 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -30,10 +30,14 @@ module ActiveRecord
def value
# `defined?` is cheaper than `||=` when we get back falsy values
- @value = type_cast(value_before_type_cast) unless defined?(@value)
+ @value = original_value unless defined?(@value)
@value
end
+ def original_value
+ type_cast(value_before_type_cast)
+ end
+
def value_for_database
type.type_cast_for_database(value)
end
@@ -54,7 +58,7 @@ module ActiveRecord
self.class.from_database(name, value, type)
end
- def type_cast
+ def type_cast(*)
raise NotImplementedError
end
@@ -62,6 +66,13 @@ module ActiveRecord
true
end
+ def ==(other)
+ self.class == other.class &&
+ name == other.name &&
+ value_before_type_cast == other.value_before_type_cast &&
+ type == other.type
+ end
+
protected
def initialize_dup(other)
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 1cdb530815..1ff28ceccc 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -57,6 +57,8 @@ module ActiveRecord
end
end
+ class GeneratedAttributeMethods < Module; end # :nodoc:
+
module ClassMethods
def inherited(child_class) #:nodoc:
child_class.initialize_generated_modules
@@ -64,7 +66,7 @@ module ActiveRecord
end
def initialize_generated_modules # :nodoc:
- @generated_attribute_methods = Module.new { extend Mutex_m }
+ @generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m }
@attribute_methods_generated = false
include @generated_attribute_methods
end
@@ -113,10 +115,11 @@ module ActiveRecord
if superclass == Base
super
else
- # If B < A and A defines its own attribute method, then we don't want to overwrite that.
- defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
- base_defined = Base.method_defined?(method_name) || Base.private_method_defined?(method_name)
- defined && !base_defined || super
+ # If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
+ # defines its own attribute method, then we don't want to overwrite that.
+ defined = method_defined_within?(method_name, superclass, Base) &&
+ ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
+ defined || super
end
end
@@ -193,7 +196,7 @@ module ActiveRecord
#
# 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, ...>
+ # # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
#
# person.column_for_attribute(:nothing)
# # => nil
@@ -279,9 +282,9 @@ module ActiveRecord
end
# Returns an <tt>#inspect</tt>-like string for the value of the
- # attribute +attr_name+. String attributes are truncated upto 50
+ # attribute +attr_name+. String attributes are truncated up to 50
# characters, Date and Time attributes are returned in the
- # <tt>:db</tt> format, Array attributes are truncated upto 10 values.
+ # <tt>:db</tt> format, Array attributes are truncated up to 10 values.
# Other attributes return the value of <tt>#inspect</tt> without
# modification.
#
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index b58295a106..d3f4e51c33 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -51,14 +51,6 @@ module ActiveRecord
super | changed_in_place
end
- def attribute_changed?(attr_name, options = {})
- result = super
- # We can't change "from" something in place. Only setters can define
- # "from" and "to"
- result ||= changed_in_place?(attr_name) unless options.key?(:from)
- result
- end
-
def changes_applied
super
store_original_raw_attributes
@@ -69,12 +61,16 @@ module ActiveRecord
original_raw_attributes.clear
end
+ def changed_attributes
+ super.reverse_merge(attributes_changed_in_place).freeze
+ end
+
private
def calculate_changes_from_defaults
@changed_attributes = nil
self.class.column_defaults.each do |attr, orig_value|
- changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value)
+ set_attribute_was(attr, orig_value) if _field_changed?(attr, orig_value)
end
end
@@ -100,9 +96,9 @@ module ActiveRecord
def save_changed_attribute(attr, old_value)
if attribute_changed?(attr)
- changed_attributes.delete(attr) unless _field_changed?(attr, old_value)
+ clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
else
- changed_attributes[attr] = old_value if _field_changed?(attr, old_value)
+ set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
end
end
@@ -132,6 +128,13 @@ module ActiveRecord
@attributes[attr].changed_from?(old_value)
end
+ def attributes_changed_in_place
+ changed_in_place.each_with_object({}) do |attr_name, h|
+ orig = @attributes[attr_name].original_value
+ h[attr_name] = orig
+ end
+ end
+
def changed_in_place
self.class.attribute_names.select do |attr_name|
changed_in_place?(attr_name)
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index cadad60ddd..9bd333bbac 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -39,6 +39,12 @@ module ActiveRecord
read_attribute_before_type_cast(self.class.primary_key)
end
+ # Returns the primary key previous value.
+ def id_was
+ sync_with_transaction_state
+ attribute_was(self.class.primary_key)
+ end
+
protected
def attribute_method?(attr_name)
@@ -54,7 +60,7 @@ module ActiveRecord
end
end
- ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast).to_set
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set
def dangerous_attribute_method?(method_name)
super && !ID_ATTRIBUTE_METHODS.include?(method_name)
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 64cc5b68cc..f978fbd0a4 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -141,6 +141,7 @@ module ActiveRecord #:nodoc:
#
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
# Query methods allow you to test whether an attribute value is present.
+ # For numeric values, present is defined as non-zero.
#
# 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:
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 cb75070e3a..a5fa9d6adc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -462,23 +462,44 @@ module ActiveRecord
#
# For example, suppose that you have 5 models, with the following hierarchy:
#
- # |
- # +-- Book
- # | |
- # | +-- ScaryBook
- # | +-- GoodBook
- # +-- Author
- # +-- BankAccount
+ # class Author < ActiveRecord::Base
+ # end
#
- # Suppose that Book is to connect to a separate database (i.e. one other
- # than the default database). Then Book, ScaryBook and GoodBook will all use
- # the same connection pool. Likewise, Author and BankAccount will use the
- # same connection pool. However, the connection pool used by Author/BankAccount
- # is not the same as the one used by Book/ScaryBook/GoodBook.
+ # class BankAccount < ActiveRecord::Base
+ # end
#
- # Normally there is only a single ConnectionHandler instance, accessible via
- # ActiveRecord::Base.connection_handler. Active Record models use this to
- # determine the connection pool that they should use.
+ # class Book < ActiveRecord::Base
+ # establish_connection "library_db"
+ # end
+ #
+ # class ScaryBook < Book
+ # end
+ #
+ # class GoodBook < Book
+ # end
+ #
+ # And a database.yml that looked like this:
+ #
+ # development:
+ # database: my_application
+ # host: localhost
+ #
+ # library_db:
+ # database: library
+ # host: some.library.org
+ #
+ # Your primary database in the development environment is "my_application"
+ # but the Book model connects to a separate database called "library_db"
+ # (this can even be a database on a different machine).
+ #
+ # Book, ScaryBook and GoodBook will all use the same connection pool to
+ # "library_db" while Author, BankAccount, and any other models you create
+ # will use the default connection pool to "my_application".
+ #
+ # The various connection pools are managed by a single instance of
+ # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
+ # All Active Record models use this handler to determine the connection pool that they
+ # should use.
class ConnectionHandler
def initialize
# These caches are keyed by klass.name, NOT klass. Keying them by klass
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 e8ce00d92b..98e96099cb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -203,62 +203,30 @@ module ActiveRecord
if options[:isolation]
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
end
-
yield
else
- within_new_transaction(options) { yield }
+ transaction_manager.within_new_transaction(options) { yield }
end
rescue ActiveRecord::Rollback
# rollbacks are silently swallowed
end
- def within_new_transaction(options = {}) #:nodoc:
- transaction = begin_transaction(options)
- yield
- rescue Exception => error
- rollback_transaction if transaction
- raise
- ensure
- begin
- commit_transaction unless error
- rescue Exception
- rollback_transaction
- raise
- end
- end
-
- def open_transactions
- @transaction.number
- end
+ attr_reader :transaction_manager #:nodoc:
- def current_transaction #:nodoc:
- @transaction
- end
+ delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction, :commit_transaction, :rollback_transaction, to: :transaction_manager
def transaction_open?
- @transaction.open?
- end
-
- def begin_transaction(options = {}) #:nodoc:
- @transaction = @transaction.begin(options)
- end
-
- def commit_transaction #:nodoc:
- @transaction = @transaction.commit
- end
-
- def rollback_transaction #:nodoc:
- @transaction = @transaction.rollback
+ current_transaction.open?
end
def reset_transaction #:nodoc:
- @transaction = ClosedTransaction.new(self)
+ @transaction_manager = TransactionManager.new(self)
end
# Register a record with the current transaction so that its after_commit and after_rollback callbacks
# can be called.
def add_transaction_record(record)
- @transaction.add_record(record)
+ current_transaction.add_record(record)
end
# Begins the transaction (and turns off auto-committing).
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 10753defc2..4957e1ac80 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -43,13 +43,14 @@ module ActiveRecord
# 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)
- if options[:unique]
- indexes(table_name).any?{ |i| i.unique && i.name == index_name }
- else
- indexes(table_name).any?{ |i| i.name == index_name }
- end
+ column_names = Array(column_name).map(&:to_s)
+ index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, column: column_names)
+ checks = []
+ checks << lambda { |i| i.name == index_name }
+ checks << lambda { |i| i.columns == column_names }
+ checks << lambda { |i| i.unique } if options[:unique]
+
+ indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
end
# Returns an array of Column objects for the table specified by +table_name+.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index bc4884b538..8f06cf3a1f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -1,20 +1,7 @@
module ActiveRecord
module ConnectionAdapters
- class Transaction #:nodoc:
- attr_reader :connection
-
- def initialize(connection)
- @connection = connection
- @state = TransactionState.new
- end
-
- def state
- @state
- end
- end
-
class TransactionState
- attr_accessor :parent
+ attr_reader :parent
VALID_STATES = Set.new([:committed, :rolledback, nil])
@@ -43,82 +30,24 @@ module ActiveRecord
end
end
- class ClosedTransaction < Transaction #:nodoc:
- def number
- 0
- end
-
- def begin(options = {})
- RealTransaction.new(connection, self, options)
- end
-
- def closed?
- true
- end
-
- def open?
- false
- end
-
- def joinable?
- false
- end
-
- # This is a noop when there are no open transactions
- def add_record(record)
- end
+ class NullTransaction #:nodoc:
+ def initialize; end
+ def closed?; true; end
+ def open?; false; end
+ def joinable?; false; end
+ def add_record(record); end
end
- class OpenTransaction < Transaction #:nodoc:
- attr_reader :parent, :records
- attr_writer :joinable
-
- def initialize(connection, parent, options = {})
- super connection
-
- @parent = parent
- @records = []
- @finishing = false
- @joinable = options.fetch(:joinable, true)
- end
-
- # This state is necessary so that we correctly handle stuff that might
- # happen in a commit/rollback. But it's kinda distasteful. Maybe we can
- # find a better way to structure it in the future.
- def finishing?
- @finishing
- end
-
- def joinable?
- @joinable && !finishing?
- end
-
- def number
- if finishing?
- parent.number
- else
- parent.number + 1
- end
- end
-
- def begin(options = {})
- if finishing?
- parent.begin
- else
- SavepointTransaction.new(connection, self, options)
- end
- end
+ class Transaction #:nodoc:
- def rollback
- @finishing = true
- perform_rollback
- parent
- end
+ attr_reader :connection, :state, :records, :savepoint_name
+ attr_writer :joinable
- def commit
- @finishing = true
- perform_commit
- parent
+ def initialize(connection, options)
+ @connection = connection
+ @state = TransactionState.new
+ @records = []
+ @joinable = options.fetch(:joinable, true)
end
def add_record(record)
@@ -129,19 +58,25 @@ module ActiveRecord
end
end
- def rollback_records
+ def rollback
@state.set_state(:rolledback)
+ end
+
+ def rollback_records
records.uniq.each do |record|
begin
- record.rolledback!(parent.closed?)
+ record.rolledback! full_rollback?
rescue => e
record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
end
- def commit_records
+ def commit
@state.set_state(:committed)
+ end
+
+ def commit_records
records.uniq.each do |record|
begin
record.committed!
@@ -151,19 +86,42 @@ module ActiveRecord
end
end
- def closed?
- false
+ def full_rollback?; true; end
+ def joinable?; @joinable; end
+ def closed?; false; end
+ def open?; !closed?; end
+ end
+
+ class SavepointTransaction < Transaction
+
+ def initialize(connection, savepoint_name, options)
+ super(connection, options)
+ if options[:isolation]
+ raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
+ end
+ connection.create_savepoint(@savepoint_name = savepoint_name)
+ end
+
+ def rollback
+ super
+ connection.rollback_to_savepoint(savepoint_name)
+ rollback_records
end
- def open?
- true
+ def commit
+ super
+ connection.release_savepoint(savepoint_name)
+ parent = connection.transaction_manager.current_transaction
+ records.each { |r| parent.add_record(r) }
end
+
+ def full_rollback?; false; end
end
- class RealTransaction < OpenTransaction #:nodoc:
- def initialize(connection, parent, options = {})
- super
+ class RealTransaction < Transaction
+ def initialize(connection, options)
+ super
if options[:isolation]
connection.begin_isolated_db_transaction(options[:isolation])
else
@@ -171,37 +129,69 @@ module ActiveRecord
end
end
- def perform_rollback
+ def rollback
+ super
connection.rollback_db_transaction
rollback_records
end
- def perform_commit
+ def commit
+ super
connection.commit_db_transaction
commit_records
end
end
- class SavepointTransaction < OpenTransaction #:nodoc:
- def initialize(connection, parent, options = {})
- if options[:isolation]
- raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
- end
+ class TransactionManager #:nodoc:
+ def initialize(connection)
+ @stack = []
+ @connection = connection
+ end
- super
- connection.create_savepoint
+ def begin_transaction(options = {})
+ transaction =
+ if @stack.empty?
+ RealTransaction.new(@connection, options)
+ else
+ SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options)
+ end
+ @stack.push(transaction)
+ transaction
end
- def perform_rollback
- connection.rollback_to_savepoint
- rollback_records
+ def commit_transaction
+ @stack.pop.commit
end
- def perform_commit
- @state.set_state(:committed)
- @state.parent = parent.state
- connection.release_savepoint
+ def rollback_transaction
+ @stack.pop.rollback
+ end
+
+ def within_new_transaction(options = {})
+ transaction = begin_transaction options
+ yield
+ rescue Exception => error
+ rollback_transaction if transaction
+ raise
+ ensure
+ begin
+ commit_transaction unless error
+ rescue Exception
+ transaction.rollback
+ raise
+ end
end
+
+ def open_transactions
+ @stack.size
+ end
+
+ def current_transaction
+ @stack.last || NULL_TRANSACTION
+ end
+
+ private
+ NULL_TRANSACTION = NullTransaction.new
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index f8c054eb69..a1b6671664 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -45,7 +45,8 @@ module ActiveRecord
end
autoload_at 'active_record/connection_adapters/abstract/transaction' do
- autoload :ClosedTransaction
+ autoload :TransactionManager
+ autoload :NullTransaction
autoload :RealTransaction
autoload :SavepointTransaction
autoload :TransactionState
@@ -357,7 +358,7 @@ module ActiveRecord
end
def current_savepoint_name
- "active_record_#{open_transactions}"
+ current_transaction.savepoint_name
end
# Check the connection back in to the connection pool
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 1f1e2c46f4..5f9cc6edd0 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -16,7 +16,7 @@ module ActiveRecord
attr_reader :name, :cast_type, :null, :sql_type, :default, :default_function
delegate :type, :precision, :scale, :limit, :klass, :accessor,
- :text?, :number?, :binary?, :changed?,
+ :number?, :binary?, :changed?,
:type_cast_from_user, :type_cast_from_database, :type_cast_for_database,
:type_cast_for_schema,
to: :cast_type
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 5693031053..d28a54b8f9 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -32,7 +32,7 @@ module ActiveRecord
# }
def initialize(url)
raise "Database URL cannot be empty" if url.blank?
- @uri = URI.parse(url)
+ @uri = uri_parser.parse(url)
@adapter = @uri.scheme.gsub('-', '_')
@adapter = "postgresql" if @adapter == "postgres"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
index 34ed32ad35..380c50fc14 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
@@ -12,7 +12,7 @@ module ActiveRecord
# roundtripping jsonb columns. This causes some false positives for
# the comparison here. Therefore, we need to parse and re-dump the
# raw value here to ensure the insignificant whitespaces are
- # consitent with our encoder's output.
+ # consistent with our encoder's output.
raw_old_value = type_cast_for_database(type_cast_from_database(raw_old_value))
super(raw_old_value, new_value)
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb
index 7323f12763..334af7c598 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb
@@ -7,10 +7,6 @@ module ActiveRecord
:xml
end
- def text?
- false
- end
-
def type_cast_for_database(value)
return unless value
Data.new(super)
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 4d8afcf16a..a10ce330c7 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
module ConnectionAdapters
class SchemaCache
@@ -20,6 +19,7 @@ module ActiveRecord
# A cached lookup for table existence.
def table_exists?(name)
+ prepare_tables if @tables.empty?
return @tables[name] if @tables.key? name
@tables[name] = connection.table_exists?(name)
@@ -83,6 +83,12 @@ module ActiveRecord
def marshal_load(array)
@version, @columns, @columns_hash, @primary_keys, @tables = array
end
+
+ private
+
+ def prepare_tables
+ connection.tables.each { |table| @tables[table] = true }
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index bf96acad4a..faf1cdc686 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -385,7 +385,7 @@ module ActiveRecord
table_name && tables(nil, table_name).any?
end
- # Returns an array of +SQLite3Column+ objects for the table specified by +table_name+.
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
def columns(table_name) #:nodoc:
table_structure(table_name).map do |field|
case field["dflt_value"]
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index a33c7c64a7..f0b6afc4b4 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -34,7 +34,7 @@ module ActiveRecord
foreign_key = has_many_association.foreign_key.to_s
child_class = has_many_association.klass
- reflection = child_class._reflections.values.find { |e| :belongs_to == e.macro && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
+ reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
counter_name = reflection.counter_cache_column
stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index f0ee433d0b..5958373e88 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -145,11 +145,11 @@ module ActiveRecord
value = read_attribute(attr_name)
if attribute_changed?(attr_name)
if mapping[old] == value
- changed_attributes.delete(attr_name)
+ clear_attribute_changes([attr_name])
end
else
if old != value
- changed_attributes[attr_name] = mapping.key old
+ set_attribute_was(attr_name, mapping.key(old))
end
end
else
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index e94b6ae9eb..a6847e28c2 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -399,7 +399,7 @@ module ActiveRecord
def load_schema_if_pending!
if ActiveRecord::Migrator.needs_migration?
- ActiveRecord::Tasks::DatabaseTasks.load_schema
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current
check_pending!
end
end
@@ -814,22 +814,22 @@ module ActiveRecord
migrations = migrations(migrations_paths)
migrations.select! { |m| yield m } if block_given?
- self.new(:up, migrations, target_version).migrate
+ new(:up, migrations, target_version).migrate
end
def down(migrations_paths, target_version = nil, &block)
migrations = migrations(migrations_paths)
migrations.select! { |m| yield m } if block_given?
- self.new(:down, migrations, target_version).migrate
+ new(:down, migrations, target_version).migrate
end
def run(direction, migrations_paths, target_version)
- self.new(direction, migrations(migrations_paths), target_version).run
+ new(direction, migrations(migrations_paths), target_version).run
end
def open(migrations_paths)
- self.new(:up, migrations(migrations_paths), nil)
+ new(:up, migrations(migrations_paths), nil)
end
def schema_migrations_table_name
@@ -892,7 +892,7 @@ module ActiveRecord
private
def move(direction, migrations_paths, steps)
- migrator = self.new(direction, migrations(migrations_paths))
+ migrator = new(direction, migrations(migrations_paths))
start_index = migrator.migrations.index(migrator.current_migration)
if start_index
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index f833caaab6..36256415df 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -87,7 +87,7 @@ module ActiveRecord
alias :add_belongs_to :add_reference
alias :remove_belongs_to :remove_reference
- def change_table(table_name, options = {})
+ def change_table(table_name, options = {}) # :nodoc:
yield delegate.update_table_definition(table_name, self)
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 96e44c2f59..755ff2b2f1 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -36,6 +36,23 @@ module ActiveRecord
end
end
+ # Creates an object (or multiple objects) and saves it to the database,
+ # if validations pass. Raises a RecordInvalid error if validations fail,
+ # unlike Base#create.
+ #
+ # The +attributes+ parameter can be either a Hash or an Array of Hashes.
+ # These describe which attributes to be created on the object, or
+ # multiple objects when given an Array of Hashes.
+ def create!(attributes = nil, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create!(attr, &block) }
+ else
+ object = new(attributes, &block)
+ object.save!
+ object
+ end
+ end
+
# Given an attributes hash, +instantiate+ returns a new instance of
# the appropriate class. Accepts only keys as strings.
#
@@ -270,7 +287,8 @@ module ActiveRecord
# This method raises an +ActiveRecord::ActiveRecordError+ when called on new
# objects, or when at least one of the attributes is marked as readonly.
def update_columns(attributes)
- raise ActiveRecordError, "cannot update on a new record object" unless persisted?
+ raise ActiveRecordError, "cannot update a new record" if new_record?
+ raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
attributes.each_key do |key|
verify_readonly_attribute(key.to_s)
@@ -448,7 +466,7 @@ module ActiveRecord
changes[self.class.locking_column] = increment_lock if locking_enabled?
- changed_attributes.except!(*changes.keys)
+ clear_attribute_changes(changes.keys)
primary_key = self.class.primary_key
self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
else
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 16ad942912..dcb2bd3d84 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
# = Active Record Query Cache
class QueryCache
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index fa94df7a52..458862a538 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -41,10 +41,7 @@ db_namespace = namespace :db do
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
task :migrate => [:environment, :load_config] do
- ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
- ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration|
- ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
- end
+ ActiveRecord::Tasks::DatabaseTasks.migrate
db_namespace['_dump'].invoke if ActiveRecord::Base.dump_schema_after_migration
end
@@ -243,7 +240,7 @@ db_namespace = namespace :db do
desc 'Load a schema.rb file into the database'
task :load => [:environment, :load_config] do
- ActiveRecord::Tasks::DatabaseTasks.load_schema(:ruby, ENV['SCHEMA'])
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA'])
end
task :load_if_ruby => ['db:create', :environment] do
@@ -289,7 +286,7 @@ db_namespace = namespace :db do
desc "Recreate the databases from the structure.sql file"
task :load => [:environment, :load_config] do
- ActiveRecord::Tasks::DatabaseTasks.load_schema(:sql, ENV['DB_STRUCTURE'])
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['DB_STRUCTURE'])
end
task :load_if_sql => ['db:create', :environment] do
@@ -320,9 +317,8 @@ db_namespace = namespace :db do
task :load_schema => %w(db:test:deprecated db:test:purge) do
begin
should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
- ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
ActiveRecord::Schema.verbose = false
- db_namespace["schema:load"].invoke
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA']
ensure
if should_reconnect
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env])
@@ -332,12 +328,7 @@ db_namespace = namespace :db do
# desc "Recreate the test database from an existent structure.sql file"
task :load_structure => %w(db:test:deprecated db:test:purge) do
- begin
- ActiveRecord::Tasks::DatabaseTasks.current_config(:config => ActiveRecord::Base.configurations['test'])
- db_namespace["structure:load"].invoke
- ensure
- ActiveRecord::Tasks::DatabaseTasks.current_config(:config => nil)
- end
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA']
end
# desc "Recreate the test database from a fresh schema"
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index b3ddfd63d4..85bbac43e4 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
module ReadonlyAttributes
extend ActiveSupport::Concern
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 86eef9ca36..1547c8e3f4 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -38,7 +38,7 @@ module ActiveRecord
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
end
- # \Reflection enables to interrogate Active Record classes and objects
+ # \Reflection enables interrogating Active Record classes and objects
# about their associations and aggregations. This information can,
# for example, be used in a form builder that takes an Active Record object
# and creates input fields for all of the attributes depending on their type
@@ -149,27 +149,25 @@ module ActiveRecord
JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
def join_keys(assoc_klass)
- if source_macro == :belongs_to
- if polymorphic?
- reflection_key = association_primary_key(assoc_klass)
- else
- reflection_key = association_primary_key
- end
- reflection_foreign_key = foreign_key
- else
- reflection_foreign_key = active_record_primary_key
- reflection_key = foreign_key
- end
- JoinKeys.new(reflection_key, reflection_foreign_key)
+ JoinKeys.new(foreign_key, active_record_primary_key)
+ end
+
+ def source_macro
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.source_macro is deprecated and " \
+ "will be removed without replacement.")
+ macro
end
end
# Base class for AggregateReflection and AssociationReflection. Objects of
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
#
# MacroReflection
- # AggregateReflection
# AssociationReflection
- # ThroughReflection
+ # AggregateReflection
+ # HasManyReflection
+ # HasOneReflection
+ # BelongsToReflection
+ # ThroughReflection
class MacroReflection < AbstractReflection
# Returns the name of the macro.
#
@@ -351,9 +349,8 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
end
alias :check_eager_loadable! :check_preloadable!
- def join_id_for(owner) #:nodoc:
- key = (source_macro == :belongs_to) ? foreign_key : active_record_primary_key
- owner[key]
+ def join_id_for(owner) # :nodoc:
+ owner[active_record_primary_key]
end
def through_reflection
@@ -380,8 +377,6 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
scope ? [[scope]] : [[]]
end
- def source_macro; macro; end
-
def has_inverse?
inverse_name
end
@@ -428,14 +423,10 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
end
# Returns +true+ if +self+ is a +belongs_to+ reflection.
- def belongs_to?
- macro == :belongs_to
- end
+ def belongs_to?; false; end
# Returns +true+ if +self+ is a +has_one+ reflection.
- def has_one?
- macro == :has_one
- end
+ def has_one?; false; end
def association_class
case macro
@@ -575,35 +566,46 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
end
end
- class HasManyReflection < AssociationReflection #:nodoc:
+ class HasManyReflection < AssociationReflection # :nodoc:
def initialize(name, scope, options, active_record)
super(name, scope, options, active_record)
end
def macro; :has_many; end
- def collection?
- true
- end
+ def collection?; true; end
end
- class HasOneReflection < AssociationReflection #:nodoc:
+ class HasOneReflection < AssociationReflection # :nodoc:
def initialize(name, scope, options, active_record)
super(name, scope, options, active_record)
end
def macro; :has_one; end
+
+ def has_one?; true; end
end
- class BelongsToReflection < AssociationReflection #:nodoc:
+ class BelongsToReflection < AssociationReflection # :nodoc:
def initialize(name, scope, options, active_record)
super(name, scope, options, active_record)
end
def macro; :belongs_to; end
+
+ def belongs_to?; true; end
+
+ def join_keys(assoc_klass)
+ key = polymorphic? ? association_primary_key(assoc_klass) : association_primary_key
+ JoinKeys.new(key, foreign_key)
+ end
+
+ def join_id_for(owner) # :nodoc:
+ owner[foreign_key]
+ end
end
- class HasAndBelongsToManyReflection < AssociationReflection #:nodoc:
+ class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
def initialize(name, scope, options, active_record)
super
end
@@ -647,7 +649,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
#
# tags_reflection = Post.reflect_on_association(:tags)
# tags_reflection.source_reflection
- # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
+ # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
#
def source_reflection
through_reflection.klass._reflect_on_association(source_reflection_name)
@@ -663,7 +665,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
#
# tags_reflection = Post.reflect_on_association(:tags)
# tags_reflection.through_reflection
- # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings">
+ # # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
#
def through_reflection
active_record._reflect_on_association(options[:through])
@@ -683,8 +685,8 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
#
# tags_reflection = Post.reflect_on_association(:tags)
# tags_reflection.chain
- # # => [<ActiveRecord::Reflection::ThroughReflection: @macro=:has_many, @name=:tags, @options={:through=>:taggings}, @active_record=Post>,
- # <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @options={}, @active_record=Post>]
+ # # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
+ # <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
#
def chain
@chain ||= begin
@@ -734,8 +736,14 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
end
end
+ def join_keys(assoc_klass)
+ source_reflection.join_keys(assoc_klass)
+ end
+
# The macro used by the source association
def source_macro
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.source_macro is deprecated and " \
+ "will be removed without replacement.")
source_reflection.source_macro
end
@@ -801,6 +809,10 @@ directive on your declaration like:
through_reflection.options
end
+ def join_id_for(owner) # :nodoc:
+ source_reflection.join_id_for(owner)
+ end
+
def check_validity!
if through_reflection.nil?
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 29fc150b3d..b069cdce7c 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
module Batches
# Looping through a collection of records from the database
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 1262b2c291..c8f382ad78 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -952,9 +952,7 @@ WARNING
self.bind_values += bind_values
attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
- attributes.values.grep(ActiveRecord::Relation) do |rel|
- self.bind_values += rel.bind_values
- end
+ add_relations_to_bind_values(attributes)
PredicateBuilder.build_from_hash(klass, attributes, table)
else
@@ -1137,5 +1135,19 @@ WARNING
raise ArgumentError, "The method .#{method_name}() must contain arguments."
end
end
+
+ # This function is recursive just for better readablity.
+ # #where argument doesn't support more than one level nested hash in real world.
+ def add_relations_to_bind_values(attributes)
+ if attributes.is_a?(Hash)
+ attributes.each_value do |value|
+ if value.is_a?(ActiveRecord::Relation)
+ self.bind_values += value.bind_values
+ else
+ add_relations_to_bind_values(value)
+ end
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 4bfd0167a4..0a5546a760 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
# = Active Record Schema
#
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index a94364bde1..fae6427ea1 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -92,16 +92,9 @@ HEADER
def tables(stream)
sorted_tables = @connection.tables.sort
- sorted_tables.each do |tbl|
- next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
- case ignored
- when String; remove_prefix_and_suffix(tbl) == ignored
- when Regexp; remove_prefix_and_suffix(tbl) =~ ignored
- else
- raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
- end
- end
- table(tbl, stream)
+
+ sorted_tables.each do |table_name|
+ table(table_name, stream) unless ignored?(table_name)
end
# dump foreign keys at the end to make sure all dependent tables exist.
@@ -254,5 +247,16 @@ HEADER
def remove_prefix_and_suffix(table)
table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2")
end
+
+ def ignored?(table_name)
+ ['schema_migrations', ignore_tables].flatten.any? do |ignored|
+ case ignored
+ when String; remove_prefix_and_suffix(table_name) == ignored
+ when Regexp; remove_prefix_and_suffix(table_name) =~ ignored
+ else
+ raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 9bc23b5ec7..892c78e479 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -110,6 +110,8 @@ module ActiveRecord
def drop(*arguments)
configuration = arguments.first
class_for_adapter(configuration['adapter']).new(*arguments).drop
+ rescue ActiveRecord::NoDatabaseError
+ $stderr.puts "Database '#{configuration['database']}' does not exist"
rescue Exception => error
$stderr.puts error, *(error.backtrace)
$stderr.puts "Couldn't drop #{configuration['database']}"
@@ -125,6 +127,16 @@ module ActiveRecord
}
end
+ def migrate
+ verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
+ version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
+ scope = ENV['SCOPE']
+ Migration.verbose = verbose
+ Migrator.migrate(Migrator.migrations_paths, version) do |migration|
+ scope.blank? || scope == migration.scope
+ end
+ end
+
def charset_current(environment = env)
charset ActiveRecord::Base.configurations[environment]
end
@@ -172,20 +184,39 @@ module ActiveRecord
end
def load_schema(format = ActiveRecord::Base.schema_format, file = nil)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
+ This method will act on a specific connection in the future.
+ To act on the current connection, use `load_schema_current` instead.
+ MESSAGE
+ load_schema_current(format, file)
+ end
+
+ # This method is the successor of +load_schema+. We should rename it
+ # after +load_schema+ went through a deprecation cycle. (Rails > 4.2)
+ def load_schema_for(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
case format
when :ruby
file ||= File.join(db_dir, "schema.rb")
check_schema_file(file)
+ purge(configuration)
+ ActiveRecord::Base.establish_connection(configuration)
load(file)
when :sql
file ||= File.join(db_dir, "structure.sql")
check_schema_file(file)
- structure_load(current_config, file)
+ purge(configuration)
+ structure_load(configuration, file)
else
raise ArgumentError, "unknown format #{format.inspect}"
end
end
+ def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
+ each_current_configuration(environment) { |configuration|
+ load_schema_for configuration, format, file
+ }
+ end
+
def check_schema_file(filename)
unless File.exist?(filename)
message = %{#{filename} doesn't exist yet. Run `rake db:migrate` to create it, then try again.}
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index 3d02ee07d0..ce1de4b76e 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -54,7 +54,7 @@ module ActiveRecord
command = "pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(configuration['database'])}"
raise 'Error dumping database' unless Kernel.system(command)
- File.open(filename, "a") { |f| f << "SET search_path TO #{ActiveRecord::Base.connection.schema_search_path};\n\n" }
+ File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
end
def structure_load(filename)
diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
index 5688931db2..9ab64d0325 100644
--- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -21,7 +21,11 @@ module ActiveRecord
FileUtils.rm(file) if File.exist?(file)
end
- alias :purge :drop
+
+ def purge
+ drop
+ create
+ end
def charset
connection.encoding
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index e2e37e7c00..936a18d99a 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
# = Active Record Timestamp
#
@@ -48,8 +47,9 @@ module ActiveRecord
current_time = current_time_from_proper_timezone
all_timestamp_attributes.each do |column|
- if respond_to?(column) && respond_to?("#{column}=") && self.send(column).nil?
- write_attribute(column.to_s, current_time)
+ column = column.to_s
+ if has_attribute?(column) && !attribute_present?(column)
+ write_attribute(column, current_time)
end
end
end
@@ -114,7 +114,7 @@ module ActiveRecord
def clear_timestamp_attributes
all_timestamp_attributes_in_model.each do |attribute_name|
self[attribute_name] = nil
- changed_attributes.delete(attribute_name)
+ clear_attribute_changes([attribute_name])
end
end
end
diff --git a/activerecord/lib/active_record/type/decimal.rb b/activerecord/lib/active_record/type/decimal.rb
index ba5d244729..d10778eeb6 100644
--- a/activerecord/lib/active_record/type/decimal.rb
+++ b/activerecord/lib/active_record/type/decimal.rb
@@ -14,12 +14,25 @@ module ActiveRecord
private
def cast_value(value)
- if value.is_a?(::Numeric) || value.is_a?(::String)
+ case value
+ when ::Float
+ BigDecimal(value, float_precision)
+ when ::Numeric, ::String
BigDecimal(value, precision.to_i)
- elsif value.respond_to?(:to_d)
- value.to_d
else
- cast_value(value.to_s)
+ if value.respond_to?(:to_d)
+ value.to_d
+ else
+ cast_value(value.to_s)
+ end
+ end
+ end
+
+ def float_precision
+ if precision.to_i > ::Float::DIG + 1
+ ::Float::DIG + 1
+ else
+ precision.to_i
end
end
end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 42bbed7103..abeea769c4 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -12,7 +12,7 @@ module ActiveRecord
end
def type_cast_from_database(value)
- if is_default_value?(value)
+ if default_value?(value)
value
else
coder.load(super)
@@ -21,7 +21,7 @@ module ActiveRecord
def type_cast_for_database(value)
return if value.nil?
- unless is_default_value?(value)
+ unless default_value?(value)
super coder.dump(value)
end
end
@@ -43,7 +43,7 @@ module ActiveRecord
private
- def is_default_value?(value)
+ def default_value?(value)
value == coder.load(nil)
end
end
diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb
index e0a783fb45..9456a4a56c 100644
--- a/activerecord/lib/active_record/type/value.rb
+++ b/activerecord/lib/active_record/type/value.rb
@@ -69,13 +69,20 @@ module ActiveRecord
end
# Determines whether the mutable value has been modified since it was
- # read. Returns +false+ by default. This method should not need to be
- # overriden directly. Types which return a mutable value should include
+ # read. Returns +false+ by default. This method should not be overridden
+ # directly. Types which return a mutable value should include
# +Type::Mutable+, which will define this method.
def changed_in_place?(*)
false
end
+ def ==(other)
+ self.class == other.class &&
+ precision == other.precision &&
+ scale == other.scale &&
+ limit == other.limit
+ end
+
private
def type_cast(value)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index b4b33804de..7f7d49cdb4 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -29,21 +29,6 @@ module ActiveRecord
extend ActiveSupport::Concern
include ActiveModel::Validations
- module ClassMethods
- # Creates an object just like Base.create but calls <tt>save!</tt> instead of +save+
- # so an exception is raised if the record is invalid.
- def create!(attributes = nil, &block)
- if attributes.is_a?(Array)
- attributes.collect { |attr| create!(attr, &block) }
- else
- object = new(attributes)
- yield(object) if block_given?
- object.save!
- object
- end
- 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.
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 2a34969a8c..2dba4c7b94 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -152,7 +152,7 @@ module ActiveRecord
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
# proc or string should return or evaluate to a +true+ or +false+ value.
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
- # determine if the validation should ot occur (e.g. <tt>unless: :skip_validation</tt>,
+ # 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.