aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-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/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.rb4
-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/connection_adapters/abstract/connection_pool.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb256
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb2
-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.rb7
-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/persistence.rb22
-rw-r--r--activerecord/lib/active_record/railties/databases.rake19
-rw-r--r--activerecord/lib/active_record/reflection.rb71
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb18
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb31
-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.rb7
-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
33 files changed, 369 insertions, 316 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/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 719eff9acc..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::HasManyReflection ...>
+ # 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 a2bb78dfcc..09e2faee86 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -279,9 +279,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/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/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 3a266512a9..8f06cf3a1f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -1,78 +1,7 @@
module ActiveRecord
module ConnectionAdapters
- class TransactionManager #:nodoc:
- def initialize(connection)
- @stack = []
- @connection = connection
- end
-
- def begin_transaction(options = {})
- transaction_class = @stack.empty? ? RealTransaction : SavepointTransaction
- transaction = transaction_class.new(@connection, current_transaction, options)
-
- @stack.push(transaction)
- transaction
- end
-
- def commit_transaction
- @stack.pop.commit
- end
-
- def rollback_transaction
- @stack.pop.rollback
- end
-
- def within_new_transaction(options = {})
- transaction = begin_transaction options
- yield
- rescue Exception => error
- transaction.rollback if transaction
- raise
- ensure
- begin
- transaction.commit unless error
- rescue Exception
- transaction.rollback
- raise
- ensure
- @stack.pop if transaction
- end
- end
-
- def open_transactions
- @stack.size
- end
-
- def current_transaction
- @stack.last || closed_transaction
- end
-
- private
-
- def closed_transaction
- @closed_transaction ||= ClosedTransaction.new(@connection)
- end
- end
-
- class Transaction #:nodoc:
- attr_reader :connection
-
- def initialize(connection)
- @connection = connection
- @state = TransactionState.new
- end
-
- def state
- @state
- end
-
- def savepoint_name
- nil
- end
- end
-
class TransactionState
- attr_accessor :parent
+ attr_reader :parent
VALID_STATES = Set.new([:committed, :rolledback, nil])
@@ -101,65 +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 = []
- @joinable = options.fetch(:joinable, true)
- end
-
-
- def joinable?
- @joinable
- end
-
- def number
- parent.number + 1
- end
-
- def begin(options = {})
- SavepointTransaction.new(connection, self, options)
- end
+ class Transaction #:nodoc:
- def rollback
- perform_rollback
- parent
- end
+ attr_reader :connection, :state, :records, :savepoint_name
+ attr_writer :joinable
- def commit
- perform_commit
- parent
+ def initialize(connection, options)
+ @connection = connection
+ @state = TransactionState.new
+ @records = []
+ @joinable = options.fetch(:joinable, true)
end
def add_record(record)
@@ -170,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!
@@ -192,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 open?
- true
+ def rollback
+ super
+ connection.rollback_to_savepoint(savepoint_name)
+ rollback_records
end
- end
- class RealTransaction < OpenTransaction #:nodoc:
- def initialize(connection, parent, options = {})
+ 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 < Transaction
+
+ def initialize(connection, options)
+ super
if options[:isolation]
connection.begin_isolated_db_transaction(options[:isolation])
else
@@ -212,42 +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:
- attr_reader :savepoint_name
+ class TransactionManager #:nodoc:
+ def initialize(connection)
+ @stack = []
+ @connection = connection
+ end
- def initialize(connection, parent, options = {})
- if options[:isolation]
- raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
- end
+ 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
- super
+ def commit_transaction
+ @stack.pop.commit
+ end
- # Savepoint name only counts the Savepoint transactions, so we need to subtract 1
- @savepoint_name = "active_record_#{number - 1}"
- connection.create_savepoint(@savepoint_name)
+ def rollback_transaction
+ @stack.pop.rollback
end
- def perform_rollback
- connection.rollback_to_savepoint(@savepoint_name)
- rollback_records
+ 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 perform_commit
- @state.set_state(:committed)
- @state.parent = parent.state
- connection.release_savepoint(@savepoint_name)
+ 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 99c728814a..a1b6671664 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -46,7 +46,7 @@ module ActiveRecord
autoload_at 'active_record/connection_adapters/abstract/transaction' do
autoload :TransactionManager
- autoload :ClosedTransaction
+ autoload :NullTransaction
autoload :RealTransaction
autoload :SavepointTransaction
autoload :TransactionState
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 3116bed596..a10ce330c7 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -19,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)
@@ -82,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/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/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/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/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 1672128aa3..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,18 +149,13 @@ 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
@@ -354,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
@@ -383,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
@@ -431,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
@@ -578,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
@@ -737,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
@@ -804,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/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/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index b7315ed4b3..892c78e479 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -127,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
@@ -174,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 ddf3e1804c..936a18d99a 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -47,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
@@ -113,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.