activerecord 4.2.11.3 → 5.0.0.beta1

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 (229) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1029 -1349
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -7
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record.rb +7 -3
  7. data/lib/active_record/aggregations.rb +35 -25
  8. data/lib/active_record/association_relation.rb +2 -2
  9. data/lib/active_record/associations.rb +305 -204
  10. data/lib/active_record/associations/alias_tracker.rb +19 -16
  11. data/lib/active_record/associations/association.rb +10 -8
  12. data/lib/active_record/associations/association_scope.rb +73 -102
  13. data/lib/active_record/associations/belongs_to_association.rb +20 -32
  14. data/lib/active_record/associations/builder/association.rb +28 -34
  15. data/lib/active_record/associations/builder/belongs_to.rb +41 -18
  16. data/lib/active_record/associations/builder/collection_association.rb +8 -24
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +11 -11
  18. data/lib/active_record/associations/builder/has_many.rb +4 -4
  19. data/lib/active_record/associations/builder/has_one.rb +10 -5
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -9
  21. data/lib/active_record/associations/collection_association.rb +40 -43
  22. data/lib/active_record/associations/collection_proxy.rb +55 -29
  23. data/lib/active_record/associations/foreign_association.rb +1 -1
  24. data/lib/active_record/associations/has_many_association.rb +20 -71
  25. data/lib/active_record/associations/has_many_through_association.rb +8 -52
  26. data/lib/active_record/associations/has_one_association.rb +12 -5
  27. data/lib/active_record/associations/join_dependency.rb +28 -18
  28. data/lib/active_record/associations/join_dependency/join_association.rb +13 -12
  29. data/lib/active_record/associations/preloader.rb +13 -4
  30. data/lib/active_record/associations/preloader/association.rb +45 -51
  31. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  32. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  33. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  34. data/lib/active_record/associations/preloader/through_association.rb +5 -4
  35. data/lib/active_record/associations/singular_association.rb +6 -0
  36. data/lib/active_record/associations/through_association.rb +11 -3
  37. data/lib/active_record/attribute.rb +61 -17
  38. data/lib/active_record/attribute/user_provided_default.rb +23 -0
  39. data/lib/active_record/attribute_assignment.rb +27 -140
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods.rb +79 -26
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  43. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  44. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  45. data/lib/active_record/attribute_methods/query.rb +2 -2
  46. data/lib/active_record/attribute_methods/read.rb +26 -42
  47. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +42 -9
  49. data/lib/active_record/attribute_methods/write.rb +13 -24
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set.rb +30 -3
  52. data/lib/active_record/attribute_set/builder.rb +6 -4
  53. data/lib/active_record/attributes.rb +194 -81
  54. data/lib/active_record/autosave_association.rb +33 -15
  55. data/lib/active_record/base.rb +30 -18
  56. data/lib/active_record/callbacks.rb +36 -40
  57. data/lib/active_record/coders/yaml_column.rb +20 -8
  58. data/lib/active_record/collection_cache_key.rb +31 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +431 -122
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +40 -22
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +62 -8
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -38
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +229 -185
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +52 -13
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +275 -115
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +32 -33
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +83 -32
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +384 -221
  70. data/lib/active_record/connection_adapters/column.rb +27 -41
  71. data/lib/active_record/connection_adapters/connection_specification.rb +2 -21
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +57 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +69 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +59 -0
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +22 -101
  77. data/lib/active_record/connection_adapters/postgresql/column.rb +6 -10
  78. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +3 -3
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  80. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +23 -57
  81. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  85. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  86. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  87. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +23 -16
  92. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  93. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +18 -11
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -0
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +174 -128
  101. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -112
  103. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  104. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +15 -0
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +134 -110
  107. data/lib/active_record/connection_adapters/statement_pool.rb +28 -11
  108. data/lib/active_record/connection_handling.rb +5 -5
  109. data/lib/active_record/core.rb +72 -104
  110. data/lib/active_record/counter_cache.rb +9 -20
  111. data/lib/active_record/dynamic_matchers.rb +1 -20
  112. data/lib/active_record/enum.rb +110 -76
  113. data/lib/active_record/errors.rb +72 -47
  114. data/lib/active_record/explain_registry.rb +1 -1
  115. data/lib/active_record/explain_subscriber.rb +1 -1
  116. data/lib/active_record/fixture_set/file.rb +19 -4
  117. data/lib/active_record/fixtures.rb +76 -40
  118. data/lib/active_record/gem_version.rb +4 -4
  119. data/lib/active_record/inheritance.rb +27 -40
  120. data/lib/active_record/integration.rb +4 -4
  121. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  122. data/lib/active_record/locale/en.yml +3 -2
  123. data/lib/active_record/locking/optimistic.rb +10 -14
  124. data/lib/active_record/locking/pessimistic.rb +1 -1
  125. data/lib/active_record/log_subscriber.rb +40 -22
  126. data/lib/active_record/migration.rb +304 -133
  127. data/lib/active_record/migration/command_recorder.rb +59 -18
  128. data/lib/active_record/migration/compatibility.rb +90 -0
  129. data/lib/active_record/model_schema.rb +92 -40
  130. data/lib/active_record/nested_attributes.rb +45 -34
  131. data/lib/active_record/null_relation.rb +15 -7
  132. data/lib/active_record/persistence.rb +112 -72
  133. data/lib/active_record/querying.rb +6 -5
  134. data/lib/active_record/railtie.rb +20 -13
  135. data/lib/active_record/railties/controller_runtime.rb +1 -1
  136. data/lib/active_record/railties/databases.rake +47 -38
  137. data/lib/active_record/readonly_attributes.rb +1 -1
  138. data/lib/active_record/reflection.rb +182 -57
  139. data/lib/active_record/relation.rb +152 -100
  140. data/lib/active_record/relation/batches.rb +133 -33
  141. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  142. data/lib/active_record/relation/calculations.rb +80 -101
  143. data/lib/active_record/relation/delegation.rb +6 -19
  144. data/lib/active_record/relation/finder_methods.rb +58 -46
  145. data/lib/active_record/relation/from_clause.rb +32 -0
  146. data/lib/active_record/relation/merger.rb +13 -42
  147. data/lib/active_record/relation/predicate_builder.rb +99 -105
  148. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  149. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +78 -0
  150. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  151. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  152. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  153. data/lib/active_record/relation/predicate_builder/range_handler.rb +17 -0
  154. data/lib/active_record/relation/query_attribute.rb +19 -0
  155. data/lib/active_record/relation/query_methods.rb +274 -238
  156. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  157. data/lib/active_record/relation/spawn_methods.rb +3 -6
  158. data/lib/active_record/relation/where_clause.rb +173 -0
  159. data/lib/active_record/relation/where_clause_factory.rb +37 -0
  160. data/lib/active_record/result.rb +4 -3
  161. data/lib/active_record/runtime_registry.rb +1 -1
  162. data/lib/active_record/sanitization.rb +94 -65
  163. data/lib/active_record/schema.rb +23 -22
  164. data/lib/active_record/schema_dumper.rb +33 -22
  165. data/lib/active_record/schema_migration.rb +10 -4
  166. data/lib/active_record/scoping.rb +17 -6
  167. data/lib/active_record/scoping/default.rb +19 -6
  168. data/lib/active_record/scoping/named.rb +39 -28
  169. data/lib/active_record/secure_token.rb +38 -0
  170. data/lib/active_record/serialization.rb +2 -4
  171. data/lib/active_record/statement_cache.rb +15 -13
  172. data/lib/active_record/store.rb +8 -3
  173. data/lib/active_record/suppressor.rb +54 -0
  174. data/lib/active_record/table_metadata.rb +64 -0
  175. data/lib/active_record/tasks/database_tasks.rb +30 -40
  176. data/lib/active_record/tasks/mysql_database_tasks.rb +7 -15
  177. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  178. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  179. data/lib/active_record/timestamp.rb +16 -9
  180. data/lib/active_record/touch_later.rb +58 -0
  181. data/lib/active_record/transactions.rb +138 -56
  182. data/lib/active_record/type.rb +66 -17
  183. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  184. data/lib/active_record/type/date.rb +2 -45
  185. data/lib/active_record/type/date_time.rb +2 -49
  186. data/lib/active_record/type/internal/abstract_json.rb +33 -0
  187. data/lib/active_record/type/internal/timezone.rb +15 -0
  188. data/lib/active_record/type/serialized.rb +9 -14
  189. data/lib/active_record/type/time.rb +3 -21
  190. data/lib/active_record/type/type_map.rb +4 -4
  191. data/lib/active_record/type_caster.rb +7 -0
  192. data/lib/active_record/type_caster/connection.rb +29 -0
  193. data/lib/active_record/type_caster/map.rb +19 -0
  194. data/lib/active_record/validations.rb +33 -32
  195. data/lib/active_record/validations/absence.rb +24 -0
  196. data/lib/active_record/validations/associated.rb +10 -3
  197. data/lib/active_record/validations/length.rb +36 -0
  198. data/lib/active_record/validations/presence.rb +12 -12
  199. data/lib/active_record/validations/uniqueness.rb +24 -21
  200. data/lib/rails/generators/active_record/migration.rb +7 -0
  201. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  202. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  203. data/lib/rails/generators/active_record/migration/templates/migration.rb +4 -1
  204. data/lib/rails/generators/active_record/model/model_generator.rb +21 -15
  205. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  206. metadata +50 -35
  207. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  208. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  209. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  210. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  211. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  212. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  213. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  214. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  215. data/lib/active_record/type/big_integer.rb +0 -13
  216. data/lib/active_record/type/binary.rb +0 -50
  217. data/lib/active_record/type/boolean.rb +0 -31
  218. data/lib/active_record/type/decimal.rb +0 -64
  219. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  220. data/lib/active_record/type/decorator.rb +0 -14
  221. data/lib/active_record/type/float.rb +0 -19
  222. data/lib/active_record/type/integer.rb +0 -59
  223. data/lib/active_record/type/mutable.rb +0 -16
  224. data/lib/active_record/type/numeric.rb +0 -36
  225. data/lib/active_record/type/string.rb +0 -40
  226. data/lib/active_record/type/text.rb +0 -11
  227. data/lib/active_record/type/time_value.rb +0 -38
  228. data/lib/active_record/type/unsigned_integer.rb +0 -15
  229. data/lib/active_record/type/value.rb +0 -110
@@ -15,7 +15,7 @@ module ActiveRecord
15
15
  end
16
16
 
17
17
  def decorate_matching_attribute_types(matcher, decorator_name, &block)
18
- clear_caches_calculated_from_columns
18
+ reload_schema_from_cache
19
19
  decorator_name = decorator_name.to_s
20
20
 
21
21
  # Create new hashes so we don't modify parent classes
@@ -24,10 +24,11 @@ module ActiveRecord
24
24
 
25
25
  private
26
26
 
27
- def add_user_provided_columns(*)
28
- super.map do |column|
29
- decorated_type = attribute_type_decorations.apply(column.name, column.cast_type)
30
- column.with_type(decorated_type)
27
+ def load_schema!
28
+ super
29
+ attribute_types.each do |name, type|
30
+ decorated_type = attribute_type_decorations.apply(name, type)
31
+ define_attribute(name, decorated_type)
31
32
  end
32
33
  end
33
34
  end
@@ -1,7 +1,7 @@
1
1
  require 'active_support/core_ext/enumerable'
2
2
  require 'active_support/core_ext/string/filters'
3
3
  require 'mutex_m'
4
- require 'thread_safe'
4
+ require 'concurrent/map'
5
5
 
6
6
  module ActiveRecord
7
7
  # = Active Record Attribute Methods
@@ -37,12 +37,12 @@ module ActiveRecord
37
37
  class AttributeMethodCache
38
38
  def initialize
39
39
  @module = Module.new
40
- @method_cache = ThreadSafe::Cache.new
40
+ @method_cache = Concurrent::Map.new
41
41
  end
42
42
 
43
43
  def [](name)
44
44
  @method_cache.compute_if_absent(name) do
45
- safe_name = name.unpack('h*').first
45
+ safe_name = name.unpack('h*'.freeze).first
46
46
  temp_method = "__temp__#{safe_name}"
47
47
  ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
48
48
  @module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
@@ -83,7 +83,7 @@ module ActiveRecord
83
83
  generated_attribute_methods.synchronize do
84
84
  return false if @attribute_methods_generated
85
85
  superclass.define_attribute_methods unless self == base_class
86
- super(column_names)
86
+ super(attribute_names)
87
87
  @attribute_methods_generated = true
88
88
  end
89
89
  true
@@ -96,7 +96,7 @@ module ActiveRecord
96
96
  end
97
97
  end
98
98
 
99
- # Raises a <tt>ActiveRecord::DangerousAttributeError</tt> exception when an
99
+ # Raises an ActiveRecord::DangerousAttributeError exception when an
100
100
  # \Active \Record method is defined in the model, otherwise +false+.
101
101
  #
102
102
  # class Person < ActiveRecord::Base
@@ -106,7 +106,7 @@ module ActiveRecord
106
106
  # end
107
107
  #
108
108
  # Person.instance_method_already_implemented?(:save)
109
- # # => ActiveRecord::DangerousAttributeError: save is defined by ActiveRecord
109
+ # # => ActiveRecord::DangerousAttributeError: save is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name.
110
110
  #
111
111
  # Person.instance_method_already_implemented?(:name)
112
112
  # # => false
@@ -150,7 +150,7 @@ module ActiveRecord
150
150
  BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
151
151
  end
152
152
 
153
- def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc
153
+ def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
154
154
  if klass.respond_to?(name, true)
155
155
  if superklass.respond_to?(name, true)
156
156
  klass.method(name).owner != superklass.method(name).owner
@@ -185,14 +185,27 @@ module ActiveRecord
185
185
  # # => ["id", "created_at", "updated_at", "name", "age"]
186
186
  def attribute_names
187
187
  @attribute_names ||= if !abstract_class? && table_exists?
188
- column_names
188
+ attribute_types.keys
189
189
  else
190
190
  []
191
191
  end
192
192
  end
193
193
 
194
+ # Returns true if the given attribute exists, otherwise false.
195
+ #
196
+ # class Person < ActiveRecord::Base
197
+ # end
198
+ #
199
+ # Person.has_attribute?('name') # => true
200
+ # Person.has_attribute?(:age) # => true
201
+ # Person.has_attribute?(:nothing) # => false
202
+ def has_attribute?(attr_name)
203
+ attribute_types.key?(attr_name.to_s)
204
+ end
205
+
194
206
  # Returns the column object for the named attribute.
195
- # Returns nil if the named attribute does not exist.
207
+ # Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the
208
+ # named attribute does not exist.
196
209
  #
197
210
  # class Person < ActiveRecord::Base
198
211
  # end
@@ -202,23 +215,18 @@ module ActiveRecord
202
215
  # # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
203
216
  #
204
217
  # person.column_for_attribute(:nothing)
205
- # # => nil
218
+ # # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
206
219
  def column_for_attribute(name)
207
- column = columns_hash[name.to_s]
208
- if column.nil?
209
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
210
- `#column_for_attribute` will return a null object for non-existent
211
- columns in Rails 5. Use `#has_attribute?` if you need to check for
212
- an attribute's existence.
213
- MSG
220
+ name = name.to_s
221
+ columns_hash.fetch(name) do
222
+ ConnectionAdapters::NullColumn.new(name)
214
223
  end
215
- column
216
224
  end
217
225
  end
218
226
 
219
227
  # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
220
228
  # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
221
- # which will all return +true+. It also define the attribute methods if they have
229
+ # which will all return +true+. It also defines the attribute methods if they have
222
230
  # not been generated.
223
231
  #
224
232
  # class Person < ActiveRecord::Base
@@ -234,7 +242,15 @@ module ActiveRecord
234
242
  # person.respond_to(:nothing) # => false
235
243
  def respond_to?(name, include_private = false)
236
244
  return false unless super
237
- name = name.to_s
245
+
246
+ case name
247
+ when :to_partial_path
248
+ name = "to_partial_path".freeze
249
+ when :to_model
250
+ name = "to_model".freeze
251
+ else
252
+ name = name.to_s
253
+ end
238
254
 
239
255
  # If the result is true then check for the select case.
240
256
  # For queries selecting a subset of columns, return false for unselected columns.
@@ -287,8 +303,9 @@ module ActiveRecord
287
303
  # Returns an <tt>#inspect</tt>-like string for the value of the
288
304
  # attribute +attr_name+. String attributes are truncated up to 50
289
305
  # characters, Date and Time attributes are returned in the
290
- # <tt>:db</tt> format. Other attributes return the value of
291
- # <tt>#inspect</tt> without modification.
306
+ # <tt>:db</tt> format, Array attributes are truncated up to 10 values.
307
+ # Other attributes return the value of <tt>#inspect</tt> without
308
+ # modification.
292
309
  #
293
310
  # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
294
311
  #
@@ -299,7 +316,7 @@ module ActiveRecord
299
316
  # # => "\"2012-10-22 00:15:07\""
300
317
  #
301
318
  # person.attribute_for_inspect(:tag_ids)
302
- # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
319
+ # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]"
303
320
  def attribute_for_inspect(attr_name)
304
321
  value = read_attribute(attr_name)
305
322
 
@@ -307,6 +324,9 @@ module ActiveRecord
307
324
  "#{value[0, 50]}...".inspect
308
325
  elsif value.is_a?(Date) || value.is_a?(Time)
309
326
  %("#{value.to_s(:db)}")
327
+ elsif value.is_a?(Array) && value.size > 10
328
+ inspected = value.first(10).inspect
329
+ %(#{inspected[0...-1]}, ...])
310
330
  else
311
331
  value.inspect
312
332
  end
@@ -338,7 +358,7 @@ module ActiveRecord
338
358
  #
339
359
  # Note: +:id+ is always present.
340
360
  #
341
- # Alias for the <tt>read_attribute</tt> method.
361
+ # Alias for the #read_attribute method.
342
362
  #
343
363
  # class Person < ActiveRecord::Base
344
364
  # belongs_to :organization
@@ -356,7 +376,7 @@ module ActiveRecord
356
376
  end
357
377
 
358
378
  # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
359
- # (Alias for the protected <tt>write_attribute</tt> method).
379
+ # (Alias for the protected #write_attribute method).
360
380
  #
361
381
  # class Person < ActiveRecord::Base
362
382
  # end
@@ -364,11 +384,44 @@ module ActiveRecord
364
384
  # person = Person.new
365
385
  # person[:age] = '22'
366
386
  # person[:age] # => 22
367
- # person[:age].class # => Integer
387
+ # person[:age] # => Fixnum
368
388
  def []=(attr_name, value)
369
389
  write_attribute(attr_name, value)
370
390
  end
371
391
 
392
+ # Returns the name of all database fields which have been read from this
393
+ # model. This can be useful in development mode to determine which fields
394
+ # need to be selected. For performance critical pages, selecting only the
395
+ # required fields can be an easy performance win (assuming you aren't using
396
+ # all of the fields on the model).
397
+ #
398
+ # For example:
399
+ #
400
+ # class PostsController < ActionController::Base
401
+ # after_action :print_accessed_fields, only: :index
402
+ #
403
+ # def index
404
+ # @posts = Post.all
405
+ # end
406
+ #
407
+ # private
408
+ #
409
+ # def print_accessed_fields
410
+ # p @posts.first.accessed_fields
411
+ # end
412
+ # end
413
+ #
414
+ # Which allows you to quickly change your code to:
415
+ #
416
+ # class PostsController < ActionController::Base
417
+ # def index
418
+ # @posts = Post.select(:id, :title, :author_id, :updated_at)
419
+ # end
420
+ # end
421
+ def accessed_fields
422
+ @attributes.accessed
423
+ end
424
+
372
425
  protected
373
426
 
374
427
  def clone_attribute_value(reader_method, attribute_name) # :nodoc:
@@ -2,7 +2,7 @@ module ActiveRecord
2
2
  module AttributeMethods
3
3
  # = Active Record Attribute Methods Before Type Cast
4
4
  #
5
- # <tt>ActiveRecord::AttributeMethods::BeforeTypeCast</tt> provides a way to
5
+ # ActiveRecord::AttributeMethods::BeforeTypeCast provides a way to
6
6
  # read the value of the attributes before typecasting and deserialization.
7
7
  #
8
8
  # class Task < ActiveRecord::Base
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/module/attribute_accessors'
2
+ require 'active_record/attribute_mutation_tracker'
2
3
 
3
4
  module ActiveRecord
4
5
  module AttributeMethods
@@ -34,24 +35,43 @@ module ActiveRecord
34
35
  # <tt>reload</tt> the record and clears changed attributes.
35
36
  def reload(*)
36
37
  super.tap do
37
- clear_changes_information
38
+ @mutation_tracker = nil
39
+ @previous_mutation_tracker = nil
40
+ @changed_attributes = HashWithIndifferentAccess.new
38
41
  end
39
42
  end
40
43
 
41
44
  def initialize_dup(other) # :nodoc:
42
45
  super
43
- @original_raw_attributes = nil
44
- calculate_changes_from_defaults
46
+ @attributes = self.class._default_attributes.map do |attr|
47
+ attr.with_value_from_user(@attributes.fetch_value(attr.name))
48
+ end
49
+ @mutation_tracker = nil
45
50
  end
46
51
 
47
52
  def changes_applied
48
- super
49
- store_original_raw_attributes
53
+ @previous_mutation_tracker = mutation_tracker
54
+ @changed_attributes = HashWithIndifferentAccess.new
55
+ store_original_attributes
50
56
  end
51
57
 
52
58
  def clear_changes_information
59
+ @previous_mutation_tracker = nil
60
+ @changed_attributes = HashWithIndifferentAccess.new
61
+ store_original_attributes
62
+ end
63
+
64
+ def raw_write_attribute(attr_name, *)
65
+ result = super
66
+ clear_attribute_change(attr_name)
67
+ result
68
+ end
69
+
70
+ def clear_attribute_changes(attr_names)
53
71
  super
54
- original_raw_attributes.clear
72
+ attr_names.each do |attr_name|
73
+ clear_attribute_change(attr_name)
74
+ end
55
75
  end
56
76
 
57
77
  def changed_attributes
@@ -60,7 +80,7 @@ module ActiveRecord
60
80
  if defined?(@cached_changed_attributes)
61
81
  @cached_changed_attributes
62
82
  else
63
- super.reverse_merge(attributes_changed_in_place).freeze
83
+ super.reverse_merge(mutation_tracker.changed_values).freeze
64
84
  end
65
85
  end
66
86
 
@@ -70,59 +90,29 @@ module ActiveRecord
70
90
  end
71
91
  end
72
92
 
93
+ def previous_changes
94
+ previous_mutation_tracker.changes
95
+ end
96
+
73
97
  def attribute_changed_in_place?(attr_name)
74
- old_value = original_raw_attribute(attr_name)
75
- @attributes[attr_name].changed_in_place_from?(old_value)
98
+ mutation_tracker.changed_in_place?(attr_name)
76
99
  end
77
100
 
78
101
  private
79
102
 
80
- def changes_include?(attr_name)
81
- super || attribute_changed_in_place?(attr_name)
82
- end
83
-
84
- def calculate_changes_from_defaults
85
- @changed_attributes = nil
86
- self.class.column_defaults.each do |attr, orig_value|
87
- set_attribute_was(attr, orig_value) if _field_changed?(attr, orig_value)
103
+ def mutation_tracker
104
+ unless defined?(@mutation_tracker)
105
+ @mutation_tracker = nil
88
106
  end
107
+ @mutation_tracker ||= AttributeMutationTracker.new(@attributes)
89
108
  end
90
109
 
91
- # Wrap write_attribute to remember original attribute value.
92
- def write_attribute(attr, value)
93
- attr = attr.to_s
94
-
95
- old_value = old_attribute_value(attr)
96
-
97
- result = super
98
- store_original_raw_attribute(attr)
99
- save_changed_attribute(attr, old_value)
100
- result
101
- end
102
-
103
- def raw_write_attribute(attr, value)
104
- attr = attr.to_s
105
-
106
- result = super
107
- original_raw_attributes[attr] = value
108
- result
110
+ def changes_include?(attr_name)
111
+ super || mutation_tracker.changed?(attr_name)
109
112
  end
110
113
 
111
- def save_changed_attribute(attr, old_value)
112
- clear_changed_attributes_cache
113
- if attribute_changed_by_setter?(attr)
114
- clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
115
- else
116
- set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
117
- end
118
- end
119
-
120
- def old_attribute_value(attr)
121
- if attribute_changed?(attr)
122
- changed_attributes[attr]
123
- else
124
- clone_attribute_value(:_read_attribute, attr)
125
- end
114
+ def clear_attribute_change(attr_name)
115
+ mutation_tracker.forget_change(attr_name)
126
116
  end
127
117
 
128
118
  def _update_record(*)
@@ -133,47 +123,17 @@ module ActiveRecord
133
123
  partial_writes? ? super(keys_for_partial_write) : super
134
124
  end
135
125
 
136
- # Serialized attributes should always be written in case they've been
137
- # changed in place.
138
126
  def keys_for_partial_write
139
- changed & persistable_attribute_names
140
- end
141
-
142
- def _field_changed?(attr, old_value)
143
- @attributes[attr].changed_from?(old_value)
144
- end
145
-
146
- def attributes_changed_in_place
147
- changed_in_place.each_with_object({}) do |attr_name, h|
148
- orig = @attributes[attr_name].original_value
149
- h[attr_name] = orig
150
- end
151
- end
152
-
153
- def changed_in_place
154
- self.class.attribute_names.select do |attr_name|
155
- attribute_changed_in_place?(attr_name)
156
- end
127
+ changed & self.class.column_names
157
128
  end
158
129
 
159
- def original_raw_attribute(attr_name)
160
- original_raw_attributes.fetch(attr_name) do
161
- read_attribute_before_type_cast(attr_name)
162
- end
130
+ def store_original_attributes
131
+ @attributes = @attributes.map(&:forgetting_assignment)
132
+ @mutation_tracker = nil
163
133
  end
164
134
 
165
- def original_raw_attributes
166
- @original_raw_attributes ||= {}
167
- end
168
-
169
- def store_original_raw_attribute(attr_name)
170
- original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil
171
- end
172
-
173
- def store_original_raw_attributes
174
- attribute_names.each do |attr|
175
- store_original_raw_attribute(attr)
176
- end
135
+ def previous_mutation_tracker
136
+ @previous_mutation_tracker ||= NullMutationTracker.instance
177
137
  end
178
138
 
179
139
  def cache_changed_attributes
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  module PrimaryKey
6
6
  extend ActiveSupport::Concern
7
7
 
8
- # Returns this record's primary key value wrapped in an Array if one is
8
+ # Returns this record's primary key value wrapped in an array if one is
9
9
  # available.
10
10
  def to_key
11
11
  sync_with_transaction_state
@@ -108,7 +108,7 @@ module ActiveRecord
108
108
  # self.primary_key = 'sysid'
109
109
  # end
110
110
  #
111
- # You can also define the +primary_key+ method yourself:
111
+ # You can also define the #primary_key method yourself:
112
112
  #
113
113
  # class Project < ActiveRecord::Base
114
114
  # def self.primary_key