aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb3
-rw-r--r--activerecord/lib/active_record/association_relation.rb4
-rw-r--r--activerecord/lib/active_record/associations.rb1
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb6
-rw-r--r--activerecord/lib/active_record/associations/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb3
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb3
-rw-r--r--activerecord/lib/active_record/associations/foreign_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb9
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb15
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb9
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb6
-rw-r--r--activerecord/lib/active_record/attributes.rb5
-rw-r--r--activerecord/lib/active_record/base.rb3
-rw-r--r--activerecord/lib/active_record/callbacks.rb2
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb174
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb47
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb26
-rw-r--r--activerecord/lib/active_record/connection_handling.rb2
-rw-r--r--activerecord/lib/active_record/core.rb19
-rw-r--r--activerecord/lib/active_record/counter_cache.rb7
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb18
-rw-r--r--activerecord/lib/active_record/migration.rb2
-rw-r--r--activerecord/lib/active_record/model_schema.rb4
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb3
-rw-r--r--activerecord/lib/active_record/no_touching.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb22
-rw-r--r--activerecord/lib/active_record/railties/databases.rake4
-rw-r--r--activerecord/lib/active_record/relation.rb12
-rw-r--r--activerecord/lib/active_record/relation/batches.rb4
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/merger.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb92
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb16
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb58
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/base_handler.rb17
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb17
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/class_handler.rb27
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/range_handler.rb17
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb11
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb4
-rw-r--r--activerecord/lib/active_record/table_metadata.rb53
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb43
-rw-r--r--activerecord/lib/active_record/timestamp.rb6
-rw-r--r--activerecord/lib/active_record/transactions.rb14
-rw-r--r--activerecord/lib/active_record/type/decimal.rb10
-rw-r--r--activerecord/lib/active_record/type/numeric.rb2
-rw-r--r--activerecord/lib/active_record/type/serialized.rb2
-rw-r--r--activerecord/lib/active_record/type/string.rb4
-rw-r--r--activerecord/lib/active_record/type/value.rb8
-rw-r--r--activerecord/lib/active_record/type_caster.rb7
-rw-r--r--activerecord/lib/active_record/type_caster/connection.rb34
-rw-r--r--activerecord/lib/active_record/type_caster/map.rb19
-rw-r--r--activerecord/lib/active_record/validations.rb1
-rw-r--r--activerecord/lib/active_record/validations/length.rb21
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb13
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb3
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb6
79 files changed, 744 insertions, 334 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 9028970a3d..2eec62846b 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -62,6 +62,7 @@ module ActiveRecord
autoload :Serialization
autoload :StatementCache
autoload :Store
+ autoload :TableMetadata
autoload :Timestamp
autoload :Transactions
autoload :Translation
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
index 5a84792f45..f2b44913db 100644
--- a/activerecord/lib/active_record/association_relation.rb
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -1,7 +1,7 @@
module ActiveRecord
class AssociationRelation < Relation
- def initialize(klass, table, association)
- super(klass, table)
+ def initialize(klass, table, predicate_builder, association)
+ super(klass, table, predicate_builder)
@association = association
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index cd5fdd5964..14af55f327 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -116,6 +116,7 @@ module ActiveRecord
autoload :Association, 'active_record/associations/association'
autoload :SingularAssociation, 'active_record/associations/singular_association'
autoload :CollectionAssociation, 'active_record/associations/collection_association'
+ autoload :ForeignAssociation, 'active_record/associations/foreign_association'
autoload :CollectionProxy, 'active_record/associations/collection_proxy'
autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 0c3234ed24..f9c9f8afda 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -56,11 +56,11 @@ module ActiveRecord
@connection = connection
end
- def aliased_table_for(table_name, aliased_name)
+ def aliased_table_for(table_name, aliased_name, **table_options)
if aliases[table_name].zero?
# If it's zero, we can have our table_name
aliases[table_name] = 1
- Arel::Table.new(table_name)
+ Arel::Table.new(table_name, table_options)
else
# Otherwise, we need to use an alias
aliased_name = connection.table_alias_for(aliased_name)
@@ -73,7 +73,7 @@ module ActiveRecord
else
aliased_name
end
- Arel::Table.new(table_name).alias(table_alias)
+ Arel::Table.new(table_name, table_options).alias(table_alias)
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index f1c36cd047..0d8e4ba870 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -121,7 +121,7 @@ module ActiveRecord
# Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
# through association's scope)
def target_scope
- AssociationRelation.create(klass, klass.arel_table, self).merge!(klass.all)
+ AssociationRelation.create(klass, klass.arel_table, klass.predicate_builder, self).merge!(klass.all)
end
# Loads the \target if needed and returns it.
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 0ac10531e5..53f65920e1 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -66,7 +66,8 @@ module ActiveRecord
chain.map do |reflection|
alias_tracker.aliased_table_for(
table_name_for(reflection, klass, refl),
- table_alias_for(reflection, refl, reflection != refl)
+ table_alias_for(reflection, refl, reflection != refl),
+ type_caster: klass.type_caster,
)
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 81fdd681de..c63b42e2a0 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -73,11 +73,11 @@ module ActiveRecord
# Checks whether record is different to the current target, without loading it
def different_target?(record)
- record.id != owner[reflection.foreign_key]
+ record.id != owner._read_attribute(reflection.foreign_key)
end
def replace_keys(record)
- owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
+ owner[reflection.foreign_key] = record._read_attribute(reflection.association_primary_key(record.class))
end
def remove_keys
@@ -85,7 +85,7 @@ module ActiveRecord
end
def foreign_key_present?
- owner[reflection.foreign_key]
+ owner._read_attribute(reflection.foreign_key)
end
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
@@ -99,12 +99,13 @@ module ActiveRecord
if options[:primary_key]
owner.send(reflection.name).try(:id)
else
- owner[reflection.foreign_key]
+ owner._read_attribute(reflection.foreign_key)
end
end
def stale_state
- owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
+ result = owner._read_attribute(reflection.foreign_key)
+ result && result.to_s
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 7b6aefe345..16b1228b8a 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -597,8 +597,8 @@ module ActiveRecord
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
assoc = owner.association(reflection.through_reflection.name)
assoc.reader.any? { |source|
- target = source.send(reflection.source_reflection.name)
- target.respond_to?(:include?) ? target.include?(record) : target == record
+ target_reflection = source.send(reflection.source_reflection.name)
+ target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record
} || target.include?(record)
else
target.include?(record)
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 060b2278d9..dc42b19a83 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -29,10 +29,11 @@ module ActiveRecord
# instantiation of the actual post records.
class CollectionProxy < Relation
delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
+ delegate :find_nth, to: :scope
def initialize(klass, association) #:nodoc:
@association = association
- super klass, klass.arel_table
+ super klass, klass.arel_table, klass.predicate_builder
merge! association.scope(nullify: false)
end
diff --git a/activerecord/lib/active_record/associations/foreign_association.rb b/activerecord/lib/active_record/associations/foreign_association.rb
new file mode 100644
index 0000000000..fe48ecec29
--- /dev/null
+++ b/activerecord/lib/active_record/associations/foreign_association.rb
@@ -0,0 +1,11 @@
+module ActiveRecord::Associations
+ module ForeignAssociation
+ def foreign_key_present?
+ if reflection.klass.primary_key
+ owner.attribute_present?(reflection.active_record_primary_key)
+ else
+ false
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 93084e0dcf..d7f655d00c 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -6,6 +6,7 @@ module ActiveRecord
# If the association has a <tt>:through</tt> option further specialization
# is provided by its child HasManyThroughAssociation.
class HasManyAssociation < CollectionAssociation #:nodoc:
+ include ForeignAssociation
def handle_dependency
case options[:dependent]
@@ -153,14 +154,6 @@ module ActiveRecord
end
end
- def foreign_key_present?
- if reflection.klass.primary_key
- owner.attribute_present?(reflection.association_primary_key)
- else
- false
- end
- end
-
def concat_records(records, *)
update_counter_if_success(super, records.length)
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 3f4d3bfc08..7a050ca224 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -13,21 +13,6 @@ module ActiveRecord
@through_association = nil
end
- # Returns the size of the collection by executing a SELECT COUNT(*) query
- # if the collection hasn't been loaded, and by calling collection.size if
- # it has. If the collection will likely have a size greater than zero,
- # and if fetching the collection will be needed afterwards, one less
- # SELECT query will be generated by using #length instead.
- def size
- if has_cached_counter?
- owner._read_attribute cached_counter_attribute_name(reflection)
- elsif loaded?
- target.size
- else
- super
- end
- end
-
def concat(*records)
unless owner.new_record?
records.flatten.each do |record|
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index e6095d84dc..74b8c53758 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -2,6 +2,7 @@ module ActiveRecord
# = Active Record Belongs To Has One Association
module Associations
class HasOneAssociation < SingularAssociation #:nodoc:
+ include ForeignAssociation
def handle_dependency
case options[:dependent]
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index cf63430a97..66e997c3c8 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -94,7 +94,7 @@ module ActiveRecord
#
def initialize(base, associations, joins)
@alias_tracker = AliasTracker.create(base.connection, joins)
- @alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
+ @alias_tracker.aliased_table_for(base.table_name, base.table_name, type_caster: base.type_caster) # Updates the count for base.table_name to 1
tree = self.class.make_tree associations
@join_root = JoinBase.new base, build(tree, base)
@join_root.children.each { |child| construct_tables! @join_root, child }
@@ -186,9 +186,13 @@ module ActiveRecord
def table_aliases_for(parent, node)
node.reflection.chain.map { |reflection|
+ if reflection.klass
+ type_caster = reflection.klass.type_caster
+ end
alias_tracker.aliased_table_for(
reflection.table_name,
- table_alias_for(reflection, parent, reflection != node.reflection)
+ table_alias_for(reflection, parent, reflection != node.reflection),
+ type_caster: type_caster,
)
}
end
@@ -257,6 +261,7 @@ module ActiveRecord
construct(model, node, row, rs, seen, model_cache, aliases)
else
model = construct_model(ar_parent, node, row, model_cache, id, aliases)
+ model.readonly!
seen[parent.base_klass][primary_id][node.base_klass][id] = model
construct(model, node, row, rs, seen, model_cache, aliases)
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 5dede5527d..c1ef86a95b 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -43,16 +43,23 @@ module ActiveRecord
constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
+ predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table))
scope_chain_items = scope_chain[scope_chain_index].map do |item|
if item.is_a?(Relation)
item
else
- ActiveRecord::Relation.create(klass, table).instance_exec(node, &item)
+ ActiveRecord::Relation.create(klass, table, predicate_builder)
+ .instance_exec(node, &item)
end
end
scope_chain_index += 1
- scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact
+ relation = ActiveRecord::Relation.create(
+ klass,
+ table,
+ predicate_builder,
+ )
+ scope_chain_items.concat [klass.send(:build_default_scope, relation)].compact
rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
left.merge right
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 7d6523dbc4..a6e1a24360 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -33,7 +33,7 @@ module ActiveRecord
end
def query_scope(ids)
- scope.where(association_key.in(ids))
+ scope.where(association_key_name => ids)
end
def table
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 83fcefa64d..b7edac791e 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -150,7 +150,7 @@ module ActiveRecord
BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
end
- def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc
+ def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
if klass.respond_to?(name, true)
if superklass.respond_to?(name, true)
klass.method(name).owner != superklass.method(name).owner
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 033e71f7b9..d5702accaf 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -76,6 +76,10 @@ module ActiveRecord
private
+ def changes_include?(attr_name)
+ super || attribute_changed_in_place?(attr_name)
+ end
+
def calculate_changes_from_defaults
@changed_attributes = nil
self.class.column_defaults.each do |attr, orig_value|
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 87274dd4e1..777f7ab4d7 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -12,7 +12,11 @@ module ActiveRecord
if value.is_a?(Array)
value.map { |v| type_cast_from_user(v) }
elsif value.respond_to?(:in_time_zone)
- value.in_time_zone || super
+ begin
+ value.in_time_zone || super
+ rescue ArgumentError
+ nil
+ end
end
end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 3288108a6a..aafb990bc1 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -53,9 +53,9 @@ module ActiveRecord
# store_listing.price_in_cents # => 10
#
# Users may also define their own custom types, as long as they respond to the methods
- # defined on the value type. The `type_cast` method on your type object will be called
+ # defined on the value type. The +type_cast+ method on your type object will be called
# with values both from the database, and from your controllers. See
- # `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your
+ # +ActiveRecord::Attributes::Type::Value+ for the expected API. It is recommended that your
# type objects inherit from an existing type, or the base value type.
#
# class MoneyType < ActiveRecord::Type::Integer
@@ -122,6 +122,7 @@ module ActiveRecord
end
def clear_caches_calculated_from_columns
+ @arel_table = nil
@attributes_builder = nil
@column_names = nil
@column_types = nil
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index f978fbd0a4..bb01231bca 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -22,6 +22,7 @@ require 'active_record/log_subscriber'
require 'active_record/explain_subscriber'
require 'active_record/relation/delegation'
require 'active_record/attributes'
+require 'active_record/type_caster'
module ActiveRecord #:nodoc:
# = Active Record
@@ -141,7 +142,7 @@ module ActiveRecord #:nodoc:
#
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
# Query methods allow you to test whether an attribute value is present.
- # For numeric values, present is defined as non-zero.
+ # Additionally, when dealing with numeric values, a query method will return false if the value is zero.
#
# For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
# to determine whether the user has a name:
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 523d492a48..497ce8c15c 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -298,7 +298,7 @@ module ActiveRecord
private
- def create_or_update #:nodoc:
+ def create_or_update(*) #:nodoc:
_run_save_callbacks { super }
end
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index d3d7396c91..9ea22ed798 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -8,6 +8,7 @@ module ActiveRecord
def initialize(object_class = Object)
@object_class = object_class
+ check_arity_of_constructor
end
def dump(obj)
@@ -33,6 +34,16 @@ module ActiveRecord
obj
end
+
+ private
+
+ def check_arity_of_constructor
+ begin
+ load(nil)
+ rescue ArgumentError
+ raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 33165cd8fa..6235745fb2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -363,7 +363,7 @@ module ActiveRecord
conn.expire
end
- release owner
+ release conn, owner
@available.add conn
end
@@ -376,7 +376,7 @@ module ActiveRecord
@connections.delete conn
@available.delete conn
- release conn.owner
+ release conn, conn.owner
@available.add checkout_new_connection if @available.any_waiting?
end
@@ -424,10 +424,12 @@ module ActiveRecord
end
end
- def release(owner)
+ def release(conn, owner)
thread_id = owner.object_id
- @reserved_connections.delete thread_id
+ if @reserved_connections[thread_id] == conn
+ @reserved_connections.delete thread_id
+ end
end
def new_connection
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 679878d860..143d7d9574 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -19,7 +19,7 @@ module ActiveRecord
# Cast a +value+ to a type that the database understands. For example,
# SQLite does not understand dates, so this method will convert a Date
# to a String.
- def type_cast(value, column)
+ def type_cast(value, column = nil)
if value.respond_to?(:quoted_id) && value.respond_to?(:id)
return value.id
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index 5c95b95184..18ff869ea6 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -30,7 +30,7 @@ module ActiveRecord
def visit_ColumnDefinition(o)
sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
column_sql = "#{quote_column_name(o.name)} #{sql_type}"
- add_column_options!(column_sql, column_options(o)) unless o.primary_key?
+ add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key
column_sql
end
@@ -65,6 +65,8 @@ module ActiveRecord
column_options[:column] = o
column_options[:first] = o.first
column_options[:after] = o.after
+ column_options[:auto_increment] = o.auto_increment
+ column_options[:primary_key] = o.primary_key
column_options
end
@@ -89,14 +91,17 @@ module ActiveRecord
if options[:auto_increment] == true
sql << " AUTO_INCREMENT"
end
+ if options[:primary_key] == true
+ sql << " PRIMARY KEY"
+ end
sql
end
def quote_default_expression(value, column)
column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale)
- column.cast_type ||= type_for_column(column)
+ value = type_for_column(column).type_cast_for_database(value)
- @conn.quote(value, column)
+ @conn.quote(value)
end
def options_include_default?(options)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 537e21029e..1cf1600d81 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -15,7 +15,7 @@ module ActiveRecord
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key, :sql_type, :cast_type) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :sql_type, :cast_type) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
@@ -68,6 +68,84 @@ module ActiveRecord
end
end
+ class ReferenceDefinition # :nodoc:
+ def initialize(
+ name,
+ polymorphic: false,
+ index: false,
+ foreign_key: false,
+ type: :integer,
+ **options
+ )
+ @name = name
+ @polymorphic = polymorphic
+ @index = index
+ @foreign_key = foreign_key
+ @type = type
+ @options = options
+
+ if polymorphic && foreign_key
+ raise ArgumentError, "Cannot add a foreign key to a polymorphic relation"
+ end
+ end
+
+ def add_to(table)
+ columns.each do |column_options|
+ table.column(*column_options)
+ end
+
+ if index
+ table.index(column_names, index_options)
+ end
+
+ if foreign_key
+ table.foreign_key(foreign_table_name, foreign_key_options)
+ end
+ end
+
+ protected
+
+ attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options
+
+ private
+
+ def as_options(value, default = {})
+ if value.is_a?(Hash)
+ value
+ else
+ default
+ end
+ end
+
+ def polymorphic_options
+ as_options(polymorphic, options)
+ end
+
+ def index_options
+ as_options(index)
+ end
+
+ def foreign_key_options
+ as_options(foreign_key)
+ end
+
+ def columns
+ result = [["#{name}_id", type, options]]
+ if polymorphic
+ result.unshift(["#{name}_type", :string, polymorphic_options])
+ end
+ result
+ end
+
+ def column_names
+ columns.map(&:first)
+ end
+
+ def foreign_table_name
+ name.to_s.pluralize
+ end
+ end
+
# Represents the schema of an SQL table in an abstract way. This class
# provides methods for manipulating the schema representation.
#
@@ -94,11 +172,12 @@ module ActiveRecord
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
attr_accessor :indexes
- attr_reader :name, :temporary, :options, :as
+ attr_reader :name, :temporary, :options, :as, :foreign_keys
def initialize(types, name, temporary, options, as = nil)
@columns_hash = {}
@indexes = {}
+ @foreign_keys = {}
@native = types
@temporary = temporary
@options = options
@@ -286,6 +365,10 @@ module ActiveRecord
indexes[column_name] = options
end
+ def foreign_key(table_name, options = {}) # :nodoc:
+ foreign_keys[table_name] = options
+ end
+
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
# <tt>:updated_at</tt> to the table. See SchemaStatements#add_timestamps
#
@@ -297,24 +380,20 @@ module ActiveRecord
column(:updated_at, :datetime, options)
end
- # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
- # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+
- # by default, the <tt>:type</tt> option can be used to specify a different type.
+ # Adds a reference. Optionally adds a +type+ column, if the
+ # +:polymorphic+ option is provided. +references+ and +belongs_to+
+ # are acceptable. The reference column will be an +integer+ by default,
+ # the +:type+ option can be used to specify a different type. A foreign
+ # key will be created if the +:foreign_key+ option is passed.
#
# t.references(:user)
# t.references(:user, type: "string")
# t.belongs_to(:supplier, polymorphic: true)
#
# See SchemaStatements#add_reference
- def references(*args)
- options = args.extract_options!
- polymorphic = options.delete(:polymorphic)
- index_options = options.delete(:index)
- type = options.delete(:type) || :integer
+ def references(*args, **options)
args.each do |col|
- column("#{col}_id", type, options)
- column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
- index(polymorphic ? %w(type id).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
+ ReferenceDefinition.new(col, **options).add_to(self)
end
end
alias :belongs_to :references
@@ -333,6 +412,7 @@ module ActiveRecord
column.null = options[:null]
column.first = options[:first]
column.after = options[:after]
+ column.auto_increment = options[:auto_increment]
column.primary_key = type == :primary_key || options[:primary_key]
column
end
@@ -422,33 +502,36 @@ module ActiveRecord
end
# Adds a new column to the named table.
- # See TableDefinition#column for details of the options you can use.
#
- # ====== Creating a simple column
# t.column(:name, :string)
+ #
+ # See TableDefinition#column for details of the options you can use.
def column(column_name, type, options = {})
@base.add_column(name, column_name, type, options)
end
- # Checks to see if a column exists. See SchemaStatements#column_exists?
+ # Checks to see if a column exists.
+ #
+ # See SchemaStatements#column_exists?
def column_exists?(column_name, type = nil, options = {})
@base.column_exists?(name, column_name, type, options)
end
# Adds a new index to the table. +column_name+ can be a single Symbol, or
- # an Array of Symbols. See SchemaStatements#add_index
+ # an Array of Symbols.
#
- # ====== Creating a simple index
# t.index(:name)
- # ====== Creating a unique index
# t.index([:branch_id, :party_id], unique: true)
- # ====== Creating a named index
# t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
+ #
+ # See SchemaStatements#add_index for details of the options you can use.
def index(column_name, options = {})
@base.add_index(name, column_name, options)
end
- # Checks to see if an index exists. See SchemaStatements#index_exists?
+ # Checks to see if an index exists.
+ #
+ # See SchemaStatements#index_exists?
def index_exists?(column_name, options = {})
@base.index_exists?(name, column_name, options)
end
@@ -456,30 +539,37 @@ module ActiveRecord
# Renames the given index on the table.
#
# t.rename_index(:user_id, :account_id)
+ #
+ # See SchemaStatements#rename_index
def rename_index(index_name, new_index_name)
@base.rename_index(name, index_name, new_index_name)
end
- # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
+ # Adds timestamps (+created_at+ and +updated_at+) columns to the table.
#
- # t.timestamps null: false
+ # t.timestamps(null: false)
+ #
+ # See SchemaStatements#add_timestamps
def timestamps(options = {})
@base.add_timestamps(name, options)
end
# Changes the column's definition according to the new options.
- # See TableDefinition#column for details of the options you can use.
#
# t.change(:name, :string, limit: 80)
# t.change(:description, :text)
+ #
+ # See TableDefinition#column for details of the options you can use.
def change(column_name, type, options = {})
@base.change_column(name, column_name, type, options)
end
- # Sets a new default value for a column. See SchemaStatements#change_column_default
+ # Sets a new default value for a column.
#
# t.change_default(:qualification, 'new')
# t.change_default(:authorized, 1)
+ #
+ # See SchemaStatements#change_column_default
def change_default(column_name, default)
@base.change_column_default(name, column_name, default)
end
@@ -488,20 +578,19 @@ module ActiveRecord
#
# t.remove(:qualification)
# t.remove(:qualification, :experience)
+ #
+ # See SchemaStatements#remove_columns
def remove(*column_names)
@base.remove_columns(name, *column_names)
end
# Removes the given index from the table.
#
- # ====== Remove the index_table_name_on_column in the table_name table
- # t.remove_index :column
- # ====== Remove the index named index_table_name_on_branch_id in the table_name table
- # t.remove_index column: :branch_id
- # ====== Remove the index named index_table_name_on_branch_id_and_party_id in the table_name table
- # t.remove_index column: [:branch_id, :party_id]
- # ====== Remove the index named by_branch_party in the table_name table
- # t.remove_index name: :by_branch_party
+ # t.remove_index(:branch_id)
+ # t.remove_index(column: [:branch_id, :party_id])
+ # t.remove_index(name: :by_branch_party)
+ #
+ # See SchemaStatements#remove_index
def remove_index(options = {})
@base.remove_index(name, options)
end
@@ -509,6 +598,8 @@ module ActiveRecord
# Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
#
# t.remove_timestamps
+ #
+ # See SchemaStatements#remove_timestamps
def remove_timestamps(options = {})
@base.remove_timestamps(name, options)
end
@@ -516,17 +607,19 @@ module ActiveRecord
# Renames a column.
#
# t.rename(:description, :name)
+ #
+ # See SchemaStatements#rename_column
def rename(column_name, new_column_name)
@base.rename_column(name, column_name, new_column_name)
end
- # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
- # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+
- # by default, the <tt>:type</tt> option can be used to specify a different type.
+ # Adds a reference. Optionally adds a +type+ column, if
+ # <tt>:polymorphic</tt> option is provided.
#
# t.references(:user)
# t.references(:user, type: "string")
# t.belongs_to(:supplier, polymorphic: true)
+ # t.belongs_to(:supplier, foreign_key: true)
#
# See SchemaStatements#add_reference
def references(*args)
@@ -538,7 +631,6 @@ module ActiveRecord
alias :belongs_to :references
# Removes a reference. Optionally removes a +type+ column.
- # <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
#
# t.remove_references(:user)
# t.remove_belongs_to(:supplier, polymorphic: true)
@@ -552,10 +644,12 @@ module ActiveRecord
end
alias :remove_belongs_to :remove_references
- # Adds a column or columns of a specified type
+ # Adds a column or columns of a specified type.
#
# t.string(:goat)
# t.string(:goat, :sheep)
+ #
+ # See SchemaStatements#add_column
[:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
define_method column_type do |*args|
options = args.extract_options!
@@ -565,6 +659,10 @@ module ActiveRecord
end
end
+ def foreign_key(*args) # :nodoc:
+ @base.add_foreign_key(name, *args)
+ end
+
private
def native
@base.native_database_types
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 fd52cdf716..24afd9c5da 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -132,6 +132,7 @@ module ActiveRecord
# Make a temporary table.
# [<tt>:force</tt>]
# Set to true to drop the table before creating it.
+ # Set to +:cascade+ to drop dependent objects as well.
# Defaults to false.
# [<tt>:as</tt>]
# SQL to use to generate the table. When this option is used, the block is
@@ -203,7 +204,17 @@ module ActiveRecord
end
result = execute schema_creation.accept td
- td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless supports_indexes_in_create?
+
+ unless supports_indexes_in_create?
+ td.indexes.each_pair do |column_name, index_options|
+ add_index(table_name, column_name, index_options)
+ end
+ end
+
+ td.foreign_keys.each_pair do |other_table_name, foreign_key_options|
+ add_foreign_key(table_name, other_table_name, foreign_key_options)
+ end
+
result
end
@@ -361,8 +372,12 @@ module ActiveRecord
# Drops a table from the database.
#
- # Although this command ignores +options+ and the block if one is given, it can be helpful
- # to provide these in a migration's +change+ method so it can be reverted.
+ # [<tt>:force</tt>]
+ # Set to +:cascade+ to drop dependent objects as well.
+ # Defaults to false.
+ #
+ # Although this command ignores most +options+ and the block if one is given,
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
# In that case, +options+ and the block will be used by create_table.
def drop_table(table_name, options = {})
execute "DROP TABLE #{quote_table_name(table_name)}"
@@ -571,9 +586,8 @@ module ActiveRecord
# rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
#
def rename_index(table_name, old_name, new_name)
- if new_name.length > allowed_index_name_length
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
- end
+ validate_index_length!(table_name, new_name)
+
# this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
old_index_def = indexes(table_name).detect { |i| i.name == old_name }
return unless old_index_def
@@ -622,17 +636,16 @@ module ActiveRecord
#
# add_belongs_to(:products, :supplier, polymorphic: true)
#
- # ====== Create a supplier_id, supplier_type columns and appropriate index
+ # ====== Create supplier_id, supplier_type columns and appropriate index
#
# add_reference(:products, :supplier, polymorphic: true, index: true)
#
- def add_reference(table_name, ref_name, options = {})
- polymorphic = options.delete(:polymorphic)
- index_options = options.delete(:index)
- type = options.delete(:type) || :integer
- add_column(table_name, "#{ref_name}_id", type, options)
- add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
- add_index(table_name, polymorphic ? %w[type id].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
+ # ====== Create a supplier_id column and appropriate foreign key
+ #
+ # add_reference(:products, :supplier, foreign_key: true)
+ #
+ def add_reference(table_name, *args)
+ ReferenceDefinition.new(*args).add_to(update_table_definition(table_name, self))
end
alias :add_belongs_to :add_reference
@@ -981,6 +994,12 @@ module ActiveRecord
"fk_rails_#{SecureRandom.hex(5)}"
end
end
+
+ def validate_index_length!(table_name, new_name)
+ if new_name.length > allowed_index_name_length
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
+ end
+ end
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 c4506885ed..308e4c77bd 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -21,6 +21,7 @@ module ActiveRecord
autoload :IndexDefinition
autoload :ColumnDefinition
autoload :ChangeColumnDefinition
+ autoload :ForeignKeyDefinition
autoload :TableDefinition
autoload :Table
autoload :AlterTable
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 69582ebb6f..d083e413a5 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -6,6 +6,13 @@ module ActiveRecord
class AbstractMysqlAdapter < AbstractAdapter
include Savepoints
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
+ def primary_key(name, type = :primary_key, options = {})
+ options[:auto_increment] ||= type == :bigint
+ super
+ end
+ end
+
class SchemaCreation < AbstractAdapter::SchemaCreation
def visit_AddColumn(o)
add_column_position!(super, column_options(o))
@@ -487,11 +494,13 @@ module ActiveRecord
end
def drop_table(table_name, options = {})
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}"
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
def rename_index(table_name, old_name, new_name)
if supports_rename_index?
+ validate_index_length!(table_name, new_name)
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
else
super
@@ -859,6 +868,10 @@ module ActiveRecord
end
end
+ def create_table_definition(name, temporary, options, as = nil) # :nodoc:
+ TableDefinition.new(native_database_types, name, temporary, options, as)
+ end
+
class MysqlString < Type::String # :nodoc:
def type_cast_for_database(value)
case value
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index dd303c73d5..af307b57a4 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,
- :number?, :binary?, :changed?,
+ :text?, :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/postgresql/oid/bytea.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
index 997613d7be..6bd1b8ecae 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
@@ -5,6 +5,7 @@ module ActiveRecord
class Bytea < Type::Binary # :nodoc:
def type_cast_from_database(value)
return if value.nil?
+ return value.to_s if value.is_a?(Type::Binary::Data)
PGconn.unescape_bytea(super)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
index 2d2fede4e8..b2a42e9ebb 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
@@ -8,6 +8,10 @@ module ActiveRecord
def initialize(type)
@type = type
end
+
+ def text?
+ false
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
index 033e0324bb..97b4fd3d08 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
@@ -3,14 +3,7 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Uuid < Type::Value # :nodoc:
- RFC_4122 = %r{\A\{?[a-fA-F0-9]{4}-?
- [a-fA-F0-9]{4}-?
- [a-fA-F0-9]{4}-?
- [1-5][a-fA-F0-9]{3}-?
- [8-Bab][a-fA-F0-9]{3}-?
- [a-fA-F0-9]{4}-?
- [a-fA-F0-9]{4}-?
- [a-fA-F0-9]{4}-?\}?\z}x
+ ACCEPTABLE_UUID = %r{\A\{?([a-fA-F0-9]{4}-?){8}\}?\z}x
alias_method :type_cast_for_database, :type_cast_from_database
@@ -19,7 +12,7 @@ module ActiveRecord
end
def type_cast(value)
- value.to_s[RFC_4122, 0]
+ value.to_s[ACCEPTABLE_UUID, 0]
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index b37630a04c..a9522e152f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -125,10 +125,8 @@ module ActiveRecord
# a record (as primary keys cannot be +nil+). This might be done via the
# +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
def primary_key(name, type = :primary_key, options = {})
- return super unless type == :uuid
- options[:default] = options.fetch(:default, 'uuid_generate_v4()')
- options[:primary_key] = true
- column name, type, options
+ options[:default] = options.fetch(:default, 'uuid_generate_v4()') if type == :uuid
+ super
end
def new_column_definition(name, type, options) # :nodoc:
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 f29b793a3f..a90adcf4aa 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -4,15 +4,6 @@ module ActiveRecord
class SchemaCreation < AbstractAdapter::SchemaCreation
private
- def visit_ColumnDefinition(o)
- sql = super
- if o.primary_key? && o.type != :primary_key
- sql << " PRIMARY KEY "
- add_column_options!(sql, column_options(o))
- end
- sql
- end
-
def column_options(o)
column_options = super
column_options[:array] = o.array
@@ -120,6 +111,10 @@ module ActiveRecord
SQL
end
+ def drop_table(table_name, options = {})
+ execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
+ end
+
# Returns true if schema exists.
def schema_exists?(name)
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
@@ -390,15 +385,15 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
- row = exec_query(<<-end_sql, 'SCHEMA').rows.first
+ pks = exec_query(<<-end_sql, 'SCHEMA').rows
SELECT attr.attname
FROM pg_attribute attr
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
WHERE cons.contype = 'p'
AND cons.conrelid = '#{quote_table_name(table)}'::regclass
end_sql
-
- row && row.first
+ return nil unless pks.count == 1
+ pks[0][0]
end
# Renames a table.
@@ -488,9 +483,8 @@ module ActiveRecord
end
def rename_index(table_name, old_name, new_name)
- if new_name.length > allowed_index_name_length
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
- end
+ validate_index_length!(table_name, new_name)
+
execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 6ef47d8a11..02cafc8079 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -445,8 +445,8 @@ module ActiveRecord
def initialize_type_map(m) # :nodoc:
register_class_with_limit m, 'int2', OID::Integer
- m.alias_type 'int4', 'int2'
- m.alias_type 'int8', 'int2'
+ register_class_with_limit m, 'int4', OID::Integer
+ register_class_with_limit m, 'int8', OID::Integer
m.alias_type 'oid', 'int2'
m.register_type 'float4', OID::Float.new
m.alias_type 'float8', 'float4'
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 0f7e0fac01..f3d2b25bfe 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -418,10 +418,9 @@ module ActiveRecord
end
def primary_key(table_name) #:nodoc:
- column = table_structure(table_name).find { |field|
- field['pk'] == 1
- }
- column && column['name']
+ pks = table_structure(table_name).select { |f| f['pk'] > 0 }
+ return nil unless pks.count == 1
+ pks[0]['name']
end
def remove_index!(table_name, index_name) #:nodoc:
@@ -578,23 +577,12 @@ module ActiveRecord
rename.each { |a| column_mappings[a.last] = a.first }
from_columns = columns(from).collect(&:name)
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
+ from_columns_to_copy = columns.map { |col| column_mappings[col] }
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
+ quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ','
- quoted_to = quote_table_name(to)
-
- raw_column_mappings = Hash[columns(from).map { |c| [c.name, c] }]
-
- exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
- sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
-
- column_values = columns.map do |col|
- quote(row[column_mappings[col]], raw_column_mappings[col])
- end
-
- sql << column_values * ', '
- sql << ')'
- exec_query sql
- end
+ exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
+ SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
end
def sqlite_version
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 8f51590c99..984af79642 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module ConnectionHandling
- RAILS_ENV = -> { (Rails.env if defined?(Rails)) || ENV["RAILS_ENV"] || ENV["RACK_ENV"] }
+ RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"] || ENV["RACK_ENV"] }
DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
# Establishes the connection to the database. Accepts a hash as input where
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index c2d5582f02..38b2d632d2 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -131,6 +131,7 @@ module ActiveRecord
return super if block_given? ||
primary_key.nil? ||
default_scopes.any? ||
+ current_scope ||
columns_hash.include?(inheritance_column) ||
ids.first.kind_of?(Array)
@@ -234,7 +235,7 @@ module ActiveRecord
# scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) }
# end
def arel_table # :nodoc:
- @arel_table ||= Arel::Table.new(table_name)
+ @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster)
end
# Returns the Arel engine.
@@ -247,10 +248,18 @@ module ActiveRecord
end
end
+ def predicate_builder # :nodoc:
+ @predicate_builder ||= PredicateBuilder.new(table_metadata)
+ end
+
+ def type_caster # :nodoc:
+ TypeCaster::Map.new(self)
+ end
+
private
- def relation #:nodoc:
- relation = Relation.create(self, arel_table)
+ def relation # :nodoc:
+ relation = Relation.create(self, arel_table, predicate_builder)
if finder_needs_type_condition?
relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
@@ -258,6 +267,10 @@ module ActiveRecord
relation
end
end
+
+ def table_metadata # :nodoc:
+ TableMetadata.new(self, arel_table)
+ end
end
# New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 101889638d..7d8e0a2063 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -37,10 +37,9 @@ module ActiveRecord
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({
- arel_table[counter_name] => object.send(counter_association).count(:all)
- }, primary_key)
- connection.update stmt
+ unscoped.where(primary_key => object.id).update_all(
+ counter_name => object.send(counter_association).count(:all)
+ )
end
return true
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index ced694ba9a..9f053453bd 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -11,7 +11,7 @@ module ActiveRecord
#
# == Usage
#
- # Active Records support optimistic locking if the field +lock_version+ is present. Each update to the
+ # Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the
# record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
# will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
#
@@ -80,17 +80,15 @@ module ActiveRecord
begin
relation = self.class.unscoped
- stmt = relation.where(
- relation.table[self.class.primary_key].eq(id).and(
- relation.table[lock_col].eq(self.class.quote_value(previous_lock_value, column_for_attribute(lock_col)))
- )
- ).arel.compile_update(
- arel_attributes_with_values_for_update(attribute_names),
- self.class.primary_key
+ affected_rows = relation.where(
+ self.class.primary_key => id,
+ lock_col => previous_lock_value,
+ ).update_all(
+ attribute_names.map do |name|
+ [name, _read_attribute(name)]
+ end.to_h
)
- affected_rows = self.class.connection.update stmt
-
unless affected_rows == 1
raise ActiveRecord::StaleObjectError.new(self, "update")
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 3cac465440..46f4794010 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -39,7 +39,7 @@ module ActiveRecord
class PendingMigrationError < MigrationError#:nodoc:
def initialize
- if defined?(Rails)
+ if defined?(Rails.env)
super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate RAILS_ENV=#{::Rails.env}")
else
super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate")
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 92ad9c9101..641512d323 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -147,7 +147,7 @@ module ActiveRecord
@quoted_table_name = nil
@arel_table = nil
@sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name
- @relation = Relation.create(self, arel_table)
+ @predicate_builder = nil
end
# Returns a quoted version of the table name, used to construct SQL statements.
@@ -298,12 +298,12 @@ module ActiveRecord
connection.schema_cache.clear_table_cache!(table_name) if table_exists?
@arel_engine = nil
+ @arel_table = nil
@column_names = nil
@column_types = nil
@content_columns = nil
@default_attributes = nil
@inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
- @relation = nil
end
private
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 8a2a06f2ca..0f9b52f69f 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -81,6 +81,9 @@ module ActiveRecord
#
# Note that the model will _not_ be destroyed until the parent is saved.
#
+ # Also note that the model will not be destroyed unless you also specify
+ # its id in the updated hash.
+ #
# === One-to-many
#
# Consider a member that has a number of posts:
diff --git a/activerecord/lib/active_record/no_touching.rb b/activerecord/lib/active_record/no_touching.rb
index dbf4564ae5..edb5066fa0 100644
--- a/activerecord/lib/active_record/no_touching.rb
+++ b/activerecord/lib/active_record/no_touching.rb
@@ -45,7 +45,7 @@ module ActiveRecord
NoTouching.applied_to?(self.class)
end
- def touch(*)
+ def touch(*) # :nodoc:
super unless no_touching?
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 1fc82f05d4..f53c5f17ef 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -109,6 +109,10 @@ module ActiveRecord
# validate: false, validations are bypassed altogether. See
# ActiveRecord::Validations for more information.
#
+ # By default, #save also sets the +updated_at+/+updated_on+ attributes to
+ # the current time. However, if you supply <tt>touch: false</tt>, these
+ # timestamps will not be updated.
+ #
# There's a series of callbacks associated with +save+. If any of the
# <tt>before_*</tt> callbacks return +false+ the action is cancelled and
# +save+ returns +false+. See ActiveRecord::Callbacks for further
@@ -116,21 +120,25 @@ module ActiveRecord
#
# Attributes marked as readonly are silently ignored if the record is
# being updated.
- def save(*)
- create_or_update
+ def save(*args)
+ create_or_update(*args)
rescue ActiveRecord::RecordInvalid
false
end
# Saves the model.
#
- # If the model is new a record gets created in the database, otherwise
+ # If the model is new, a record gets created in the database, otherwise
# the existing record gets updated.
#
# With <tt>save!</tt> validations always run. If any of them fail
# ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
# for more information.
#
+ # By default, #save! also sets the +updated_at+/+updated_on+ attributes to
+ # the current time. However, if you supply <tt>touch: false</tt>, these
+ # timestamps will not be updated.
+ #
# There's a series of callbacks associated with <tt>save!</tt>. If any of
# the <tt>before_*</tt> callbacks return +false+ the action is cancelled
# and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
@@ -138,8 +146,8 @@ module ActiveRecord
#
# Attributes marked as readonly are silently ignored if the record is
# being updated.
- def save!(*)
- create_or_update || raise(RecordNotSaved.new(nil, self))
+ def save!(*args)
+ create_or_update(*args) || raise(RecordNotSaved.new(nil, self))
end
# Deletes the record in the database and freezes this instance to
@@ -498,9 +506,9 @@ module ActiveRecord
relation
end
- def create_or_update
+ def create_or_update(*args)
raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
- result = new_record? ? _create_record : _update_record
+ result = new_record? ? _create_record : _update_record(*args)
result != false
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 4daf2a0e2b..04c2be045d 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -319,7 +319,7 @@ db_namespace = namespace :db do
begin
should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
ActiveRecord::Schema.verbose = false
- ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA']
+ ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA']
ensure
if should_reconnect
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env])
@@ -329,7 +329,7 @@ db_namespace = namespace :db do
# desc "Recreate the test database from an existent structure.sql file"
task :load_structure => %w(db:test:purge) do
- ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA']
+ ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA']
end
# desc "Recreate the test database from a fresh schema"
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index daafb0b645..cdafa6a50a 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -16,17 +16,17 @@ module ActiveRecord
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
- attr_reader :table, :klass, :loaded
+ attr_reader :table, :klass, :loaded, :predicate_builder
alias :model :klass
alias :loaded? :loaded
- def initialize(klass, table, values = {})
+ def initialize(klass, table, predicate_builder, values = {})
@klass = klass
@table = table
@values = values
@offsets = {}
@loaded = false
- @predicate_builder = PredicateBuilder.new(klass, table)
+ @predicate_builder = predicate_builder
end
def initialize_copy(other)
@@ -569,7 +569,7 @@ module ActiveRecord
[name, binds.fetch(name.to_s) {
case where.right
when Array then where.right.map(&:val)
- else
+ when Arel::Nodes::Casted, Arel::Nodes::Quoted
where.right.val
end
}]
@@ -633,10 +633,6 @@ module ActiveRecord
"#<#{self.class.name} [#{entries.join(', ')}]>"
end
- protected
-
- attr_reader :predicate_builder
-
private
def exec_queries
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 20d24b409b..4f0502ae75 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -27,7 +27,7 @@ module ActiveRecord
#
# ==== Options
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
- # * <tt>:start</tt> - Specifies the starting point for the batch processing.
+ # * <tt>:start</tt> - Specifies the primary key value to start from.
# This is especially useful if you want multiple workers dealing with
# the same processing queue. You can make worker 1 handle all the records
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
@@ -77,7 +77,7 @@ module ActiveRecord
#
# ==== Options
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
- # * <tt>:start</tt> - Specifies the starting point for the batch processing.
+ # * <tt>:start</tt> - Specifies the primary key value to start from.
# This is especially useful if you want multiple workers dealing with
# the same processing queue. You can make worker 1 handle all the records
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 357861caaa..088bc203b7 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -397,7 +397,7 @@ module ActiveRecord
else
if relation.limit_value
limited_ids = limited_ids_for(relation)
- limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
+ limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
end
relation.except(:limit, :offset)
end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index a27f990f74..afb0b208c3 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -22,7 +22,7 @@ module ActiveRecord
# build a relation to merge in rather than directly merging
# the values.
def other
- other = Relation.create(relation.klass, relation.table)
+ other = Relation.create(relation.klass, relation.table, relation.predicate_builder)
hash.each { |k, v|
if k == :joins
if Hash === v
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 67e646bf18..567efce8ae 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -1,23 +1,26 @@
module ActiveRecord
class PredicateBuilder # :nodoc:
- @handlers = []
+ require 'active_record/relation/predicate_builder/array_handler'
+ require 'active_record/relation/predicate_builder/association_query_handler'
+ require 'active_record/relation/predicate_builder/base_handler'
+ require 'active_record/relation/predicate_builder/basic_object_handler'
+ require 'active_record/relation/predicate_builder/class_handler'
+ require 'active_record/relation/predicate_builder/range_handler'
+ require 'active_record/relation/predicate_builder/relation_handler'
- autoload :RelationHandler, 'active_record/relation/predicate_builder/relation_handler'
- autoload :ArrayHandler, 'active_record/relation/predicate_builder/array_handler'
+ delegate :resolve_column_aliases, to: :table
- def initialize(klass, table)
- @klass = klass
+ def initialize(table)
@table = table
- 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
- end
- end
- hash
+ @handlers = []
+
+ register_handler(BasicObject, BasicObjectHandler.new(self))
+ register_handler(Class, ClassHandler.new(self))
+ register_handler(Base, BaseHandler.new(self))
+ register_handler(Range, RangeHandler.new(self))
+ register_handler(Relation, RelationHandler.new)
+ register_handler(Array, ArrayHandler.new(self))
+ register_handler(AssociationQueryValue, AssociationQueryHandler.new(self))
end
def build_from_hash(attributes)
@@ -26,35 +29,16 @@ module ActiveRecord
end
def expand(column, value)
- queries = []
-
# Find the foreign key when using queries such as:
# Post.where(author: author)
#
# For polymorphic relationships, find the foreign key and type:
# PriceEstimate.where(estimate_of: treasure)
- if klass && reflection = klass._reflect_on_association(column)
- if reflection.polymorphic? && base_class = polymorphic_base_class_from_value(value)
- queries << self.class.build(table[reflection.foreign_type], base_class.name)
- end
-
- column = reflection.foreign_key
+ if table.associated_with?(column)
+ value = AssociationQueryValue.new(table.associated_table(column), value)
end
- queries << self.class.build(table[column], value)
- queries
- end
-
- def polymorphic_base_class_from_value(value)
- case value
- when Relation
- value.klass.base_class
- when Array
- val = value.compact.first
- val.class.base_class if val.is_a?(Base)
- when Base
- value.class.base_class
- end
+ build(table.arel_attribute(column), value)
end
def self.references(attributes)
@@ -79,46 +63,24 @@ module ActiveRecord
# )
# end
# ActiveRecord::PredicateBuilder.register_handler(MyCustomDateRange, handler)
- def self.register_handler(klass, handler)
+ def register_handler(klass, handler)
@handlers.unshift([klass, handler])
end
- register_handler(BasicObject, ->(attribute, value) { attribute.eq(value) })
- register_handler(Class, ->(attribute, value) { deprecate_class_handler; attribute.eq(value.name) })
- register_handler(Base, ->(attribute, value) { attribute.eq(value.id) })
- register_handler(Range, ->(attribute, value) { attribute.between(value) })
- register_handler(Relation, RelationHandler.new)
- register_handler(Array, ArrayHandler.new)
-
- def self.build(attribute, value)
+ def build(attribute, value)
handler_for(value).call(attribute, value)
end
- def self.handler_for(object)
- @handlers.detect { |klass, _| klass === object }.last
- end
- private_class_method :handler_for
-
- def self.deprecate_class_handler
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing a class as a value in an Active Record query is deprecated and
- will be removed. Pass a string instead.
- MSG
- end
-
protected
- attr_reader :klass, :table
+ attr_reader :table
def expand_from_hash(attributes)
return ["1=0"] if attributes.empty?
attributes.flat_map do |key, value|
if value.is_a?(Hash)
- arel_table = Arel::Table.new(key)
- association = klass._reflect_on_association(key)
- builder = self.class.new(association && association.klass, arel_table)
-
+ builder = self.class.new(table.associated_table(key))
builder.expand_from_hash(value)
else
expand(key, value)
@@ -141,5 +103,9 @@ module ActiveRecord
attributes
end
+
+ def handler_for(object)
+ @handlers.detect { |klass, _| klass === object }.last
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index 4cba297be5..4b5f5773a0 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -3,6 +3,10 @@ require 'active_support/core_ext/string/filters'
module ActiveRecord
class PredicateBuilder
class ArrayHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
def call(attribute, value)
values = value.map { |x| x.is_a?(Base) ? x.id : x }
nils, values = values.partition(&:nil?)
@@ -14,20 +18,24 @@ module ActiveRecord
values_predicate =
case values.length
when 0 then NullPredicate
- when 1 then attribute.eq(values.first)
+ when 1 then predicate_builder.build(attribute, values.first)
else attribute.in(values)
end
unless nils.empty?
- values_predicate = values_predicate.or(attribute.eq(nil))
+ values_predicate = values_predicate.or(predicate_builder.build(attribute, nil))
end
- array_predicates = ranges.map { |range| attribute.between(range) }
+ array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
array_predicates.unshift(values_predicate)
array_predicates.inject { |composite, predicate| composite.or(predicate) }
end
- module NullPredicate
+ protected
+
+ attr_reader :predicate_builder
+
+ module NullPredicate # :nodoc:
def self.or(other)
other
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
new file mode 100644
index 0000000000..aabcf20c1d
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
@@ -0,0 +1,58 @@
+module ActiveRecord
+ class PredicateBuilder
+ class AssociationQueryHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ queries = {}
+
+ table = value.associated_table
+ if value.base_class
+ queries[table.association_foreign_type] = value.base_class.name
+ end
+
+ queries[table.association_foreign_key] = value.ids
+ predicate_builder.build_from_hash(queries)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+
+ class AssociationQueryValue # :nodoc:
+ attr_reader :associated_table, :value
+
+ def initialize(associated_table, value)
+ @associated_table = associated_table
+ @value = value
+ end
+
+ def ids
+ value
+ end
+
+ def base_class
+ if associated_table.polymorphic_association?
+ @base_class ||= polymorphic_base_class_from_value
+ end
+ end
+
+ private
+
+ def polymorphic_base_class_from_value
+ case value
+ when Relation
+ value.klass.base_class
+ when Array
+ val = value.compact.first
+ val.class.base_class if val.is_a?(Base)
+ when Base
+ value.class.base_class
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb
new file mode 100644
index 0000000000..6fa5b16f73
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ class PredicateBuilder
+ class BaseHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ predicate_builder.build(attribute, value.id)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb
new file mode 100644
index 0000000000..6cec75dc0a
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ class PredicateBuilder
+ class BasicObjectHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ attribute.eq(value)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb
new file mode 100644
index 0000000000..ed313fc9d4
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb
@@ -0,0 +1,27 @@
+module ActiveRecord
+ class PredicateBuilder
+ class ClassHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ print_deprecation_warning
+ predicate_builder.build(attribute, value.name)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+
+ private
+
+ def print_deprecation_warning
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing a class as a value in an Active Record query is deprecated and
+ will be removed. Pass a string instead.
+ MSG
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
new file mode 100644
index 0000000000..1b3849e3ad
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ class PredicateBuilder
+ class RangeHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ attribute.between(value)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 57d66bce4b..01bddea6c9 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -67,7 +67,7 @@ module ActiveRecord
private
def relation_with(values) # :nodoc:
- result = Relation.create(klass, table, values)
+ result = Relation.create(klass, table, predicate_builder, values)
result.extend(*extending_values) if extending_values.any?
result
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 6c103e331f..f5aa60a69a 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -88,7 +88,7 @@ module ActiveRecord
# # => "address_street='123 abc st.' and address_city='chicago'"
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
table = Arel::Table.new(table_name).alias(default_table_name)
- predicate_builder = PredicateBuilder.new(self, table)
+ predicate_builder = PredicateBuilder.new(TableMetadata.new(self, table))
ActiveSupport::Deprecation.warn(<<-EOWARN)
sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
EOWARN
@@ -107,7 +107,8 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
def sanitize_sql_hash_for_assignment(attrs, table)
c = connection
attrs.map do |attr, value|
- "#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}"
+ value = type_for_attribute(attr.to_s).type_cast_for_database(value)
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
end.join(', ')
end
@@ -163,10 +164,8 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
end
end
- def quote_bound_value(value, c = connection, column = nil) #:nodoc:
- if column
- c.quote(value, column)
- elsif value.respond_to?(:map) && !value.acts_like?(:string)
+ def quote_bound_value(value, c = connection) #:nodoc:
+ if value.respond_to?(:map) && !value.acts_like?(:string)
if value.respond_to?(:empty?) && value.empty?
c.quote(nil)
else
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 77aa2efc47..2a570e1323 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -121,12 +121,12 @@ HEADER
tbl.print ", id: :bigserial"
elsif pkcol.sql_type == 'uuid'
tbl.print ", id: :uuid"
- tbl.print %Q(, default: "#{pkcol.default_function}") if pkcol.default_function
+ tbl.print %Q(, default: #{pkcol.default_function.inspect})
end
else
tbl.print ", id: false"
end
- tbl.print ", force: true"
+ tbl.print ", force: :cascade"
tbl.puts " do |t|"
# then dump all non-primary key columns
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
new file mode 100644
index 0000000000..11e33e8dfe
--- /dev/null
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -0,0 +1,53 @@
+module ActiveRecord
+ class TableMetadata # :nodoc:
+ delegate :foreign_type, :foreign_key, to: :association, prefix: true
+
+ def initialize(klass, arel_table, association = nil)
+ @klass = klass
+ @arel_table = arel_table
+ @association = association
+ 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
+ end
+ end
+ hash
+ end
+
+ def arel_attribute(column_name)
+ arel_table[column_name]
+ end
+
+ def associated_with?(association_name)
+ klass && klass._reflect_on_association(association_name)
+ end
+
+ def associated_table(table_name)
+ return self if table_name == arel_table.name
+
+ association = klass._reflect_on_association(table_name)
+ if association && !association.polymorphic?
+ association_klass = association.klass
+ arel_table = association_klass.arel_table
+ else
+ type_caster = TypeCaster::Connection.new(klass.connection, table_name)
+ association_klass = nil
+ arel_table = Arel::Table.new(table_name, type_caster: type_caster)
+ end
+
+ TableMetadata.new(association_klass, arel_table, association)
+ end
+
+ def polymorphic_association?
+ association && association.polymorphic?
+ end
+
+ protected
+
+ attr_reader :klass, :arel_table, :association
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 1228de2bfd..69aceb66b1 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -188,44 +188,39 @@ module ActiveRecord
class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename)
end
- def load_schema(format = ActiveRecord::Base.schema_format, file = nil)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- This method will act on a specific connection in the future.
- To act on the current connection, use `load_schema_current` instead.
- MSG
-
- load_schema_current(format, file)
- end
-
- def schema_file(format = ActiveSupport::Base.schema_format)
- case format
- when :ruby
- File.join(db_dir, "schema.rb")
- when :sql
- File.join(db_dir, "structure.sql")
- end
- 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:
+ def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
file ||= schema_file(format)
case format
when :ruby
check_schema_file(file)
- purge(configuration)
ActiveRecord::Base.establish_connection(configuration)
load(file)
when :sql
check_schema_file(file)
- purge(configuration)
structure_load(configuration, file)
else
raise ArgumentError, "unknown format #{format.inspect}"
end
end
+ def load_schema_for(*args)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ This method was renamed to `#load_schema` and will be removed in the future.
+ Use `#load_schema` instead.
+ MSG
+ load_schema(*args)
+ end
+
+ def schema_file(format = ActiveSupport::Base.schema_format)
+ case format
+ when :ruby
+ File.join(db_dir, "schema.rb")
+ when :sql
+ File.join(db_dir, "structure.sql")
+ 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)
@@ -234,7 +229,7 @@ module ActiveRecord
def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
each_current_configuration(environment) { |configuration|
- load_schema_for configuration, format, file
+ load_schema configuration, format, file
}
ActiveRecord::Base.establish_connection(environment.to_sym)
end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 936a18d99a..20e4235788 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -57,8 +57,8 @@ module ActiveRecord
super
end
- def _update_record(*args)
- if should_record_timestamps?
+ def _update_record(*args, touch: true, **options)
+ if touch && should_record_timestamps?
current_time = current_time_from_proper_timezone
timestamp_attributes_for_update_in_model.each do |column|
@@ -67,7 +67,7 @@ module ActiveRecord
write_attribute(column, current_time)
end
end
- super
+ super(*args)
end
def should_record_timestamps?
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 01e8f69b02..de701edca0 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -2,7 +2,9 @@ module ActiveRecord
# See ActiveRecord::Transactions::ClassMethods for documentation.
module Transactions
extend ActiveSupport::Concern
+ #:nodoc:
ACTIONS = [:create, :destroy, :update]
+ #:nodoc:
CALLBACK_WARN_MESSAGE = "Currently, Active Record suppresses errors raised " \
"within `after_rollback`/`after_commit` callbacks and only print them to " \
"the logs. In the next version, these errors will no longer be suppressed. " \
@@ -358,14 +360,12 @@ module ActiveRecord
# Save the new record state and id of a record so it can be restored later if a transaction fails.
def remember_transaction_record_state #:nodoc:
@_start_transaction_state[:id] = id
- unless @_start_transaction_state.include?(:new_record)
- @_start_transaction_state[:new_record] = @new_record
- end
- unless @_start_transaction_state.include?(:destroyed)
- @_start_transaction_state[:destroyed] = @destroyed
- end
+ @_start_transaction_state.reverse_merge!(
+ new_record: @new_record,
+ destroyed: @destroyed,
+ frozen?: frozen?,
+ )
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
- @_start_transaction_state[:frozen?] = frozen?
end
# Clear the new record state and id of a record.
diff --git a/activerecord/lib/active_record/type/decimal.rb b/activerecord/lib/active_record/type/decimal.rb
index d10778eeb6..7b2bee2c42 100644
--- a/activerecord/lib/active_record/type/decimal.rb
+++ b/activerecord/lib/active_record/type/decimal.rb
@@ -16,7 +16,7 @@ module ActiveRecord
def cast_value(value)
case value
when ::Float
- BigDecimal(value, float_precision)
+ convert_float_to_big_decimal(value)
when ::Numeric, ::String
BigDecimal(value, precision.to_i)
else
@@ -28,6 +28,14 @@ module ActiveRecord
end
end
+ def convert_float_to_big_decimal(value)
+ if precision
+ BigDecimal(value, float_precision)
+ else
+ value.to_d
+ end
+ end
+
def float_precision
if precision.to_i > ::Float::DIG + 1
::Float::DIG + 1
diff --git a/activerecord/lib/active_record/type/numeric.rb b/activerecord/lib/active_record/type/numeric.rb
index fa43266504..674f996f38 100644
--- a/activerecord/lib/active_record/type/numeric.rb
+++ b/activerecord/lib/active_record/type/numeric.rb
@@ -29,7 +29,7 @@ module ActiveRecord
# 'wibble'.to_i will give zero, we want to make sure
# that we aren't marking int zero to string zero as
# changed.
- value.to_s !~ /\A\d+\.?\d*\z/
+ value.to_s !~ /\A-?\d+\.?\d*\z/
end
end
end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 3191a868ef..3cac03464e 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -29,7 +29,7 @@ module ActiveRecord
def changed_in_place?(raw_old_value, value)
return false if value.nil?
- subtype.changed_in_place?(raw_old_value, coder.dump(value))
+ subtype.changed_in_place?(raw_old_value, type_cast_for_database(value))
end
def accessor
diff --git a/activerecord/lib/active_record/type/string.rb b/activerecord/lib/active_record/type/string.rb
index fbc0af2c5a..cf95e25be0 100644
--- a/activerecord/lib/active_record/type/string.rb
+++ b/activerecord/lib/active_record/type/string.rb
@@ -21,6 +21,10 @@ module ActiveRecord
end
end
+ def text?
+ true
+ end
+
private
def cast_value(value)
diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb
index 9456a4a56c..60ae47db3d 100644
--- a/activerecord/lib/active_record/type/value.rb
+++ b/activerecord/lib/active_record/type/value.rb
@@ -50,6 +50,10 @@ module ActiveRecord
# These predicates are not documented, as I need to look further into
# their use, and see if they can be removed entirely.
+ def text? # :nodoc:
+ false
+ end
+
def number? # :nodoc:
false
end
@@ -91,8 +95,8 @@ module ActiveRecord
# Convenience method for types which do not need separate type casting
# behavior for user and database inputs. Called by
- # `type_cast_from_database` and `type_cast_from_user` for all values
- # except `nil`.
+ # +type_cast_from_database+ and +type_cast_from_user+ for all values
+ # except +nil+.
def cast_value(value) # :doc:
value
end
diff --git a/activerecord/lib/active_record/type_caster.rb b/activerecord/lib/active_record/type_caster.rb
new file mode 100644
index 0000000000..63ba10c289
--- /dev/null
+++ b/activerecord/lib/active_record/type_caster.rb
@@ -0,0 +1,7 @@
+require 'active_record/type_caster/map'
+require 'active_record/type_caster/connection'
+
+module ActiveRecord
+ module TypeCaster
+ end
+end
diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb
new file mode 100644
index 0000000000..9e4a130b40
--- /dev/null
+++ b/activerecord/lib/active_record/type_caster/connection.rb
@@ -0,0 +1,34 @@
+module ActiveRecord
+ module TypeCaster
+ class Connection
+ def initialize(connection, table_name)
+ @connection = connection
+ @table_name = table_name
+ end
+
+ def type_cast_for_database(attribute_name, value)
+ return value if value.is_a?(Arel::Nodes::BindParam)
+ type = type_for(attribute_name)
+ type.type_cast_for_database(value)
+ end
+
+ protected
+
+ attr_reader :connection, :table_name
+
+ private
+
+ def type_for(attribute_name)
+ if connection.schema_cache.table_exists?(table_name)
+ column_for(attribute_name).cast_type
+ else
+ Type::Value.new
+ end
+ end
+
+ def column_for(attribute_name)
+ connection.schema_cache.columns_hash(table_name)[attribute_name.to_s]
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb
new file mode 100644
index 0000000000..03c9e8ff83
--- /dev/null
+++ b/activerecord/lib/active_record/type_caster/map.rb
@@ -0,0 +1,19 @@
+module ActiveRecord
+ module TypeCaster
+ class Map
+ def initialize(types)
+ @types = types
+ end
+
+ def type_cast_for_database(attr_name, value)
+ return value if value.is_a?(Arel::Nodes::BindParam)
+ type = types.type_for_attribute(attr_name.to_s)
+ type.type_cast_for_database(value)
+ end
+
+ protected
+
+ attr_reader :types
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index a6c8ff7f3a..f27adc9c40 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -88,3 +88,4 @@ end
require "active_record/validations/associated"
require "active_record/validations/uniqueness"
require "active_record/validations/presence"
+require "active_record/validations/length"
diff --git a/activerecord/lib/active_record/validations/length.rb b/activerecord/lib/active_record/validations/length.rb
new file mode 100644
index 0000000000..ef5a6cbbe7
--- /dev/null
+++ b/activerecord/lib/active_record/validations/length.rb
@@ -0,0 +1,21 @@
+module ActiveRecord
+ module Validations
+ class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc:
+ def validate_each(record, attribute, association_or_value)
+ if association_or_value.respond_to?(:loaded?) && association_or_value.loaded?
+ association_or_value = association_or_value.target.reject(&:marked_for_destruction?)
+ end
+ super
+ end
+ end
+
+ module ClassMethods
+ # See <tt>ActiveModel::Validation::LengthValidator</tt> for more information.
+ def validates_length_of(*attr_names)
+ validates_with LengthValidator, _merge_attributes(attr_names)
+ end
+
+ alias_method :validates_size_of, :validates_length_of
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 3e8afe37a8..f52f91e89c 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -16,9 +16,8 @@ module ActiveRecord
value = map_enum_attribute(finder_class, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
- relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted?
+ relation = relation.where.not(finder_class.primary_key => record.id) if record.persisted?
relation = scope_relation(record, table, relation)
- relation = finder_class.unscoped.where(relation)
relation = relation.merge(options[:conditions]) if options[:conditions]
if relation.exists?
@@ -60,17 +59,21 @@ module ActiveRecord
end
column = klass.columns_hash[attribute_name]
- value = klass.connection.type_cast(value, column)
+ value = klass.type_for_attribute(attribute_name).type_cast_for_database(value)
+ value = klass.connection.type_cast(value)
if value.is_a?(String) && column.limit
value = value.to_s[0, column.limit]
end
- if !options[:case_sensitive] && value.is_a?(String)
+ value = Arel::Nodes::Quoted.new(value)
+
+ comparison = if !options[:case_sensitive] && value && column.text?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
klass.connection.case_sensitive_comparison(table, attribute, column, value)
end
+ klass.unscoped.where(comparison)
end
def scope_relation(record, table, relation)
@@ -81,7 +84,7 @@ module ActiveRecord
else
scope_value = record._read_attribute(scope_item)
end
- relation = relation.and(table[scope_item].eq(scope_value))
+ relation = relation.where(scope_item => scope_value)
end
relation
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
index fb0fbb4759..f7bf6987c4 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
@@ -15,8 +15,5 @@ class <%= migration_class_name %> < ActiveRecord::Migration
<% attributes_with_index.each do |attribute| -%>
add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
<% end -%>
-<% attributes.select(&:reference?).reject(&:polymorphic?).each do |attribute| -%>
- add_foreign_key :<%= table_name %>, :<%= attribute.name.pluralize %>
-<% end -%>
end
end
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
index 7df9bcad25..ae9c74fd05 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -4,9 +4,6 @@ class <%= migration_class_name %> < ActiveRecord::Migration
<% attributes.each do |attribute| -%>
<%- if attribute.reference? -%>
add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %>
- <%- unless attribute.polymorphic? -%>
- add_foreign_key :<%= table_name %>, :<%= attribute.name.pluralize %>
- <%- end -%>
<%- else -%>
add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %>
<%- if attribute.has_index? -%>
@@ -29,9 +26,6 @@ class <%= migration_class_name %> < ActiveRecord::Migration
<%- if migration_action -%>
<%- if attribute.reference? -%>
remove_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %>
- <%- unless attribute.polymorphic? -%>
- remove_foreign_key :<%= table_name %>, :<%= attribute.name.pluralize %>
- <%- end -%>
<%- else -%>
<%- if attribute.has_index? -%>
remove_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>