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
@@ -0,0 +1,108 @@
1
+ require 'active_record/attribute'
2
+
3
+ module ActiveRecord
4
+ class AttributeSet # :nodoc:
5
+ class Builder # :nodoc:
6
+ attr_reader :types, :always_initialized
7
+
8
+ def initialize(types, always_initialized = nil)
9
+ @types = types
10
+ @always_initialized = always_initialized
11
+ end
12
+
13
+ def build_from_database(values = {}, additional_types = {})
14
+ if always_initialized && !values.key?(always_initialized)
15
+ values[always_initialized] = nil
16
+ end
17
+
18
+ attributes = LazyAttributeHash.new(types, values, additional_types)
19
+ AttributeSet.new(attributes)
20
+ end
21
+ end
22
+ end
23
+
24
+ class LazyAttributeHash # :nodoc:
25
+ delegate :transform_values, :each_key, to: :materialize
26
+
27
+ def initialize(types, values, additional_types)
28
+ @types = types
29
+ @values = values
30
+ @additional_types = additional_types
31
+ @materialized = false
32
+ @delegate_hash = {}
33
+ end
34
+
35
+ def key?(key)
36
+ delegate_hash.key?(key) || values.key?(key) || types.key?(key)
37
+ end
38
+
39
+ def [](key)
40
+ delegate_hash[key] || assign_default_value(key)
41
+ end
42
+
43
+ def []=(key, value)
44
+ if frozen?
45
+ raise RuntimeError, "Can't modify frozen hash"
46
+ end
47
+ delegate_hash[key] = value
48
+ end
49
+
50
+ def deep_dup
51
+ dup.tap do |copy|
52
+ copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup))
53
+ end
54
+ end
55
+
56
+ def initialize_dup(_)
57
+ @delegate_hash = Hash[delegate_hash]
58
+ super
59
+ end
60
+
61
+ def select
62
+ keys = types.keys | values.keys | delegate_hash.keys
63
+ keys.each_with_object({}) do |key, hash|
64
+ attribute = self[key]
65
+ if yield(key, attribute)
66
+ hash[key] = attribute
67
+ end
68
+ end
69
+ end
70
+
71
+ def ==(other)
72
+ if other.is_a?(LazyAttributeHash)
73
+ materialize == other.materialize
74
+ else
75
+ materialize == other
76
+ end
77
+ end
78
+
79
+ protected
80
+
81
+ attr_reader :types, :values, :additional_types, :delegate_hash
82
+
83
+ def materialize
84
+ unless @materialized
85
+ values.each_key { |key| self[key] }
86
+ types.each_key { |key| self[key] }
87
+ unless frozen?
88
+ @materialized = true
89
+ end
90
+ end
91
+ delegate_hash
92
+ end
93
+
94
+ private
95
+
96
+ def assign_default_value(name)
97
+ type = additional_types.fetch(name, types[name])
98
+ value_present = true
99
+ value = values.fetch(name) { value_present = false }
100
+
101
+ if value_present
102
+ delegate_hash[name] = Attribute.from_database(name, value, type)
103
+ elsif types.key?(name)
104
+ delegate_hash[name] = Attribute.uninitialized(name, type)
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,108 @@
1
+ require 'active_record/attribute_set/builder'
2
+
3
+ module ActiveRecord
4
+ class AttributeSet # :nodoc:
5
+ def initialize(attributes)
6
+ @attributes = attributes
7
+ end
8
+
9
+ def [](name)
10
+ attributes[name] || Attribute.null(name)
11
+ end
12
+
13
+ def []=(name, value)
14
+ attributes[name] = value
15
+ end
16
+
17
+ def values_before_type_cast
18
+ attributes.transform_values(&:value_before_type_cast)
19
+ end
20
+
21
+ def to_hash
22
+ initialized_attributes.transform_values(&:value)
23
+ end
24
+ alias_method :to_h, :to_hash
25
+
26
+ def key?(name)
27
+ attributes.key?(name) && self[name].initialized?
28
+ end
29
+
30
+ def keys
31
+ attributes.each_key.select { |name| self[name].initialized? }
32
+ end
33
+
34
+ if defined?(JRUBY_VERSION)
35
+ # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
36
+ # https://github.com/jruby/jruby/pull/2562
37
+ def fetch_value(name, &block)
38
+ self[name].value(&block)
39
+ end
40
+ else
41
+ def fetch_value(name)
42
+ self[name].value { |n| yield n if block_given? }
43
+ end
44
+ end
45
+
46
+ def write_from_database(name, value)
47
+ attributes[name] = self[name].with_value_from_database(value)
48
+ end
49
+
50
+ def write_from_user(name, value)
51
+ attributes[name] = self[name].with_value_from_user(value)
52
+ end
53
+
54
+ def write_cast_value(name, value)
55
+ attributes[name] = self[name].with_cast_value(value)
56
+ end
57
+
58
+ def freeze
59
+ @attributes.freeze
60
+ super
61
+ end
62
+
63
+ def deep_dup
64
+ dup.tap do |copy|
65
+ copy.instance_variable_set(:@attributes, attributes.deep_dup)
66
+ end
67
+ end
68
+
69
+ def initialize_dup(_)
70
+ @attributes = attributes.dup
71
+ super
72
+ end
73
+
74
+ def initialize_clone(_)
75
+ @attributes = attributes.clone
76
+ super
77
+ end
78
+
79
+ def reset(key)
80
+ if key?(key)
81
+ write_from_database(key, nil)
82
+ end
83
+ end
84
+
85
+ def accessed
86
+ attributes.select { |_, attr| attr.has_been_read? }.keys
87
+ end
88
+
89
+ def map(&block)
90
+ new_attributes = attributes.transform_values(&block)
91
+ AttributeSet.new(new_attributes)
92
+ end
93
+
94
+ def ==(other)
95
+ attributes == other.attributes
96
+ end
97
+
98
+ protected
99
+
100
+ attr_reader :attributes
101
+
102
+ private
103
+
104
+ def initialized_attributes
105
+ attributes.select { |_, attr| attr.initialized? }
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,265 @@
1
+ require 'active_record/attribute/user_provided_default'
2
+
3
+ module ActiveRecord
4
+ # See ActiveRecord::Attributes::ClassMethods for documentation
5
+ module Attributes
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false # :internal:
10
+ self.attributes_to_define_after_schema_loads = {}
11
+ end
12
+
13
+ module ClassMethods
14
+ # Defines an attribute with a type on this model. It will override the
15
+ # type of existing attributes if needed. This allows control over how
16
+ # values are converted to and from SQL when assigned to a model. It also
17
+ # changes the behavior of values passed to
18
+ # {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]. This will let you use
19
+ # your domain objects across much of Active Record, without having to
20
+ # rely on implementation details or monkey patching.
21
+ #
22
+ # +name+ The name of the methods to define attribute methods for, and the
23
+ # column which this will persist to.
24
+ #
25
+ # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
26
+ # to be used for this attribute. See the examples below for more
27
+ # information about providing custom type objects.
28
+ #
29
+ # ==== Options
30
+ #
31
+ # The following options are accepted:
32
+ #
33
+ # +default+ The default value to use when no value is provided. If this option
34
+ # is not passed, the previous default value (if any) will be used.
35
+ # Otherwise, the default will be +nil+.
36
+ #
37
+ # +array+ (PG only) specifies that the type should be an array (see the
38
+ # examples below).
39
+ #
40
+ # +range+ (PG only) specifies that the type should be a range (see the
41
+ # examples below).
42
+ #
43
+ # ==== Examples
44
+ #
45
+ # The type detected by Active Record can be overridden.
46
+ #
47
+ # # db/schema.rb
48
+ # create_table :store_listings, force: true do |t|
49
+ # t.decimal :price_in_cents
50
+ # end
51
+ #
52
+ # # app/models/store_listing.rb
53
+ # class StoreListing < ActiveRecord::Base
54
+ # end
55
+ #
56
+ # store_listing = StoreListing.new(price_in_cents: '10.1')
57
+ #
58
+ # # before
59
+ # store_listing.price_in_cents # => BigDecimal.new(10.1)
60
+ #
61
+ # class StoreListing < ActiveRecord::Base
62
+ # attribute :price_in_cents, :integer
63
+ # end
64
+ #
65
+ # # after
66
+ # store_listing.price_in_cents # => 10
67
+ #
68
+ # A default can also be provided.
69
+ #
70
+ # # db/schema.rb
71
+ # create_table :store_listings, force: true do |t|
72
+ # t.string :my_string, default: "original default"
73
+ # end
74
+ #
75
+ # StoreListing.new.my_string # => "original default"
76
+ #
77
+ # # app/models/store_listing.rb
78
+ # class StoreListing < ActiveRecord::Base
79
+ # attribute :my_string, :string, default: "new default"
80
+ # end
81
+ #
82
+ # StoreListing.new.my_string # => "new default"
83
+ #
84
+ # class Product < ActiveRecord::Base
85
+ # attribute :my_default_proc, :datetime, default: -> { Time.now }
86
+ # end
87
+ #
88
+ # Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
89
+ # sleep 1
90
+ # Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
91
+ #
92
+ # \Attributes do not need to be backed by a database column.
93
+ #
94
+ # # app/models/my_model.rb
95
+ # class MyModel < ActiveRecord::Base
96
+ # attribute :my_string, :string
97
+ # attribute :my_int_array, :integer, array: true
98
+ # attribute :my_float_range, :float, range: true
99
+ # end
100
+ #
101
+ # model = MyModel.new(
102
+ # my_string: "string",
103
+ # my_int_array: ["1", "2", "3"],
104
+ # my_float_range: "[1,3.5]",
105
+ # )
106
+ # model.attributes
107
+ # # =>
108
+ # {
109
+ # my_string: "string",
110
+ # my_int_array: [1, 2, 3],
111
+ # my_float_range: 1.0..3.5
112
+ # }
113
+ #
114
+ # ==== Creating Custom Types
115
+ #
116
+ # Users may also define their own custom types, as long as they respond
117
+ # to the methods defined on the value type. The method +deserialize+ or
118
+ # +cast+ will be called on your type object, with raw input from the
119
+ # database or from your controllers. See ActiveRecord::Type::Value for the
120
+ # expected API. It is recommended that your type objects inherit from an
121
+ # existing type, or from ActiveRecord::Type::Value
122
+ #
123
+ # class MoneyType < ActiveRecord::Type::Integer
124
+ # def cast(value)
125
+ # if !value.kind_of?(Numeric) && value.include?('$')
126
+ # price_in_dollars = value.gsub(/\$/, '').to_f
127
+ # super(price_in_dollars * 100)
128
+ # else
129
+ # super
130
+ # end
131
+ # end
132
+ # end
133
+ #
134
+ # # config/initializers/types.rb
135
+ # ActiveRecord::Type.register(:money, MoneyType)
136
+ #
137
+ # # app/models/store_listing.rb
138
+ # class StoreListing < ActiveRecord::Base
139
+ # attribute :price_in_cents, :money
140
+ # end
141
+ #
142
+ # store_listing = StoreListing.new(price_in_cents: '$10.00')
143
+ # store_listing.price_in_cents # => 1000
144
+ #
145
+ # For more details on creating custom types, see the documentation for
146
+ # ActiveRecord::Type::Value. For more details on registering your types
147
+ # to be referenced by a symbol, see ActiveRecord::Type.register. You can
148
+ # also pass a type object directly, in place of a symbol.
149
+ #
150
+ # ==== \Querying
151
+ #
152
+ # When {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where] is called, it will
153
+ # use the type defined by the model class to convert the value to SQL,
154
+ # calling +serialize+ on your type object. For example:
155
+ #
156
+ # class Money < Struct.new(:amount, :currency)
157
+ # end
158
+ #
159
+ # class MoneyType < Type::Value
160
+ # def initialize(currency_converter:)
161
+ # @currency_converter = currency_converter
162
+ # end
163
+ #
164
+ # # value will be the result of +deserialize+ or
165
+ # # +cast+. Assumed to be an instance of +Money+ in
166
+ # # this case.
167
+ # def serialize(value)
168
+ # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
169
+ # value_in_bitcoins.amount
170
+ # end
171
+ # end
172
+ #
173
+ # # config/initializers/types.rb
174
+ # ActiveRecord::Type.register(:money, MoneyType)
175
+ #
176
+ # # app/models/product.rb
177
+ # class Product < ActiveRecord::Base
178
+ # currency_converter = ConversionRatesFromTheInternet.new
179
+ # attribute :price_in_bitcoins, :money, currency_converter: currency_converter
180
+ # end
181
+ #
182
+ # Product.where(price_in_bitcoins: Money.new(5, "USD"))
183
+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230
184
+ #
185
+ # Product.where(price_in_bitcoins: Money.new(5, "GBP"))
186
+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412
187
+ #
188
+ # ==== Dirty Tracking
189
+ #
190
+ # The type of an attribute is given the opportunity to change how dirty
191
+ # tracking is performed. The methods +changed?+ and +changed_in_place?+
192
+ # will be called from ActiveModel::Dirty. See the documentation for those
193
+ # methods in ActiveRecord::Type::Value for more details.
194
+ def attribute(name, cast_type, **options)
195
+ name = name.to_s
196
+ reload_schema_from_cache
197
+
198
+ self.attributes_to_define_after_schema_loads =
199
+ attributes_to_define_after_schema_loads.merge(
200
+ name => [cast_type, options]
201
+ )
202
+ end
203
+
204
+ # This is the low level API which sits beneath +attribute+. It only
205
+ # accepts type objects, and will do its work immediately instead of
206
+ # waiting for the schema to load. Automatic schema detection and
207
+ # ClassMethods#attribute both call this under the hood. While this method
208
+ # is provided so it can be used by plugin authors, application code
209
+ # should probably use ClassMethods#attribute.
210
+ #
211
+ # +name+ The name of the attribute being defined. Expected to be a +String+.
212
+ #
213
+ # +cast_type+ The type object to use for this attribute.
214
+ #
215
+ # +default+ The default value to use when no value is provided. If this option
216
+ # is not passed, the previous default value (if any) will be used.
217
+ # Otherwise, the default will be +nil+. A proc can also be passed, and
218
+ # will be called once each time a new value is needed.
219
+ #
220
+ # +user_provided_default+ Whether the default value should be cast using
221
+ # +cast+ or +deserialize+.
222
+ def define_attribute(
223
+ name,
224
+ cast_type,
225
+ default: NO_DEFAULT_PROVIDED,
226
+ user_provided_default: true
227
+ )
228
+ attribute_types[name] = cast_type
229
+ define_default_attribute(name, default, cast_type, from_user: user_provided_default)
230
+ end
231
+
232
+ def load_schema! # :nodoc:
233
+ super
234
+ attributes_to_define_after_schema_loads.each do |name, (type, options)|
235
+ if type.is_a?(Symbol)
236
+ type = ActiveRecord::Type.lookup(type, **options.except(:default))
237
+ end
238
+
239
+ define_attribute(name, type, **options.slice(:default))
240
+ end
241
+ end
242
+
243
+ private
244
+
245
+ NO_DEFAULT_PROVIDED = Object.new # :nodoc:
246
+ private_constant :NO_DEFAULT_PROVIDED
247
+
248
+ def define_default_attribute(name, value, type, from_user:)
249
+ if value == NO_DEFAULT_PROVIDED
250
+ default_attribute = _default_attributes[name].with_type(type)
251
+ elsif from_user
252
+ default_attribute = Attribute::UserProvidedDefault.new(
253
+ name,
254
+ value,
255
+ type,
256
+ _default_attributes[name],
257
+ )
258
+ else
259
+ default_attribute = Attribute.from_database(name, value, type)
260
+ end
261
+ _default_attributes[name] = default_attribute
262
+ end
263
+ end
264
+ end
265
+ end