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,136 +1,85 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module AttributeMethods
3
5
  module Read
4
6
  extend ActiveSupport::Concern
5
7
 
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
- 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
8
+ module ClassMethods # :nodoc:
9
+ private
59
10
 
60
- 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).
11
+ # We want to generate the methods via module_eval rather than
12
+ # define_method, because define_method is slower on dispatch.
13
+ # Evaluating many similar methods may use more memory as the instruction
14
+ # sequences are duplicated and cached (in MRI). define_method may
15
+ # be slower on dispatch, but if you're careful about the closure
16
+ # created, then define_method will consume much less memory.
64
17
  #
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
18
+ # But sometimes the database might return columns with
19
+ # characters that are not allowed in normal method names (like
20
+ # 'my_column(omg)'. So to work around this we first define with
21
+ # the __temp__ identifier, and then use alias method to rename
22
+ # it to what we want.
23
+ #
24
+ # We are also defining a constant to hold the frozen string of
25
+ # the attribute name. Using a constant means that we do not have
26
+ # to allocate an object on each call to the attribute method.
27
+ # Making it frozen means that it doesn't get duped when used to
28
+ # key the @attributes in read_attribute.
29
+ def define_method_attribute(name)
30
+ safe_name = name.unpack("h*".freeze).first
31
+ temp_method = "__temp__#{safe_name}"
32
+
33
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
34
+ sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
80
35
 
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))}
36
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
37
+ def #{temp_method}
38
+ #{sync_with_transaction_state}
39
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
40
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
85
41
  end
86
- alias_method '#{attr_name}', :__temp__
87
- undef_method :__temp__
88
42
  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); ")
100
- end
101
43
 
102
- if cache_attribute?(attr_name)
103
- access_code = "@attributes_cache[attr_name] ||= (#{access_code})"
44
+ generated_attribute_methods.module_eval do
45
+ alias_method name, temp_method
46
+ undef_method temp_method
104
47
  end
105
-
106
- "attr_name = '#{attr_name}'; #{access_code}"
107
48
  end
49
+ end
108
50
 
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
51
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after
52
+ # it has been typecast (for example, "2004-12-12" in a date column is cast
53
+ # to a date object, like Date.new(2004, 12, 12)).
54
+ def read_attribute(attr_name, &block)
55
+ name = if self.class.attribute_alias?(attr_name)
56
+ self.class.attribute_alias(attr_name).to_s
57
+ else
58
+ attr_name.to_s
59
+ end
118
60
 
119
- def attribute_cast_code(attr_name)
120
- columns_hash[attr_name].type_cast_code('v')
121
- end
61
+ primary_key = self.class.primary_key
62
+ name = primary_key if name == "id".freeze && primary_key
63
+ sync_with_transaction_state if name == primary_key
64
+ _read_attribute(name, &block)
122
65
  end
123
66
 
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)
67
+ # This method exists to avoid the expensive primary_key check internally, without
68
+ # breaking compatibility with the read_attribute API
69
+ if defined?(JRUBY_VERSION)
70
+ # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
71
+ # https://github.com/jruby/jruby/pull/2562
72
+ def _read_attribute(attr_name, &block) # :nodoc:
73
+ @attributes.fetch_value(attr_name.to_s, &block)
74
+ end
75
+ else
76
+ def _read_attribute(attr_name) # :nodoc:
77
+ @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? }
78
+ end
128
79
  end
129
80
 
130
- private
131
- def attribute(attribute_name)
132
- read_attribute(attribute_name)
133
- end
81
+ alias :attribute :_read_attribute
82
+ private :attribute
134
83
  end
135
84
  end
136
85
  end
@@ -1,119 +1,89 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module AttributeMethods
3
5
  module Serialization
4
6
  extend ActiveSupport::Concern
5
7
 
6
- included do
7
- # Returns a hash of all the attributes that have been specified for serialization as
8
- # keys and their class restriction as values.
9
- class_attribute :serialized_attributes
10
- self.serialized_attributes = {}
11
- end
12
-
13
- class Attribute < Struct.new(:coder, :value, :state)
14
- def unserialized_value
15
- state == :serialized ? unserialize : value
16
- end
17
-
18
- def serialized_value
19
- state == :unserialized ? serialize : value
20
- end
21
-
22
- def unserialize
23
- self.state = :unserialized
24
- self.value = coder.load(value)
25
- end
26
-
27
- def serialize
28
- self.state = :serialized
29
- self.value = coder.dump(value)
8
+ class ColumnNotSerializableError < StandardError
9
+ def initialize(name, type)
10
+ super <<-EOS.strip_heredoc
11
+ Column `#{name}` of type #{type.class} does not support `serialize` feature.
12
+ Usually it means that you are trying to use `serialize`
13
+ on a column that already implements serialization natively.
14
+ EOS
30
15
  end
31
16
  end
32
17
 
33
18
  module ClassMethods
34
- # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
35
- # then specify the name of that attribute using this method and it will be handled automatically.
36
- # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
37
- # class on retrieval or SerializationTypeMismatch will be raised.
19
+ # If you have an attribute that needs to be saved to the database as an
20
+ # object, and retrieved as the same object, then specify the name of that
21
+ # attribute using this method and it will be handled automatically. The
22
+ # serialization is done through YAML. If +class_name+ is specified, the
23
+ # serialized object must be of that class on assignment and retrieval.
24
+ # Otherwise SerializationTypeMismatch will be raised.
25
+ #
26
+ # Empty objects as <tt>{}</tt>, in the case of +Hash+, or <tt>[]</tt>, in the case of
27
+ # +Array+, will always be persisted as null.
28
+ #
29
+ # Keep in mind that database adapters handle certain serialization tasks
30
+ # for you. For instance: +json+ and +jsonb+ types in PostgreSQL will be
31
+ # converted between JSON object/array syntax and Ruby +Hash+ or +Array+
32
+ # objects transparently. There is no need to use #serialize in this
33
+ # case.
34
+ #
35
+ # For more complex cases, such as conversion to or from your application
36
+ # domain objects, consider using the ActiveRecord::Attributes API.
38
37
  #
39
38
  # ==== Parameters
40
39
  #
41
40
  # * +attr_name+ - The field name that should be serialized.
42
- # * +class_name+ - Optional, class name that the object type should be equal to.
41
+ # * +class_name_or_coder+ - Optional, a coder object, which responds to +.load+ and +.dump+
42
+ # or a class name that the object type should be equal to.
43
43
  #
44
44
  # ==== Example
45
- # # Serialize a preferences attribute
45
+ #
46
+ # # Serialize a preferences attribute.
46
47
  # class User < ActiveRecord::Base
47
48
  # serialize :preferences
48
49
  # end
49
- def serialize(attr_name, class_name = Object)
50
- coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
51
- class_name
52
- else
53
- Coders::YAMLColumn.new(class_name)
54
- end
55
-
56
- # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
57
- # has its own hash of own serialized attributes
58
- self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
59
- end
60
-
61
- def initialize_attributes(attributes, options = {}) #:nodoc:
62
- serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
63
- super(attributes, options)
64
-
65
- serialized_attributes.each do |key, coder|
66
- if attributes.key?(key)
67
- attributes[key] = Attribute.new(coder, attributes[key], serialized)
68
- end
69
- end
70
-
71
- attributes
72
- end
73
-
74
- private
75
-
76
- def attribute_cast_code(attr_name)
77
- if serialized_attributes.include?(attr_name)
78
- "v.unserialized_value"
50
+ #
51
+ # # Serialize preferences using JSON as coder.
52
+ # class User < ActiveRecord::Base
53
+ # serialize :preferences, JSON
54
+ # end
55
+ #
56
+ # # Serialize preferences as Hash using YAML coder.
57
+ # class User < ActiveRecord::Base
58
+ # serialize :preferences, Hash
59
+ # end
60
+ def serialize(attr_name, class_name_or_coder = Object)
61
+ # When ::JSON is used, force it to go through the Active Support JSON encoder
62
+ # to ensure special objects (e.g. Active Record models) are dumped correctly
63
+ # using the #as_json hook.
64
+ coder = if class_name_or_coder == ::JSON
65
+ Coders::JSON
66
+ elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
67
+ class_name_or_coder
79
68
  else
80
- super
69
+ Coders::YAMLColumn.new(attr_name, class_name_or_coder)
81
70
  end
82
- end
83
- end
84
71
 
85
- def type_cast_attribute_for_write(column, value)
86
- if column && coder = self.class.serialized_attributes[column.name]
87
- Attribute.new(coder, value, :unserialized)
88
- else
89
- super
90
- end
91
- end
72
+ decorate_attribute_type(attr_name, :serialize) do |type|
73
+ if type_incompatible_with_serialize?(type, class_name_or_coder)
74
+ raise ColumnNotSerializableError.new(attr_name, type)
75
+ end
92
76
 
93
- def _field_changed?(attr, old, value)
94
- if self.class.serialized_attributes.include?(attr)
95
- old != value
96
- else
97
- super
77
+ Type::Serialized.new(type, coder)
78
+ end
98
79
  end
99
- end
100
80
 
101
- def read_attribute_before_type_cast(attr_name)
102
- if serialized_attributes.include?(attr_name)
103
- super.unserialized_value
104
- else
105
- super
106
- end
107
- end
81
+ private
108
82
 
109
- def attributes_before_type_cast
110
- super.dup.tap do |attributes|
111
- self.class.serialized_attributes.each_key do |key|
112
- if attributes.key?(key)
113
- attributes[key] = attributes[key].unserialized_value
114
- end
83
+ def type_incompatible_with_serialize?(type, class_name)
84
+ type.is_a?(ActiveRecord::Type::Json) && class_name == ::JSON ||
85
+ type.respond_to?(:type_cast_array, true) && class_name == ::Array
115
86
  end
116
- end
117
87
  end
118
88
  end
119
89
  end
@@ -1,63 +1,89 @@
1
- require 'active_support/core_ext/class/attribute'
2
- require 'active_support/core_ext/object/inclusion'
1
+ # frozen_string_literal: true
3
2
 
4
3
  module ActiveRecord
5
4
  module AttributeMethods
6
5
  module TimeZoneConversion
7
- extend ActiveSupport::Concern
6
+ class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc:
7
+ def deserialize(value)
8
+ convert_time_to_time_zone(super)
9
+ end
8
10
 
9
- included do
10
- cattr_accessor :time_zone_aware_attributes, :instance_writer => false
11
- self.time_zone_aware_attributes = false
11
+ def cast(value)
12
+ return if value.nil?
12
13
 
13
- class_attribute :skip_time_zone_conversion_for_attributes, :instance_writer => false
14
- self.skip_time_zone_conversion_for_attributes = []
15
- end
14
+ if value.is_a?(Hash)
15
+ set_time_zone_without_conversion(super)
16
+ elsif value.respond_to?(:in_time_zone)
17
+ begin
18
+ super(user_input_in_time_zone(value)) || super
19
+ rescue ArgumentError
20
+ nil
21
+ end
22
+ else
23
+ map_avoiding_infinite_recursion(super) { |v| cast(v) }
24
+ end
25
+ end
16
26
 
17
- module ClassMethods
18
- protected
19
- # The enhanced read method automatically converts the UTC time stored in the database to the time
20
- # zone stored in Time.zone.
21
- def attribute_cast_code(attr_name)
22
- column = columns_hash[attr_name]
27
+ private
23
28
 
24
- if create_time_zone_conversion_attribute?(attr_name, column)
25
- typecast = "v = #{super}"
26
- time_zone_conversion = "v.acts_like?(:time) ? v.in_time_zone : v"
29
+ def convert_time_to_time_zone(value)
30
+ return if value.nil?
27
31
 
28
- "((#{typecast}) && (#{time_zone_conversion}))"
32
+ if value.acts_like?(:time)
33
+ value.in_time_zone
34
+ elsif value.is_a?(::Float)
35
+ value
29
36
  else
30
- super
37
+ map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
31
38
  end
32
39
  end
33
40
 
34
- # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
35
- # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
36
- def define_method_attribute=(attr_name)
37
- if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
38
- method_body, line = <<-EOV, __LINE__ + 1
39
- def #{attr_name}=(original_time)
40
- original_time = nil if original_time.blank?
41
- time = original_time
42
- unless time.acts_like?(:time)
43
- time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
44
- end
45
- time = time.in_time_zone rescue nil if time
46
- previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
47
- write_attribute(:#{attr_name}, original_time)
48
- #{attr_name}_will_change! if previous_time != time
49
- @attributes_cache["#{attr_name}"] = time
50
- end
51
- EOV
52
- generated_attribute_methods.module_eval(method_body, __FILE__, line)
53
- else
54
- super
41
+ def set_time_zone_without_conversion(value)
42
+ ::Time.zone.local_to_utc(value).try(:in_time_zone) if value
43
+ end
44
+
45
+ def map_avoiding_infinite_recursion(value)
46
+ map(value) do |v|
47
+ if value.equal?(v)
48
+ nil
49
+ else
50
+ yield(v)
51
+ end
55
52
  end
56
53
  end
54
+ end
57
55
 
56
+ extend ActiveSupport::Concern
57
+
58
+ included do
59
+ mattr_accessor :time_zone_aware_attributes, instance_writer: false, default: false
60
+
61
+ class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false, default: []
62
+ class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ]
63
+ end
64
+
65
+ module ClassMethods # :nodoc:
58
66
  private
59
- def create_time_zone_conversion_attribute?(name, column)
60
- time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && column.type.in?([:datetime, :timestamp])
67
+
68
+ def inherited(subclass)
69
+ super
70
+ # We need to apply this decorator here, rather than on module inclusion. The closure
71
+ # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
72
+ # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or
73
+ # `skip_time_zone_conversion_for_attributes` would not be picked up.
74
+ subclass.class_eval do
75
+ matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
76
+ decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type|
77
+ TimeZoneConverter.new(type)
78
+ end
79
+ end
80
+ end
81
+
82
+ def create_time_zone_conversion_attribute?(name, cast_type)
83
+ enabled_for_column = time_zone_aware_attributes &&
84
+ !skip_time_zone_conversion_for_attributes.include?(name.to_sym)
85
+
86
+ enabled_for_column && time_zone_aware_types.include?(cast_type.type)
61
87
  end
62
88
  end
63
89
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module AttributeMethods
3
5
  module Write
@@ -7,63 +9,59 @@ module ActiveRecord
7
9
  attribute_method_suffix "="
8
10
  end
9
11
 
10
- module ClassMethods
11
- protected
12
- def define_method_attribute=(attr_name)
13
- if attr_name =~ ActiveModel::AttributeMethods::NAME_COMPILABLE_REGEXP
14
- generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
15
- else
16
- generated_attribute_methods.send(:define_method, "#{attr_name}=") do |new_value|
17
- write_attribute(attr_name, new_value)
12
+ module ClassMethods # :nodoc:
13
+ private
14
+
15
+ def define_method_attribute=(name)
16
+ safe_name = name.unpack("h*".freeze).first
17
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
18
+ sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
19
+
20
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
21
+ def __temp__#{safe_name}=(value)
22
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
23
+ #{sync_with_transaction_state}
24
+ _write_attribute(name, value)
18
25
  end
19
- end
26
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
27
+ undef_method :__temp__#{safe_name}=
28
+ STR
20
29
  end
21
30
  end
22
31
 
23
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings
24
- # for fixnum and float columns are turned into +nil+.
32
+ # Updates the attribute identified by <tt>attr_name</tt> with the
33
+ # specified +value+. Empty strings for Integer and Float columns are
34
+ # turned into +nil+.
25
35
  def write_attribute(attr_name, value)
26
- attr_name = attr_name.to_s
27
- attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
28
- @attributes_cache.delete(attr_name)
29
- column = column_for_attribute(attr_name)
30
-
31
- unless column || @attributes.has_key?(attr_name)
32
- ActiveSupport::Deprecation.warn(
33
- "You're trying to create an attribute `#{attr_name}'. Writing arbitrary " \
34
- "attributes on a model is deprecated. Please just use `attr_writer` etc."
35
- )
36
+ name = if self.class.attribute_alias?(attr_name)
37
+ self.class.attribute_alias(attr_name).to_s
38
+ else
39
+ attr_name.to_s
36
40
  end
37
41
 
38
- @attributes[attr_name] = type_cast_attribute_for_write(column, value)
42
+ primary_key = self.class.primary_key
43
+ name = primary_key if name == "id".freeze && primary_key
44
+ sync_with_transaction_state if name == primary_key
45
+ _write_attribute(name, value)
39
46
  end
40
- alias_method :raw_write_attribute, :write_attribute
41
47
 
42
- private
43
- # Handle *= for method_missing.
44
- def attribute=(attribute_name, value)
45
- write_attribute(attribute_name, value)
46
- end
48
+ # This method exists to avoid the expensive primary_key check internally, without
49
+ # breaking compatibility with the write_attribute API
50
+ def _write_attribute(attr_name, value) # :nodoc:
51
+ @attributes.write_from_user(attr_name.to_s, value)
52
+ value
53
+ end
47
54
 
48
- def type_cast_attribute_for_write(column, value)
49
- if column && column.number?
50
- convert_number_column_value(value)
51
- else
52
- value
53
- end
55
+ private
56
+ def write_attribute_without_type_cast(attr_name, value)
57
+ name = attr_name.to_s
58
+ @attributes.write_cast_value(name, value)
59
+ value
54
60
  end
55
61
 
56
- def convert_number_column_value(value)
57
- case value
58
- when FalseClass
59
- 0
60
- when TrueClass
61
- 1
62
- when String
63
- value.presence
64
- else
65
- value
66
- end
62
+ # Handle *= for method_missing.
63
+ def attribute=(attribute_name, value)
64
+ _write_attribute(attribute_name, value)
67
65
  end
68
66
  end
69
67
  end