activerecord 3.2.19 → 5.0.0

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 (264) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1715 -604
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +40 -45
  5. data/examples/performance.rb +33 -22
  6. data/examples/simple.rb +3 -4
  7. data/lib/active_record/aggregations.rb +76 -51
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +54 -40
  10. data/lib/active_record/associations/association.rb +76 -56
  11. data/lib/active_record/associations/association_scope.rb +125 -93
  12. data/lib/active_record/associations/belongs_to_association.rb +57 -28
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +120 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +115 -62
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -53
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
  18. data/lib/active_record/associations/builder/has_many.rb +9 -65
  19. data/lib/active_record/associations/builder/has_one.rb +18 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +18 -19
  21. data/lib/active_record/associations/collection_association.rb +268 -186
  22. data/lib/active_record/associations/collection_proxy.rb +1003 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +81 -41
  25. data/lib/active_record/associations/has_many_through_association.rb +76 -55
  26. data/lib/active_record/associations/has_one_association.rb +51 -21
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +239 -155
  32. data/lib/active_record/associations/preloader/association.rb +97 -62
  33. data/lib/active_record/associations/preloader/collection_association.rb +2 -8
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +75 -33
  38. data/lib/active_record/associations/preloader.rb +111 -79
  39. data/lib/active_record/associations/singular_association.rb +35 -13
  40. data/lib/active_record/associations/through_association.rb +41 -19
  41. data/lib/active_record/associations.rb +727 -501
  42. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  43. data/lib/active_record/attribute.rb +213 -0
  44. data/lib/active_record/attribute_assignment.rb +32 -162
  45. data/lib/active_record/attribute_decorators.rb +67 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  47. data/lib/active_record/attribute_methods/dirty.rb +101 -61
  48. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  49. data/lib/active_record/attribute_methods/query.rb +7 -6
  50. data/lib/active_record/attribute_methods/read.rb +56 -117
  51. data/lib/active_record/attribute_methods/serialization.rb +43 -96
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
  53. data/lib/active_record/attribute_methods/write.rb +34 -45
  54. data/lib/active_record/attribute_methods.rb +333 -144
  55. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  56. data/lib/active_record/attribute_set/builder.rb +108 -0
  57. data/lib/active_record/attribute_set.rb +108 -0
  58. data/lib/active_record/attributes.rb +265 -0
  59. data/lib/active_record/autosave_association.rb +285 -223
  60. data/lib/active_record/base.rb +95 -490
  61. data/lib/active_record/callbacks.rb +95 -61
  62. data/lib/active_record/coders/json.rb +13 -0
  63. data/lib/active_record/coders/yaml_column.rb +28 -19
  64. data/lib/active_record/collection_cache_key.rb +40 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
  78. data/lib/active_record/connection_adapters/column.rb +30 -259
  79. data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
  80. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  81. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  82. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  83. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  84. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  89. data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
  90. data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
  91. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
  92. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
  115. data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
  116. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
  117. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
  118. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  119. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
  120. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  121. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  122. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
  123. data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
  124. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  125. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
  129. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  130. data/lib/active_record/connection_handling.rb +155 -0
  131. data/lib/active_record/core.rb +561 -0
  132. data/lib/active_record/counter_cache.rb +146 -105
  133. data/lib/active_record/dynamic_matchers.rb +101 -64
  134. data/lib/active_record/enum.rb +234 -0
  135. data/lib/active_record/errors.rb +153 -56
  136. data/lib/active_record/explain.rb +15 -63
  137. data/lib/active_record/explain_registry.rb +30 -0
  138. data/lib/active_record/explain_subscriber.rb +10 -6
  139. data/lib/active_record/fixture_set/file.rb +77 -0
  140. data/lib/active_record/fixtures.rb +355 -232
  141. data/lib/active_record/gem_version.rb +15 -0
  142. data/lib/active_record/inheritance.rb +144 -79
  143. data/lib/active_record/integration.rb +66 -13
  144. data/lib/active_record/internal_metadata.rb +56 -0
  145. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  146. data/lib/active_record/locale/en.yml +9 -1
  147. data/lib/active_record/locking/optimistic.rb +77 -56
  148. data/lib/active_record/locking/pessimistic.rb +6 -6
  149. data/lib/active_record/log_subscriber.rb +53 -28
  150. data/lib/active_record/migration/command_recorder.rb +166 -33
  151. data/lib/active_record/migration/compatibility.rb +126 -0
  152. data/lib/active_record/migration/join_table.rb +15 -0
  153. data/lib/active_record/migration.rb +792 -264
  154. data/lib/active_record/model_schema.rb +192 -130
  155. data/lib/active_record/nested_attributes.rb +238 -145
  156. data/lib/active_record/no_touching.rb +52 -0
  157. data/lib/active_record/null_relation.rb +89 -0
  158. data/lib/active_record/persistence.rb +357 -157
  159. data/lib/active_record/query_cache.rb +22 -43
  160. data/lib/active_record/querying.rb +34 -23
  161. data/lib/active_record/railtie.rb +88 -48
  162. data/lib/active_record/railties/console_sandbox.rb +3 -4
  163. data/lib/active_record/railties/controller_runtime.rb +5 -4
  164. data/lib/active_record/railties/databases.rake +170 -422
  165. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  166. data/lib/active_record/readonly_attributes.rb +2 -5
  167. data/lib/active_record/reflection.rb +715 -189
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  169. data/lib/active_record/relation/batches.rb +203 -50
  170. data/lib/active_record/relation/calculations.rb +203 -194
  171. data/lib/active_record/relation/delegation.rb +103 -25
  172. data/lib/active_record/relation/finder_methods.rb +457 -261
  173. data/lib/active_record/relation/from_clause.rb +32 -0
  174. data/lib/active_record/relation/merger.rb +167 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  179. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  180. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  181. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  182. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  183. data/lib/active_record/relation/predicate_builder.rb +153 -48
  184. data/lib/active_record/relation/query_attribute.rb +19 -0
  185. data/lib/active_record/relation/query_methods.rb +1019 -194
  186. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  187. data/lib/active_record/relation/spawn_methods.rb +46 -150
  188. data/lib/active_record/relation/where_clause.rb +174 -0
  189. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  190. data/lib/active_record/relation.rb +450 -245
  191. data/lib/active_record/result.rb +104 -12
  192. data/lib/active_record/runtime_registry.rb +22 -0
  193. data/lib/active_record/sanitization.rb +120 -94
  194. data/lib/active_record/schema.rb +28 -18
  195. data/lib/active_record/schema_dumper.rb +141 -74
  196. data/lib/active_record/schema_migration.rb +50 -0
  197. data/lib/active_record/scoping/default.rb +64 -57
  198. data/lib/active_record/scoping/named.rb +93 -108
  199. data/lib/active_record/scoping.rb +73 -121
  200. data/lib/active_record/secure_token.rb +38 -0
  201. data/lib/active_record/serialization.rb +7 -5
  202. data/lib/active_record/statement_cache.rb +113 -0
  203. data/lib/active_record/store.rb +173 -15
  204. data/lib/active_record/suppressor.rb +58 -0
  205. data/lib/active_record/table_metadata.rb +68 -0
  206. data/lib/active_record/tasks/database_tasks.rb +313 -0
  207. data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
  208. data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
  209. data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
  210. data/lib/active_record/timestamp.rb +42 -24
  211. data/lib/active_record/touch_later.rb +58 -0
  212. data/lib/active_record/transactions.rb +233 -105
  213. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  214. data/lib/active_record/type/date.rb +7 -0
  215. data/lib/active_record/type/date_time.rb +7 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  217. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  218. data/lib/active_record/type/internal/timezone.rb +15 -0
  219. data/lib/active_record/type/serialized.rb +63 -0
  220. data/lib/active_record/type/time.rb +20 -0
  221. data/lib/active_record/type/type_map.rb +64 -0
  222. data/lib/active_record/type.rb +72 -0
  223. data/lib/active_record/type_caster/connection.rb +29 -0
  224. data/lib/active_record/type_caster/map.rb +19 -0
  225. data/lib/active_record/type_caster.rb +7 -0
  226. data/lib/active_record/validations/absence.rb +23 -0
  227. data/lib/active_record/validations/associated.rb +33 -18
  228. data/lib/active_record/validations/length.rb +24 -0
  229. data/lib/active_record/validations/presence.rb +66 -0
  230. data/lib/active_record/validations/uniqueness.rb +128 -68
  231. data/lib/active_record/validations.rb +48 -40
  232. data/lib/active_record/version.rb +5 -7
  233. data/lib/active_record.rb +71 -47
  234. data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
  235. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
  236. data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
  237. data/lib/rails/generators/active_record/migration.rb +18 -8
  238. data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
  239. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  240. data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
  241. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  242. data/lib/rails/generators/active_record.rb +3 -11
  243. metadata +188 -134
  244. data/examples/associations.png +0 -0
  245. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  246. data/lib/active_record/associations/join_helper.rb +0 -55
  247. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  248. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  249. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  250. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  251. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  252. data/lib/active_record/dynamic_finder_match.rb +0 -68
  253. data/lib/active_record/dynamic_scope_match.rb +0 -23
  254. data/lib/active_record/fixtures/file.rb +0 -65
  255. data/lib/active_record/identity_map.rb +0 -162
  256. data/lib/active_record/observer.rb +0 -121
  257. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  258. data/lib/active_record/session_store.rb +0 -360
  259. data/lib/active_record/test_case.rb +0 -73
  260. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  261. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  262. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  263. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  264. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,110 +1,150 @@
1
- require 'active_support/core_ext/class/attribute'
2
- require 'active_support/core_ext/object/blank'
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+ require 'active_record/attribute_mutation_tracker'
3
3
 
4
4
  module ActiveRecord
5
5
  module AttributeMethods
6
- module Dirty
6
+ module Dirty # :nodoc:
7
7
  extend ActiveSupport::Concern
8
+
8
9
  include ActiveModel::Dirty
9
- include AttributeMethods::Write
10
10
 
11
11
  included do
12
12
  if self < ::ActiveRecord::Timestamp
13
13
  raise "You cannot include Dirty after Timestamp"
14
14
  end
15
15
 
16
- class_attribute :partial_updates
17
- self.partial_updates = true
16
+ class_attribute :partial_writes, instance_writer: false
17
+ self.partial_writes = true
18
18
  end
19
19
 
20
20
  # Attempts to +save+ the record and clears changed attributes if successful.
21
- def save(*) #:nodoc:
21
+ def save(*)
22
22
  if status = super
23
- @previously_changed = changes
24
- @changed_attributes.clear
25
- elsif IdentityMap.enabled?
26
- IdentityMap.remove(self)
23
+ changes_applied
27
24
  end
28
25
  status
29
26
  end
30
27
 
31
28
  # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
32
- def save!(*) #:nodoc:
29
+ def save!(*)
33
30
  super.tap do
34
- @previously_changed = changes
35
- @changed_attributes.clear
31
+ changes_applied
36
32
  end
37
- rescue
38
- IdentityMap.remove(self) if IdentityMap.enabled?
39
- raise
40
33
  end
41
34
 
42
35
  # <tt>reload</tt> the record and clears changed attributes.
43
- def reload(*) #:nodoc:
36
+ def reload(*)
44
37
  super.tap do
45
- @previously_changed.clear
46
- @changed_attributes.clear
38
+ @mutation_tracker = nil
39
+ @previous_mutation_tracker = nil
40
+ @changed_attributes = HashWithIndifferentAccess.new
47
41
  end
48
42
  end
49
43
 
50
- private
51
- # Wrap write_attribute to remember original attribute value.
52
- def write_attribute(attr, value)
53
- attr = attr.to_s
54
-
55
- # The attribute already has an unsaved change.
56
- if attribute_changed?(attr)
57
- old = @changed_attributes[attr]
58
- @changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
59
- else
60
- old = clone_attribute_value(:read_attribute, attr)
61
- # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
62
- old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
63
- @changed_attributes[attr] = old if _field_changed?(attr, old, value)
44
+ def initialize_dup(other) # :nodoc:
45
+ super
46
+ @attributes = self.class._default_attributes.map do |attr|
47
+ attr.with_value_from_user(@attributes.fetch_value(attr.name))
64
48
  end
49
+ @mutation_tracker = nil
50
+ end
51
+
52
+ def changes_applied
53
+ @previous_mutation_tracker = mutation_tracker
54
+ @changed_attributes = HashWithIndifferentAccess.new
55
+ store_original_attributes
56
+ end
57
+
58
+ def clear_changes_information
59
+ @previous_mutation_tracker = nil
60
+ @changed_attributes = HashWithIndifferentAccess.new
61
+ store_original_attributes
62
+ end
65
63
 
66
- # Carry on.
67
- super(attr, value)
64
+ def raw_write_attribute(attr_name, *)
65
+ result = super
66
+ clear_attribute_change(attr_name)
67
+ result
68
68
  end
69
69
 
70
- def update(*)
71
- if partial_updates?
72
- # Serialized attributes should always be written in case they've been
73
- # changed in place.
74
- super(changed | (attributes.keys & self.class.serialized_attributes.keys))
70
+ def clear_attribute_changes(attr_names)
71
+ super
72
+ attr_names.each do |attr_name|
73
+ clear_attribute_change(attr_name)
74
+ end
75
+ end
76
+
77
+ def changed_attributes
78
+ # This should only be set by methods which will call changed_attributes
79
+ # multiple times when it is known that the computed value cannot change.
80
+ if defined?(@cached_changed_attributes)
81
+ @cached_changed_attributes
75
82
  else
83
+ super.reverse_merge(mutation_tracker.changed_values).freeze
84
+ end
85
+ end
86
+
87
+ def changes
88
+ cache_changed_attributes do
76
89
  super
77
90
  end
78
91
  end
79
92
 
80
- def _field_changed?(attr, old, value)
81
- if column = column_for_attribute(attr)
82
- if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
83
- changes_from_zero_to_string?(old, value))
84
- value = nil
85
- else
86
- value = column.type_cast(value)
87
- end
93
+ def previous_changes
94
+ previous_mutation_tracker.changes
95
+ end
96
+
97
+ def attribute_changed_in_place?(attr_name)
98
+ mutation_tracker.changed_in_place?(attr_name)
99
+ end
100
+
101
+ private
102
+
103
+ def mutation_tracker
104
+ unless defined?(@mutation_tracker)
105
+ @mutation_tracker = nil
88
106
  end
107
+ @mutation_tracker ||= AttributeMutationTracker.new(@attributes)
108
+ end
109
+
110
+ def changes_include?(attr_name)
111
+ super || mutation_tracker.changed?(attr_name)
112
+ end
113
+
114
+ def clear_attribute_change(attr_name)
115
+ mutation_tracker.forget_change(attr_name)
116
+ end
117
+
118
+ def _update_record(*)
119
+ partial_writes? ? super(keys_for_partial_write) : super
120
+ end
121
+
122
+ def _create_record(*)
123
+ partial_writes? ? super(keys_for_partial_write) : super
124
+ end
125
+
126
+ def keys_for_partial_write
127
+ changed & self.class.column_names
128
+ end
89
129
 
90
- old != value
130
+ def store_original_attributes
131
+ @attributes = @attributes.map(&:forgetting_assignment)
132
+ @mutation_tracker = nil
91
133
  end
92
134
 
93
- def clone_with_time_zone_conversion_attribute?(attr, old)
94
- old.class.name == "Time" && time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
135
+ def previous_mutation_tracker
136
+ @previous_mutation_tracker ||= NullMutationTracker.instance
95
137
  end
96
138
 
97
- def changes_from_nil_to_empty_string?(column, old, value)
98
- # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
99
- # Hence we don't record it as a change if the value changes from nil to ''.
100
- # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
101
- # be typecast back to 0 (''.to_i => 0)
102
- column.null && (old.nil? || old == 0) && value.blank?
139
+ def cache_changed_attributes
140
+ @cached_changed_attributes = changed_attributes
141
+ yield
142
+ ensure
143
+ clear_changed_attributes_cache
103
144
  end
104
145
 
105
- def changes_from_zero_to_string?(old, value)
106
- # For columns with old 0 and value non-empty string
107
- old == 0 && value.is_a?(String) && value.present? && value != '0'
146
+ def clear_changed_attributes_cache
147
+ remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
108
148
  end
109
149
  end
110
150
  end
@@ -1,56 +1,81 @@
1
+ require 'set'
2
+
1
3
  module ActiveRecord
2
4
  module AttributeMethods
3
5
  module PrimaryKey
4
6
  extend ActiveSupport::Concern
5
7
 
6
- # Returns this record's primary key value wrapped in an Array if one is available
8
+ # Returns this record's primary key value wrapped in an array if one is
9
+ # available.
7
10
  def to_key
11
+ sync_with_transaction_state
8
12
  key = self.id
9
13
  [key] if key
10
14
  end
11
15
 
12
- # Returns the primary key value
16
+ # Returns the primary key value.
13
17
  def id
14
- read_attribute(self.class.primary_key)
18
+ if pk = self.class.primary_key
19
+ sync_with_transaction_state
20
+ _read_attribute(pk)
21
+ end
15
22
  end
16
23
 
17
- # Sets the primary key value
24
+ # Sets the primary key value.
18
25
  def id=(value)
19
- write_attribute(self.class.primary_key, value)
26
+ sync_with_transaction_state
27
+ write_attribute(self.class.primary_key, value) if self.class.primary_key
20
28
  end
21
29
 
22
- # Queries the primary key value
30
+ # Queries the primary key value.
23
31
  def id?
32
+ sync_with_transaction_state
24
33
  query_attribute(self.class.primary_key)
25
34
  end
26
35
 
36
+ # Returns the primary key value before type cast.
37
+ def id_before_type_cast
38
+ sync_with_transaction_state
39
+ read_attribute_before_type_cast(self.class.primary_key)
40
+ end
41
+
42
+ # Returns the primary key previous value.
43
+ def id_was
44
+ sync_with_transaction_state
45
+ attribute_was(self.class.primary_key)
46
+ end
47
+
48
+ protected
49
+
50
+ def attribute_method?(attr_name)
51
+ attr_name == 'id' || super
52
+ end
53
+
27
54
  module ClassMethods
28
55
  def define_method_attribute(attr_name)
29
56
  super
30
57
 
31
58
  if attr_name == primary_key && attr_name != 'id'
32
59
  generated_attribute_methods.send(:alias_method, :id, primary_key)
33
- generated_external_attribute_methods.module_eval <<-CODE, __FILE__, __LINE__
34
- def id(v, attributes, attributes_cache, attr_name)
35
- attr_name = '#{primary_key}'
36
- send(attr_name, attributes[attr_name], attributes, attributes_cache, attr_name)
37
- end
38
- CODE
39
60
  end
40
61
  end
41
62
 
63
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set
64
+
42
65
  def dangerous_attribute_method?(method_name)
43
- super && !['id', 'id=', 'id?'].include?(method_name)
66
+ super && !ID_ATTRIBUTE_METHODS.include?(method_name)
44
67
  end
45
68
 
46
- # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
47
- # primary_key_prefix_type setting, though.
69
+ # Defines the primary key field -- can be overridden in subclasses.
70
+ # Overwriting will negate any effect of the +primary_key_prefix_type+
71
+ # setting, though.
48
72
  def primary_key
49
73
  @primary_key = reset_primary_key unless defined? @primary_key
50
74
  @primary_key
51
75
  end
52
76
 
53
- # Returns a quoted version of the primary key name, used to construct SQL statements.
77
+ # Returns a quoted version of the primary key name, used to construct
78
+ # SQL statements.
54
79
  def quoted_primary_key
55
80
  @quoted_primary_key ||= connection.quote_column_name(primary_key)
56
81
  end
@@ -64,49 +89,38 @@ module ActiveRecord
64
89
  end
65
90
 
66
91
  def get_primary_key(base_name) #:nodoc:
67
- return 'id' unless base_name && !base_name.blank?
68
-
69
- case primary_key_prefix_type
70
- when :table_name
92
+ if base_name && primary_key_prefix_type == :table_name
71
93
  base_name.foreign_key(false)
72
- when :table_name_with_underscore
94
+ elsif base_name && primary_key_prefix_type == :table_name_with_underscore
73
95
  base_name.foreign_key
74
96
  else
75
97
  if ActiveRecord::Base != self && table_exists?
76
- connection.schema_cache.primary_keys[table_name]
98
+ connection.schema_cache.primary_keys(table_name)
77
99
  else
78
100
  'id'
79
101
  end
80
102
  end
81
103
  end
82
104
 
83
- def original_primary_key #:nodoc:
84
- deprecated_original_property_getter :primary_key
85
- end
86
-
87
105
  # Sets the name of the primary key column.
88
106
  #
89
107
  # class Project < ActiveRecord::Base
90
- # self.primary_key = "sysid"
108
+ # self.primary_key = 'sysid'
91
109
  # end
92
110
  #
93
- # You can also define the primary_key method yourself:
111
+ # You can also define the #primary_key method yourself:
94
112
  #
95
113
  # class Project < ActiveRecord::Base
96
114
  # def self.primary_key
97
- # "foo_" + super
115
+ # 'foo_' + super
98
116
  # end
99
117
  # end
118
+ #
100
119
  # Project.primary_key # => "foo_id"
101
120
  def primary_key=(value)
102
- @original_primary_key = @primary_key if defined?(@primary_key)
103
- @primary_key = value && value.to_s
104
- @quoted_primary_key = nil
105
- end
106
-
107
- def set_primary_key(value = nil, &block) #:nodoc:
108
- deprecated_property_setter :primary_key, value, block
121
+ @primary_key = value && value.to_s
109
122
  @quoted_primary_key = nil
123
+ @attributes_builder = nil
110
124
  end
111
125
  end
112
126
  end
@@ -1,5 +1,3 @@
1
- require 'active_support/core_ext/object/blank'
2
-
3
1
  module ActiveRecord
4
2
  module AttributeMethods
5
3
  module Query
@@ -10,18 +8,21 @@ module ActiveRecord
10
8
  end
11
9
 
12
10
  def query_attribute(attr_name)
13
- unless value = read_attribute(attr_name)
14
- false
11
+ value = self[attr_name]
12
+
13
+ case value
14
+ when true then true
15
+ when false, nil then false
15
16
  else
16
17
  column = self.class.columns_hash[attr_name]
17
18
  if column.nil?
18
19
  if Numeric === value || value !~ /[^0-9]/
19
20
  !value.to_i.zero?
20
21
  else
21
- return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
22
+ return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
22
23
  !value.blank?
23
24
  end
24
- elsif column.number?
25
+ elsif value.respond_to?(:zero?)
25
26
  !value.zero?
26
27
  else
27
28
  !value.blank?
@@ -3,134 +3,73 @@ module ActiveRecord
3
3
  module Read
4
4
  extend ActiveSupport::Concern
5
5
 
6
- ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
7
-
8
- included do
9
- cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
10
- self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
11
- end
12
-
13
6
  module ClassMethods
14
- # +cache_attributes+ allows you to declare which converted attribute values should
15
- # be cached. Usually caching only pays off for attributes with expensive conversion
16
- # methods, like time related columns (e.g. +created_at+, +updated_at+).
17
- def cache_attributes(*attribute_names)
18
- cached_attributes.merge attribute_names.map { |attr| attr.to_s }
19
- end
20
-
21
- # Returns the attributes which are cached. By default time related columns
22
- # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
23
- def cached_attributes
24
- @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
25
- end
26
-
27
- # Returns +true+ if the provided attribute is being cached.
28
- def cache_attribute?(attr_name)
29
- cached_attributes.include?(attr_name)
30
- end
31
-
32
- def undefine_attribute_methods
33
- generated_external_attribute_methods.module_eval do
34
- instance_methods.each { |m| undef_method(m) }
35
- end
36
-
37
- super
38
- end
39
-
40
- def type_cast_attribute(attr_name, attributes, cache = {}) #:nodoc:
41
- return unless attr_name
42
- attr_name = attr_name.to_s
43
-
44
- if generated_external_attribute_methods.method_defined?(attr_name)
45
- if attributes.has_key?(attr_name) || attr_name == 'id'
46
- generated_external_attribute_methods.send(attr_name, attributes[attr_name], attributes, cache, attr_name)
47
- end
48
- elsif !attribute_methods_generated?
49
- # If we haven't generated the caster methods yet, do that and
50
- # then try again
51
- define_attribute_methods
52
- type_cast_attribute(attr_name, attributes, cache)
53
- else
54
- # If we get here, the attribute has no associated DB column, so
55
- # just return it verbatim.
56
- attributes[attr_name]
57
- end
58
- end
59
-
60
7
  protected
61
- # We want to generate the methods via module_eval rather than define_method,
62
- # because define_method is slower on dispatch and uses more memory (because it
63
- # creates a closure).
64
- #
65
- # But sometimes the database might return columns with characters that are not
66
- # allowed in normal method names (like 'my_column(omg)'. So to work around this
67
- # we first define with the __temp__ identifier, and then use alias method to
68
- # rename it to what we want.
69
- def define_method_attribute(attr_name)
70
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
71
- def __temp__
72
- #{internal_attribute_access_code(attr_name, attribute_cast_code(attr_name))}
73
- end
74
- alias_method '#{attr_name}', :__temp__
75
- undef_method :__temp__
76
- STR
77
- end
78
-
79
- private
80
8
 
81
- def define_external_attribute_method(attr_name)
82
- generated_external_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
83
- def __temp__(v, attributes, attributes_cache, attr_name)
84
- #{external_attribute_access_code(attr_name, attribute_cast_code(attr_name))}
85
- end
86
- alias_method '#{attr_name}', :__temp__
87
- undef_method :__temp__
88
- STR
89
- end
90
-
91
- def cacheable_column?(column)
92
- attribute_types_cached_by_default.include?(column.type)
93
- end
94
-
95
- def internal_attribute_access_code(attr_name, cast_code)
96
- access_code = "(v=@attributes[attr_name]) && #{cast_code}"
97
-
98
- unless attr_name == primary_key
99
- access_code.insert(0, "missing_attribute(attr_name, caller) unless @attributes.has_key?(attr_name); ")
9
+ # We want to generate the methods via module_eval rather than
10
+ # define_method, because define_method is slower on dispatch.
11
+ # Evaluating many similar methods may use more memory as the instruction
12
+ # sequences are duplicated and cached (in MRI). define_method may
13
+ # be slower on dispatch, but if you're careful about the closure
14
+ # created, then define_method will consume much less memory.
15
+ #
16
+ # But sometimes the database might return columns with
17
+ # characters that are not allowed in normal method names (like
18
+ # 'my_column(omg)'. So to work around this we first define with
19
+ # the __temp__ identifier, and then use alias method to rename
20
+ # it to what we want.
21
+ #
22
+ # We are also defining a constant to hold the frozen string of
23
+ # the attribute name. Using a constant means that we do not have
24
+ # to allocate an object on each call to the attribute method.
25
+ # Making it frozen means that it doesn't get duped when used to
26
+ # key the @attributes in read_attribute.
27
+ def define_method_attribute(name)
28
+ safe_name = name.unpack('h*'.freeze).first
29
+ temp_method = "__temp__#{safe_name}"
30
+
31
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
32
+
33
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
34
+ def #{temp_method}
35
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
36
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
100
37
  end
38
+ STR
101
39
 
102
- if cache_attribute?(attr_name)
103
- access_code = "@attributes_cache[attr_name] ||= (#{access_code})"
104
- end
105
-
106
- "attr_name = '#{attr_name}'; #{access_code}"
107
- end
108
-
109
- def external_attribute_access_code(attr_name, cast_code)
110
- access_code = "v && #{cast_code}"
111
-
112
- if cache_attribute?(attr_name)
113
- access_code = "attributes_cache[attr_name] ||= (#{access_code})"
114
- end
115
-
116
- access_code
117
- end
118
-
119
- def attribute_cast_code(attr_name)
120
- columns_hash[attr_name].type_cast_code('v')
40
+ generated_attribute_methods.module_eval do
41
+ alias_method name, temp_method
42
+ undef_method temp_method
121
43
  end
44
+ end
122
45
  end
123
46
 
124
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
125
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
126
- def read_attribute(attr_name)
127
- self.class.type_cast_attribute(attr_name, @attributes, @attributes_cache)
47
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after
48
+ # it has been typecast (for example, "2004-12-12" in a date column is cast
49
+ # to a date object, like Date.new(2004, 12, 12)).
50
+ def read_attribute(attr_name, &block)
51
+ name = attr_name.to_s
52
+ name = self.class.primary_key if name == 'id'.freeze
53
+ _read_attribute(name, &block)
128
54
  end
129
55
 
130
- private
131
- def attribute(attribute_name)
132
- read_attribute(attribute_name)
56
+ # This method exists to avoid the expensive primary_key check internally, without
57
+ # breaking compatibility with the read_attribute API
58
+ if defined?(JRUBY_VERSION)
59
+ # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
60
+ # https://github.com/jruby/jruby/pull/2562
61
+ def _read_attribute(attr_name, &block) # :nodoc
62
+ @attributes.fetch_value(attr_name.to_s, &block)
133
63
  end
64
+ else
65
+ def _read_attribute(attr_name) # :nodoc:
66
+ @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? }
67
+ end
68
+ end
69
+
70
+ alias :attribute :_read_attribute
71
+ private :attribute
72
+
134
73
  end
135
74
  end
136
75
  end