activerecord 4.2.0.beta1 → 4.2.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +137 -38
- data/lib/active_record/associations.rb +78 -13
- data/lib/active_record/associations/association_scope.rb +53 -40
- data/lib/active_record/associations/collection_association.rb +1 -1
- data/lib/active_record/associations/collection_proxy.rb +4 -4
- data/lib/active_record/associations/has_many_through_association.rb +6 -6
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +32 -23
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +5 -1
- data/lib/active_record/attribute_methods.rb +7 -7
- data/lib/active_record/attribute_methods/dirty.rb +20 -9
- data/lib/active_record/attribute_methods/query.rb +1 -1
- data/lib/active_record/attribute_methods/read.rb +1 -3
- data/lib/active_record/attribute_methods/serialization.rb +3 -4
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -0
- data/lib/active_record/autosave_association.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +5 -8
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/transaction.rb +11 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +7 -1
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +23 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +3 -5
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -6
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +6 -5
- data/lib/active_record/core.rb +11 -3
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/fixtures.rb +15 -8
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/migration.rb +8 -12
- data/lib/active_record/reflection.rb +20 -18
- data/lib/active_record/relation/calculations.rb +7 -7
- data/lib/active_record/relation/finder_methods.rb +10 -9
- data/lib/active_record/relation/predicate_builder.rb +2 -2
- data/lib/active_record/relation/predicate_builder/array_handler.rb +12 -4
- data/lib/active_record/relation/query_methods.rb +8 -12
- data/lib/active_record/schema_dumper.rb +20 -28
- data/lib/active_record/tasks/database_tasks.rb +9 -5
- data/lib/active_record/transactions.rb +9 -9
- data/lib/active_record/type.rb +1 -0
- data/lib/active_record/type/binary.rb +10 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/serialized.rb +8 -3
- metadata +7 -6
@@ -45,20 +45,20 @@ module ActiveRecord
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def self.get_bind_values(owner, chain)
|
48
|
-
|
49
|
-
chain.
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
48
|
+
binds = []
|
49
|
+
last_reflection = chain.last
|
50
|
+
|
51
|
+
binds << last_reflection.join_id_for(owner)
|
52
|
+
if last_reflection.type
|
53
|
+
binds << owner.class.base_class.name
|
54
|
+
end
|
55
|
+
|
56
|
+
chain.each_cons(2).each do |reflection, next_reflection|
|
57
|
+
if reflection.type
|
58
|
+
binds << next_reflection.klass.base_class.name
|
59
59
|
end
|
60
60
|
end
|
61
|
-
|
61
|
+
binds
|
62
62
|
end
|
63
63
|
|
64
64
|
private
|
@@ -96,38 +96,55 @@ module ActiveRecord
|
|
96
96
|
bind_value scope, column, value, tracker
|
97
97
|
end
|
98
98
|
|
99
|
+
def last_chain_scope(scope, table, reflection, owner, tracker, assoc_klass)
|
100
|
+
join_keys = reflection.join_keys(assoc_klass)
|
101
|
+
key = join_keys.key
|
102
|
+
foreign_key = join_keys.foreign_key
|
103
|
+
|
104
|
+
bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
|
105
|
+
scope = scope.where(table[key].eq(bind_val))
|
106
|
+
|
107
|
+
if reflection.type
|
108
|
+
value = owner.class.base_class.name
|
109
|
+
bind_val = bind scope, table.table_name, reflection.type, value, tracker
|
110
|
+
scope = scope.where(table[reflection.type].eq(bind_val))
|
111
|
+
else
|
112
|
+
scope
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
|
117
|
+
join_keys = reflection.join_keys(assoc_klass)
|
118
|
+
key = join_keys.key
|
119
|
+
foreign_key = join_keys.foreign_key
|
120
|
+
|
121
|
+
constraint = table[key].eq(foreign_table[foreign_key])
|
122
|
+
|
123
|
+
if reflection.type
|
124
|
+
value = next_reflection.klass.base_class.name
|
125
|
+
bind_val = bind scope, table.table_name, reflection.type, value, tracker
|
126
|
+
scope = scope.where(table[reflection.type].eq(bind_val))
|
127
|
+
end
|
128
|
+
|
129
|
+
scope = scope.joins(join(foreign_table, constraint))
|
130
|
+
end
|
131
|
+
|
99
132
|
def add_constraints(scope, owner, assoc_klass, refl, tracker)
|
100
133
|
chain = refl.chain
|
101
134
|
scope_chain = refl.scope_chain
|
102
135
|
|
103
136
|
tables = construct_tables(chain, assoc_klass, refl, tracker)
|
104
137
|
|
138
|
+
owner_reflection = chain.last
|
139
|
+
table = tables.last
|
140
|
+
scope = last_chain_scope(scope, table, owner_reflection, owner, tracker, assoc_klass)
|
141
|
+
|
105
142
|
chain.each_with_index do |reflection, i|
|
106
143
|
table, foreign_table = tables.shift, tables.first
|
107
144
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
if reflection == chain.last
|
113
|
-
bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
|
114
|
-
scope = scope.where(table[key].eq(bind_val))
|
115
|
-
|
116
|
-
if reflection.type
|
117
|
-
value = owner.class.base_class.name
|
118
|
-
bind_val = bind scope, table.table_name, reflection.type, value, tracker
|
119
|
-
scope = scope.where(table[reflection.type].eq(bind_val))
|
120
|
-
end
|
121
|
-
else
|
122
|
-
constraint = table[key].eq(foreign_table[foreign_key])
|
123
|
-
|
124
|
-
if reflection.type
|
125
|
-
value = chain[i + 1].klass.base_class.name
|
126
|
-
bind_val = bind scope, table.table_name, reflection.type, value, tracker
|
127
|
-
scope = scope.where(table[reflection.type].eq(bind_val))
|
128
|
-
end
|
129
|
-
|
130
|
-
scope = scope.joins(join(foreign_table, constraint))
|
145
|
+
unless reflection == chain.last
|
146
|
+
next_reflection = chain[i + 1]
|
147
|
+
scope = next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
|
131
148
|
end
|
132
149
|
|
133
150
|
is_first_chain = i == 0
|
@@ -171,11 +188,7 @@ module ActiveRecord
|
|
171
188
|
end
|
172
189
|
|
173
190
|
def eval_scope(klass, scope, owner)
|
174
|
-
|
175
|
-
scope
|
176
|
-
else
|
177
|
-
klass.unscoped.instance_exec(owner, &scope)
|
178
|
-
end
|
191
|
+
klass.unscoped.instance_exec(owner, &scope)
|
179
192
|
end
|
180
193
|
end
|
181
194
|
end
|
@@ -407,7 +407,7 @@ module ActiveRecord
|
|
407
407
|
|
408
408
|
private
|
409
409
|
def get_records
|
410
|
-
return scope.to_a if reflection.scope_chain.any?(&:any?)
|
410
|
+
return scope.to_a if reflection.scope_chain.any?(&:any?) || scope.eager_loading?
|
411
411
|
|
412
412
|
conn = klass.connection
|
413
413
|
sc = reflection.association_scope_cache(conn, owner) do
|
@@ -783,7 +783,7 @@ module ActiveRecord
|
|
783
783
|
# person.pets.count # => 0
|
784
784
|
# person.pets.any? # => true
|
785
785
|
#
|
786
|
-
# You can also pass a block to define criteria. The behavior
|
786
|
+
# You can also pass a +block+ to define criteria. The behavior
|
787
787
|
# is the same, it returns true if the collection based on the
|
788
788
|
# criteria is not empty.
|
789
789
|
#
|
@@ -817,7 +817,7 @@ module ActiveRecord
|
|
817
817
|
# person.pets.count # => 2
|
818
818
|
# person.pets.many? # => true
|
819
819
|
#
|
820
|
-
# You can also pass a block to define criteria. The
|
820
|
+
# You can also pass a +block+ to define criteria. The
|
821
821
|
# behavior is the same, it returns true if the collection
|
822
822
|
# based on the criteria has more than one record.
|
823
823
|
#
|
@@ -841,7 +841,7 @@ module ActiveRecord
|
|
841
841
|
@association.many?(&block)
|
842
842
|
end
|
843
843
|
|
844
|
-
# Returns +true+ if the given
|
844
|
+
# Returns +true+ if the given +record+ is present in the collection.
|
845
845
|
#
|
846
846
|
# class Person < ActiveRecord::Base
|
847
847
|
# has_many :pets
|
@@ -879,7 +879,7 @@ module ActiveRecord
|
|
879
879
|
|
880
880
|
# Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
|
881
881
|
# contain the same number of elements and if each element is equal
|
882
|
-
# to the corresponding element in the other array, otherwise returns
|
882
|
+
# to the corresponding element in the +other+ array, otherwise returns
|
883
883
|
# +false+.
|
884
884
|
#
|
885
885
|
# class Person < ActiveRecord::Base
|
@@ -63,12 +63,12 @@ module ActiveRecord
|
|
63
63
|
|
64
64
|
save_through_record(record)
|
65
65
|
if has_cached_counter? && !through_reflection_updates_counter_cache?
|
66
|
-
ActiveSupport::Deprecation.warn
|
67
|
-
Automatic updating of counter caches on through associations has been
|
68
|
-
deprecated, and will be removed in Rails 5.0. Instead, please set the
|
69
|
-
appropriate counter_cache options on the has_many and belongs_to for
|
70
|
-
your associations to #{through_reflection.name}.
|
71
|
-
|
66
|
+
ActiveSupport::Deprecation.warn \
|
67
|
+
"Automatic updating of counter caches on through associations has been " \
|
68
|
+
"deprecated, and will be removed in Rails 5.0. Instead, please set the " \
|
69
|
+
"appropriate counter_cache options on the has_many and belongs_to for " \
|
70
|
+
"your associations to #{through_reflection.name}."
|
71
|
+
|
72
72
|
update_counter_in_database(1)
|
73
73
|
end
|
74
74
|
record
|
@@ -65,7 +65,7 @@ module ActiveRecord
|
|
65
65
|
|
66
66
|
if reflection.type
|
67
67
|
value = foreign_klass.base_class.name
|
68
|
-
column = klass.columns_hash[
|
68
|
+
column = klass.columns_hash[reflection.type.to_s]
|
69
69
|
|
70
70
|
substitute = klass.connection.substitute_at(column, bind_values.length)
|
71
71
|
bind_values.push [column, value]
|
@@ -2,33 +2,42 @@ module ActiveRecord
|
|
2
2
|
module Associations
|
3
3
|
# Implements the details of eager loading of Active Record associations.
|
4
4
|
#
|
5
|
-
#
|
6
|
-
# However, there are two different eager loading strategies.
|
5
|
+
# Suppose that you have the following two Active Record models:
|
7
6
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# and all of its books via a single query:
|
7
|
+
# class Author < ActiveRecord::Base
|
8
|
+
# # columns: name, age
|
9
|
+
# has_many :books
|
10
|
+
# end
|
13
11
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
12
|
+
# class Book < ActiveRecord::Base
|
13
|
+
# # columns: title, sales
|
14
|
+
# end
|
17
15
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
16
|
+
# When you load an author with all associated books Active Record will make
|
17
|
+
# multiple queries like this:
|
18
|
+
#
|
19
|
+
# Author.includes(:books).where(:name => ['bell hooks', 'Homer').to_a
|
20
|
+
#
|
21
|
+
# => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
|
22
|
+
# => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
|
23
|
+
#
|
24
|
+
# Active Record saves the ids of the records from the first query to use in
|
25
|
+
# the second. Depending on the number of associations involved there can be
|
26
|
+
# arbitrarily many SQL queries made.
|
27
|
+
#
|
28
|
+
# However, if there is a WHERE clause that spans across tables Active
|
29
|
+
# Record will fall back to a slightly more resource-intensive single query:
|
30
|
+
#
|
31
|
+
# Author.includes(:books).where(books: {title: 'Illiad'}).to_a
|
32
|
+
# => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
|
33
|
+
# `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
|
34
|
+
# FROM `authors`
|
35
|
+
# LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
|
36
|
+
# WHERE `books`.`title` = 'Illiad'
|
37
|
+
#
|
38
|
+
# This could result in many rows that contain redundant data and it performs poorly at scale
|
39
|
+
# and is therefore only used when necessary.
|
26
40
|
#
|
27
|
-
# The second strategy is to use multiple database queries, one for each
|
28
|
-
# level of association. Since Rails 2.1, this is the default strategy. In
|
29
|
-
# situations where a table join is necessary (e.g. when the +:conditions+
|
30
|
-
# option references an association's column), it will fallback to the table
|
31
|
-
# join strategy.
|
32
41
|
class Preloader #:nodoc:
|
33
42
|
extend ActiveSupport::Autoload
|
34
43
|
|
@@ -39,7 +39,7 @@ module ActiveRecord
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def get_records
|
42
|
-
return scope.limit(1).to_a if reflection.scope_chain.any?(&:any?)
|
42
|
+
return scope.limit(1).to_a if reflection.scope_chain.any?(&:any?) || scope.eager_loading?
|
43
43
|
|
44
44
|
conn = klass.connection
|
45
45
|
sc = reflection.association_scope_cache(conn, owner) do
|
@@ -15,7 +15,11 @@ module ActiveRecord
|
|
15
15
|
scope = super
|
16
16
|
reflection.chain.drop(1).each do |reflection|
|
17
17
|
relation = reflection.klass.all
|
18
|
-
|
18
|
+
|
19
|
+
reflection_scope = reflection.scope
|
20
|
+
if reflection_scope && reflection_scope.arity.zero?
|
21
|
+
relation.merge!(reflection_scope)
|
22
|
+
end
|
19
23
|
|
20
24
|
scope.merge!(
|
21
25
|
relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
|
@@ -31,7 +31,7 @@ module ActiveRecord
|
|
31
31
|
end
|
32
32
|
}
|
33
33
|
|
34
|
-
BLACKLISTED_CLASS_METHODS = %w(private public protected)
|
34
|
+
BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
|
35
35
|
|
36
36
|
class AttributeMethodCache
|
37
37
|
def initialize
|
@@ -69,6 +69,8 @@ module ActiveRecord
|
|
69
69
|
@generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m }
|
70
70
|
@attribute_methods_generated = false
|
71
71
|
include @generated_attribute_methods
|
72
|
+
|
73
|
+
super
|
72
74
|
end
|
73
75
|
|
74
76
|
# Generates all the attribute related methods for columns in the database
|
@@ -109,7 +111,7 @@ module ActiveRecord
|
|
109
111
|
# # => false
|
110
112
|
def instance_method_already_implemented?(method_name)
|
111
113
|
if dangerous_attribute_method?(method_name)
|
112
|
-
raise DangerousAttributeError, "#{method_name} is defined by Active Record"
|
114
|
+
raise DangerousAttributeError, "#{method_name} is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name."
|
113
115
|
end
|
114
116
|
|
115
117
|
if superclass == Base
|
@@ -203,11 +205,9 @@ module ActiveRecord
|
|
203
205
|
def column_for_attribute(name)
|
204
206
|
column = columns_hash[name.to_s]
|
205
207
|
if column.nil?
|
206
|
-
ActiveSupport::Deprecation.warn
|
207
|
-
`column_for_attribute` will return a null object for non-existent columns
|
208
|
-
in Rails 5.0. Use `has_attribute?` if you need to check for an
|
209
|
-
attribute's existence.
|
210
|
-
MESSAGE
|
208
|
+
ActiveSupport::Deprecation.warn \
|
209
|
+
"`column_for_attribute` will return a null object for non-existent columns " \
|
210
|
+
"in Rails 5.0. Use `has_attribute?` if you need to check for an attribute's existence."
|
211
211
|
end
|
212
212
|
column
|
213
213
|
end
|
@@ -43,14 +43,6 @@ module ActiveRecord
|
|
43
43
|
calculate_changes_from_defaults
|
44
44
|
end
|
45
45
|
|
46
|
-
def changed?
|
47
|
-
super || changed_in_place.any?
|
48
|
-
end
|
49
|
-
|
50
|
-
def changed
|
51
|
-
super | changed_in_place
|
52
|
-
end
|
53
|
-
|
54
46
|
def changes_applied
|
55
47
|
super
|
56
48
|
store_original_raw_attributes
|
@@ -62,7 +54,19 @@ module ActiveRecord
|
|
62
54
|
end
|
63
55
|
|
64
56
|
def changed_attributes
|
65
|
-
|
57
|
+
# This should only be set by methods which will call changed_attributes
|
58
|
+
# multiple times when it is known that the computed value cannot change.
|
59
|
+
if defined?(@cached_changed_attributes)
|
60
|
+
@cached_changed_attributes
|
61
|
+
else
|
62
|
+
super.reverse_merge(attributes_changed_in_place).freeze
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def changes
|
67
|
+
cache_changed_attributes do
|
68
|
+
super
|
69
|
+
end
|
66
70
|
end
|
67
71
|
|
68
72
|
private
|
@@ -165,6 +169,13 @@ module ActiveRecord
|
|
165
169
|
store_original_raw_attribute(attr)
|
166
170
|
end
|
167
171
|
end
|
172
|
+
|
173
|
+
def cache_changed_attributes
|
174
|
+
@cached_changed_attributes = changed_attributes
|
175
|
+
yield
|
176
|
+
ensure
|
177
|
+
remove_instance_variable(:@cached_changed_attributes)
|
178
|
+
end
|
168
179
|
end
|
169
180
|
end
|
170
181
|
end
|
@@ -46,9 +46,7 @@ module ActiveRecord
|
|
46
46
|
protected
|
47
47
|
|
48
48
|
def cached_attributes_deprecation_warning(method_name)
|
49
|
-
ActiveSupport::Deprecation.warn
|
50
|
-
Calling `#{method_name}` is no longer necessary. All attributes are cached.
|
51
|
-
MESSAGE
|
49
|
+
ActiveSupport::Deprecation.warn "Calling `#{method_name}` is no longer necessary. All attributes are cached."
|
52
50
|
end
|
53
51
|
|
54
52
|
if Module.methods_transplantable?
|
@@ -54,10 +54,9 @@ module ActiveRecord
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def serialized_attributes
|
57
|
-
ActiveSupport::Deprecation.warn
|
58
|
-
|
59
|
-
|
60
|
-
WARNING
|
57
|
+
ActiveSupport::Deprecation.warn "`serialized_attributes` is deprecated " \
|
58
|
+
"without replacement, and will be removed in Rails 5.0."
|
59
|
+
|
61
60
|
@serialized_attributes ||= Hash[
|
62
61
|
columns.select { |t| t.cast_type.is_a?(Type::Serialized) }.map { |c|
|
63
62
|
[c.name, c.cast_type.coder]
|
@@ -338,7 +338,6 @@ module ActiveRecord
|
|
338
338
|
autosave = reflection.options[:autosave]
|
339
339
|
|
340
340
|
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
|
341
|
-
|
342
341
|
if autosave
|
343
342
|
records_to_destroy = records.select(&:marked_for_destruction?)
|
344
343
|
records_to_destroy.each { |record| association.destroy(record) }
|
@@ -362,7 +361,6 @@ module ActiveRecord
|
|
362
361
|
|
363
362
|
raise ActiveRecord::Rollback unless saved
|
364
363
|
end
|
365
|
-
@new_record_before_save = false
|
366
364
|
end
|
367
365
|
|
368
366
|
# reconstruct the scope now that we know the owner's id
|
@@ -405,7 +403,9 @@ module ActiveRecord
|
|
405
403
|
|
406
404
|
# If the record is new or it has changed, returns true.
|
407
405
|
def record_changed?(reflection, record, key)
|
408
|
-
record.new_record? ||
|
406
|
+
record.new_record? ||
|
407
|
+
(record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) ||
|
408
|
+
record.attribute_changed?(reflection.foreign_key)
|
409
409
|
end
|
410
410
|
|
411
411
|
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
|