activerecord 3.2.22.5 → 5.2.8

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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,111 +1,150 @@
1
- require 'active_support/core_ext/class/attribute'
2
- require 'active_support/core_ext/object/blank'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/attribute_accessors"
3
4
 
4
5
  module ActiveRecord
5
6
  module AttributeMethods
6
7
  module Dirty
7
8
  extend ActiveSupport::Concern
9
+
8
10
  include ActiveModel::Dirty
9
- include AttributeMethods::Write
10
11
 
11
12
  included do
12
13
  if self < ::ActiveRecord::Timestamp
13
14
  raise "You cannot include Dirty after Timestamp"
14
15
  end
15
16
 
16
- class_attribute :partial_updates
17
- self.partial_updates = true
18
- end
17
+ class_attribute :partial_writes, instance_writer: false, default: true
19
18
 
20
- # Attempts to +save+ the record and clears changed attributes if successful.
21
- def save(*) #:nodoc:
22
- if status = super
23
- @previously_changed = changes
24
- @changed_attributes.clear
25
- elsif IdentityMap.enabled?
26
- IdentityMap.remove(self)
27
- end
28
- status
29
- end
19
+ # Attribute methods for "changed in last call to save?"
20
+ attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
21
+ attribute_method_prefix("saved_change_to_")
22
+ attribute_method_suffix("_before_last_save")
30
23
 
31
- # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
32
- def save!(*) #:nodoc:
33
- super.tap do
34
- @previously_changed = changes
35
- @changed_attributes.clear
36
- end
37
- rescue
38
- IdentityMap.remove(self) if IdentityMap.enabled?
39
- raise
24
+ # Attribute methods for "will change if I call save?"
25
+ attribute_method_affix(prefix: "will_save_change_to_", suffix: "?")
26
+ attribute_method_suffix("_change_to_be_saved", "_in_database")
40
27
  end
41
28
 
42
29
  # <tt>reload</tt> the record and clears changed attributes.
43
- def reload(*) #:nodoc:
30
+ def reload(*)
44
31
  super.tap do
45
- @previously_changed.clear
46
- @changed_attributes.clear
32
+ @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
33
+ @mutations_before_last_save = nil
34
+ @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
35
+ @mutations_from_database = nil
47
36
  end
48
37
  end
49
38
 
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)
64
- end
39
+ # Did this attribute change when we last saved? This method can be invoked
40
+ # as +saved_change_to_name?+ instead of <tt>saved_change_to_attribute?("name")</tt>.
41
+ # Behaves similarly to +attribute_changed?+. This method is useful in
42
+ # after callbacks to determine if the call to save changed a certain
43
+ # attribute.
44
+ #
45
+ # ==== Options
46
+ #
47
+ # +from+ When passed, this method will return false unless the original
48
+ # value is equal to the given option
49
+ #
50
+ # +to+ When passed, this method will return false unless the value was
51
+ # changed to the given value
52
+ def saved_change_to_attribute?(attr_name, **options)
53
+ mutations_before_last_save.changed?(attr_name, **options)
54
+ end
65
55
 
66
- # Carry on.
67
- super(attr, value)
56
+ # Returns the change to an attribute during the last save. If the
57
+ # attribute was changed, the result will be an array containing the
58
+ # original value and the saved value.
59
+ #
60
+ # Behaves similarly to +attribute_change+. This method is useful in after
61
+ # callbacks, to see the change in an attribute that just occurred
62
+ #
63
+ # This method can be invoked as +saved_change_to_name+ in instead of
64
+ # <tt>saved_change_to_attribute("name")</tt>
65
+ def saved_change_to_attribute(attr_name)
66
+ mutations_before_last_save.change_to_attribute(attr_name)
68
67
  end
69
68
 
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))
75
- else
76
- super
77
- end
69
+ # Returns the original value of an attribute before the last save.
70
+ # Behaves similarly to +attribute_was+. This method is useful in after
71
+ # callbacks to get the original value of an attribute before the save that
72
+ # just occurred
73
+ def attribute_before_last_save(attr_name)
74
+ mutations_before_last_save.original_value(attr_name)
78
75
  end
79
76
 
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
88
- end
77
+ # Did the last call to +save+ have any changes to change?
78
+ def saved_changes?
79
+ mutations_before_last_save.any_changes?
80
+ end
81
+
82
+ # Returns a hash containing all the changes that were just saved.
83
+ def saved_changes
84
+ mutations_before_last_save.changes
85
+ end
89
86
 
90
- old != value
87
+ # Alias for +attribute_changed?+
88
+ def will_save_change_to_attribute?(attr_name, **options)
89
+ mutations_from_database.changed?(attr_name, **options)
91
90
  end
92
91
 
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)
92
+ # Alias for +attribute_change+
93
+ def attribute_change_to_be_saved(attr_name)
94
+ mutations_from_database.change_to_attribute(attr_name)
95
95
  end
96
96
 
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?
97
+ # Alias for +attribute_was+
98
+ def attribute_in_database(attr_name)
99
+ mutations_from_database.original_value(attr_name)
103
100
  end
104
101
 
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'
102
+ # Alias for +changed?+
103
+ def has_changes_to_save?
104
+ mutations_from_database.any_changes?
108
105
  end
106
+
107
+ # Alias for +changes+
108
+ def changes_to_save
109
+ mutations_from_database.changes
110
+ end
111
+
112
+ # Alias for +changed+
113
+ def changed_attribute_names_to_save
114
+ mutations_from_database.changed_attribute_names
115
+ end
116
+
117
+ # Alias for +changed_attributes+
118
+ def attributes_in_database
119
+ mutations_from_database.changed_values
120
+ end
121
+
122
+ private
123
+ def write_attribute_without_type_cast(attr_name, value)
124
+ name = attr_name.to_s
125
+ if self.class.attribute_alias?(name)
126
+ name = self.class.attribute_alias(name)
127
+ end
128
+ result = super(name, value)
129
+ clear_attribute_change(name)
130
+ result
131
+ end
132
+
133
+ def _update_record(*)
134
+ affected_rows = partial_writes? ? super(keys_for_partial_write) : super
135
+ changes_applied
136
+ affected_rows
137
+ end
138
+
139
+ def _create_record(*)
140
+ id = partial_writes? ? super(keys_for_partial_write) : super
141
+ changes_applied
142
+ id
143
+ end
144
+
145
+ def keys_for_partial_write
146
+ changed_attribute_names_to_save & self.class.column_names
147
+ end
109
148
  end
110
149
  end
111
150
  end
@@ -1,114 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
1
5
  module ActiveRecord
2
6
  module AttributeMethods
3
7
  module PrimaryKey
4
8
  extend ActiveSupport::Concern
5
9
 
6
- # Returns this record's primary key value wrapped in an Array if one is available
10
+ # Returns this record's primary key value wrapped in an array if one is
11
+ # available.
7
12
  def to_key
8
- key = self.id
13
+ key = id
9
14
  [key] if key
10
15
  end
11
16
 
12
- # Returns the primary key value
17
+ # Returns the primary key value.
13
18
  def id
14
- read_attribute(self.class.primary_key)
19
+ sync_with_transaction_state
20
+ primary_key = self.class.primary_key
21
+ _read_attribute(primary_key) if primary_key
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
+ primary_key = self.class.primary_key
28
+ _write_attribute(primary_key, value) if primary_key
20
29
  end
21
30
 
22
- # Queries the primary key value
31
+ # Queries the primary key value.
23
32
  def id?
33
+ sync_with_transaction_state
24
34
  query_attribute(self.class.primary_key)
25
35
  end
26
36
 
27
- module ClassMethods
28
- def define_method_attribute(attr_name)
29
- super
37
+ # Returns the primary key value before type cast.
38
+ def id_before_type_cast
39
+ sync_with_transaction_state
40
+ read_attribute_before_type_cast(self.class.primary_key)
41
+ end
30
42
 
31
- if attr_name == primary_key && attr_name != 'id'
32
- 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
- end
40
- end
43
+ # Returns the primary key previous value.
44
+ def id_was
45
+ sync_with_transaction_state
46
+ attribute_was(self.class.primary_key)
47
+ end
41
48
 
42
- def dangerous_attribute_method?(method_name)
43
- super && !['id', 'id=', 'id?'].include?(method_name)
44
- end
49
+ def id_in_database
50
+ sync_with_transaction_state
51
+ attribute_in_database(self.class.primary_key)
52
+ end
45
53
 
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.
48
- def primary_key
49
- @primary_key = reset_primary_key unless defined? @primary_key
50
- @primary_key
51
- end
54
+ private
52
55
 
53
- # Returns a quoted version of the primary key name, used to construct SQL statements.
54
- def quoted_primary_key
55
- @quoted_primary_key ||= connection.quote_column_name(primary_key)
56
+ def attribute_method?(attr_name)
57
+ attr_name == "id" || super
56
58
  end
57
59
 
58
- def reset_primary_key #:nodoc:
59
- if self == base_class
60
- self.primary_key = get_primary_key(base_class.name)
61
- else
62
- self.primary_key = base_class.primary_key
60
+ module ClassMethods
61
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set
62
+
63
+ def instance_method_already_implemented?(method_name)
64
+ super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
65
+ end
66
+
67
+ def dangerous_attribute_method?(method_name)
68
+ super && !ID_ATTRIBUTE_METHODS.include?(method_name)
69
+ end
70
+
71
+ # Defines the primary key field -- can be overridden in subclasses.
72
+ # Overwriting will negate any effect of the +primary_key_prefix_type+
73
+ # setting, though.
74
+ def primary_key
75
+ @primary_key = reset_primary_key unless defined? @primary_key
76
+ @primary_key
63
77
  end
64
- end
65
78
 
66
- 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
71
- base_name.foreign_key(false)
72
- when :table_name_with_underscore
73
- base_name.foreign_key
74
- else
75
- if ActiveRecord::Base != self && table_exists?
76
- connection.schema_cache.primary_keys[table_name]
79
+ # Returns a quoted version of the primary key name, used to construct
80
+ # SQL statements.
81
+ def quoted_primary_key
82
+ @quoted_primary_key ||= connection.quote_column_name(primary_key)
83
+ end
84
+
85
+ def reset_primary_key #:nodoc:
86
+ if self == base_class
87
+ self.primary_key = get_primary_key(base_class.name)
77
88
  else
78
- 'id'
89
+ self.primary_key = base_class.primary_key
79
90
  end
80
91
  end
81
- end
82
92
 
83
- def original_primary_key #:nodoc:
84
- deprecated_original_property_getter :primary_key
85
- end
93
+ def get_primary_key(base_name) #:nodoc:
94
+ if base_name && primary_key_prefix_type == :table_name
95
+ base_name.foreign_key(false)
96
+ elsif base_name && primary_key_prefix_type == :table_name_with_underscore
97
+ base_name.foreign_key
98
+ else
99
+ if ActiveRecord::Base != self && table_exists?
100
+ pk = connection.schema_cache.primary_keys(table_name)
101
+ suppress_composite_primary_key(pk)
102
+ else
103
+ "id"
104
+ end
105
+ end
106
+ end
86
107
 
87
- # Sets the name of the primary key column.
88
- #
89
- # class Project < ActiveRecord::Base
90
- # self.primary_key = "sysid"
91
- # end
92
- #
93
- # You can also define the primary_key method yourself:
94
- #
95
- # class Project < ActiveRecord::Base
96
- # def self.primary_key
97
- # "foo_" + super
98
- # end
99
- # end
100
- # Project.primary_key # => "foo_id"
101
- 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
108
+ # Sets the name of the primary key column.
109
+ #
110
+ # class Project < ActiveRecord::Base
111
+ # self.primary_key = 'sysid'
112
+ # end
113
+ #
114
+ # You can also define the #primary_key method yourself:
115
+ #
116
+ # class Project < ActiveRecord::Base
117
+ # def self.primary_key
118
+ # 'foo_' + super
119
+ # end
120
+ # end
121
+ #
122
+ # Project.primary_key # => "foo_id"
123
+ def primary_key=(value)
124
+ @primary_key = value && value.to_s
125
+ @quoted_primary_key = nil
126
+ @attributes_builder = nil
127
+ end
128
+
129
+ private
130
+
131
+ def suppress_composite_primary_key(pk)
132
+ return pk unless pk.is_a?(Array)
106
133
 
107
- def set_primary_key(value = nil, &block) #:nodoc:
108
- deprecated_property_setter :primary_key, value, block
109
- @quoted_primary_key = nil
134
+ warn <<-WARNING.strip_heredoc
135
+ WARNING: Active Record does not support composite primary key.
136
+
137
+ #{table_name} has composite primary key. Composite primary key is ignored.
138
+ WARNING
139
+ end
110
140
  end
111
- end
112
141
  end
113
142
  end
114
143
  end
@@ -1,4 +1,4 @@
1
- require 'active_support/core_ext/object/blank'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
4
  module AttributeMethods
@@ -10,18 +10,21 @@ module ActiveRecord
10
10
  end
11
11
 
12
12
  def query_attribute(attr_name)
13
- unless value = read_attribute(attr_name)
14
- false
13
+ value = self[attr_name]
14
+
15
+ case value
16
+ when true then true
17
+ when false, nil then false
15
18
  else
16
19
  column = self.class.columns_hash[attr_name]
17
20
  if column.nil?
18
21
  if Numeric === value || value !~ /[^0-9]/
19
22
  !value.to_i.zero?
20
23
  else
21
- return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
24
+ return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
22
25
  !value.blank?
23
26
  end
24
- elsif column.number?
27
+ elsif value.respond_to?(:zero?)
25
28
  !value.zero?
26
29
  else
27
30
  !value.blank?