activerecord 5.0.7.2 → 6.1.1

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 (363) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +829 -2015
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +11 -9
  5. data/examples/performance.rb +31 -29
  6. data/examples/simple.rb +5 -3
  7. data/lib/active_record.rb +37 -29
  8. data/lib/active_record/aggregations.rb +249 -247
  9. data/lib/active_record/association_relation.rb +30 -18
  10. data/lib/active_record/associations.rb +1714 -1596
  11. data/lib/active_record/associations/alias_tracker.rb +36 -42
  12. data/lib/active_record/associations/association.rb +143 -68
  13. data/lib/active_record/associations/association_scope.rb +98 -94
  14. data/lib/active_record/associations/belongs_to_association.rb +76 -46
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
  16. data/lib/active_record/associations/builder/association.rb +27 -28
  17. data/lib/active_record/associations/builder/belongs_to.rb +52 -60
  18. data/lib/active_record/associations/builder/collection_association.rb +12 -22
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +40 -62
  20. data/lib/active_record/associations/builder/has_many.rb +10 -2
  21. data/lib/active_record/associations/builder/has_one.rb +35 -2
  22. data/lib/active_record/associations/builder/singular_association.rb +5 -1
  23. data/lib/active_record/associations/collection_association.rb +104 -259
  24. data/lib/active_record/associations/collection_proxy.rb +169 -125
  25. data/lib/active_record/associations/foreign_association.rb +22 -0
  26. data/lib/active_record/associations/has_many_association.rb +46 -31
  27. data/lib/active_record/associations/has_many_through_association.rb +66 -46
  28. data/lib/active_record/associations/has_one_association.rb +71 -52
  29. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  30. data/lib/active_record/associations/join_dependency.rb +169 -180
  31. data/lib/active_record/associations/join_dependency/join_association.rb +53 -79
  32. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  33. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  34. data/lib/active_record/associations/preloader.rb +97 -104
  35. data/lib/active_record/associations/preloader/association.rb +109 -97
  36. data/lib/active_record/associations/preloader/through_association.rb +77 -76
  37. data/lib/active_record/associations/singular_association.rb +12 -45
  38. data/lib/active_record/associations/through_association.rb +27 -15
  39. data/lib/active_record/attribute_assignment.rb +55 -60
  40. data/lib/active_record/attribute_methods.rb +111 -141
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -9
  42. data/lib/active_record/attribute_methods/dirty.rb +172 -112
  43. data/lib/active_record/attribute_methods/primary_key.rb +88 -91
  44. data/lib/active_record/attribute_methods/query.rb +6 -8
  45. data/lib/active_record/attribute_methods/read.rb +18 -50
  46. data/lib/active_record/attribute_methods/serialization.rb +38 -10
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -66
  48. data/lib/active_record/attribute_methods/write.rb +25 -32
  49. data/lib/active_record/attributes.rb +69 -31
  50. data/lib/active_record/autosave_association.rb +102 -66
  51. data/lib/active_record/base.rb +16 -25
  52. data/lib/active_record/callbacks.rb +202 -43
  53. data/lib/active_record/coders/json.rb +2 -0
  54. data/lib/active_record/coders/yaml_column.rb +11 -12
  55. data/lib/active_record/connection_adapters.rb +50 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +661 -375
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +14 -38
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +269 -105
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +54 -35
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +137 -93
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +155 -113
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -162
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +68 -80
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +591 -259
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +229 -91
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +392 -244
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +457 -582
  69. data/lib/active_record/connection_adapters/column.rb +55 -13
  70. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  71. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +8 -31
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +135 -49
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +24 -23
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -20
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +79 -49
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +66 -56
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +70 -36
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +20 -12
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +74 -37
  82. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  83. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/column.rb +39 -28
  85. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +70 -101
  86. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +5 -3
  87. data/lib/active_record/connection_adapters/postgresql/oid.rb +26 -21
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +22 -11
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +6 -5
  90. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -6
  93. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -4
  95. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +19 -18
  98. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -5
  104. data/lib/active_record/connection_adapters/postgresql/oid/{json.rb → oid.rb} +6 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +30 -9
  106. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -30
  107. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  108. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  109. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
  110. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +98 -38
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +21 -27
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +147 -105
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +34 -32
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +426 -324
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +32 -23
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -6
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +418 -293
  121. data/lib/active_record/connection_adapters/schema_cache.rb +135 -18
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +22 -7
  123. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  124. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +3 -1
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +72 -18
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -6
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +170 -0
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +282 -290
  131. data/lib/active_record/connection_adapters/statement_pool.rb +9 -8
  132. data/lib/active_record/connection_handling.rb +287 -45
  133. data/lib/active_record/core.rb +385 -181
  134. data/lib/active_record/counter_cache.rb +60 -28
  135. data/lib/active_record/database_configurations.rb +272 -0
  136. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  137. data/lib/active_record/database_configurations/database_config.rb +80 -0
  138. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  139. data/lib/active_record/database_configurations/url_config.rb +53 -0
  140. data/lib/active_record/delegated_type.rb +209 -0
  141. data/lib/active_record/destroy_association_async_job.rb +36 -0
  142. data/lib/active_record/dynamic_matchers.rb +87 -87
  143. data/lib/active_record/enum.rb +122 -47
  144. data/lib/active_record/errors.rb +153 -22
  145. data/lib/active_record/explain.rb +13 -8
  146. data/lib/active_record/explain_registry.rb +3 -1
  147. data/lib/active_record/explain_subscriber.rb +9 -4
  148. data/lib/active_record/fixture_set/file.rb +20 -22
  149. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  150. data/lib/active_record/fixture_set/render_context.rb +17 -0
  151. data/lib/active_record/fixture_set/table_row.rb +152 -0
  152. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  153. data/lib/active_record/fixtures.rb +246 -507
  154. data/lib/active_record/gem_version.rb +6 -4
  155. data/lib/active_record/inheritance.rb +168 -95
  156. data/lib/active_record/insert_all.rb +208 -0
  157. data/lib/active_record/integration.rb +114 -25
  158. data/lib/active_record/internal_metadata.rb +30 -24
  159. data/lib/active_record/legacy_yaml_adapter.rb +11 -5
  160. data/lib/active_record/locking/optimistic.rb +81 -85
  161. data/lib/active_record/locking/pessimistic.rb +22 -6
  162. data/lib/active_record/log_subscriber.rb +68 -31
  163. data/lib/active_record/middleware/database_selector.rb +77 -0
  164. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  165. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  166. data/lib/active_record/migration.rb +439 -342
  167. data/lib/active_record/migration/command_recorder.rb +152 -98
  168. data/lib/active_record/migration/compatibility.rb +229 -60
  169. data/lib/active_record/migration/join_table.rb +8 -7
  170. data/lib/active_record/model_schema.rb +230 -122
  171. data/lib/active_record/nested_attributes.rb +213 -203
  172. data/lib/active_record/no_touching.rb +11 -2
  173. data/lib/active_record/null_relation.rb +12 -34
  174. data/lib/active_record/persistence.rb +471 -97
  175. data/lib/active_record/query_cache.rb +23 -12
  176. data/lib/active_record/querying.rb +43 -25
  177. data/lib/active_record/railtie.rb +155 -43
  178. data/lib/active_record/railties/console_sandbox.rb +2 -0
  179. data/lib/active_record/railties/controller_runtime.rb +34 -33
  180. data/lib/active_record/railties/databases.rake +507 -195
  181. data/lib/active_record/readonly_attributes.rb +9 -4
  182. data/lib/active_record/reflection.rb +245 -269
  183. data/lib/active_record/relation.rb +475 -324
  184. data/lib/active_record/relation/batches.rb +125 -72
  185. data/lib/active_record/relation/batches/batch_enumerator.rb +28 -10
  186. data/lib/active_record/relation/calculations.rb +267 -171
  187. data/lib/active_record/relation/delegation.rb +73 -69
  188. data/lib/active_record/relation/finder_methods.rb +238 -248
  189. data/lib/active_record/relation/from_clause.rb +7 -9
  190. data/lib/active_record/relation/merger.rb +95 -77
  191. data/lib/active_record/relation/predicate_builder.rb +109 -110
  192. data/lib/active_record/relation/predicate_builder/array_handler.rb +22 -17
  193. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -0
  194. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +6 -4
  195. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +55 -0
  196. data/lib/active_record/relation/predicate_builder/range_handler.rb +7 -18
  197. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  198. data/lib/active_record/relation/query_attribute.rb +33 -2
  199. data/lib/active_record/relation/query_methods.rb +654 -374
  200. data/lib/active_record/relation/record_fetch_warning.rb +8 -6
  201. data/lib/active_record/relation/spawn_methods.rb +15 -14
  202. data/lib/active_record/relation/where_clause.rb +171 -109
  203. data/lib/active_record/result.rb +88 -51
  204. data/lib/active_record/runtime_registry.rb +5 -3
  205. data/lib/active_record/sanitization.rb +73 -100
  206. data/lib/active_record/schema.rb +7 -14
  207. data/lib/active_record/schema_dumper.rb +101 -69
  208. data/lib/active_record/schema_migration.rb +16 -12
  209. data/lib/active_record/scoping.rb +20 -20
  210. data/lib/active_record/scoping/default.rb +92 -95
  211. data/lib/active_record/scoping/named.rb +39 -30
  212. data/lib/active_record/secure_token.rb +19 -9
  213. data/lib/active_record/serialization.rb +7 -3
  214. data/lib/active_record/signed_id.rb +116 -0
  215. data/lib/active_record/statement_cache.rb +80 -29
  216. data/lib/active_record/store.rb +122 -42
  217. data/lib/active_record/suppressor.rb +6 -3
  218. data/lib/active_record/table_metadata.rb +51 -39
  219. data/lib/active_record/tasks/database_tasks.rb +332 -115
  220. data/lib/active_record/tasks/mysql_database_tasks.rb +66 -104
  221. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -56
  222. data/lib/active_record/tasks/sqlite_database_tasks.rb +40 -19
  223. data/lib/active_record/test_databases.rb +24 -0
  224. data/lib/active_record/test_fixtures.rb +246 -0
  225. data/lib/active_record/timestamp.rb +70 -38
  226. data/lib/active_record/touch_later.rb +26 -24
  227. data/lib/active_record/transactions.rb +121 -184
  228. data/lib/active_record/translation.rb +3 -1
  229. data/lib/active_record/type.rb +29 -17
  230. data/lib/active_record/type/adapter_specific_registry.rb +44 -48
  231. data/lib/active_record/type/date.rb +2 -0
  232. data/lib/active_record/type/date_time.rb +2 -0
  233. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  234. data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
  235. data/lib/active_record/type/internal/timezone.rb +2 -0
  236. data/lib/active_record/type/json.rb +30 -0
  237. data/lib/active_record/type/serialized.rb +20 -9
  238. data/lib/active_record/type/text.rb +11 -0
  239. data/lib/active_record/type/time.rb +12 -1
  240. data/lib/active_record/type/type_map.rb +14 -17
  241. data/lib/active_record/type/unsigned_integer.rb +16 -0
  242. data/lib/active_record/type_caster.rb +4 -2
  243. data/lib/active_record/type_caster/connection.rb +17 -13
  244. data/lib/active_record/type_caster/map.rb +10 -6
  245. data/lib/active_record/validations.rb +8 -5
  246. data/lib/active_record/validations/absence.rb +2 -0
  247. data/lib/active_record/validations/associated.rb +4 -3
  248. data/lib/active_record/validations/length.rb +2 -0
  249. data/lib/active_record/validations/numericality.rb +35 -0
  250. data/lib/active_record/validations/presence.rb +4 -2
  251. data/lib/active_record/validations/uniqueness.rb +52 -45
  252. data/lib/active_record/version.rb +3 -1
  253. data/lib/arel.rb +54 -0
  254. data/lib/arel/alias_predication.rb +9 -0
  255. data/lib/arel/attributes/attribute.rb +41 -0
  256. data/lib/arel/collectors/bind.rb +29 -0
  257. data/lib/arel/collectors/composite.rb +39 -0
  258. data/lib/arel/collectors/plain_string.rb +20 -0
  259. data/lib/arel/collectors/sql_string.rb +27 -0
  260. data/lib/arel/collectors/substitute_binds.rb +35 -0
  261. data/lib/arel/crud.rb +42 -0
  262. data/lib/arel/delete_manager.rb +18 -0
  263. data/lib/arel/errors.rb +9 -0
  264. data/lib/arel/expressions.rb +29 -0
  265. data/lib/arel/factory_methods.rb +49 -0
  266. data/lib/arel/insert_manager.rb +49 -0
  267. data/lib/arel/math.rb +45 -0
  268. data/lib/arel/nodes.rb +70 -0
  269. data/lib/arel/nodes/and.rb +32 -0
  270. data/lib/arel/nodes/ascending.rb +23 -0
  271. data/lib/arel/nodes/binary.rb +126 -0
  272. data/lib/arel/nodes/bind_param.rb +44 -0
  273. data/lib/arel/nodes/case.rb +55 -0
  274. data/lib/arel/nodes/casted.rb +62 -0
  275. data/lib/arel/nodes/comment.rb +29 -0
  276. data/lib/arel/nodes/count.rb +12 -0
  277. data/lib/arel/nodes/delete_statement.rb +45 -0
  278. data/lib/arel/nodes/descending.rb +23 -0
  279. data/lib/arel/nodes/equality.rb +15 -0
  280. data/lib/arel/nodes/extract.rb +24 -0
  281. data/lib/arel/nodes/false.rb +16 -0
  282. data/lib/arel/nodes/full_outer_join.rb +8 -0
  283. data/lib/arel/nodes/function.rb +44 -0
  284. data/lib/arel/nodes/grouping.rb +11 -0
  285. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  286. data/lib/arel/nodes/in.rb +15 -0
  287. data/lib/arel/nodes/infix_operation.rb +92 -0
  288. data/lib/arel/nodes/inner_join.rb +8 -0
  289. data/lib/arel/nodes/insert_statement.rb +37 -0
  290. data/lib/arel/nodes/join_source.rb +20 -0
  291. data/lib/arel/nodes/matches.rb +18 -0
  292. data/lib/arel/nodes/named_function.rb +23 -0
  293. data/lib/arel/nodes/node.rb +51 -0
  294. data/lib/arel/nodes/node_expression.rb +13 -0
  295. data/lib/arel/nodes/ordering.rb +27 -0
  296. data/lib/arel/nodes/outer_join.rb +8 -0
  297. data/lib/arel/nodes/over.rb +15 -0
  298. data/lib/arel/nodes/regexp.rb +16 -0
  299. data/lib/arel/nodes/right_outer_join.rb +8 -0
  300. data/lib/arel/nodes/select_core.rb +67 -0
  301. data/lib/arel/nodes/select_statement.rb +41 -0
  302. data/lib/arel/nodes/sql_literal.rb +19 -0
  303. data/lib/arel/nodes/string_join.rb +11 -0
  304. data/lib/arel/nodes/table_alias.rb +31 -0
  305. data/lib/arel/nodes/terminal.rb +16 -0
  306. data/lib/arel/nodes/true.rb +16 -0
  307. data/lib/arel/nodes/unary.rb +44 -0
  308. data/lib/arel/nodes/unary_operation.rb +20 -0
  309. data/lib/arel/nodes/unqualified_column.rb +22 -0
  310. data/lib/arel/nodes/update_statement.rb +41 -0
  311. data/lib/arel/nodes/values_list.rb +9 -0
  312. data/lib/arel/nodes/window.rb +126 -0
  313. data/lib/arel/nodes/with.rb +11 -0
  314. data/lib/arel/order_predications.rb +13 -0
  315. data/lib/arel/predications.rb +250 -0
  316. data/lib/arel/select_manager.rb +270 -0
  317. data/lib/arel/table.rb +118 -0
  318. data/lib/arel/tree_manager.rb +72 -0
  319. data/lib/arel/update_manager.rb +34 -0
  320. data/lib/arel/visitors.rb +13 -0
  321. data/lib/arel/visitors/dot.rb +308 -0
  322. data/lib/arel/visitors/mysql.rb +93 -0
  323. data/lib/arel/visitors/postgresql.rb +120 -0
  324. data/lib/arel/visitors/sqlite.rb +38 -0
  325. data/lib/arel/visitors/to_sql.rb +899 -0
  326. data/lib/arel/visitors/visitor.rb +45 -0
  327. data/lib/arel/window_predications.rb +9 -0
  328. data/lib/rails/generators/active_record.rb +7 -5
  329. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  330. data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
  331. data/lib/rails/generators/active_record/migration.rb +22 -3
  332. data/lib/rails/generators/active_record/migration/migration_generator.rb +38 -35
  333. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +3 -1
  334. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +7 -5
  335. data/lib/rails/generators/active_record/model/model_generator.rb +41 -25
  336. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  337. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +10 -1
  338. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  339. metadata +141 -57
  340. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  341. data/lib/active_record/associations/preloader/collection_association.rb +0 -17
  342. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  343. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  344. data/lib/active_record/associations/preloader/has_one.rb +0 -15
  345. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  346. data/lib/active_record/associations/preloader/singular_association.rb +0 -20
  347. data/lib/active_record/attribute.rb +0 -213
  348. data/lib/active_record/attribute/user_provided_default.rb +0 -28
  349. data/lib/active_record/attribute_decorators.rb +0 -67
  350. data/lib/active_record/attribute_mutation_tracker.rb +0 -70
  351. data/lib/active_record/attribute_set.rb +0 -110
  352. data/lib/active_record/attribute_set/builder.rb +0 -132
  353. data/lib/active_record/collection_cache_key.rb +0 -50
  354. data/lib/active_record/connection_adapters/connection_specification.rb +0 -263
  355. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -22
  356. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +0 -50
  357. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  358. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
  359. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -17
  360. data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
  361. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -57
  362. data/lib/active_record/relation/where_clause_factory.rb +0 -38
  363. data/lib/active_record/type/internal/abstract_json.rb +0 -33
data/MIT-LICENSE CHANGED
@@ -1,4 +1,6 @@
1
- Copyright (c) 2004-2016 David Heinemeier Hansson
1
+ Copyright (c) 2004-2020 David Heinemeier Hansson
2
+
3
+ Arel originally copyright (c) 2007-2016 Nick Kallen, Bryan Helmkamp, Emilio Tagua, Aaron Patterson
2
4
 
3
5
  Permission is hereby granted, free of charge, to any person obtaining
4
6
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -13,6 +13,8 @@ columns. Although these mappings can be defined explicitly, it's recommended
13
13
  to follow naming conventions, especially when getting started with the
14
14
  library.
15
15
 
16
+ You can read more about Active Record in the {Active Record Basics}[https://edgeguides.rubyonrails.org/active_record_basics.html] guide.
17
+
16
18
  A short rundown of some of the major features:
17
19
 
18
20
  * Automated mapping between classes and tables, attributes and columns.
@@ -26,7 +28,7 @@ The Product class is automatically mapped to the table named "products",
26
28
  which might look like this:
27
29
 
28
30
  CREATE TABLE products (
29
- id int NOT NULL auto_increment,
31
+ id bigint NOT NULL auto_increment,
30
32
  name varchar(255),
31
33
  PRIMARY KEY (id)
32
34
  );
@@ -130,7 +132,7 @@ This would also define the following accessors: <tt>Product#name</tt> and
130
132
  SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
131
133
 
132
134
 
133
- * Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc].
135
+ * Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[https://ruby-doc.org/stdlib/libdoc/logger/rdoc/].
134
136
 
135
137
  ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
136
138
  ActiveRecord::Base.logger = Log4r::Logger.new('Application Log')
@@ -138,7 +140,7 @@ This would also define the following accessors: <tt>Product#name</tt> and
138
140
 
139
141
  * Database agnostic schema management with Migrations.
140
142
 
141
- class AddSystemSettings < ActiveRecord::Migration[5.0]
143
+ class AddSystemSettings < ActiveRecord::Migration[6.0]
142
144
  def up
143
145
  create_table :system_settings do |t|
144
146
  t.string :name
@@ -162,7 +164,7 @@ This would also define the following accessors: <tt>Product#name</tt> and
162
164
  == Philosophy
163
165
 
164
166
  Active Record is an implementation of the object-relational mapping (ORM)
165
- pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html] by the same
167
+ pattern[https://www.martinfowler.com/eaaCatalog/activeRecord.html] by the same
166
168
  name described by Martin Fowler:
167
169
 
168
170
  "An object that wraps a row in a database table or view,
@@ -192,26 +194,26 @@ The latest version of Active Record can be installed with RubyGems:
192
194
 
193
195
  Source code can be downloaded as part of the Rails project on GitHub:
194
196
 
195
- * https://github.com/rails/rails/tree/5-0-stable/activerecord
197
+ * https://github.com/rails/rails/tree/master/activerecord
196
198
 
197
199
 
198
200
  == License
199
201
 
200
202
  Active Record is released under the MIT license:
201
203
 
202
- * http://www.opensource.org/licenses/MIT
204
+ * https://opensource.org/licenses/MIT
203
205
 
204
206
 
205
207
  == Support
206
208
 
207
209
  API documentation is at:
208
210
 
209
- * http://api.rubyonrails.org
211
+ * https://api.rubyonrails.org
210
212
 
211
- Bug reports can be filed for the Ruby on Rails project here:
213
+ Bug reports for the Ruby on Rails project can be filed here:
212
214
 
213
215
  * https://github.com/rails/rails/issues
214
216
 
215
217
  Feature requests should be discussed on the rails-core mailing list here:
216
218
 
217
- * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
219
+ * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_record"
2
- require 'benchmark/ips'
4
+ require "benchmark/ips"
3
5
 
4
- TIME = (ENV['BENCHMARK_TIME'] || 20).to_i
5
- RECORDS = (ENV['BENCHMARK_RECORDS'] || TIME*1000).to_i
6
+ TIME = (ENV["BENCHMARK_TIME"] || 20).to_i
7
+ RECORDS = (ENV["BENCHMARK_RECORDS"] || TIME * 1000).to_i
6
8
 
7
- conn = { adapter: 'sqlite3', database: ':memory:' }
9
+ conn = { adapter: "sqlite3", database: ":memory:" }
8
10
 
9
11
  ActiveRecord::Base.establish_connection(conn)
10
12
 
@@ -42,26 +44,26 @@ class Exhibit < ActiveRecord::Base
42
44
  def self.feel(exhibits) exhibits.each(&:feel) end
43
45
  end
44
46
 
45
- def progress_bar(int); print "." if (int%100).zero? ; end
47
+ def progress_bar(int); print "." if (int % 100).zero? ; end
46
48
 
47
- puts 'Generating data...'
49
+ puts "Generating data..."
48
50
 
49
51
  module ActiveRecord
50
52
  class Faker
51
- LOREM = %Q{Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit.
53
+ LOREM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit.
52
54
  Integer consequat tincidunt felis. Etiam non erat dolor. Vivamus imperdiet nibh sit amet diam eleifend id posuere diam malesuada. Mauris at accumsan sem.
53
55
  Donec id lorem neque. Fusce erat lorem, ornare eu congue vitae, malesuada quis neque. Maecenas vel urna a velit pretium fermentum. Donec tortor enim,
54
56
  tempor venenatis egestas a, tempor sed ipsum. Ut arcu justo, faucibus non imperdiet ac, interdum at diam. Pellentesque ipsum enim, venenatis ut iaculis vitae,
55
57
  varius vitae sem. Sed rutrum quam ac elit euismod bibendum. Donec ultricies ultricies magna, at lacinia libero mollis aliquam. Sed ac arcu in tortor elementum
56
58
  tincidunt vel interdum sem. Curabitur eget erat arcu. Praesent eget eros leo. Nam magna enim, sollicitudin vehicula scelerisque in, vulputate ut libero.
57
- Praesent varius tincidunt commodo}.split
59
+ Praesent varius tincidunt commodo".split
58
60
 
59
61
  def self.name
60
- LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join ' '
62
+ LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join " "
61
63
  end
62
64
 
63
65
  def self.email
64
- LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join('@') + ".com"
66
+ LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join("@") + ".com"
65
67
  end
66
68
  end
67
69
  end
@@ -72,7 +74,7 @@ end
72
74
 
73
75
  # Using the same paragraph for all exhibits because it is very slow
74
76
  # to generate unique paragraphs for all exhibits.
75
- notes = ActiveRecord::Faker::LOREM.join ' '
77
+ notes = ActiveRecord::Faker::LOREM.join " "
76
78
  today = Date.today
77
79
 
78
80
  puts "Inserting #{RECORDS} users and exhibits..."
@@ -95,9 +97,9 @@ puts "Done!\n"
95
97
 
96
98
  Benchmark.ips(TIME) do |x|
97
99
  ar_obj = Exhibit.find(1)
98
- attrs = { name: 'sam' }
99
- attrs_first = { name: 'sam' }
100
- attrs_second = { name: 'tom' }
100
+ attrs = { name: "sam" }
101
+ attrs_first = { name: "sam" }
102
+ attrs_second = { name: "tom" }
101
103
  exhibit = {
102
104
  name: ActiveRecord::Faker.name,
103
105
  notes: notes,
@@ -108,22 +110,22 @@ Benchmark.ips(TIME) do |x|
108
110
  ar_obj.id
109
111
  end
110
112
 
111
- x.report 'Model.new (instantiation)' do
113
+ x.report "Model.new (instantiation)" do
112
114
  Exhibit.new
113
115
  end
114
116
 
115
- x.report 'Model.new (setting attributes)' do
117
+ x.report "Model.new (setting attributes)" do
116
118
  Exhibit.new(attrs)
117
119
  end
118
120
 
119
- x.report 'Model.first' do
121
+ x.report "Model.first" do
120
122
  Exhibit.first.look
121
123
  end
122
124
 
123
- x.report 'Model.take' do
125
+ x.report "Model.take" do
124
126
  Exhibit.take
125
127
  end
126
-
128
+
127
129
  x.report("Model.all limit(100)") do
128
130
  Exhibit.look Exhibit.limit(100)
129
131
  end
@@ -140,41 +142,41 @@ Benchmark.ips(TIME) do |x|
140
142
  Exhibit.look Exhibit.limit(10000)
141
143
  end
142
144
 
143
- x.report 'Model.named_scope' do
145
+ x.report "Model.named_scope" do
144
146
  Exhibit.limit(10).with_name.with_notes
145
147
  end
146
148
 
147
- x.report 'Model.create' do
149
+ x.report "Model.create" do
148
150
  Exhibit.create(exhibit)
149
151
  end
150
152
 
151
- x.report 'Resource#attributes=' do
153
+ x.report "Resource#attributes=" do
152
154
  e = Exhibit.new(attrs_first)
153
155
  e.attributes = attrs_second
154
156
  end
155
157
 
156
- x.report 'Resource#update' do
157
- Exhibit.first.update(name: 'bob')
158
+ x.report "Resource#update" do
159
+ Exhibit.first.update(name: "bob")
158
160
  end
159
161
 
160
- x.report 'Resource#destroy' do
162
+ x.report "Resource#destroy" do
161
163
  Exhibit.first.destroy
162
164
  end
163
165
 
164
- x.report 'Model.transaction' do
166
+ x.report "Model.transaction" do
165
167
  Exhibit.transaction { Exhibit.new }
166
168
  end
167
169
 
168
- x.report 'Model.find(id)' do
170
+ x.report "Model.find(id)" do
169
171
  User.find(1)
170
172
  end
171
173
 
172
- x.report 'Model.find_by_sql' do
174
+ x.report "Model.find_by_sql" do
173
175
  Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first
174
176
  end
175
177
 
176
178
  x.report "Model.log" do
177
- Exhibit.connection.send(:log, "hello", "world") {}
179
+ Exhibit.connection.send(:log, "hello", "world") { }
178
180
  end
179
181
 
180
182
  x.report "AR.execute(query)" do
data/examples/simple.rb CHANGED
@@ -1,13 +1,15 @@
1
- require 'active_record'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
2
4
 
3
5
  class Person < ActiveRecord::Base
4
- establish_connection adapter: 'sqlite3', database: 'foobar.db'
6
+ establish_connection adapter: "sqlite3", database: "foobar.db"
5
7
  connection.create_table table_name, force: true do |t|
6
8
  t.string :name
7
9
  end
8
10
  end
9
11
 
10
- bob = Person.create!(name: 'bob')
12
+ bob = Person.create!(name: "bob")
11
13
  puts Person.all.inspect
12
14
  bob.destroy
13
15
  puts Person.all.inspect
data/lib/active_record.rb CHANGED
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #--
2
- # Copyright (c) 2004-2016 David Heinemeier Hansson
4
+ # Copyright (c) 2004-2020 David Heinemeier Hansson
3
5
  #
4
6
  # Permission is hereby granted, free of charge, to any person obtaining
5
7
  # a copy of this software and associated documentation files (the
@@ -21,31 +23,33 @@
21
23
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
24
  #++
23
25
 
24
- require 'active_support'
25
- require 'active_support/rails'
26
- require 'active_model'
27
- require 'arel'
26
+ require "active_support"
27
+ require "active_support/rails"
28
+ require "active_model"
29
+ require "arel"
30
+ require "yaml"
28
31
 
29
- require 'active_record/version'
30
- require 'active_record/attribute_set'
32
+ require "active_record/version"
33
+ require "active_model/attribute_set"
34
+ require "active_record/errors"
31
35
 
32
36
  module ActiveRecord
33
37
  extend ActiveSupport::Autoload
34
38
 
35
- autoload :Attribute
36
39
  autoload :Base
37
40
  autoload :Callbacks
38
41
  autoload :Core
39
42
  autoload :ConnectionHandling
40
43
  autoload :CounterCache
41
44
  autoload :DynamicMatchers
45
+ autoload :DelegatedType
42
46
  autoload :Enum
43
47
  autoload :InternalMetadata
44
48
  autoload :Explain
45
49
  autoload :Inheritance
46
50
  autoload :Integration
47
51
  autoload :Migration
48
- autoload :Migrator, 'active_record/migration'
52
+ autoload :Migrator, "active_record/migration"
49
53
  autoload :ModelSchema
50
54
  autoload :NestedAttributes
51
55
  autoload :NoTouching
@@ -53,9 +57,8 @@ module ActiveRecord
53
57
  autoload :Persistence
54
58
  autoload :QueryCache
55
59
  autoload :Querying
56
- autoload :CollectionCacheKey
57
60
  autoload :ReadonlyAttributes
58
- autoload :RecordInvalid, 'active_record/validations'
61
+ autoload :RecordInvalid, "active_record/validations"
59
62
  autoload :Reflection
60
63
  autoload :RuntimeRegistry
61
64
  autoload :Sanitization
@@ -66,17 +69,17 @@ module ActiveRecord
66
69
  autoload :Serialization
67
70
  autoload :StatementCache
68
71
  autoload :Store
72
+ autoload :SignedId
69
73
  autoload :Suppressor
70
74
  autoload :Timestamp
71
75
  autoload :Transactions
72
76
  autoload :Translation
73
77
  autoload :Validations
74
78
  autoload :SecureToken
79
+ autoload :DestroyAssociationAsyncJob
75
80
 
76
81
  eager_autoload do
77
- autoload :ActiveRecordError, 'active_record/errors'
78
- autoload :ConnectionNotEstablished, 'active_record/errors'
79
- autoload :ConnectionAdapters, 'active_record/connection_adapters/abstract_adapter'
82
+ autoload :ConnectionAdapters
80
83
 
81
84
  autoload :Aggregations
82
85
  autoload :Associations
@@ -90,7 +93,7 @@ module ActiveRecord
90
93
  autoload :AssociationRelation
91
94
  autoload :NullRelation
92
95
 
93
- autoload_under 'relation' do
96
+ autoload_under "relation" do
94
97
  autoload :QueryMethods
95
98
  autoload :FinderMethods
96
99
  autoload :Calculations
@@ -102,11 +105,12 @@ module ActiveRecord
102
105
 
103
106
  autoload :Result
104
107
  autoload :TableMetadata
108
+ autoload :Type
105
109
  end
106
110
 
107
111
  module Coders
108
- autoload :YAMLColumn, 'active_record/coders/yaml_column'
109
- autoload :JSON, 'active_record/coders/json'
112
+ autoload :YAMLColumn, "active_record/coders/yaml_column"
113
+ autoload :JSON, "active_record/coders/json"
110
114
  end
111
115
 
112
116
  module AttributeMethods
@@ -133,34 +137,33 @@ module ActiveRecord
133
137
  end
134
138
  end
135
139
 
136
- module ConnectionAdapters
140
+ module Scoping
137
141
  extend ActiveSupport::Autoload
138
142
 
139
143
  eager_autoload do
140
- autoload :AbstractAdapter
144
+ autoload :Named
145
+ autoload :Default
141
146
  end
142
147
  end
143
148
 
144
- module Scoping
149
+ module Middleware
145
150
  extend ActiveSupport::Autoload
146
151
 
147
- eager_autoload do
148
- autoload :Named
149
- autoload :Default
150
- end
152
+ autoload :DatabaseSelector, "active_record/middleware/database_selector"
151
153
  end
152
154
 
153
155
  module Tasks
154
156
  extend ActiveSupport::Autoload
155
157
 
156
158
  autoload :DatabaseTasks
157
- autoload :SQLiteDatabaseTasks, 'active_record/tasks/sqlite_database_tasks'
158
- autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
159
+ autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks"
160
+ autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks"
159
161
  autoload :PostgreSQLDatabaseTasks,
160
- 'active_record/tasks/postgresql_database_tasks'
162
+ "active_record/tasks/postgresql_database_tasks"
161
163
  end
162
164
 
163
- autoload :TestFixtures, 'active_record/fixtures'
165
+ autoload :TestDatabases, "active_record/test_databases"
166
+ autoload :TestFixtures, "active_record/fixtures"
164
167
 
165
168
  def self.eager_load!
166
169
  super
@@ -177,5 +180,10 @@ ActiveSupport.on_load(:active_record) do
177
180
  end
178
181
 
179
182
  ActiveSupport.on_load(:i18n) do
180
- I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
183
+ I18n.load_path << File.expand_path("active_record/locale/en.yml", __dir__)
181
184
  end
185
+
186
+ YAML.load_tags["!ruby/object:ActiveRecord::AttributeSet"] = "ActiveModel::AttributeSet"
187
+ YAML.load_tags["!ruby/object:ActiveRecord::Attribute::FromDatabase"] = "ActiveModel::Attribute::FromDatabase"
188
+ YAML.load_tags["!ruby/object:ActiveRecord::LazyAttributeHash"] = "ActiveModel::LazyAttributeHash"
189
+ YAML.load_tags["!ruby/object:ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString"] = "ActiveRecord::Type::String"
@@ -1,8 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  # See ActiveRecord::Aggregations::ClassMethods for documentation
3
5
  module Aggregations
4
- extend ActiveSupport::Concern
5
-
6
6
  def initialize_dup(*) # :nodoc:
7
7
  @aggregation_cache = {}
8
8
  super
@@ -14,269 +14,271 @@ module ActiveRecord
14
14
  end
15
15
 
16
16
  private
17
-
18
- def clear_aggregation_cache # :nodoc:
17
+ def clear_aggregation_cache
19
18
  @aggregation_cache.clear if persisted?
20
19
  end
21
20
 
22
- def init_internals # :nodoc:
21
+ def init_internals
23
22
  @aggregation_cache = {}
24
23
  super
25
24
  end
26
25
 
27
- # Active Record implements aggregation through a macro-like class method called #composed_of
28
- # for representing attributes as value objects. It expresses relationships like "Account [is]
29
- # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
30
- # to the macro adds a description of how the value objects are created from the attributes of
31
- # the entity object (when the entity is initialized either as a new object or from finding an
32
- # existing object) and how it can be turned back into attributes (when the entity is saved to
33
- # the database).
34
- #
35
- # class Customer < ActiveRecord::Base
36
- # composed_of :balance, class_name: "Money", mapping: %w(amount currency)
37
- # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
38
- # end
39
- #
40
- # The customer class now has the following methods to manipulate the value objects:
41
- # * <tt>Customer#balance, Customer#balance=(money)</tt>
42
- # * <tt>Customer#address, Customer#address=(address)</tt>
43
- #
44
- # These methods will operate with value objects like the ones described below:
45
- #
46
- # class Money
47
- # include Comparable
48
- # attr_reader :amount, :currency
49
- # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
50
- #
51
- # def initialize(amount, currency = "USD")
52
- # @amount, @currency = amount, currency
53
- # end
54
- #
55
- # def exchange_to(other_currency)
56
- # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
57
- # Money.new(exchanged_amount, other_currency)
58
- # end
59
- #
60
- # def ==(other_money)
61
- # amount == other_money.amount && currency == other_money.currency
62
- # end
63
- #
64
- # def <=>(other_money)
65
- # if currency == other_money.currency
66
- # amount <=> other_money.amount
67
- # else
68
- # amount <=> other_money.exchange_to(currency).amount
69
- # end
70
- # end
71
- # end
72
- #
73
- # class Address
74
- # attr_reader :street, :city
75
- # def initialize(street, city)
76
- # @street, @city = street, city
77
- # end
78
- #
79
- # def close_to?(other_address)
80
- # city == other_address.city
81
- # end
82
- #
83
- # def ==(other_address)
84
- # city == other_address.city && street == other_address.street
85
- # end
86
- # end
87
- #
88
- # Now it's possible to access attributes from the database through the value objects instead. If
89
- # you choose to name the composition the same as the attribute's name, it will be the only way to
90
- # access that attribute. That's the case with our +balance+ attribute. You interact with the value
91
- # objects just like you would with any other attribute:
92
- #
93
- # customer.balance = Money.new(20) # sets the Money value object and the attribute
94
- # customer.balance # => Money value object
95
- # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
96
- # customer.balance > Money.new(10) # => true
97
- # customer.balance == Money.new(20) # => true
98
- # customer.balance < Money.new(5) # => false
99
- #
100
- # Value objects can also be composed of multiple attributes, such as the case of Address. The order
101
- # of the mappings will determine the order of the parameters.
102
- #
103
- # customer.address_street = "Hyancintvej"
104
- # customer.address_city = "Copenhagen"
105
- # customer.address # => Address.new("Hyancintvej", "Copenhagen")
106
- #
107
- # customer.address = Address.new("May Street", "Chicago")
108
- # customer.address_street # => "May Street"
109
- # customer.address_city # => "Chicago"
110
- #
111
- # == Writing value objects
112
- #
113
- # Value objects are immutable and interchangeable objects that represent a given value, such as
114
- # a Money object representing $5. Two Money objects both representing $5 should be equal (through
115
- # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
116
- # unlike entity objects where equality is determined by identity. An entity class such as Customer can
117
- # easily have two different objects that both have an address on Hyancintvej. Entity identity is
118
- # determined by object or relational unique identifiers (such as primary keys). Normal
119
- # ActiveRecord::Base classes are entity objects.
120
- #
121
- # It's also important to treat the value objects as immutable. Don't allow the Money object to have
122
- # its amount changed after creation. Create a new Money object with the new value instead. The
123
- # <tt>Money#exchange_to</tt> method is an example of this. It returns a new value object instead of changing
124
- # its own values. Active Record won't persist value objects that have been changed through means
125
- # other than the writer method.
126
- #
127
- # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
128
- # object. Attempting to change it afterwards will result in a +RuntimeError+.
129
- #
130
- # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
131
- # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
132
- #
133
- # == Custom constructors and converters
134
- #
135
- # By default value objects are initialized by calling the <tt>new</tt> constructor of the value
136
- # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
137
- # option, as arguments. If the value class doesn't support this convention then #composed_of allows
138
- # a custom constructor to be specified.
139
- #
140
- # When a new value is assigned to the value object, the default assumption is that the new value
141
- # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
142
- # converted to an instance of value class if necessary.
143
- #
144
- # For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be
145
- # aggregated using the +NetAddr::CIDR+ value class (http://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR).
146
- # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
147
- # New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string
148
- # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
149
- # these requirements:
150
- #
151
- # class NetworkResource < ActiveRecord::Base
152
- # composed_of :cidr,
153
- # class_name: 'NetAddr::CIDR',
154
- # mapping: [ %w(network_address network), %w(cidr_range bits) ],
155
- # allow_nil: true,
156
- # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
157
- # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
158
- # end
159
- #
160
- # # This calls the :constructor
161
- # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24)
162
- #
163
- # # These assignments will both use the :converter
164
- # network_resource.cidr = [ '192.168.2.1', 8 ]
165
- # network_resource.cidr = '192.168.0.1/24'
166
- #
167
- # # This assignment won't use the :converter as the value is already an instance of the value class
168
- # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
169
- #
170
- # # Saving and then reloading will use the :constructor on reload
171
- # network_resource.save
172
- # network_resource.reload
173
- #
174
- # == Finding records by a value object
175
- #
176
- # Once a #composed_of relationship is specified for a model, records can be loaded from the database
177
- # by specifying an instance of the value object in the conditions hash. The following example
178
- # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
179
- #
180
- # Customer.where(balance: Money.new(20, "USD"))
181
- #
182
- module ClassMethods
183
- # Adds reader and writer methods for manipulating a value object:
184
- # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
185
- #
186
- # Options are:
187
- # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
188
- # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
189
- # to the Address class, but if the real class name is +CompanyAddress+, you'll have to specify it
190
- # with this option.
191
- # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
192
- # object. Each mapping is represented as an array where the first item is the name of the
193
- # entity attribute and the second item is the name of the attribute in the value object. The
194
- # order in which mappings are defined determines the order in which attributes are sent to the
195
- # value class constructor.
196
- # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
197
- # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
198
- # mapped attributes.
199
- # This defaults to +false+.
200
- # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
201
- # is called to initialize the value object. The constructor is passed all of the mapped attributes,
202
- # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
203
- # to instantiate a <tt>:class_name</tt> object.
204
- # The default is <tt>:new</tt>.
205
- # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
206
- # or a Proc that is called when a new value is assigned to the value object. The converter is
207
- # passed the single value that is used in the assignment and is only called if the new value is
208
- # not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
209
- # can return nil to skip the assignment.
210
- #
211
- # Option examples:
212
- # composed_of :temperature, mapping: %w(reading celsius)
213
- # composed_of :balance, class_name: "Money", mapping: %w(balance amount),
214
- # converter: Proc.new { |balance| balance.to_money }
215
- # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
216
- # composed_of :gps_location
217
- # composed_of :gps_location, allow_nil: true
218
- # composed_of :ip_address,
219
- # class_name: 'IPAddr',
220
- # mapping: %w(ip to_i),
221
- # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
222
- # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
223
- #
224
- def composed_of(part_id, options = {})
225
- options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
26
+ # Active Record implements aggregation through a macro-like class method called #composed_of
27
+ # for representing attributes as value objects. It expresses relationships like "Account [is]
28
+ # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
29
+ # to the macro adds a description of how the value objects are created from the attributes of
30
+ # the entity object (when the entity is initialized either as a new object or from finding an
31
+ # existing object) and how it can be turned back into attributes (when the entity is saved to
32
+ # the database).
33
+ #
34
+ # class Customer < ActiveRecord::Base
35
+ # composed_of :balance, class_name: "Money", mapping: %w(balance amount)
36
+ # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
37
+ # end
38
+ #
39
+ # The customer class now has the following methods to manipulate the value objects:
40
+ # * <tt>Customer#balance, Customer#balance=(money)</tt>
41
+ # * <tt>Customer#address, Customer#address=(address)</tt>
42
+ #
43
+ # These methods will operate with value objects like the ones described below:
44
+ #
45
+ # class Money
46
+ # include Comparable
47
+ # attr_reader :amount, :currency
48
+ # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
49
+ #
50
+ # def initialize(amount, currency = "USD")
51
+ # @amount, @currency = amount, currency
52
+ # end
53
+ #
54
+ # def exchange_to(other_currency)
55
+ # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
56
+ # Money.new(exchanged_amount, other_currency)
57
+ # end
58
+ #
59
+ # def ==(other_money)
60
+ # amount == other_money.amount && currency == other_money.currency
61
+ # end
62
+ #
63
+ # def <=>(other_money)
64
+ # if currency == other_money.currency
65
+ # amount <=> other_money.amount
66
+ # else
67
+ # amount <=> other_money.exchange_to(currency).amount
68
+ # end
69
+ # end
70
+ # end
71
+ #
72
+ # class Address
73
+ # attr_reader :street, :city
74
+ # def initialize(street, city)
75
+ # @street, @city = street, city
76
+ # end
77
+ #
78
+ # def close_to?(other_address)
79
+ # city == other_address.city
80
+ # end
81
+ #
82
+ # def ==(other_address)
83
+ # city == other_address.city && street == other_address.street
84
+ # end
85
+ # end
86
+ #
87
+ # Now it's possible to access attributes from the database through the value objects instead. If
88
+ # you choose to name the composition the same as the attribute's name, it will be the only way to
89
+ # access that attribute. That's the case with our +balance+ attribute. You interact with the value
90
+ # objects just like you would with any other attribute:
91
+ #
92
+ # customer.balance = Money.new(20) # sets the Money value object and the attribute
93
+ # customer.balance # => Money value object
94
+ # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
95
+ # customer.balance > Money.new(10) # => true
96
+ # customer.balance == Money.new(20) # => true
97
+ # customer.balance < Money.new(5) # => false
98
+ #
99
+ # Value objects can also be composed of multiple attributes, such as the case of Address. The order
100
+ # of the mappings will determine the order of the parameters.
101
+ #
102
+ # customer.address_street = "Hyancintvej"
103
+ # customer.address_city = "Copenhagen"
104
+ # customer.address # => Address.new("Hyancintvej", "Copenhagen")
105
+ #
106
+ # customer.address = Address.new("May Street", "Chicago")
107
+ # customer.address_street # => "May Street"
108
+ # customer.address_city # => "Chicago"
109
+ #
110
+ # == Writing value objects
111
+ #
112
+ # Value objects are immutable and interchangeable objects that represent a given value, such as
113
+ # a Money object representing $5. Two Money objects both representing $5 should be equal (through
114
+ # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
115
+ # unlike entity objects where equality is determined by identity. An entity class such as Customer can
116
+ # easily have two different objects that both have an address on Hyancintvej. Entity identity is
117
+ # determined by object or relational unique identifiers (such as primary keys). Normal
118
+ # ActiveRecord::Base classes are entity objects.
119
+ #
120
+ # It's also important to treat the value objects as immutable. Don't allow the Money object to have
121
+ # its amount changed after creation. Create a new Money object with the new value instead. The
122
+ # <tt>Money#exchange_to</tt> method is an example of this. It returns a new value object instead of changing
123
+ # its own values. Active Record won't persist value objects that have been changed through means
124
+ # other than the writer method.
125
+ #
126
+ # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
127
+ # object. Attempting to change it afterwards will result in a +RuntimeError+.
128
+ #
129
+ # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
130
+ # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
131
+ #
132
+ # == Custom constructors and converters
133
+ #
134
+ # By default value objects are initialized by calling the <tt>new</tt> constructor of the value
135
+ # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
136
+ # option, as arguments. If the value class doesn't support this convention then #composed_of allows
137
+ # a custom constructor to be specified.
138
+ #
139
+ # When a new value is assigned to the value object, the default assumption is that the new value
140
+ # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
141
+ # converted to an instance of value class if necessary.
142
+ #
143
+ # For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be
144
+ # aggregated using the +NetAddr::CIDR+ value class (https://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR).
145
+ # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
146
+ # New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string
147
+ # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
148
+ # these requirements:
149
+ #
150
+ # class NetworkResource < ActiveRecord::Base
151
+ # composed_of :cidr,
152
+ # class_name: 'NetAddr::CIDR',
153
+ # mapping: [ %w(network_address network), %w(cidr_range bits) ],
154
+ # allow_nil: true,
155
+ # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
156
+ # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
157
+ # end
158
+ #
159
+ # # This calls the :constructor
160
+ # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24)
161
+ #
162
+ # # These assignments will both use the :converter
163
+ # network_resource.cidr = [ '192.168.2.1', 8 ]
164
+ # network_resource.cidr = '192.168.0.1/24'
165
+ #
166
+ # # This assignment won't use the :converter as the value is already an instance of the value class
167
+ # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
168
+ #
169
+ # # Saving and then reloading will use the :constructor on reload
170
+ # network_resource.save
171
+ # network_resource.reload
172
+ #
173
+ # == Finding records by a value object
174
+ #
175
+ # Once a #composed_of relationship is specified for a model, records can be loaded from the database
176
+ # by specifying an instance of the value object in the conditions hash. The following example
177
+ # finds all customers with +address_street+ equal to "May Street" and +address_city+ equal to "Chicago":
178
+ #
179
+ # Customer.where(address: Address.new("May Street", "Chicago"))
180
+ #
181
+ module ClassMethods
182
+ # Adds reader and writer methods for manipulating a value object:
183
+ # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
184
+ #
185
+ # Options are:
186
+ # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
187
+ # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
188
+ # to the Address class, but if the real class name is +CompanyAddress+, you'll have to specify it
189
+ # with this option.
190
+ # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
191
+ # object. Each mapping is represented as an array where the first item is the name of the
192
+ # entity attribute and the second item is the name of the attribute in the value object. The
193
+ # order in which mappings are defined determines the order in which attributes are sent to the
194
+ # value class constructor.
195
+ # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
196
+ # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
197
+ # mapped attributes.
198
+ # This defaults to +false+.
199
+ # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
200
+ # is called to initialize the value object. The constructor is passed all of the mapped attributes,
201
+ # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
202
+ # to instantiate a <tt>:class_name</tt> object.
203
+ # The default is <tt>:new</tt>.
204
+ # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
205
+ # or a Proc that is called when a new value is assigned to the value object. The converter is
206
+ # passed the single value that is used in the assignment and is only called if the new value is
207
+ # not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
208
+ # can return +nil+ to skip the assignment.
209
+ #
210
+ # Option examples:
211
+ # composed_of :temperature, mapping: %w(reading celsius)
212
+ # composed_of :balance, class_name: "Money", mapping: %w(balance amount)
213
+ # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
214
+ # composed_of :gps_location
215
+ # composed_of :gps_location, allow_nil: true
216
+ # composed_of :ip_address,
217
+ # class_name: 'IPAddr',
218
+ # mapping: %w(ip to_i),
219
+ # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
220
+ # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
221
+ #
222
+ def composed_of(part_id, options = {})
223
+ options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
226
224
 
227
- name = part_id.id2name
228
- class_name = options[:class_name] || name.camelize
229
- mapping = options[:mapping] || [ name, name ]
230
- mapping = [ mapping ] unless mapping.first.is_a?(Array)
231
- allow_nil = options[:allow_nil] || false
232
- constructor = options[:constructor] || :new
233
- converter = options[:converter]
225
+ unless self < Aggregations
226
+ include Aggregations
227
+ end
234
228
 
235
- reader_method(name, class_name, mapping, allow_nil, constructor)
236
- writer_method(name, class_name, mapping, allow_nil, converter)
229
+ name = part_id.id2name
230
+ class_name = options[:class_name] || name.camelize
231
+ mapping = options[:mapping] || [ name, name ]
232
+ mapping = [ mapping ] unless mapping.first.is_a?(Array)
233
+ allow_nil = options[:allow_nil] || false
234
+ constructor = options[:constructor] || :new
235
+ converter = options[:converter]
237
236
 
238
- reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self)
239
- Reflection.add_aggregate_reflection self, part_id, reflection
240
- end
237
+ reader_method(name, class_name, mapping, allow_nil, constructor)
238
+ writer_method(name, class_name, mapping, allow_nil, converter)
241
239
 
242
- private
243
- def reader_method(name, class_name, mapping, allow_nil, constructor)
244
- define_method(name) do
245
- if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !_read_attribute(key).nil? })
246
- attrs = mapping.collect {|key, _| _read_attribute(key)}
247
- object = constructor.respond_to?(:call) ?
248
- constructor.call(*attrs) :
249
- class_name.constantize.send(constructor, *attrs)
250
- @aggregation_cache[name] = object
240
+ reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self)
241
+ Reflection.add_aggregate_reflection self, part_id, reflection
242
+ end
243
+
244
+ private
245
+ def reader_method(name, class_name, mapping, allow_nil, constructor)
246
+ define_method(name) do
247
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? { |key, _| !read_attribute(key).nil? })
248
+ attrs = mapping.collect { |key, _| read_attribute(key) }
249
+ object = constructor.respond_to?(:call) ?
250
+ constructor.call(*attrs) :
251
+ class_name.constantize.send(constructor, *attrs)
252
+ @aggregation_cache[name] = object
253
+ end
254
+ @aggregation_cache[name]
251
255
  end
252
- @aggregation_cache[name]
253
256
  end
254
- end
255
257
 
256
- def writer_method(name, class_name, mapping, allow_nil, converter)
257
- define_method("#{name}=") do |part|
258
- klass = class_name.constantize
258
+ def writer_method(name, class_name, mapping, allow_nil, converter)
259
+ define_method("#{name}=") do |part|
260
+ klass = class_name.constantize
259
261
 
260
- unless part.is_a?(klass) || converter.nil? || part.nil?
261
- part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
262
- end
262
+ unless part.is_a?(klass) || converter.nil? || part.nil?
263
+ part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
264
+ end
263
265
 
264
- hash_from_multiparameter_assignment = part.is_a?(Hash) &&
265
- part.each_key.all? { |k| k.is_a?(Integer) }
266
- if hash_from_multiparameter_assignment
267
- raise ArgumentError unless part.size == part.each_key.max
268
- part = klass.new(*part.sort.map(&:last))
269
- end
266
+ hash_from_multiparameter_assignment = part.is_a?(Hash) &&
267
+ part.each_key.all? { |k| k.is_a?(Integer) }
268
+ if hash_from_multiparameter_assignment
269
+ raise ArgumentError unless part.size == part.each_key.max
270
+ part = klass.new(*part.sort.map(&:last))
271
+ end
270
272
 
271
- if part.nil? && allow_nil
272
- mapping.each { |key, _| self[key] = nil }
273
- @aggregation_cache[name] = nil
274
- else
275
- mapping.each { |key, value| self[key] = part.send(value) }
276
- @aggregation_cache[name] = part.freeze
273
+ if part.nil? && allow_nil
274
+ mapping.each { |key, _| write_attribute(key, nil) }
275
+ @aggregation_cache[name] = nil
276
+ else
277
+ mapping.each { |key, value| write_attribute(key, part.send(value)) }
278
+ @aggregation_cache[name] = part.freeze
279
+ end
277
280
  end
278
281
  end
279
- end
280
- end
282
+ end
281
283
  end
282
284
  end