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.

Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +137 -38
  3. data/lib/active_record/associations.rb +78 -13
  4. data/lib/active_record/associations/association_scope.rb +53 -40
  5. data/lib/active_record/associations/collection_association.rb +1 -1
  6. data/lib/active_record/associations/collection_proxy.rb +4 -4
  7. data/lib/active_record/associations/has_many_through_association.rb +6 -6
  8. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  9. data/lib/active_record/associations/preloader.rb +32 -23
  10. data/lib/active_record/associations/singular_association.rb +1 -1
  11. data/lib/active_record/associations/through_association.rb +5 -1
  12. data/lib/active_record/attribute_methods.rb +7 -7
  13. data/lib/active_record/attribute_methods/dirty.rb +20 -9
  14. data/lib/active_record/attribute_methods/query.rb +1 -1
  15. data/lib/active_record/attribute_methods/read.rb +1 -3
  16. data/lib/active_record/attribute_methods/serialization.rb +3 -4
  17. data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -0
  18. data/lib/active_record/autosave_association.rb +3 -3
  19. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1 -1
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +5 -0
  21. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +2 -0
  22. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +5 -8
  23. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +8 -4
  24. data/lib/active_record/connection_adapters/abstract/transaction.rb +11 -5
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +7 -1
  26. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +23 -15
  27. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -1
  28. data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -1
  29. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -4
  30. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -0
  31. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +3 -5
  32. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +8 -6
  33. data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -6
  34. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +6 -5
  35. data/lib/active_record/core.rb +11 -3
  36. data/lib/active_record/counter_cache.rb +1 -1
  37. data/lib/active_record/fixtures.rb +15 -8
  38. data/lib/active_record/gem_version.rb +2 -2
  39. data/lib/active_record/migration.rb +8 -12
  40. data/lib/active_record/reflection.rb +20 -18
  41. data/lib/active_record/relation/calculations.rb +7 -7
  42. data/lib/active_record/relation/finder_methods.rb +10 -9
  43. data/lib/active_record/relation/predicate_builder.rb +2 -2
  44. data/lib/active_record/relation/predicate_builder/array_handler.rb +12 -4
  45. data/lib/active_record/relation/query_methods.rb +8 -12
  46. data/lib/active_record/schema_dumper.rb +20 -28
  47. data/lib/active_record/tasks/database_tasks.rb +9 -5
  48. data/lib/active_record/transactions.rb +9 -9
  49. data/lib/active_record/type.rb +1 -0
  50. data/lib/active_record/type/binary.rb +10 -0
  51. data/lib/active_record/type/decorator.rb +14 -0
  52. data/lib/active_record/type/serialized.rb +8 -3
  53. metadata +7 -6
@@ -45,20 +45,20 @@ module ActiveRecord
45
45
  end
46
46
 
47
47
  def self.get_bind_values(owner, chain)
48
- bvs = []
49
- chain.each_with_index do |reflection, i|
50
- if reflection == chain.last
51
- bvs << reflection.join_id_for(owner)
52
- if reflection.type
53
- bvs << owner.class.base_class.name
54
- end
55
- else
56
- if reflection.type
57
- bvs << chain[i + 1].klass.base_class.name
58
- end
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
- bvs
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
- join_keys = reflection.join_keys(assoc_klass)
109
- key = join_keys.key
110
- foreign_key = join_keys.foreign_key
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
- if scope.is_a?(Relation)
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 object is present in the collection.
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(<<-MESSAGE.strip_heredoc)
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
- MESSAGE
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[column.to_s]
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
- # Note that 'eager loading' and 'preloading' are actually the same thing.
6
- # However, there are two different eager loading strategies.
5
+ # Suppose that you have the following two Active Record models:
7
6
  #
8
- # The first one is by using table joins. This was only strategy available
9
- # prior to Rails 2.1. Suppose that you have an Author model with columns
10
- # 'name' and 'age', and a Book model with columns 'name' and 'sales'. Using
11
- # this strategy, Active Record would try to retrieve all data for an author
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
- # SELECT * FROM authors
15
- # LEFT OUTER JOIN books ON authors.id = books.author_id
16
- # WHERE authors.name = 'Ken Akamatsu'
12
+ # class Book < ActiveRecord::Base
13
+ # # columns: title, sales
14
+ # end
17
15
  #
18
- # However, this could result in many rows that contain redundant data. After
19
- # having received the first row, we already have enough data to instantiate
20
- # the Author object. In all subsequent rows, only the data for the joined
21
- # 'books' table is useful; the joined 'authors' data is just redundant, and
22
- # processing this redundant data takes memory and CPU time. The problem
23
- # quickly becomes worse and worse as the level of eager loading increases
24
- # (i.e. if Active Record is to eager load the associations' associations as
25
- # well).
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
- relation.merge!(reflection.scope) if reflection.scope
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(<<-MESSAGE.strip_heredoc)
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
- super.reverse_merge(attributes_changed_in_place).freeze
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
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
  end
9
9
 
10
10
  def query_attribute(attr_name)
11
- value = read_attribute(attr_name) { |n| missing_attribute(n, caller) }
11
+ value = self[attr_name]
12
12
 
13
13
  case value
14
14
  when true then true
@@ -46,9 +46,7 @@ module ActiveRecord
46
46
  protected
47
47
 
48
48
  def cached_attributes_deprecation_warning(method_name)
49
- ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
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(<<-WARNING.strip_heredoc)
58
- `serialized_attributes` is deprecated without replacement, and will
59
- be removed in Rails 5.0.
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]
@@ -2,6 +2,8 @@ module ActiveRecord
2
2
  module AttributeMethods
3
3
  module TimeZoneConversion
4
4
  class TimeZoneConverter < SimpleDelegator # :nodoc:
5
+ include Type::Decorator
6
+
5
7
  def type_cast_from_database(value)
6
8
  convert_time_to_time_zone(super)
7
9
  end
@@ -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? || record[reflection.foreign_key] != key || record.attribute_changed?(reflection.foreign_key)
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.