aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/associations.rb140
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb27
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb69
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb4
-rw-r--r--activerecord/lib/active_record/base.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb3
-rw-r--r--activerecord/lib/active_record/enum.rb3
-rw-r--r--activerecord/lib/active_record/errors.rb38
-rw-r--r--activerecord/lib/active_record/inheritance.rb2
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb34
-rw-r--r--activerecord/lib/active_record/migration.rb142
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb39
-rw-r--r--activerecord/lib/active_record/model_schema.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake16
-rw-r--r--activerecord/lib/active_record/reflection.rb86
-rw-r--r--activerecord/lib/active_record/relation.rb23
-rw-r--r--activerecord/lib/active_record/relation/merger.rb12
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb4
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb4
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb1
-rw-r--r--activerecord/lib/active_record/schema.rb36
-rw-r--r--activerecord/lib/active_record/scoping/default.rb12
-rw-r--r--activerecord/lib/active_record/table_metadata.rb12
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb8
-rw-r--r--activerecord/lib/active_record/timestamp.rb17
-rw-r--r--activerecord/lib/active_record/type/decimal.rb4
-rw-r--r--activerecord/lib/active_record/validations.rb20
37 files changed, 539 insertions, 284 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index a830b0e0e4..65eb6fba27 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -5,65 +5,105 @@ require 'active_record/errors'
module ActiveRecord
class AssociationNotFoundError < ConfigurationError #:nodoc:
- def initialize(record, association_name)
- super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
+ def initialize(record = nil, association_name = nil)
+ if record && association_name
+ super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
+ else
+ super("Association was not found.")
+ end
end
end
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
- def initialize(reflection, associated_class = nil)
- super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
+ def initialize(reflection = nil, associated_class = nil)
+ if reflection
+ super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
+ else
+ super("Could not find the inverse association.")
+ end
end
end
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
- def initialize(owner_class_name, reflection)
- super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
+ def initialize(owner_class_name = nil, reflection = nil)
+ if owner_class_name && reflection
+ super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
+ else
+ super("Could not find the association.")
+ end
end
end
class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc:
- def initialize(owner_class_name, reflection, source_reflection)
- super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.")
+ def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil)
+ if owner_class_name && reflection && source_reflection
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.")
+ else
+ super("Cannot have a has_many :through association.")
+ end
end
end
class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
- def initialize(owner_class_name, reflection)
- super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
+ def initialize(owner_class_name = nil, reflection = nil)
+ if owner_class_name && reflection
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
+ else
+ super("Cannot have a has_many :through association.")
+ end
end
end
class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
- def initialize(owner_class_name, reflection, source_reflection)
- super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
+ def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil)
+ if owner_class_name && reflection && source_reflection
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
+ else
+ super("Cannot have a has_many :through association.")
+ end
end
end
class HasOneThroughCantAssociateThroughCollection < ActiveRecordError #:nodoc:
- def initialize(owner_class_name, reflection, through_reflection)
- super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.")
+ def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil)
+ if owner_class_name && reflection && through_reflection
+ super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.")
+ else
+ super("Cannot have a has_one :through association.")
+ end
end
end
class HasOneAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
- def initialize(owner_class_name, reflection)
- super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
+ def initialize(owner_class_name = nil, reflection = nil)
+ if owner_class_name && reflection
+ super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
+ else
+ super("Cannot have a has_one :through association.")
+ end
end
end
class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
- def initialize(reflection)
- through_reflection = reflection.through_reflection
- source_reflection_names = reflection.source_reflection_names
- source_associations = reflection.through_reflection.klass._reflections.keys
- super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
+ def initialize(reflection = nil)
+ if reflection
+ through_reflection = reflection.through_reflection
+ source_reflection_names = reflection.source_reflection_names
+ source_associations = reflection.through_reflection.klass._reflections.keys
+ super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
+ else
+ super("Could not find the source association(s).")
+ end
end
end
class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
- def initialize(owner, reflection)
- super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
+ def initialize(owner = nil, reflection = nil)
+ if owner && reflection
+ super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
+ else
+ super("Cannot modify association.")
+ end
end
end
@@ -74,20 +114,32 @@ module ActiveRecord
end
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
- def initialize(owner, reflection)
- super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
+ def initialize(owner = nil, reflection = nil)
+ if owner && reflection
+ super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
+ else
+ super("Cannot associate new records.")
+ end
end
end
class HasManyThroughCantDissociateNewRecords < ActiveRecordError #:nodoc:
- def initialize(owner, reflection)
- super("Cannot dissociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.")
+ def initialize(owner = nil, reflection = nil)
+ if owner && reflection
+ super("Cannot dissociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.")
+ else
+ super("Cannot dissociate new records.")
+ end
end
end
class ThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc:
- def initialize(owner, reflection)
- super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
+ def initialize(owner = nil, reflection = nil)
+ if owner && reflection
+ super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
+ else
+ super("Through nested associations are read-only.")
+ end
end
end
@@ -98,14 +150,22 @@ module ActiveRecord
end
class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
- def initialize(reflection)
- super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}")
+ def initialize(reflection = nil)
+ if reflection
+ super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}")
+ else
+ super("Eager load polymorphic error.")
+ end
end
end
class ReadOnlyAssociation < ActiveRecordError #:nodoc:
- def initialize(reflection)
- super("Cannot add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
+ def initialize(reflection = nil)
+ if reflection
+ super("Cannot add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
+ else
+ super("Read-only reflection error.")
+ end
end
end
@@ -113,8 +173,12 @@ module ActiveRecord
# (has_many, has_one) when there is at least 1 child associated instance.
# ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
class DeleteRestrictionError < ActiveRecordError #:nodoc:
- def initialize(name)
- super("Cannot delete record because of dependent #{name}")
+ def initialize(name = nil)
+ if name
+ super("Cannot delete record because of dependent #{name}")
+ else
+ super("Delete restriction error.")
+ end
end
end
@@ -619,10 +683,10 @@ module ActiveRecord
# @tag = @post.tags.build name: "ruby"
# @tag.save
#
- # The last line ought to save the through record (a <tt>Taggable</tt>). This will only work if the
+ # The last line ought to save the through record (a <tt>Tagging</tt>). This will only work if the
# <tt>:inverse_of</tt> is set:
#
- # class Taggable < ActiveRecord::Base
+ # class Tagging < ActiveRecord::Base
# belongs_to :post
# belongs_to :tag, inverse_of: :taggings
# end
@@ -643,7 +707,7 @@ module ActiveRecord
# You can turn off the automatic detection of inverse associations by setting
# the <tt>:inverse_of</tt> option to <tt>false</tt> like so:
#
- # class Taggable < ActiveRecord::Base
+ # class Tagging < ActiveRecord::Base
# belongs_to :tag, inverse_of: false
# end
#
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index b5a8c81fe4..79b6b80d84 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -227,6 +227,31 @@ module ActiveRecord
@association.last(*args)
end
+ # Gives a record (or N records if a parameter is supplied) from the collection
+ # using the same rules as <tt>ActiveRecord::Base.take</tt>.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.take # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
+ #
+ # person.pets.take(2)
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
+ # # ]
+ #
+ # another_person_without.pets # => []
+ # another_person_without.pets.take # => nil
+ # another_person_without.pets.take(2) # => []
def take(n = nil)
@association.take(n)
end
@@ -856,7 +881,7 @@ module ActiveRecord
!!@association.include?(record)
end
- def arel
+ def arel #:nodoc:
scope.arel
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 19cdd7f470..f8211ef9fb 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -50,7 +50,7 @@ module ActiveRecord
end
def empty?
- if has_cached_counter?
+ if reflection.has_cached_counter?
size.zero?
else
super
@@ -73,8 +73,8 @@ module ActiveRecord
# If the collection is empty the target is set to an empty array and
# the loaded flag is set to true as well.
def count_records
- count = if has_cached_counter?
- owner._read_attribute cached_counter_attribute_name
+ count = if reflection.has_cached_counter?
+ owner._read_attribute reflection.counter_cache_column
else
scope.count
end
@@ -87,77 +87,26 @@ module ActiveRecord
[association_scope.limit_value, count].compact.min
end
-
- # Returns whether a counter cache should be used for this association.
- #
- # The counter_cache option must be given on either the owner or inverse
- # association, and the column must be present on the owner.
- def has_cached_counter?(reflection = reflection())
- if reflection.options[:counter_cache] || (inverse = inverse_which_updates_counter_cache(reflection)) && inverse.options[:counter_cache]
- owner.attribute_present?(cached_counter_attribute_name(reflection))
- end
- end
-
- def cached_counter_attribute_name(reflection = reflection())
- if reflection.options[:counter_cache]
- reflection.options[:counter_cache].to_s
- else
- "#{reflection.name}_count"
- end
- end
-
def update_counter(difference, reflection = reflection())
update_counter_in_database(difference, reflection)
update_counter_in_memory(difference, reflection)
end
def update_counter_in_database(difference, reflection = reflection())
- if has_cached_counter?(reflection)
- counter = cached_counter_attribute_name(reflection)
- owner.class.update_counters(owner.id, counter => difference)
+ if reflection.has_cached_counter?
+ owner.class.update_counters(owner.id, reflection.counter_cache_column => difference)
end
end
def update_counter_in_memory(difference, reflection = reflection())
- if counter_must_be_updated_by_has_many?(reflection)
- counter = cached_counter_attribute_name(reflection)
+ if reflection.counter_must_be_updated_by_has_many?
+ counter = reflection.counter_cache_column
+ owner[counter] ||= 0
owner[counter] += difference
owner.send(:clear_attribute_changes, counter) # eww
end
end
- # This shit is nasty. We need to avoid the following situation:
- #
- # * An associated record is deleted via record.destroy
- # * Hence the callbacks run, and they find a belongs_to on the record with a
- # :counter_cache options which points back at our owner. So they update the
- # counter cache.
- # * In which case, we must make sure to *not* update the counter cache, or else
- # it will be decremented twice.
- #
- # Hence this method.
- def inverse_which_updates_counter_cache(reflection = reflection())
- counter_name = cached_counter_attribute_name(reflection)
- inverse_which_updates_counter_named(counter_name, reflection)
- end
- alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
-
- def inverse_which_updates_counter_named(counter_name, reflection)
- reflection.klass._reflections.values.find { |inverse_reflection|
- inverse_reflection.belongs_to? &&
- inverse_reflection.counter_cache_column == counter_name
- }
- end
-
- def inverse_updates_counter_in_memory?(reflection)
- inverse = inverse_which_updates_counter_cache(reflection)
- inverse && inverse == reflection.inverse_of
- end
-
- def counter_must_be_updated_by_has_many?(reflection)
- !inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection)
- end
-
def delete_count(method, scope)
if method == :delete_all
scope.delete_all
@@ -175,7 +124,7 @@ module ActiveRecord
def delete_records(records, method)
if method == :destroy
records.each(&:destroy!)
- update_counter(-records.length) unless inverse_updates_counter_cache?
+ update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
else
scope = self.scope.where(reflection.klass.primary_key => records)
update_counter(-delete_count(method, scope))
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 1aa6a2ca74..deb0f8c9f5 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -110,7 +110,7 @@ module ActiveRecord
def update_through_counter?(method)
case method
when :destroy
- !inverse_updates_counter_cache?(through_reflection)
+ !through_reflection.inverse_updates_counter_cache?
when :nullify
false
else
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 5a92bc5e8a..1829453d73 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -65,7 +65,7 @@ module ActiveRecord
when :destroy
target.destroy
when :nullify
- target.update_columns(reflection.foreign_key => nil)
+ target.update_columns(reflection.foreign_key => nil) if target.persisted?
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 2363cf7608..5197e21fa4 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -56,14 +56,12 @@ module ActiveRecord
end
end
- ID = 'id'.freeze
-
# Returns the value of the attribute identified by <tt>attr_name</tt> after
# it has been typecast (for example, "2004-12-12" in a date column is cast
# to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name, &block)
name = attr_name.to_s
- name = self.class.primary_key if name == ID
+ name = self.class.primary_key if name == 'id'.freeze
_read_attribute(name, &block)
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 55a7e053bc..4b66d8cd36 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1,5 +1,4 @@
require 'yaml'
-require 'set'
require 'active_support/benchmarkable'
require 'active_support/dependencies'
require 'active_support/descendants_tracker'
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 a30945d0ee..08803303cf 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -23,6 +23,11 @@ module ActiveRecord
table_name[0...table_alias_length].tr('.', '_')
end
+ # Returns an array of table names defined in the database.
+ def tables(name = nil)
+ raise NotImplementedError, "#tables is not implemented"
+ end
+
# Checks to see if the table +table_name+ exists on the database.
#
# table_exists?(:developers)
@@ -607,10 +612,7 @@ module ActiveRecord
# remove_index :accounts, name: :by_branch_party
#
def remove_index(table_name, options = {})
- remove_index!(table_name, index_name_for_remove(table_name, options))
- end
-
- def remove_index!(table_name, index_name) #:nodoc:
+ index_name = index_name_for_remove(table_name, options)
execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
end
@@ -857,7 +859,7 @@ module ActiveRecord
ActiveRecord::SchemaMigration.create_table
end
- def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
+ def assume_migrated_upto_version(version, migrations_paths)
migrations_paths = Array(migrations_paths)
version = version.to_i
sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index b3e55a0b90..7b47539596 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -727,8 +727,10 @@ module ActiveRecord
# SHOW VARIABLES LIKE 'name'
def show_variable(name)
- variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
+ variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
variables.first['Value'] unless variables.empty?
+ rescue ActiveRecord::StatementInvalid
+ nil
end
# Returns a table's primary key and belonging sequence.
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index ff43c7ec42..734b384e80 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,6 +1,6 @@
require 'active_record/connection_adapters/abstract_mysql_adapter'
-gem 'mysql2', '~> 0.3.18'
+gem 'mysql2', '>= 0.3.18', '< 0.5'
require 'mysql2'
module ActiveRecord
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 2ae462d773..0738c59ddf 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -223,7 +223,7 @@ module ActiveRecord
return @client_encoding if @client_encoding
result = exec_query(
- "SHOW VARIABLES WHERE Variable_name = 'character_set_client'",
+ "select @@character_set_client",
'SCHEMA')
@client_encoding = ENCODINGS[result.rows.last.last]
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
index 191c828e60..6155e53632 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
@@ -36,7 +36,7 @@ module ActiveRecord
WHERE
t.typname IN (%s)
OR t.typtype IN (%s)
- OR t.typinput::varchar = 'array_in'
+ OR t.typinput = 'array_in(cstring,oid,integer)'::regprocedure
OR t.typelem != 0
SQL
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index f175730551..d5879ea7df 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -31,6 +31,11 @@ module ActiveRecord
Utils.extract_schema_qualified_name(name.to_s).quoted
end
+ # Quotes schema names for use in SQL queries.
+ def quote_schema_name(name)
+ PGconn.quote_ident(name)
+ end
+
def quote_table_name_for_assignment(table, attr)
quote_column_name(attr)
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index d114cad16b..69aa02ccf4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -68,7 +68,7 @@ module ActiveRecord
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
end
- # Returns the list of all tables in the schema search path or a specified schema.
+ # Returns the list of all tables in the schema search path.
def tables(name = nil)
select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA')
end
@@ -101,15 +101,19 @@ module ActiveRecord
# Verifies existence of an index with a given name.
def index_name_exists?(table_name, index_name, default)
+ table = Utils.extract_schema_qualified_name(table_name.to_s)
+ index = Utils.extract_schema_qualified_name(index_name.to_s)
+
select_value(<<-SQL, 'SCHEMA').to_i > 0
SELECT COUNT(*)
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
INNER JOIN pg_class i ON d.indexrelid = i.oid
+ LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
WHERE i.relkind = 'i'
- AND i.relname = '#{index_name}'
- AND t.relname = '#{table_name}'
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
+ AND i.relname = '#{index.identifier}'
+ AND t.relname = '#{table.identifier}'
+ AND n.nspname = #{index.schema ? "'#{index.schema}'" : 'ANY (current_schemas(false))'}
SQL
end
@@ -210,12 +214,12 @@ module ActiveRecord
# Creates a schema for the given schema name.
def create_schema schema_name
- execute "CREATE SCHEMA #{schema_name}"
+ execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
end
# Drops the schema for the given schema name.
- def drop_schema schema_name
- execute "DROP SCHEMA #{schema_name} CASCADE"
+ def drop_schema(schema_name, options = {})
+ execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
end
# Sets the schema search path to a string of comma-separated schema names.
@@ -376,7 +380,7 @@ module ActiveRecord
new_seq = "#{new_name}_#{pk}_seq"
idx = "#{table_name}_pkey"
new_idx = "#{new_name}_pkey"
- execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
+ execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
end
@@ -447,8 +451,15 @@ module ActiveRecord
execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
end
- def remove_index!(table_name, index_name) #:nodoc:
- execute "DROP INDEX #{quote_table_name(index_name)}"
+ def remove_index(table_name, options = {}) #:nodoc:
+ index_name = index_name_for_remove(table_name, options)
+ algorithm =
+ if Hash === options && options.key?(:algorithm)
+ index_algorithms.fetch(options[:algorithm]) do
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
+ end
+ end
+ execute "DROP INDEX #{algorithm} #{quote_table_name(index_name)}"
end
# Renames an index of a table. Raises error if length of new
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 358039723f..24fc67938d 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -375,7 +375,8 @@ module ActiveRecord
pks[0]['name']
end
- def remove_index!(table_name, index_name) #:nodoc:
+ def remove_index(table_name, options = {}) #:nodoc:
+ index_name = index_name_for_remove(table_name, options)
exec_query "DROP INDEX #{quote_column_name(index_name)}"
end
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 91a13cb0cd..4902fcb1a2 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -18,10 +18,9 @@ module ActiveRecord
# conversation.archived? # => true
# conversation.status # => "archived"
#
- # # conversation.update! status: 1
+ # # conversation.status = 1
# conversation.status = "archived"
#
- # # conversation.update! status: nil
# conversation.status = nil
# conversation.status.nil? # => true
# conversation.status # => nil
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 718f04871d..6721fe144f 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -63,7 +63,7 @@ module ActiveRecord
class RecordNotSaved < ActiveRecordError
attr_reader :record
- def initialize(message, record = nil)
+ def initialize(message = nil, record = nil)
@record = record
super(message)
end
@@ -80,7 +80,7 @@ module ActiveRecord
class RecordNotDestroyed < ActiveRecordError
attr_reader :record
- def initialize(message, record = nil)
+ def initialize(message = nil, record = nil)
@record = record
super(message)
end
@@ -92,9 +92,9 @@ module ActiveRecord
class StatementInvalid < ActiveRecordError
attr_reader :original_exception
- def initialize(message, original_exception = nil)
- super(message)
+ def initialize(message = nil, original_exception = nil)
@original_exception = original_exception
+ super(message)
end
end
@@ -134,10 +134,14 @@ module ActiveRecord
class StaleObjectError < ActiveRecordError
attr_reader :record, :attempted_action
- def initialize(record, attempted_action)
- super("Attempted to #{attempted_action} a stale object: #{record.class.name}")
- @record = record
- @attempted_action = attempted_action
+ def initialize(record = nil, attempted_action = nil)
+ if record && attempted_action
+ @record = record
+ @attempted_action = attempted_action
+ super("Attempted to #{attempted_action} a stale object: #{record.class.name}.")
+ else
+ super("Stale object error.")
+ end
end
end
@@ -196,7 +200,7 @@ module ActiveRecord
class AttributeAssignmentError < ActiveRecordError
attr_reader :exception, :attribute
- def initialize(message, exception, attribute)
+ def initialize(message = nil, exception = nil, attribute = nil)
super(message)
@exception = exception
@attribute = attribute
@@ -209,7 +213,7 @@ module ActiveRecord
class MultiparameterAssignmentErrors < ActiveRecordError
attr_reader :errors
- def initialize(errors)
+ def initialize(errors = nil)
@errors = errors
end
end
@@ -218,11 +222,15 @@ module ActiveRecord
class UnknownPrimaryKey < ActiveRecordError
attr_reader :model
- def initialize(model, description = nil)
- message = "Unknown primary key for table #{model.table_name} in model #{model}."
- message += "\n#{description}" if description
- super(message)
- @model = model
+ def initialize(model = nil, description = nil)
+ if model
+ message = "Unknown primary key for table #{model.table_name} in model #{model}."
+ message += "\n#{description}" if description
+ @model = model
+ super(message)
+ else
+ super("Unknown primary key.")
+ end
end
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index e613d157aa..c26842014d 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -82,7 +82,7 @@ module ActiveRecord
# Returns the class descending directly from ActiveRecord::Base, or
# an abstract class, if any, in the inheritance hierarchy.
#
- # If A extends AR::Base, A.base_class will return A. If B descends from A
+ # If A extends ActiveRecord::Base, A.base_class will return A. If B descends from A
# through some arbitrarily deep hierarchy, B.base_class will return A.
#
# If B < A and C < B and if A is an abstract_class then both B.base_class
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 4d597a0ab1..b63caa4473 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -47,20 +47,40 @@ module ActiveRecord
binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect
end
- name = color(name, nil, true)
+ name = colorize_payload_name(name, payload[:name])
sql = color(sql, sql_color(sql), true)
debug " #{name} #{sql}#{binds}"
end
+ private
+
+ def colorize_payload_name(name, payload_name)
+ if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
+ color(name, MAGENTA, true)
+ else
+ color(name, CYAN, true)
+ end
+ end
+
def sql_color(sql)
case sql
- when /\s*\Ainsert/i then GREEN
- when /\s*\Aselect/i then BLUE
- when /\s*\Aupdate/i then YELLOW
- when /\s*\Adelete/i then RED
- when /transaction\s*\Z/i then CYAN
- else MAGENTA
+ when /\A\s*rollback/mi
+ RED
+ when /\s*.*?select .*for update/mi, /\A\s*lock/mi
+ WHITE
+ when /\A\s*select/i
+ BLUE
+ when /\A\s*insert/i
+ GREEN
+ when /\A\s*update/i
+ YELLOW
+ when /\A\s*delete/i
+ RED
+ when /transaction\s*\Z/i
+ CYAN
+ else
+ MAGENTA
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index c35efbdee8..5bdb7213cd 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -9,40 +9,128 @@ module ActiveRecord
end
end
- # Exception that can be raised to stop migrations from going backwards.
+ # Exception that can be raised to stop migrations from being rolled back.
+ # For example the following migration is not reversible.
+ # Rolling back this migration will raise an ActiveRecord::IrreversibleMigration error.
+ #
+ # class IrreversibleMigrationExample < ActiveRecord::Migration
+ # def change
+ # create_table :distributors do |t|
+ # t.string :zipcode
+ # end
+ #
+ # execute <<-SQL
+ # ALTER TABLE distributors
+ # ADD CONSTRAINT zipchk
+ # CHECK (char_length(zipcode) = 5) NO INHERIT;
+ # SQL
+ # end
+ # end
+ #
+ # There are two ways to mitigate this problem.
+ #
+ # 1. Define <tt>#up</tt> and <tt>#down</tt> methods instead of <tt>#change</tt>:
+ #
+ # class ReversibleMigrationExample < ActiveRecord::Migration
+ # def up
+ # create_table :distributors do |t|
+ # t.string :zipcode
+ # end
+ #
+ # execute <<-SQL
+ # ALTER TABLE distributors
+ # ADD CONSTRAINT zipchk
+ # CHECK (char_length(zipcode) = 5) NO INHERIT;
+ # SQL
+ # end
+ #
+ # def down
+ # execute <<-SQL
+ # ALTER TABLE distributors
+ # DROP CONSTRAINT zipchk
+ # SQL
+ #
+ # drop_table :distributors
+ # end
+ # end
+ #
+ # 2. Use the #reversible method in <tt>#change</tt> method:
+ #
+ # class ReversibleMigrationExample < ActiveRecord::Migration
+ # def change
+ # create_table :distributors do |t|
+ # t.string :zipcode
+ # end
+ #
+ # reversible do |dir|
+ # dir.up do
+ # execute <<-SQL
+ # ALTER TABLE distributors
+ # ADD CONSTRAINT zipchk
+ # CHECK (char_length(zipcode) = 5) NO INHERIT;
+ # SQL
+ # end
+ #
+ # dir.down do
+ # execute <<-SQL
+ # ALTER TABLE distributors
+ # DROP CONSTRAINT zipchk
+ # SQL
+ # end
+ # end
+ # end
+ # end
class IrreversibleMigration < MigrationError
end
class DuplicateMigrationVersionError < MigrationError#:nodoc:
- def initialize(version)
- super("Multiple migrations have the version number #{version}")
+ def initialize(version = nil)
+ if version
+ super("Multiple migrations have the version number #{version}.")
+ else
+ super("Duplicate migration version error.")
+ end
end
end
class DuplicateMigrationNameError < MigrationError#:nodoc:
- def initialize(name)
- super("Multiple migrations have the name #{name}")
+ def initialize(name = nil)
+ if name
+ super("Multiple migrations have the name #{name}.")
+ else
+ super("Duplicate migration name.")
+ end
end
end
class UnknownMigrationVersionError < MigrationError #:nodoc:
- def initialize(version)
- super("No migration with version number #{version}")
+ def initialize(version = nil)
+ if version
+ super("No migration with version number #{version}.")
+ else
+ super("Unknown migration version.")
+ end
end
end
class IllegalMigrationNameError < MigrationError#:nodoc:
- def initialize(name)
- super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
+ def initialize(name = nil)
+ if name
+ super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed).")
+ else
+ super("Illegal name for migration.")
+ end
end
end
class PendingMigrationError < MigrationError#:nodoc:
- def initialize
- if defined?(Rails.env)
- super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate RAILS_ENV=#{::Rails.env}")
+ def initialize(message = nil)
+ if !message && defined?(Rails.env)
+ super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate RAILS_ENV=#{::Rails.env}.")
+ elsif !message
+ super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate.")
else
- super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate")
+ super
end
end
end
@@ -168,7 +256,7 @@ module ActiveRecord
#
# rails generate migration add_fieldname_to_tablename fieldname:string
#
- # This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
+ # This will generate the file <tt>timestamp_add_fieldname_to_tablename.rb</tt>, which will look like this:
# class AddFieldnameToTablename < ActiveRecord::Migration
# def change
# add_column :tablenames, :fieldname, :string
@@ -459,7 +547,7 @@ module ActiveRecord
# Or equivalently, if +TenderloveMigration+ is defined as in the
# documentation for Migration:
#
- # require_relative '2012121212_tenderlove_migration'
+ # require_relative '20121212123456_tenderlove_migration'
#
# class FixupTLMigration < ActiveRecord::Migration
# def change
@@ -475,13 +563,13 @@ module ActiveRecord
def revert(*migration_classes)
run(*migration_classes.reverse, revert: true) unless migration_classes.empty?
if block_given?
- if @connection.respond_to? :revert
- @connection.revert { yield }
+ if connection.respond_to? :revert
+ connection.revert { yield }
else
- recorder = CommandRecorder.new(@connection)
+ recorder = CommandRecorder.new(connection)
@connection = recorder
suppress_messages do
- @connection.revert { yield }
+ connection.revert { yield }
end
@connection = recorder.delegate
recorder.commands.each do |cmd, args, block|
@@ -492,7 +580,7 @@ module ActiveRecord
end
def reverting?
- @connection.respond_to?(:reverting) && @connection.reverting
+ connection.respond_to?(:reverting) && connection.reverting
end
class ReversibleBlockHelper < Struct.new(:reverting) # :nodoc:
@@ -549,7 +637,7 @@ module ActiveRecord
revert { run(*migration_classes, direction: dir, revert: true) }
else
migration_classes.each do |migration_class|
- migration_class.new.exec_migration(@connection, dir)
+ migration_class.new.exec_migration(connection, dir)
end
end
end
@@ -641,7 +729,7 @@ module ActiveRecord
arg_list = arguments.map(&:inspect) * ', '
say_with_time "#{method}(#{arg_list})" do
- unless @connection.respond_to? :revert
+ unless connection.respond_to? :revert
unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method)
arguments[0] = proper_table_name(arguments.first, table_name_options)
if [:rename_table, :add_foreign_key].include?(method) ||
@@ -814,7 +902,7 @@ module ActiveRecord
new(:up, migrations, target_version).migrate
end
- def down(migrations_paths, target_version = nil, &block)
+ def down(migrations_paths, target_version = nil)
migrations = migrations(migrations_paths)
migrations.select! { |m| yield m } if block_given?
@@ -853,10 +941,6 @@ module ActiveRecord
migrations(migrations_paths).any?
end
- def last_version
- last_migration.version
- end
-
def last_migration #:nodoc:
migrations(migrations_paths).last || NullMigration.new
end
@@ -867,10 +951,6 @@ module ActiveRecord
Array(@migrations_paths)
end
- def migrations_path
- migrations_paths.first
- end
-
def migrations(paths)
paths = Array(paths)
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index dcc2362397..4c4afb4dbd 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -5,15 +5,34 @@ module ActiveRecord
# knows how to invert the following commands:
#
# * add_column
+ # * add_foreign_key
# * add_index
+ # * add_reference
# * add_timestamps
- # * create_table
+ # * change_column_default (must supply a :from and :to option)
+ # * change_column_null
# * create_join_table
+ # * create_table
+ # * disable_extension
+ # * drop_join_table
+ # * drop_table (must supply a block)
+ # * enable_extension
+ # * remove_column (must supply a type)
+ # * remove_foreign_key (must supply a second table)
+ # * remove_index
+ # * remove_reference
# * remove_timestamps
# * rename_column
# * rename_index
# * rename_table
class CommandRecorder
+ ReversibleAndIrreversibleMethods = [:create_table, :create_join_table, :rename_table, :add_column, :remove_column,
+ :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps,
+ :change_column_default, :add_reference, :remove_reference, :transaction,
+ :drop_join_table, :drop_table, :execute_block, :enable_extension, :disable_extension,
+ :change_column, :execute, :remove_columns, :change_column_null,
+ :add_foreign_key, :remove_foreign_key
+ ]
include JoinTable
attr_accessor :commands, :delegate, :reverting
@@ -41,7 +60,7 @@ module ActiveRecord
@reverting = !@reverting
end
- # record +command+. +command+ should be a method name and arguments.
+ # Record +command+. +command+ should be a method name and arguments.
# For example:
#
# recorder.record(:method_name, [:arg1, :arg2])
@@ -62,7 +81,12 @@ module ActiveRecord
# invert the +command+.
def inverse_of(command, args, &block)
method = :"invert_#{command}"
- raise IrreversibleMigration unless respond_to?(method, true)
+ raise IrreversibleMigration, <<-MSG.strip_heredoc unless respond_to?(method, true)
+ This migration uses #{command}, which is not automatically reversible.
+ To make the migration reversible you can either:
+ 1. Define #up and #down methods in place of the #change method.
+ 2. Use the #reversible method to define reversible behavior.
+ MSG
send(method, args, &block)
end
@@ -70,14 +94,7 @@ module ActiveRecord
super || delegate.respond_to?(*args)
end
- [:create_table, :create_join_table, :rename_table, :add_column, :remove_column,
- :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps,
- :add_reference, :remove_reference, :transaction,
- :drop_join_table, :drop_table, :execute_block, :enable_extension,
- :change_column, :execute, :remove_columns, :change_column_null,
- :add_foreign_key, :remove_foreign_key
- # irreversible methods need to be here too
- ].each do |method|
+ ReversibleAndIrreversibleMethods.each do |method|
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{method}(*args, &block) # def create_table(*args, &block)
record(:"#{method}", args, &block) # record(:create_table, args, &block)
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 5a6f42ba09..2b0c755ef4 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -240,7 +240,7 @@ module ActiveRecord
end
# Returns a hash where the keys are column names and the values are
- # default values when instantiating the AR object for this table.
+ # default values when instantiating the Active Record object for this table.
def column_defaults
load_schema
_default_attributes.to_hash
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 6a72d528b4..d543a84ff1 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -79,7 +79,7 @@ db_namespace = namespace :db do
task :up => [:environment, :load_config] do
version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
raise 'VERSION is required' unless version
- ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_paths, version)
+ ActiveRecord::Migrator.run(:up, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version)
db_namespace['_dump'].invoke
end
@@ -87,7 +87,7 @@ db_namespace = namespace :db do
task :down => [:environment, :load_config] do
version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
raise 'VERSION is required - To go down one migration, run db:rollback' unless version
- ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version)
+ ActiveRecord::Migrator.run(:down, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version)
db_namespace['_dump'].invoke
end
@@ -99,7 +99,7 @@ db_namespace = namespace :db do
db_list = ActiveRecord::SchemaMigration.normalized_versions
file_list =
- ActiveRecord::Migrator.migrations_paths.flat_map do |path|
+ ActiveRecord::Tasks::DatabaseTasks.migrations_paths.flat_map do |path|
# match "20091231235959_some_name.rb" and "001_some_name.rb" pattern
Dir.foreach(path).grep(/^(\d{3,})_(.+)\.rb$/) do
version = ActiveRecord::SchemaMigration.normalize_migration_number($1)
@@ -125,14 +125,14 @@ db_namespace = namespace :db do
desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n).'
task :rollback => [:environment, :load_config] do
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
- ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step)
+ ActiveRecord::Migrator.rollback(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step)
db_namespace['_dump'].invoke
end
# desc 'Pushes the schema to the next version (specify steps w/ STEP=n).'
task :forward => [:environment, :load_config] do
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
- ActiveRecord::Migrator.forward(ActiveRecord::Migrator.migrations_paths, step)
+ ActiveRecord::Migrator.forward(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step)
db_namespace['_dump'].invoke
end
@@ -160,7 +160,7 @@ db_namespace = namespace :db do
# desc "Raises an error if there are pending migrations"
task :abort_if_pending_migrations => [:environment, :load_config] do
- pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Migrator.migrations_paths).pending_migrations
+ pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Tasks::DatabaseTasks.migrations_paths).pending_migrations
if pending_migrations.any?
puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
@@ -229,7 +229,7 @@ db_namespace = namespace :db do
end
namespace :schema do
- desc 'Creates a db/schema.rb file that is portable against any DB supported by AR'
+ desc 'Creates a db/schema.rb file that is portable against any DB supported by Active Record'
task :dump => [:environment, :load_config] do
require 'active_record/schema_dumper'
filename = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb')
@@ -384,7 +384,7 @@ namespace :railties do
puts "Copied migration #{migration.basename} from #{name}"
end
- ActiveRecord::Migration.copy(ActiveRecord::Migrator.migrations_paths.first, railties,
+ ActiveRecord::Migration.copy(ActiveRecord::Tasks::DatabaseTasks.migrations_paths.first, railties,
:on_skip => on_skip, :on_copy => on_copy)
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 5360db6a19..2b648978b3 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -32,6 +32,7 @@ module ActiveRecord
end
def self.add_reflection(ar, name, reflection)
+ ar.clear_reflections_cache
ar._reflections = ar._reflections.merge(name.to_s => reflection)
end
@@ -67,18 +68,22 @@ module ActiveRecord
#
# @api public
def reflections
- ref = {}
- _reflections.each do |name, reflection|
- parent_reflection = reflection.parent_reflection
+ @__reflections ||= begin
+ ref = {}
- if parent_reflection
- parent_name = parent_reflection.name
- ref[parent_name.to_s] = parent_reflection
- else
- ref[name] = reflection
+ _reflections.each do |name, reflection|
+ parent_reflection = reflection.parent_reflection
+
+ if parent_reflection
+ parent_name = parent_reflection.name
+ ref[parent_name.to_s] = parent_reflection
+ else
+ ref[name] = reflection
+ end
end
+
+ ref
end
- ref
end
# Returns an array of AssociationReflection objects for all the
@@ -94,7 +99,8 @@ module ActiveRecord
# @api public
def reflect_on_all_associations(macro = nil)
association_reflections = reflections.values
- macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
+ association_reflections.select! { |reflection| reflection.macro == macro } if macro
+ association_reflections
end
# Returns the AssociationReflection object for the +association+ (use the symbol).
@@ -118,6 +124,10 @@ module ActiveRecord
def reflect_on_all_autosave_associations
reflections.values.select { |reflection| reflection.options[:autosave] }
end
+
+ def clear_reflections_cache #:nodoc:
+ @__reflections = nil
+ end
end
# Holds all the methods that are shared between MacroReflection, AssociationReflection
@@ -159,6 +169,54 @@ module ActiveRecord
scope_chain.flatten
end
+ def counter_cache_column
+ if belongs_to?
+ if options[:counter_cache] == true
+ "#{active_record.name.demodulize.underscore.pluralize}_count"
+ elsif options[:counter_cache]
+ options[:counter_cache].to_s
+ end
+ else
+ options[:counter_cache] ? options[:counter_cache].to_s : "#{name}_count"
+ end
+ end
+
+ # This shit is nasty. We need to avoid the following situation:
+ #
+ # * An associated record is deleted via record.destroy
+ # * Hence the callbacks run, and they find a belongs_to on the record with a
+ # :counter_cache options which points back at our owner. So they update the
+ # counter cache.
+ # * In which case, we must make sure to *not* update the counter cache, or else
+ # it will be decremented twice.
+ #
+ # Hence this method.
+ def inverse_which_updates_counter_cache
+ return @inverse_which_updates_counter_cache if defined?(@inverse_which_updates_counter_cache)
+ @inverse_which_updates_counter_cache = klass.reflect_on_all_associations(:belongs_to).find do |inverse|
+ inverse.counter_cache_column == counter_cache_column
+ end
+ end
+ alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
+
+ def inverse_updates_counter_in_memory?
+ inverse_of && inverse_which_updates_counter_cache == inverse_of
+ end
+
+ # Returns whether a counter cache should be used for this association.
+ #
+ # The counter_cache option must be given on either the owner or inverse
+ # association, and the column must be present on the owner.
+ def has_cached_counter?
+ options[:counter_cache] ||
+ inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache] &&
+ !!active_record.columns_hash[counter_cache_column]
+ end
+
+ def counter_must_be_updated_by_has_many?
+ !inverse_updates_counter_in_memory? && has_cached_counter?
+ end
+
def alias_candidate(name)
"#{plural_name}_#{name}"
end
@@ -321,14 +379,6 @@ module ActiveRecord
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
end
- def counter_cache_column
- if options[:counter_cache] == true
- "#{active_record.name.demodulize.underscore.pluralize}_count"
- elsif options[:counter_cache]
- options[:counter_cache].to_s
- end
- end
-
def check_validity!
check_validity_of_inverse!
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index e47b7b1ed9..bf08cdbbf3 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -418,7 +418,7 @@ module ActiveRecord
end
end
- # Destroys the records matching +conditions+ by instantiating each
+ # Destroys the records by instantiating each
# record and calling its +destroy+ method. Each object's callbacks are
# executed (including <tt>:dependent</tt> association options). Returns the
# collection of objects that were destroyed; each will be frozen, to
@@ -431,20 +431,15 @@ module ActiveRecord
# rows quickly, without concern for their associations or callbacks, use
# +delete_all+ instead.
#
- # ==== Parameters
- #
- # * +conditions+ - A string, array, or hash that specifies which records
- # to destroy. If omitted, all records are destroyed. See the
- # Conditions section in the introduction to ActiveRecord::Base for
- # more information.
- #
# ==== Examples
#
- # Person.destroy_all("last_login < '2004-04-04'")
- # Person.destroy_all(status: "inactive")
# Person.where(age: 0..18).destroy_all
def destroy_all(conditions = nil)
if conditions
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ Passing conditions to destroy_all is deprecated and will be removed in Rails 5.1.
+ To achieve the same use where(conditions).destroy_all
+ MESSAGE
where(conditions).destroy_all
else
to_a.each(&:destroy).tap { reset }
@@ -478,15 +473,13 @@ module ActiveRecord
end
end
- # Deletes the records matching +conditions+ without instantiating the records
+ # Deletes the records without instantiating the records
# first, and hence not calling the +destroy+ method nor invoking callbacks. This
# is a single SQL DELETE statement that goes straight to the database, much more
# efficient than +destroy_all+. Be careful with relations though, in particular
# <tt>:dependent</tt> rules defined on associations are not honored. Returns the
# number of rows affected.
#
- # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
- # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
# Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
#
# Both calls delete the affected posts all at once with a single DELETE statement.
@@ -512,6 +505,10 @@ module ActiveRecord
end
if conditions
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ Passing conditions to delete_all is deprecated and will be removed in Rails 5.1.
+ To achieve the same use where(conditions).delete_all
+ MESSAGE
where(conditions).delete_all
else
stmt = Arel::DeleteManager.new
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 0b38666ce9..cb971eb255 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -148,11 +148,15 @@ module ActiveRecord
end
end
+ CLAUSE_METHOD_NAMES = CLAUSE_METHODS.map do |name|
+ ["#{name}_clause", "#{name}_clause="]
+ end
+
def merge_clauses
- CLAUSE_METHODS.each do |name|
- clause = relation.send("#{name}_clause")
- other_clause = other.send("#{name}_clause")
- relation.send("#{name}_clause=", clause.merge(other_clause))
+ CLAUSE_METHOD_NAMES.each do |(reader, writer)|
+ clause = relation.send(reader)
+ other_clause = other.send(reader)
+ relation.send(writer, clause.merge(other_clause))
end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index d26db7d4cf..e232516b0c 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -24,12 +24,12 @@ module ActiveRecord
end
def build_from_hash(attributes)
- attributes = convert_dot_notation_to_hash(attributes.stringify_keys)
+ attributes = convert_dot_notation_to_hash(attributes)
expand_from_hash(attributes)
end
def create_binds(attributes)
- attributes = convert_dot_notation_to_hash(attributes.stringify_keys)
+ attributes = convert_dot_notation_to_hash(attributes)
create_binds_for_hash(attributes)
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
index 159889d3b8..e81be63cd3 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
@@ -10,10 +10,10 @@ module ActiveRecord
table = value.associated_table
if value.base_class
- queries[table.association_foreign_type] = value.base_class.name
+ queries[table.association_foreign_type.to_s] = value.base_class.name
end
- queries[table.association_foreign_key] = value.ids
+ queries[table.association_foreign_key.to_s] = value.ids
predicate_builder.build_from_hash(queries)
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 706c99c245..e25b889851 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -548,7 +548,7 @@ module ActiveRecord
# If the condition is any blank-ish object, then #where is a no-op and returns
# the current relation.
def where(opts = :chain, *rest)
- if opts == :chain
+ if :chain == opts
WhereChain.new(spawn)
elsif opts.blank?
self
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
index 0430922be3..23eaab4699 100644
--- a/activerecord/lib/active_record/relation/where_clause_factory.rb
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -15,6 +15,7 @@ module ActiveRecord
when Hash
attributes = predicate_builder.resolve_column_aliases(opts)
attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
+ attributes.stringify_keys!
attributes, binds = predicate_builder.create_binds(attributes)
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 0a5546a760..c1a42dc629 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -28,24 +28,6 @@ module ActiveRecord
# ActiveRecord::Schema is only supported by database adapters that also
# support migrations, the two features being very similar.
class Schema < Migration
-
- # Returns the migrations paths.
- #
- # ActiveRecord::Schema.new.migrations_paths
- # # => ["db/migrate"] # Rails migration path by default.
- def migrations_paths
- ActiveRecord::Migrator.migrations_paths
- end
-
- def define(info, &block) # :nodoc:
- instance_eval(&block)
-
- unless info[:version].blank?
- initialize_schema_migrations_table
- connection.assume_migrated_upto_version(info[:version], migrations_paths)
- end
- end
-
# Eval the given block. All methods available to the current connection
# adapter are available within the block, so you can easily use the
# database definition DSL to build up your schema (+create_table+,
@@ -60,5 +42,23 @@ module ActiveRecord
def self.define(info={}, &block)
new.define(info, &block)
end
+
+ def define(info, &block) # :nodoc:
+ instance_eval(&block)
+
+ if info[:version].present?
+ initialize_schema_migrations_table
+ connection.assume_migrated_upto_version(info[:version], migrations_paths)
+ end
+ end
+
+ private
+ # Returns the migrations paths.
+ #
+ # ActiveRecord::Schema.new.migrations_paths
+ # # => ["db/migrate"] # Rails migration path by default.
+ def migrations_paths # :nodoc:
+ ActiveRecord::Migrator.migrations_paths
+ end
end
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index a1adf8e3ee..fac566e12b 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -6,8 +6,10 @@ module ActiveRecord
included do
# Stores the default scope for the class.
class_attribute :default_scopes, instance_writer: false, instance_predicate: false
+ class_attribute :default_scope_override, instance_predicate: false
self.default_scopes = []
+ self.default_scope_override = nil
end
module ClassMethods
@@ -99,12 +101,18 @@ module ActiveRecord
self.default_scopes += [scope]
end
- def build_default_scope(base_rel = relation) # :nodoc:
+ def build_default_scope(base_rel = nil) # :nodoc:
return if abstract_class?
- if !Base.is_a?(method(:default_scope).owner)
+
+ if self.default_scope_override.nil?
+ self.default_scope_override = !Base.is_a?(method(:default_scope).owner)
+ end
+
+ if self.default_scope_override
# The user has defined their own default scope method, so call that
evaluate_default_scope { default_scope }
elsif default_scopes.any?
+ base_rel ||= relation
evaluate_default_scope do
default_scopes.inject(base_rel) do |default_scope, scope|
default_scope.merge(base_rel.scoping { scope.call })
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index 41f1c55c3c..f9bb1cf5e0 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -10,13 +10,15 @@ module ActiveRecord
end
def resolve_column_aliases(hash)
- hash = hash.dup
- hash.keys.grep(Symbol) do |key|
- if klass.attribute_alias? key
- hash[klass.attribute_alias(key)] = hash.delete key
+ # This method is a hot spot, so for now, use Hash[] to dup the hash.
+ # https://bugs.ruby-lang.org/issues/7166
+ new_hash = Hash[hash]
+ hash.each do |key, _|
+ if (key.is_a?(Symbol)) && klass.attribute_alias?(key)
+ new_hash[klass.attribute_alias(key)] = new_hash.delete(key)
end
end
- hash
+ new_hash
end
def arel_attribute(column_name)
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 683741768b..f1141a8613 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -134,7 +134,7 @@ module ActiveRecord
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
scope = ENV['SCOPE']
verbose_was, Migration.verbose = Migration.verbose, verbose
- Migrator.migrate(Migrator.migrations_paths, version) do |migration|
+ Migrator.migrate(migrations_paths, version) do |migration|
scope.blank? || scope == migration.scope
end
ensure
@@ -221,12 +221,6 @@ module ActiveRecord
end
end
- def load_schema_current_if_exists(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
- if File.exist?(file || schema_file(format))
- load_schema_current(format, file, environment)
- end
- end
-
def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
each_current_configuration(environment) { |configuration|
load_schema configuration, format, file
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 20e4235788..e759475cfb 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -15,14 +15,21 @@ module ActiveRecord
#
# == Time Zone aware attributes
#
- # By default, ActiveRecord::Base keeps all the datetime columns time zone aware by executing following code.
+ # Active Record keeps all the <tt>datetime</tt> and <tt>time</tt> columns
+ # time-zone aware. By default, these values are stored in the database as UTC
+ # and converted back to the current Time.zone when pulled from the database.
#
- # config.active_record.time_zone_aware_attributes = true
+ # This feature can be turned off completely by setting:
#
- # This feature can easily be turned off by assigning value <tt>false</tt> .
+ # config.active_record.time_zone_aware_attributes = false
#
- # If your attributes are time zone aware and you desire to skip time zone conversion to the current Time.zone
- # when reading certain attributes then you can do following:
+ # You can also specify that only <tt>datetime</tt> columns should be time-zone
+ # aware (while <tt>time</tt> should not) by setting:
+ #
+ # ActiveRecord::Base.time_zone_aware_types = [:datetime]
+ #
+ # Finally, you can indicate specific attributes of a model for which time zone
+ # conversion should not applied, for instance by setting:
#
# class Topic < ActiveRecord::Base
# self.skip_time_zone_conversion_for_attributes = [:written_on]
diff --git a/activerecord/lib/active_record/type/decimal.rb b/activerecord/lib/active_record/type/decimal.rb
index f200a92d10..f5b145230d 100644
--- a/activerecord/lib/active_record/type/decimal.rb
+++ b/activerecord/lib/active_record/type/decimal.rb
@@ -14,7 +14,7 @@ module ActiveRecord
private
def cast_value(value)
- case value
+ casted_value = case value
when ::Float
convert_float_to_big_decimal(value)
when ::Numeric, ::String
@@ -26,6 +26,8 @@ module ActiveRecord
cast_value(value.to_s)
end
end
+
+ scale ? casted_value.round(scale) : casted_value
end
def convert_float_to_big_decimal(value)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 34d96b19fe..4113ca4561 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -12,10 +12,16 @@ module ActiveRecord
class RecordInvalid < ActiveRecordError
attr_reader :record
- def initialize(record)
- @record = record
- errors = @record.errors.full_messages.join(", ")
- super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid"))
+ def initialize(record = nil)
+ if record
+ @record = record
+ errors = @record.errors.full_messages.join(", ")
+ message = I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", errors: errors, default: :"errors.messages.record_invalid")
+ else
+ message = "Record invalid"
+ end
+
+ super(message)
end
end
@@ -54,7 +60,7 @@ module ActiveRecord
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
# some <tt>:on</tt> option will only run in the specified context.
def valid?(context = nil)
- context ||= (new_record? ? :create : :update)
+ context ||= default_validation_context
output = super(context)
errors.empty? && output
end
@@ -63,6 +69,10 @@ module ActiveRecord
protected
+ def default_validation_context
+ new_record? ? :create : :update
+ end
+
def raise_validation_error
raise(RecordInvalid.new(self))
end