aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md59
-rw-r--r--activerecord/lib/active_record/association_relation.rb12
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb9
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb21
-rw-r--r--activerecord/lib/active_record/autosave_association.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb23
-rw-r--r--activerecord/lib/active_record/counter_cache.rb21
-rw-r--r--activerecord/lib/active_record/fixtures.rb33
-rw-r--r--activerecord/lib/active_record/reflection.rb12
-rw-r--r--activerecord/lib/active_record/relation.rb11
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb36
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/domain_test.rb3
-rw-r--r--activerecord/test/cases/adapters/postgresql/enum_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb39
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb1
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb27
-rw-r--r--activerecord/test/cases/associations/eager_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb14
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb8
-rw-r--r--activerecord/test/cases/base_test.rb35
-rw-r--r--activerecord/test/cases/fixtures_test.rb9
-rw-r--r--activerecord/test/cases/helper.rb9
-rw-r--r--activerecord/test/cases/reflection_test.rb2
-rw-r--r--activerecord/test/cases/relation_test.rb4
-rw-r--r--activerecord/test/cases/relations_test.rb10
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb2
-rw-r--r--activerecord/test/fixtures/uuid_children.yml3
-rw-r--r--activerecord/test/fixtures/uuid_parents.yml2
-rw-r--r--activerecord/test/models/club.rb2
-rw-r--r--activerecord/test/models/uuid_child.rb3
-rw-r--r--activerecord/test/models/uuid_parent.rb3
-rw-r--r--activerecord/test/schema/schema.rb9
38 files changed, 390 insertions, 80 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index d39e808f5b..a45d912017 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,62 @@
+* The comparison between `Relation` and `CollectionProxy` should be consistent.
+
+ Example:
+
+ author.posts == Post.where(author_id: author.id)
+ # => true
+ Post.where(author_id: author.id) == author.posts
+ # => true
+
+ Fixes #13506.
+
+ *Lauro Caetano*
+
+* Calling `delete_all` on an unloaded `CollectionProxy` no longer
+ generates a SQL statement containing each id of the collection:
+
+ Before:
+
+ DELETE FROM `model` WHERE `model`.`parent_id` = 1
+ AND `model`.`id` IN (1, 2, 3...)
+
+ After:
+
+ DELETE FROM `model` WHERE `model`.`parent_id` = 1
+
+ *Eileen M. Uchitelle*, *Aaron Patterson*
+
+* Fixed error for aggregate methods (`empty?`, `any?`, `count`) with `select`
+ which created invalid SQL.
+
+ Fixes #13648.
+
+ *Simon Woker*
+
+* PostgreSQL adapter only warns once for every missing OID per connection.
+
+ Fixes #14275.
+
+ *Matthew Draper*, *Yves Senn*
+
+* PostgreSQL adapter automatically reloads it's type map when encountering
+ unknown OIDs.
+
+ Fixes #14678.
+
+ *Matthew Draper*, *Yves Senn*
+
+* Fix insertion of records via `has_many :through` association with scope.
+
+ Fixes #3548.
+
+ *Ivan Antropov*
+
+* Auto-generate stable fixture UUIDs on PostgreSQL.
+
+ Fixes: #11524
+
+ *Roderick van Domburg*
+
* Fixed a problem where an enum would overwrite values of another enum
with the same name in an unrelated class.
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
index 20516bba0c..45f1b07f69 100644
--- a/activerecord/lib/active_record/association_relation.rb
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -9,6 +9,18 @@ module ActiveRecord
@association
end
+ def size
+ @association.size
+ end
+
+ def empty?
+ @association.empty?
+ end
+
+ def ==(other)
+ other == to_a
+ end
+
private
def exec_queries
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 5ccaa55a32..11be92ae01 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -37,13 +37,14 @@ module ActiveRecord::Associations::Builder
end
end
- def belongs_to_counter_cache_before_destroy(reflection)
+ def belongs_to_counter_cache_after_destroy(reflection)
foreign_key = reflection.foreign_key.to_sym
unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
record = send reflection.name
- if record && !self.destroyed?
+ if record && self.actually_destroyed?
cache_column = reflection.counter_cache_column
record.class.decrement_counter(cache_column, record.id)
+ self.clear_destroy_state
end
end
end
@@ -77,8 +78,8 @@ module ActiveRecord::Associations::Builder
record.belongs_to_counter_cache_after_create(reflection)
}
- model.before_destroy lambda { |record|
- record.belongs_to_counter_cache_before_destroy(reflection)
+ model.after_destroy lambda { |record|
+ record.belongs_to_counter_cache_after_destroy(reflection)
}
model.after_update lambda { |record|
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 3e4b7902c0..aac85a36c8 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -58,7 +58,7 @@ module ActiveRecord
# the loaded flag is set to true as well.
def count_records
count = if has_cached_counter?
- owner.send(:read_attribute, cached_counter_attribute_name)
+ owner.read_attribute cached_counter_attribute_name
else
scope.count
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 64bc98c642..73baefb8e1 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -18,7 +18,7 @@ module ActiveRecord
# SELECT query if you use #length.
def size
if has_cached_counter?
- owner.send(:read_attribute, cached_counter_attribute_name)
+ owner.read_attribute cached_counter_attribute_name(reflection)
elsif loaded?
target.size
else
@@ -83,12 +83,16 @@ module ActiveRecord
@through_records[record.object_id] ||= begin
ensure_mutable
- through_record = through_association.build
+ through_record = through_association.build through_scope_attributes
through_record.send("#{source_reflection.name}=", record)
through_record
end
end
+ def through_scope_attributes
+ scope.where_values_hash(through_association.reflection.name.to_s)
+ end
+
def save_through_record(record)
build_through_record(record).save!
ensure
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 67abbbc2a0..c3466153d6 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -30,7 +30,8 @@ module ActiveRecord
# ==== Parameters
#
# * +attr_name+ - The field name that should be serialized.
- # * +class_name+ - Optional, class name that the object type should be equal to.
+ # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump`
+ # or a class name that the object type should be equal to.
#
# ==== Example
#
@@ -38,13 +39,23 @@ module ActiveRecord
# class User < ActiveRecord::Base
# serialize :preferences
# end
- def serialize(attr_name, class_name = Object)
+ #
+ # # Serialize preferences using JSON as coder.
+ # class User < ActiveRecord::Base
+ # serialize :preferences, JSON
+ # end
+ #
+ # # Serialize preferences as Hash using YAML coder.
+ # class User < ActiveRecord::Base
+ # serialize :preferences, Hash
+ # end
+ def serialize(attr_name, class_name_or_coder = Object)
include Behavior
- coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
- class_name
+ coder = if [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
+ class_name_or_coder
else
- Coders::YAMLColumn.new(class_name)
+ Coders::YAMLColumn.new(class_name_or_coder)
end
# merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index e9622ca0c1..f149d8f127 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -35,7 +35,7 @@ module ActiveRecord
#
# === One-to-one Example
#
- # class Post
+ # class Post < ActiveRecord::Base
# has_one :author, autosave: true
# end
#
@@ -76,7 +76,7 @@ module ActiveRecord
#
# When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
#
- # class Post
+ # class Post < ActiveRecord::Base
# has_many :comments # :autosave option is not declared
# end
#
@@ -95,20 +95,23 @@ module ActiveRecord
# When <tt>:autosave</tt> is true all children are saved, no matter whether they
# are new records or not:
#
- # class Post
+ # class Post < ActiveRecord::Base
# has_many :comments, autosave: true
# end
#
# post = Post.create(title: 'ruby rocks')
# post.comments.create(body: 'hello world')
# post.comments[0].body = 'hi everyone'
- # post.save # => saves both post and comment, with 'hi everyone' as body
+ # post.comments.build(body: "good morning.")
+ # post.title += "!"
+ # post.save # => saves both post and comments.
#
# Destroying one of the associated models as part of the parent's save action
# is as simple as marking it for destruction:
#
- # post.comments.last.mark_for_destruction
- # post.comments.last.marked_for_destruction? # => true
+ # post.comments # => [#<Comment id: 1, ...>, #<Comment id: 2, ...]>
+ # post.comments[1].mark_for_destruction
+ # post.comments[1].marked_for_destruction? # => true
# post.comments.length # => 2
#
# Note that the model is _not_ yet removed from the database:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 270071a166..b7b9a4363e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -385,7 +385,7 @@ module ActiveRecord
end
def binds_from_relation(relation, binds)
- if relation.is_a?(Relation) && binds.blank?
+ if relation.is_a?(Relation) && binds.empty?
relation, binds = relation.arel, relation.bind_values
end
[relation, binds]
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index 51ee2829b2..a5fb048b1e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -142,10 +142,7 @@ module ActiveRecord
fields.each_with_index do |fname, i|
ftype = result.ftype i
fmod = result.fmod i
- types[fname] = type_map.fetch(ftype, fmod) { |oid, mod|
- warn "unknown OID: #{fname}(#{oid}) (#{sql})"
- OID::Identity.new
- }
+ types[fname] = get_oid_type(ftype, fmod, fname)
end
ret = ActiveRecord::Result.new(fields, result.values, types)
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 50a73aa666..1229b4851a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -182,9 +182,7 @@ module ActiveRecord
def columns(table_name)
# Limit, precision, and scale are all handled by the superclass.
column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
- oid = type_map.fetch(oid.to_i, fmod.to_i) {
- OID::Identity.new
- }
+ oid = get_oid_type(oid.to_i, fmod.to_i, column_name)
PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 87cbfa8d8b..56dd2da249 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -555,6 +555,17 @@ module ActiveRecord
@type_map
end
+ def get_oid_type(oid, fmod, column_name)
+ if !type_map.key?(oid)
+ initialize_type_map(type_map, [oid])
+ end
+
+ type_map.fetch(oid, fmod) {
+ warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
+ type_map[oid] = OID::Identity.new
+ }
+ end
+
def reload_type_map
type_map.clear
initialize_type_map(type_map)
@@ -579,19 +590,25 @@ module ActiveRecord
type_map
end
- def initialize_type_map(type_map)
+ def initialize_type_map(type_map, oids = nil)
if supports_ranges?
- result = execute(<<-SQL, 'SCHEMA')
+ query = <<-SQL
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
FROM pg_type as t
LEFT JOIN pg_range as r ON oid = rngtypid
SQL
else
- result = execute(<<-SQL, 'SCHEMA')
+ query = <<-SQL
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
FROM pg_type as t
SQL
end
+
+ if oids
+ query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
+ end
+
+ result = execute(query, 'SCHEMA')
ranges, nodes = result.partition { |row| row['typtype'] == 'r' }
enums, nodes = nodes.partition { |row| row['typtype'] == 'e' }
domains, nodes = nodes.partition { |row| row['typtype'] == 'd' }
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index dcbdf75627..a5897edf03 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -118,5 +118,26 @@ module ActiveRecord
update_counters(id, counter_name => -1)
end
end
+
+ protected
+
+ def actually_destroyed?
+ @_actually_destroyed
+ end
+
+ def clear_destroy_state
+ @_actually_destroyed = nil
+ end
+
+ private
+
+ def destroy_row
+ affected_rows = super
+
+ @_actually_destroyed = affected_rows > 0
+
+ affected_rows
+ end
+
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 6f134bbef8..47d32fae05 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -2,6 +2,7 @@ require 'erb'
require 'yaml'
require 'zlib'
require 'active_support/dependencies'
+require 'active_support/core_ext/securerandom'
require 'active_record/fixture_set/file'
require 'active_record/errors'
@@ -550,9 +551,13 @@ module ActiveRecord
end
# Returns a consistent, platform-independent identifier for +label+.
- # Identifiers are positive integers less than 2^30.
- def self.identify(label)
- Zlib.crc32(label.to_s) % MAX_ID
+ # Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes.
+ def self.identify(label, column_type = :integer)
+ if column_type == :uuid
+ SecureRandom.uuid_v5(SecureRandom::UUID_OID_NAMESPACE, label.to_s)
+ else
+ Zlib.crc32(label.to_s) % MAX_ID
+ end
end
# Superclass for the evaluation contexts used by ERB fixtures.
@@ -633,7 +638,7 @@ module ActiveRecord
# generate a primary key if necessary
if has_primary_key_column? && !row.include?(primary_key_name)
- row[primary_key_name] = ActiveRecord::FixtureSet.identify(label)
+ row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type)
end
# If STI is used, find the correct subclass for association reflection
@@ -656,7 +661,8 @@ module ActiveRecord
row[association.foreign_type] = $1
end
- row[fk_name] = ActiveRecord::FixtureSet.identify(value)
+ fk_type = association.active_record.columns_hash[association.foreign_key].type
+ row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
end
when :has_many
if association.options[:through]
@@ -683,6 +689,10 @@ module ActiveRecord
def name
@association.name
end
+
+ def primary_key_type
+ @association.klass.column_types[@association.klass.primary_key].type
+ end
end
class HasManyThroughProxy < ReflectionProxy # :nodoc:
@@ -700,17 +710,22 @@ module ActiveRecord
@primary_key_name ||= model_class && model_class.primary_key
end
+ def primary_key_type
+ @primary_key_type ||= model_class && model_class.column_types[model_class.primary_key].type
+ end
+
def add_join_records(rows, row, association)
# This is the case when the join table has no fixtures file
if (targets = row.delete(association.name.to_s))
- table_name = association.join_table
- lhs_key = association.lhs_key
- rhs_key = association.rhs_key
+ table_name = association.join_table
+ column_type = association.primary_key_type
+ lhs_key = association.lhs_key
+ rhs_key = association.rhs_key
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
rows[table_name].concat targets.map { |target|
{ lhs_key => row[primary_key_name],
- rhs_key => ActiveRecord::FixtureSet.identify(target) }
+ rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
}
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 5465a7bfd7..1724ea95b0 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -22,11 +22,11 @@ module ActiveRecord
end
def self.add_reflection(ar, name, reflection)
- ar.reflections = ar.reflections.merge(name => reflection)
+ ar.reflections = ar.reflections.merge(name.to_s => reflection)
end
def self.add_aggregate_reflection(ar, name, reflection)
- ar.aggregate_reflections = ar.aggregate_reflections.merge(name => reflection)
+ ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
end
# \Reflection enables to interrogate Active Record classes and objects
@@ -48,7 +48,7 @@ module ActiveRecord
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
#
def reflect_on_aggregation(aggregation)
- aggregate_reflections[aggregation]
+ aggregate_reflections[aggregation.to_s]
end
# Returns an array of AssociationReflection objects for all the
@@ -72,7 +72,7 @@ module ActiveRecord
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
#
def reflect_on_association(association)
- reflections[association]
+ reflections[association.to_s]
end
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
@@ -617,11 +617,11 @@ module ActiveRecord
# # => [:tag, :tags]
#
def source_reflection_names
- (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq
+ options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
end
def source_reflection_name # :nodoc:
- return @source_reflection_name.to_sym if @source_reflection_name
+ return @source_reflection_name if @source_reflection_name
names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
names = names.find_all { |n|
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 5b635ca7c1..24b33ab0a8 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -240,7 +240,7 @@ module ActiveRecord
# Returns size of the records.
def size
- loaded? ? @records.length : count
+ loaded? ? @records.length : count(:all)
end
# Returns true if there are no records.
@@ -250,8 +250,7 @@ module ActiveRecord
if limit_value == 0
true
else
- # FIXME: This count is not compatible with #select('authors.*') or other select narrows
- c = count
+ c = count(:all)
c.respond_to?(:zero?) ? c.zero? : c.empty?
end
end
@@ -532,9 +531,9 @@ module ActiveRecord
#
# User.where(name: 'Oscar').where_values_hash
# # => {name: "Oscar"}
- def where_values_hash
+ def where_values_hash(relation_table_name = table_name)
equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node|
- node.left.relation.name == table_name
+ node.left.relation.name == relation_table_name
}
binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
@@ -579,6 +578,8 @@ module ActiveRecord
# Compares two relations for equality.
def ==(other)
case other
+ when Associations::CollectionProxy, AssociationRelation
+ self == other.to_a
when Relation
other.to_sql == to_sql
when Array
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 7192aecee1..6f57441a66 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -129,9 +129,9 @@ module ActiveRecord
#
def first(limit = nil)
if limit
- find_nth_with_limit(offset_value, limit)
+ find_nth_with_limit(offset_index, limit)
else
- find_nth(:first, offset_value)
+ find_nth(0, offset_index)
end
end
@@ -181,7 +181,7 @@ module ActiveRecord
# Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
# Person.where(["user_name = :u", { u: user_name }]).second
def second
- find_nth(:second, offset_value ? offset_value + 1 : 1)
+ find_nth(1, offset_index)
end
# Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -197,7 +197,7 @@ module ActiveRecord
# Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
# Person.where(["user_name = :u", { u: user_name }]).third
def third
- find_nth(:third, offset_value ? offset_value + 2 : 2)
+ find_nth(2, offset_index)
end
# Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -213,7 +213,7 @@ module ActiveRecord
# Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
# Person.where(["user_name = :u", { u: user_name }]).fourth
def fourth
- find_nth(:fourth, offset_value ? offset_value + 3 : 3)
+ find_nth(3, offset_index)
end
# Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -229,7 +229,7 @@ module ActiveRecord
# Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
# Person.where(["user_name = :u", { u: user_name }]).fifth
def fifth
- find_nth(:fifth, offset_value ? offset_value + 4 : 4)
+ find_nth(4, offset_index)
end
# Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -245,7 +245,7 @@ module ActiveRecord
# Person.offset(3).forty_two # returns the fifth object from OFFSET 3 (which is OFFSET 44)
# Person.where(["user_name = :u", { u: user_name }]).forty_two
def forty_two
- find_nth(:forty_two, offset_value ? offset_value + 41 : 41)
+ find_nth(41, offset_index)
end
# Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -331,6 +331,10 @@ module ActiveRecord
private
+ def offset_index
+ offset_value || 0
+ end
+
def find_with_associations
join_dependency = construct_join_dependency
@@ -466,20 +470,24 @@ module ActiveRecord
end
end
- def find_nth(ordinal, offset)
+ def find_nth(index, offset)
if loaded?
- @records.send(ordinal)
+ @records[index]
else
+ offset += index
@offsets[offset] ||= find_nth_with_limit(offset, 1).first
end
end
def find_nth_with_limit(offset, limit)
- if order_values.empty? && primary_key
- order(arel_table[primary_key].asc).limit(limit).offset(offset).to_a
- else
- limit(limit).offset(offset).to_a
- end
+ relation = if order_values.empty? && primary_key
+ order(arel_table[primary_key].asc)
+ else
+ self
+ end
+
+ relation = relation.offset(offset) unless offset.zero?
+ relation.limit(limit).to_a
end
def find_last
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index 84fa199f17..e3478856c8 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -72,7 +72,7 @@ class PostgresqlByteaTest < ActiveRecord::TestCase
def test_via_to_sql
data = "'\u001F\\"
- record = ByteaDataType.create(payload: data)
+ ByteaDataType.create(payload: data)
sql = ByteaDataType.where(payload: data).select(:payload).to_sql
result = @connection.query(sql)
assert_equal([[data]], result)
diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb
index 214e89dd7f..5286a847a4 100644
--- a/activerecord/test/cases/adapters/postgresql/domain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb
@@ -19,9 +19,6 @@ class PostgresqlDomainTest < ActiveRecord::TestCase
t.column :price, :custom_money
end
end
-
- # reload type map after creating the enum type
- @connection.send(:reload_type_map)
end
teardown do
diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb
index 73da5a74ab..4146b117f6 100644
--- a/activerecord/test/cases/adapters/postgresql/enum_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb
@@ -21,8 +21,6 @@ class PostgresqlEnumTest < ActiveRecord::TestCase
t.column :current_mood, :mood
end
end
- # reload type map after creating the enum type
- @connection.send(:reload_type_map)
end
teardown do
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 49d8ec238d..b7791078db 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -1,11 +1,13 @@
# encoding: utf-8
require "cases/helper"
require 'support/ddl_helper'
+require 'support/connection_helper'
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapterTest < ActiveRecord::TestCase
include DdlHelper
+ include ConnectionHelper
def setup
@connection = ActiveRecord::Base.connection
@@ -357,6 +359,43 @@ module ActiveRecord
end
end
+ def test_reload_type_map_for_newly_defined_types
+ @connection.execute "CREATE TYPE feeling AS ENUM ('good', 'bad')"
+ result = @connection.select_all "SELECT 'good'::feeling"
+ assert_instance_of(PostgreSQLAdapter::OID::Enum,
+ result.column_types["feeling"])
+ ensure
+ @connection.execute "DROP TYPE IF EXISTS feeling"
+ reset_connection
+ end
+
+ def test_only_reload_type_map_once_for_every_unknown_type
+ silence_warnings do
+ assert_queries 2, ignore_none: true do
+ @connection.select_all "SELECT NULL::anyelement"
+ end
+ assert_queries 1, ignore_none: true do
+ @connection.select_all "SELECT NULL::anyelement"
+ end
+ assert_queries 2, ignore_none: true do
+ @connection.select_all "SELECT NULL::anyarray"
+ end
+ end
+ ensure
+ reset_connection
+ end
+
+ def test_only_warn_on_first_encounter_of_unknown_oid
+ warning = capture(:stderr) {
+ @connection.select_all "SELECT NULL::anyelement"
+ @connection.select_all "SELECT NULL::anyelement"
+ @connection.select_all "SELECT NULL::anyelement"
+ }
+ assert_match(/\Aunknown OID \d+: failed to recognize type of 'anyelement'. It will be treated as String.\n\z/, warning)
+ ensure
+ reset_connection
+ end
+
private
def insert(ctx, data)
binds = data.map { |name, value|
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index 57c7da2657..060b17d071 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -34,7 +34,6 @@ _SQL
@connection.add_column 'postgresql_ranges', 'float_range', 'floatrange'
end
- @connection.send :reload_type_map
PostgresqlRange.reset_column_information
rescue ActiveRecord::StatementInvalid
skip "do not test on PG without range"
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 27f6fa575d..3b484a0d64 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -233,13 +233,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_belongs_to_counter
debate = Topic.create("title" => "debate")
- assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet"
+ assert_equal 0, debate.read_attribute("replies_count"), "No replies yet"
trash = debate.replies.create("title" => "blah!", "content" => "world around!")
- assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply created"
+ assert_equal 1, Topic.find(debate.id).read_attribute("replies_count"), "First reply created"
trash.destroy
- assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted"
+ assert_equal 0, Topic.find(debate.id).read_attribute("replies_count"), "First reply deleted"
end
def test_belongs_to_counter_with_assigning_nil
@@ -502,6 +502,27 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 4, topic.replies.size
end
+ def test_concurrent_counter_cache_double_destroy
+ topic = Topic.create :title => "Zoom-zoom-zoom"
+
+ 5.times do
+ topic.replies.create(:title => "re: zoom", :content => "speedy quick!")
+ end
+
+ assert_equal 5, topic.reload[:replies_count]
+ assert_equal 5, topic.replies.size
+
+ reply = topic.replies.first
+ reply_clone = Reply.find(reply.id)
+
+ reply.destroy
+ assert_equal 4, topic.reload[:replies_count]
+
+ reply_clone.destroy
+ assert_equal 4, topic.reload[:replies_count]
+ assert_equal 4, topic.replies.size
+ end
+
def test_custom_counter_cache
reply = Reply.create(:title => "re: zoom", :content => "speedy quick!")
assert_equal 0, reply[:replies_count]
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 8c9797861c..7eaa5adc86 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1198,6 +1198,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal authors(:bob), author
end
+ test "preloading with a polymorphic association and using the existential predicate but also using a select" do
+ assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer
+
+ assert_nothing_raised do
+ authors(:david).essays.includes(:writer).select(:name).any?
+ end
+ end
+
test "preloading with a polymorphic association and using the existential predicate" do
assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 026a7fe635..6675e19dd9 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -1102,7 +1102,19 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [posts(:thinking)], person.reload.first_posts
end
- def test_has_many_through_with_includes_in_through_association_scope
+ test "has many through with includes in through association scope" do
assert_not_empty posts(:welcome).author_address_extra_with_address
end
+
+ test "insert records via has_many_through association with scope" do
+ club = Club.create!
+ member = Member.create!
+ Membership.create!(club: club, member: member)
+
+ club.favourites << member
+ assert_equal [member], club.favourites
+
+ club.reload
+ assert_equal [member], club.favourites
+ end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 952baaca36..38e93288e4 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -288,10 +288,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_read_attribute
topic = Topic.new
topic.title = "Don't change the topic"
- assert_equal "Don't change the topic", topic.send(:read_attribute, "title")
+ assert_equal "Don't change the topic", topic.read_attribute("title")
assert_equal "Don't change the topic", topic["title"]
- assert_equal "Don't change the topic", topic.send(:read_attribute, :title)
+ assert_equal "Don't change the topic", topic.read_attribute(:title)
assert_equal "Don't change the topic", topic[:title]
end
@@ -358,10 +358,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
super(attr_name).upcase
end
- assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, "title")
+ assert_equal "STOP CHANGING THE TOPIC", topic.read_attribute("title")
assert_equal "STOP CHANGING THE TOPIC", topic["title"]
- assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, :title)
+ assert_equal "STOP CHANGING THE TOPIC", topic.read_attribute(:title)
assert_equal "STOP CHANGING THE TOPIC", topic[:title]
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 4969344763..2e5b8cffa6 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -551,6 +551,41 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal one, two
end
+ def test_equality_of_relation_and_collection_proxy
+ car = Car.create!
+ car.bulbs.build
+ car.save
+
+ assert car.bulbs == Bulb.where(car_id: car.id), 'CollectionProxy should be comparable with Relation'
+ assert Bulb.where(car_id: car.id) == car.bulbs, 'Relation should be comparable with CollectionProxy'
+ end
+
+ def test_equality_of_relation_and_array
+ car = Car.create!
+ car.bulbs.build
+ car.save
+
+ assert Bulb.where(car_id: car.id) == car.bulbs.to_a, 'Relation should be comparable with Array'
+ end
+
+ def test_equality_of_relation_and_association_relation
+ car = Car.create!
+ car.bulbs.build
+ car.save
+
+ assert_equal Bulb.where(car_id: car.id), car.bulbs.includes(:car), 'Relation should be comparable with AssociationRelation'
+ assert_equal car.bulbs.includes(:car), Bulb.where(car_id: car.id), 'AssociationRelation should be comparable with Relation'
+ end
+
+ def test_equality_of_collection_proxy_and_association_relation
+ car = Car.create!
+ car.bulbs.build
+ car.save
+
+ assert_equal car.bulbs, car.bulbs.includes(:car), 'CollectionProxy should be comparable with AssociationRelation'
+ assert_equal car.bulbs.includes(:car), car.bulbs, 'AssociationRelation should be comparable with CollectionProxy'
+ end
+
def test_hashing
assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index cf0235b8c5..8bbc0af758 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -677,6 +677,12 @@ end
class FoxyFixturesTest < ActiveRecord::TestCase
fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users"
+ if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
+ require 'models/uuid_parent'
+ require 'models/uuid_child'
+ fixtures :uuid_parents, :uuid_children
+ end
+
def test_identifies_strings
assert_equal(ActiveRecord::FixtureSet.identify("foo"), ActiveRecord::FixtureSet.identify("foo"))
assert_not_equal(ActiveRecord::FixtureSet.identify("foo"), ActiveRecord::FixtureSet.identify("FOO"))
@@ -689,6 +695,9 @@ class FoxyFixturesTest < ActiveRecord::TestCase
def test_identifies_consistently
assert_equal 207281424, ActiveRecord::FixtureSet.identify(:ruby)
assert_equal 1066363776, ActiveRecord::FixtureSet.identify(:sapphire_2)
+
+ assert_equal 'f92b6bda-0d0d-5fe1-9124-502b18badded', ActiveRecord::FixtureSet.identify(:daddy, :uuid)
+ assert_equal 'b4b10018-ad47-595d-b42f-d8bdaa6d01bf', ActiveRecord::FixtureSet.identify(:sonny, :uuid)
end
TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on)
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 5ed508a799..eaf2cada9d 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -111,6 +111,15 @@ def verify_default_timezone_config
end
end
+def enable_uuid_ossp!(connection)
+ return false unless connection.supports_extensions?
+ return true if connection.extension_enabled?('uuid-ossp')
+
+ connection.enable_extension 'uuid-ossp'
+ connection.commit_db_transaction
+ connection.reconnect!
+end
+
unless ENV['FIXTURE_DEBUG']
module ActiveRecord::TestFixtures::ClassMethods
def try_to_load_dependency_with_silence(*args)
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index ad77472333..fed199f6e9 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -192,7 +192,7 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_reflection_should_not_raise_error_when_compared_to_other_object
- assert_nothing_raised { Firm.reflections[:clients] == Object.new }
+ assert_nothing_raised { Firm.reflections['clients'] == Object.new }
end
def test_has_many_through_reflection
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 15611656fd..fb0b906c07 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -16,6 +16,10 @@ module ActiveRecord
def self.connection
Post.connection
end
+
+ def self.table_name
+ 'fake_table'
+ end
end
def test_construction
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index dc196f4432..a2a2a79180 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -824,6 +824,16 @@ class RelationTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all }
end
+ def test_select_with_aggregates
+ posts = Post.select(:title, :body)
+
+ assert_equal 11, posts.count(:all)
+ assert_equal 11, posts.size
+ assert posts.any?
+ assert posts.many?
+ assert_not posts.empty?
+ end
+
def test_select_takes_a_variable_list_of_args
david = developers(:david)
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 575eb34a9c..fd0ef2f89f 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -63,7 +63,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
next if column_set.empty?
lengths = column_set.map do |column|
- if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean)\s+"/)
+ if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean|uuid)\s+"/)
match[0].length
end
end
diff --git a/activerecord/test/fixtures/uuid_children.yml b/activerecord/test/fixtures/uuid_children.yml
new file mode 100644
index 0000000000..a7b15016e2
--- /dev/null
+++ b/activerecord/test/fixtures/uuid_children.yml
@@ -0,0 +1,3 @@
+sonny:
+ uuid_parent: daddy
+ name: Sonny
diff --git a/activerecord/test/fixtures/uuid_parents.yml b/activerecord/test/fixtures/uuid_parents.yml
new file mode 100644
index 0000000000..0b40225c5c
--- /dev/null
+++ b/activerecord/test/fixtures/uuid_parents.yml
@@ -0,0 +1,2 @@
+daddy:
+ name: Daddy
diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb
index 566e0873f1..a762ad4bb5 100644
--- a/activerecord/test/models/club.rb
+++ b/activerecord/test/models/club.rb
@@ -6,6 +6,8 @@ class Club < ActiveRecord::Base
has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member"
belongs_to :category
+ has_many :favourites, -> { where(memberships: { favourite: true }) }, through: :memberships, source: :member
+
private
def private_method
diff --git a/activerecord/test/models/uuid_child.rb b/activerecord/test/models/uuid_child.rb
new file mode 100644
index 0000000000..a3d0962ad6
--- /dev/null
+++ b/activerecord/test/models/uuid_child.rb
@@ -0,0 +1,3 @@
+class UuidChild < ActiveRecord::Base
+ belongs_to :uuid_parent
+end
diff --git a/activerecord/test/models/uuid_parent.rb b/activerecord/test/models/uuid_parent.rb
new file mode 100644
index 0000000000..5634f22d0c
--- /dev/null
+++ b/activerecord/test/models/uuid_parent.rb
@@ -0,0 +1,3 @@
+class UuidParent < ActiveRecord::Base
+ has_many :uuid_children
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index d9e1745029..da3074e90f 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -17,6 +17,15 @@ ActiveRecord::Schema.define do
ActiveRecord::Base.connection.create_table(*args, &block)
ActiveRecord::Base.connection.execute "SET GENERATOR #{args.first}_seq TO 10000"
end
+ when "PostgreSQL"
+ enable_uuid_ossp!(ActiveRecord::Base.connection)
+ create_table :uuid_parents, id: :uuid, force: true do |t|
+ t.string :name
+ end
+ create_table :uuid_children, id: :uuid, force: true do |t|
+ t.string :name
+ t.uuid :uuid_parent_id
+ end
end