activerecord 4.2.0 → 6.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 (372) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +612 -971
  3. data/MIT-LICENSE +4 -2
  4. data/README.rdoc +13 -12
  5. data/examples/performance.rb +33 -32
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +267 -248
  8. data/lib/active_record/association_relation.rb +24 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +135 -56
  11. data/lib/active_record/associations/association_scope.rb +103 -131
  12. data/lib/active_record/associations/belongs_to_association.rb +67 -54
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -12
  14. data/lib/active_record/associations/builder/association.rb +27 -40
  15. data/lib/active_record/associations/builder/belongs_to.rb +69 -55
  16. data/lib/active_record/associations/builder/collection_association.rb +10 -29
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +60 -70
  18. data/lib/active_record/associations/builder/has_many.rb +8 -4
  19. data/lib/active_record/associations/builder/has_one.rb +46 -5
  20. data/lib/active_record/associations/builder/singular_association.rb +16 -10
  21. data/lib/active_record/associations/collection_association.rb +138 -274
  22. data/lib/active_record/associations/collection_proxy.rb +252 -151
  23. data/lib/active_record/associations/foreign_association.rb +20 -0
  24. data/lib/active_record/associations/has_many_association.rb +35 -83
  25. data/lib/active_record/associations/has_many_through_association.rb +62 -80
  26. data/lib/active_record/associations/has_one_association.rb +62 -49
  27. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  28. data/lib/active_record/associations/join_dependency/join_association.rb +38 -80
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  31. data/lib/active_record/associations/join_dependency.rb +138 -162
  32. data/lib/active_record/associations/preloader/association.rb +90 -119
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  34. data/lib/active_record/associations/preloader.rb +92 -94
  35. data/lib/active_record/associations/singular_association.rb +18 -45
  36. data/lib/active_record/associations/through_association.rb +48 -23
  37. data/lib/active_record/associations.rb +1737 -1596
  38. data/lib/active_record/attribute_assignment.rb +56 -183
  39. data/lib/active_record/attribute_decorators.rb +39 -15
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +15 -5
  41. data/lib/active_record/attribute_methods/dirty.rb +174 -134
  42. data/lib/active_record/attribute_methods/primary_key.rb +91 -83
  43. data/lib/active_record/attribute_methods/query.rb +6 -5
  44. data/lib/active_record/attribute_methods/read.rb +20 -76
  45. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -36
  47. data/lib/active_record/attribute_methods/write.rb +33 -55
  48. data/lib/active_record/attribute_methods.rb +124 -143
  49. data/lib/active_record/attributes.rb +214 -74
  50. data/lib/active_record/autosave_association.rb +115 -46
  51. data/lib/active_record/base.rb +60 -49
  52. data/lib/active_record/callbacks.rb +100 -74
  53. data/lib/active_record/coders/json.rb +3 -1
  54. data/lib/active_record/coders/yaml_column.rb +24 -12
  55. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +796 -290
  56. data/lib/active_record/connection_adapters/abstract/database_limits.rb +26 -8
  57. data/lib/active_record/connection_adapters/abstract/database_statements.rb +247 -108
  58. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -23
  59. data/lib/active_record/connection_adapters/abstract/quoting.rb +171 -53
  60. data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
  61. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +74 -46
  62. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +366 -227
  63. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  64. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +706 -222
  65. data/lib/active_record/connection_adapters/abstract/transaction.rb +191 -87
  66. data/lib/active_record/connection_adapters/abstract_adapter.rb +468 -194
  67. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +535 -597
  68. data/lib/active_record/connection_adapters/column.rb +56 -43
  69. data/lib/active_record/connection_adapters/connection_specification.rb +174 -152
  70. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +200 -0
  73. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  74. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
  79. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
  80. data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -195
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +21 -11
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +65 -115
  83. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -57
  85. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +5 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  89. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -13
  91. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -3
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  94. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  96. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  98. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  102. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +67 -51
  103. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +10 -5
  104. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  106. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  107. data/lib/active_record/connection_adapters/postgresql/quoting.rb +144 -47
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +474 -286
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -363
  116. data/lib/active_record/connection_adapters/schema_cache.rb +72 -25
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
  118. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +288 -359
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +176 -41
  128. data/lib/active_record/core.rb +266 -233
  129. data/lib/active_record/counter_cache.rb +68 -50
  130. data/lib/active_record/database_configurations/database_config.rb +37 -0
  131. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  132. data/lib/active_record/database_configurations/url_config.rb +79 -0
  133. data/lib/active_record/database_configurations.rb +233 -0
  134. data/lib/active_record/define_callbacks.rb +22 -0
  135. data/lib/active_record/dynamic_matchers.rb +87 -105
  136. data/lib/active_record/enum.rb +164 -88
  137. data/lib/active_record/errors.rb +189 -53
  138. data/lib/active_record/explain.rb +23 -11
  139. data/lib/active_record/explain_registry.rb +4 -2
  140. data/lib/active_record/explain_subscriber.rb +11 -6
  141. data/lib/active_record/fixture_set/file.rb +35 -9
  142. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  143. data/lib/active_record/fixture_set/render_context.rb +17 -0
  144. data/lib/active_record/fixture_set/table_row.rb +153 -0
  145. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  146. data/lib/active_record/fixtures.rb +226 -495
  147. data/lib/active_record/gem_version.rb +4 -2
  148. data/lib/active_record/inheritance.rb +158 -112
  149. data/lib/active_record/insert_all.rb +179 -0
  150. data/lib/active_record/integration.rb +123 -29
  151. data/lib/active_record/internal_metadata.rb +53 -0
  152. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  153. data/lib/active_record/locale/en.yml +3 -2
  154. data/lib/active_record/locking/optimistic.rb +91 -98
  155. data/lib/active_record/locking/pessimistic.rb +18 -6
  156. data/lib/active_record/log_subscriber.rb +76 -33
  157. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  158. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  159. data/lib/active_record/middleware/database_selector.rb +75 -0
  160. data/lib/active_record/migration/command_recorder.rb +177 -90
  161. data/lib/active_record/migration/compatibility.rb +244 -0
  162. data/lib/active_record/migration/join_table.rb +8 -6
  163. data/lib/active_record/migration.rb +634 -288
  164. data/lib/active_record/model_schema.rb +314 -112
  165. data/lib/active_record/nested_attributes.rb +266 -214
  166. data/lib/active_record/no_touching.rb +15 -2
  167. data/lib/active_record/null_relation.rb +24 -37
  168. data/lib/active_record/persistence.rb +559 -124
  169. data/lib/active_record/query_cache.rb +19 -23
  170. data/lib/active_record/querying.rb +43 -29
  171. data/lib/active_record/railtie.rb +148 -47
  172. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  173. data/lib/active_record/railties/console_sandbox.rb +2 -0
  174. data/lib/active_record/railties/controller_runtime.rb +34 -33
  175. data/lib/active_record/railties/databases.rake +338 -202
  176. data/lib/active_record/readonly_attributes.rb +5 -4
  177. data/lib/active_record/reflection.rb +460 -299
  178. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  179. data/lib/active_record/relation/batches.rb +207 -55
  180. data/lib/active_record/relation/calculations.rb +269 -248
  181. data/lib/active_record/relation/delegation.rb +70 -80
  182. data/lib/active_record/relation/finder_methods.rb +279 -255
  183. data/lib/active_record/relation/from_clause.rb +26 -0
  184. data/lib/active_record/relation/merger.rb +83 -69
  185. data/lib/active_record/relation/predicate_builder/array_handler.rb +27 -25
  186. data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
  187. data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -0
  188. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  189. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
  190. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  191. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  192. data/lib/active_record/relation/predicate_builder.rb +116 -92
  193. data/lib/active_record/relation/query_attribute.rb +50 -0
  194. data/lib/active_record/relation/query_methods.rb +574 -391
  195. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  196. data/lib/active_record/relation/spawn_methods.rb +18 -16
  197. data/lib/active_record/relation/where_clause.rb +190 -0
  198. data/lib/active_record/relation/where_clause_factory.rb +33 -0
  199. data/lib/active_record/relation.rb +518 -340
  200. data/lib/active_record/result.rb +79 -42
  201. data/lib/active_record/runtime_registry.rb +6 -4
  202. data/lib/active_record/sanitization.rb +144 -121
  203. data/lib/active_record/schema.rb +21 -24
  204. data/lib/active_record/schema_dumper.rb +112 -93
  205. data/lib/active_record/schema_migration.rb +24 -20
  206. data/lib/active_record/scoping/default.rb +101 -84
  207. data/lib/active_record/scoping/named.rb +86 -33
  208. data/lib/active_record/scoping.rb +45 -26
  209. data/lib/active_record/secure_token.rb +40 -0
  210. data/lib/active_record/serialization.rb +5 -5
  211. data/lib/active_record/statement_cache.rb +73 -36
  212. data/lib/active_record/store.rb +127 -42
  213. data/lib/active_record/suppressor.rb +61 -0
  214. data/lib/active_record/table_metadata.rb +75 -0
  215. data/lib/active_record/tasks/database_tasks.rb +309 -99
  216. data/lib/active_record/tasks/mysql_database_tasks.rb +58 -88
  217. data/lib/active_record/tasks/postgresql_database_tasks.rb +82 -31
  218. data/lib/active_record/tasks/sqlite_database_tasks.rb +38 -16
  219. data/lib/active_record/test_databases.rb +23 -0
  220. data/lib/active_record/test_fixtures.rb +224 -0
  221. data/lib/active_record/timestamp.rb +86 -40
  222. data/lib/active_record/touch_later.rb +66 -0
  223. data/lib/active_record/transactions.rb +215 -139
  224. data/lib/active_record/translation.rb +3 -1
  225. data/lib/active_record/type/adapter_specific_registry.rb +129 -0
  226. data/lib/active_record/type/date.rb +4 -41
  227. data/lib/active_record/type/date_time.rb +4 -38
  228. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  229. data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
  230. data/lib/active_record/type/internal/timezone.rb +17 -0
  231. data/lib/active_record/type/json.rb +30 -0
  232. data/lib/active_record/type/serialized.rb +30 -15
  233. data/lib/active_record/type/text.rb +2 -2
  234. data/lib/active_record/type/time.rb +11 -16
  235. data/lib/active_record/type/type_map.rb +15 -17
  236. data/lib/active_record/type/unsigned_integer.rb +9 -7
  237. data/lib/active_record/type.rb +78 -23
  238. data/lib/active_record/type_caster/connection.rb +34 -0
  239. data/lib/active_record/type_caster/map.rb +20 -0
  240. data/lib/active_record/type_caster.rb +9 -0
  241. data/lib/active_record/validations/absence.rb +25 -0
  242. data/lib/active_record/validations/associated.rb +13 -4
  243. data/lib/active_record/validations/length.rb +26 -0
  244. data/lib/active_record/validations/presence.rb +14 -13
  245. data/lib/active_record/validations/uniqueness.rb +43 -46
  246. data/lib/active_record/validations.rb +39 -35
  247. data/lib/active_record/version.rb +3 -1
  248. data/lib/active_record.rb +43 -21
  249. data/lib/arel/alias_predication.rb +9 -0
  250. data/lib/arel/attributes/attribute.rb +37 -0
  251. data/lib/arel/attributes.rb +22 -0
  252. data/lib/arel/collectors/bind.rb +24 -0
  253. data/lib/arel/collectors/composite.rb +31 -0
  254. data/lib/arel/collectors/plain_string.rb +20 -0
  255. data/lib/arel/collectors/sql_string.rb +20 -0
  256. data/lib/arel/collectors/substitute_binds.rb +28 -0
  257. data/lib/arel/crud.rb +42 -0
  258. data/lib/arel/delete_manager.rb +18 -0
  259. data/lib/arel/errors.rb +9 -0
  260. data/lib/arel/expressions.rb +29 -0
  261. data/lib/arel/factory_methods.rb +49 -0
  262. data/lib/arel/insert_manager.rb +49 -0
  263. data/lib/arel/math.rb +45 -0
  264. data/lib/arel/nodes/and.rb +32 -0
  265. data/lib/arel/nodes/ascending.rb +23 -0
  266. data/lib/arel/nodes/binary.rb +52 -0
  267. data/lib/arel/nodes/bind_param.rb +36 -0
  268. data/lib/arel/nodes/case.rb +55 -0
  269. data/lib/arel/nodes/casted.rb +50 -0
  270. data/lib/arel/nodes/comment.rb +29 -0
  271. data/lib/arel/nodes/count.rb +12 -0
  272. data/lib/arel/nodes/delete_statement.rb +45 -0
  273. data/lib/arel/nodes/descending.rb +23 -0
  274. data/lib/arel/nodes/equality.rb +18 -0
  275. data/lib/arel/nodes/extract.rb +24 -0
  276. data/lib/arel/nodes/false.rb +16 -0
  277. data/lib/arel/nodes/full_outer_join.rb +8 -0
  278. data/lib/arel/nodes/function.rb +44 -0
  279. data/lib/arel/nodes/grouping.rb +8 -0
  280. data/lib/arel/nodes/in.rb +8 -0
  281. data/lib/arel/nodes/infix_operation.rb +80 -0
  282. data/lib/arel/nodes/inner_join.rb +8 -0
  283. data/lib/arel/nodes/insert_statement.rb +37 -0
  284. data/lib/arel/nodes/join_source.rb +20 -0
  285. data/lib/arel/nodes/matches.rb +18 -0
  286. data/lib/arel/nodes/named_function.rb +23 -0
  287. data/lib/arel/nodes/node.rb +50 -0
  288. data/lib/arel/nodes/node_expression.rb +13 -0
  289. data/lib/arel/nodes/outer_join.rb +8 -0
  290. data/lib/arel/nodes/over.rb +15 -0
  291. data/lib/arel/nodes/regexp.rb +16 -0
  292. data/lib/arel/nodes/right_outer_join.rb +8 -0
  293. data/lib/arel/nodes/select_core.rb +67 -0
  294. data/lib/arel/nodes/select_statement.rb +41 -0
  295. data/lib/arel/nodes/sql_literal.rb +16 -0
  296. data/lib/arel/nodes/string_join.rb +11 -0
  297. data/lib/arel/nodes/table_alias.rb +27 -0
  298. data/lib/arel/nodes/terminal.rb +16 -0
  299. data/lib/arel/nodes/true.rb +16 -0
  300. data/lib/arel/nodes/unary.rb +45 -0
  301. data/lib/arel/nodes/unary_operation.rb +20 -0
  302. data/lib/arel/nodes/unqualified_column.rb +22 -0
  303. data/lib/arel/nodes/update_statement.rb +41 -0
  304. data/lib/arel/nodes/values_list.rb +9 -0
  305. data/lib/arel/nodes/window.rb +126 -0
  306. data/lib/arel/nodes/with.rb +11 -0
  307. data/lib/arel/nodes.rb +68 -0
  308. data/lib/arel/order_predications.rb +13 -0
  309. data/lib/arel/predications.rb +257 -0
  310. data/lib/arel/select_manager.rb +271 -0
  311. data/lib/arel/table.rb +110 -0
  312. data/lib/arel/tree_manager.rb +72 -0
  313. data/lib/arel/update_manager.rb +34 -0
  314. data/lib/arel/visitors/depth_first.rb +204 -0
  315. data/lib/arel/visitors/dot.rb +297 -0
  316. data/lib/arel/visitors/ibm_db.rb +34 -0
  317. data/lib/arel/visitors/informix.rb +62 -0
  318. data/lib/arel/visitors/mssql.rb +157 -0
  319. data/lib/arel/visitors/mysql.rb +83 -0
  320. data/lib/arel/visitors/oracle.rb +159 -0
  321. data/lib/arel/visitors/oracle12.rb +66 -0
  322. data/lib/arel/visitors/postgresql.rb +110 -0
  323. data/lib/arel/visitors/sqlite.rb +39 -0
  324. data/lib/arel/visitors/to_sql.rb +889 -0
  325. data/lib/arel/visitors/visitor.rb +46 -0
  326. data/lib/arel/visitors/where_sql.rb +23 -0
  327. data/lib/arel/visitors.rb +20 -0
  328. data/lib/arel/window_predications.rb +9 -0
  329. data/lib/arel.rb +51 -0
  330. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  331. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  332. data/lib/rails/generators/active_record/migration/migration_generator.rb +42 -37
  333. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  334. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +11 -8
  335. data/lib/rails/generators/active_record/migration.rb +31 -1
  336. data/lib/rails/generators/active_record/model/model_generator.rb +19 -22
  337. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  338. data/lib/rails/generators/active_record.rb +7 -5
  339. metadata +166 -60
  340. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  341. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  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 -23
  345. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  346. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  347. data/lib/active_record/attribute.rb +0 -149
  348. data/lib/active_record/attribute_set/builder.rb +0 -86
  349. data/lib/active_record/attribute_set.rb +0 -77
  350. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  351. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  352. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  353. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  354. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  355. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  356. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  357. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  358. data/lib/active_record/type/big_integer.rb +0 -13
  359. data/lib/active_record/type/binary.rb +0 -50
  360. data/lib/active_record/type/boolean.rb +0 -30
  361. data/lib/active_record/type/decimal.rb +0 -40
  362. data/lib/active_record/type/decorator.rb +0 -14
  363. data/lib/active_record/type/float.rb +0 -19
  364. data/lib/active_record/type/integer.rb +0 -55
  365. data/lib/active_record/type/mutable.rb +0 -16
  366. data/lib/active_record/type/numeric.rb +0 -36
  367. data/lib/active_record/type/string.rb +0 -36
  368. data/lib/active_record/type/time_value.rb +0 -38
  369. data/lib/active_record/type/value.rb +0 -101
  370. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -22
  371. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
  372. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -1,266 +1,285 @@
1
- module ActiveRecord
2
- # = Active Record Aggregations
3
- module Aggregations # :nodoc:
4
- extend ActiveSupport::Concern
1
+ # frozen_string_literal: true
5
2
 
6
- def clear_aggregation_cache #:nodoc:
7
- @aggregation_cache.clear if persisted?
3
+ module ActiveRecord
4
+ # See ActiveRecord::Aggregations::ClassMethods for documentation
5
+ module Aggregations
6
+ def initialize_dup(*) # :nodoc:
7
+ @aggregation_cache = {}
8
+ super
8
9
  end
9
10
 
10
- # Active Record implements aggregation through a macro-like class method called +composed_of+
11
- # for representing attributes as value objects. It expresses relationships like "Account [is]
12
- # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
13
- # to the macro adds a description of how the value objects are created from the attributes of
14
- # the entity object (when the entity is initialized either as a new object or from finding an
15
- # existing object) and how it can be turned back into attributes (when the entity is saved to
16
- # the database).
17
- #
18
- # class Customer < ActiveRecord::Base
19
- # composed_of :balance, class_name: "Money", mapping: %w(balance amount)
20
- # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
21
- # end
22
- #
23
- # The customer class now has the following methods to manipulate the value objects:
24
- # * <tt>Customer#balance, Customer#balance=(money)</tt>
25
- # * <tt>Customer#address, Customer#address=(address)</tt>
26
- #
27
- # These methods will operate with value objects like the ones described below:
28
- #
29
- # class Money
30
- # include Comparable
31
- # attr_reader :amount, :currency
32
- # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
33
- #
34
- # def initialize(amount, currency = "USD")
35
- # @amount, @currency = amount, currency
36
- # end
37
- #
38
- # def exchange_to(other_currency)
39
- # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
40
- # Money.new(exchanged_amount, other_currency)
41
- # end
42
- #
43
- # def ==(other_money)
44
- # amount == other_money.amount && currency == other_money.currency
45
- # end
46
- #
47
- # def <=>(other_money)
48
- # if currency == other_money.currency
49
- # amount <=> other_money.amount
50
- # else
51
- # amount <=> other_money.exchange_to(currency).amount
52
- # end
53
- # end
54
- # end
55
- #
56
- # class Address
57
- # attr_reader :street, :city
58
- # def initialize(street, city)
59
- # @street, @city = street, city
60
- # end
61
- #
62
- # def close_to?(other_address)
63
- # city == other_address.city
64
- # end
65
- #
66
- # def ==(other_address)
67
- # city == other_address.city && street == other_address.street
68
- # end
69
- # end
70
- #
71
- # Now it's possible to access attributes from the database through the value objects instead. If
72
- # you choose to name the composition the same as the attribute's name, it will be the only way to
73
- # access that attribute. That's the case with our +balance+ attribute. You interact with the value
74
- # objects just like you would with any other attribute:
75
- #
76
- # customer.balance = Money.new(20) # sets the Money value object and the attribute
77
- # customer.balance # => Money value object
78
- # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
79
- # customer.balance > Money.new(10) # => true
80
- # customer.balance == Money.new(20) # => true
81
- # customer.balance < Money.new(5) # => false
82
- #
83
- # Value objects can also be composed of multiple attributes, such as the case of Address. The order
84
- # of the mappings will determine the order of the parameters.
85
- #
86
- # customer.address_street = "Hyancintvej"
87
- # customer.address_city = "Copenhagen"
88
- # customer.address # => Address.new("Hyancintvej", "Copenhagen")
89
- #
90
- # customer.address_street = "Vesterbrogade"
91
- # customer.address # => Address.new("Hyancintvej", "Copenhagen")
92
- # customer.clear_aggregation_cache
93
- # customer.address # => Address.new("Vesterbrogade", "Copenhagen")
94
- #
95
- # customer.address = Address.new("May Street", "Chicago")
96
- # customer.address_street # => "May Street"
97
- # customer.address_city # => "Chicago"
98
- #
99
- # == Writing value objects
100
- #
101
- # Value objects are immutable and interchangeable objects that represent a given value, such as
102
- # a Money object representing $5. Two Money objects both representing $5 should be equal (through
103
- # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
104
- # unlike entity objects where equality is determined by identity. An entity class such as Customer can
105
- # easily have two different objects that both have an address on Hyancintvej. Entity identity is
106
- # determined by object or relational unique identifiers (such as primary keys). Normal
107
- # ActiveRecord::Base classes are entity objects.
108
- #
109
- # It's also important to treat the value objects as immutable. Don't allow the Money object to have
110
- # its amount changed after creation. Create a new Money object with the new value instead. The
111
- # Money#exchange_to method is an example of this. It returns a new value object instead of changing
112
- # its own values. Active Record won't persist value objects that have been changed through means
113
- # other than the writer method.
114
- #
115
- # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
116
- # object. Attempting to change it afterwards will result in a RuntimeError.
117
- #
118
- # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
119
- # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
120
- #
121
- # == Custom constructors and converters
122
- #
123
- # By default value objects are initialized by calling the <tt>new</tt> constructor of the value
124
- # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
125
- # option, as arguments. If the value class doesn't support this convention then +composed_of+ allows
126
- # a custom constructor to be specified.
127
- #
128
- # When a new value is assigned to the value object, the default assumption is that the new value
129
- # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
130
- # converted to an instance of value class if necessary.
131
- #
132
- # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be
133
- # aggregated using the NetAddr::CIDR value class (http://www.ruby-doc.org/gems/docs/n/netaddr-1.5.0/NetAddr/CIDR.html).
134
- # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
135
- # New values can be assigned to the value object using either another NetAddr::CIDR object, a string
136
- # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
137
- # these requirements:
138
- #
139
- # class NetworkResource < ActiveRecord::Base
140
- # composed_of :cidr,
141
- # class_name: 'NetAddr::CIDR',
142
- # mapping: [ %w(network_address network), %w(cidr_range bits) ],
143
- # allow_nil: true,
144
- # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
145
- # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
146
- # end
147
- #
148
- # # This calls the :constructor
149
- # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24)
150
- #
151
- # # These assignments will both use the :converter
152
- # network_resource.cidr = [ '192.168.2.1', 8 ]
153
- # network_resource.cidr = '192.168.0.1/24'
154
- #
155
- # # This assignment won't use the :converter as the value is already an instance of the value class
156
- # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
157
- #
158
- # # Saving and then reloading will use the :constructor on reload
159
- # network_resource.save
160
- # network_resource.reload
161
- #
162
- # == Finding records by a value object
163
- #
164
- # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database
165
- # by specifying an instance of the value object in the conditions hash. The following example
166
- # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
167
- #
168
- # Customer.where(balance: Money.new(20, "USD"))
169
- #
170
- module ClassMethods
171
- # Adds reader and writer methods for manipulating a value object:
172
- # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
173
- #
174
- # Options are:
175
- # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
176
- # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
177
- # to the Address class, but if the real class name is CompanyAddress, you'll have to specify it
178
- # with this option.
179
- # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
180
- # object. Each mapping is represented as an array where the first item is the name of the
181
- # entity attribute and the second item is the name of the attribute in the value object. The
182
- # order in which mappings are defined determines the order in which attributes are sent to the
183
- # value class constructor.
184
- # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
185
- # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
186
- # mapped attributes.
187
- # This defaults to +false+.
188
- # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
189
- # is called to initialize the value object. The constructor is passed all of the mapped attributes,
190
- # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
191
- # to instantiate a <tt>:class_name</tt> object.
192
- # The default is <tt>:new</tt>.
193
- # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
194
- # or a Proc that is called when a new value is assigned to the value object. The converter is
195
- # passed the single value that is used in the assignment and is only called if the new value is
196
- # not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
197
- # can return nil to skip the assignment.
198
- #
199
- # Option examples:
200
- # composed_of :temperature, mapping: %w(reading celsius)
201
- # composed_of :balance, class_name: "Money", mapping: %w(balance amount),
202
- # converter: Proc.new { |balance| balance.to_money }
203
- # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
204
- # composed_of :gps_location
205
- # composed_of :gps_location, allow_nil: true
206
- # composed_of :ip_address,
207
- # class_name: 'IPAddr',
208
- # mapping: %w(ip to_i),
209
- # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
210
- # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
211
- #
212
- def composed_of(part_id, options = {})
213
- options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
11
+ def reload(*) # :nodoc:
12
+ clear_aggregation_cache
13
+ super
14
+ end
214
15
 
215
- name = part_id.id2name
216
- class_name = options[:class_name] || name.camelize
217
- mapping = options[:mapping] || [ name, name ]
218
- mapping = [ mapping ] unless mapping.first.is_a?(Array)
219
- allow_nil = options[:allow_nil] || false
220
- constructor = options[:constructor] || :new
221
- converter = options[:converter]
16
+ private
222
17
 
223
- reader_method(name, class_name, mapping, allow_nil, constructor)
224
- writer_method(name, class_name, mapping, allow_nil, converter)
18
+ def clear_aggregation_cache
19
+ @aggregation_cache.clear if persisted?
20
+ end
225
21
 
226
- reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self)
227
- Reflection.add_aggregate_reflection self, part_id, reflection
22
+ def init_internals
23
+ @aggregation_cache = {}
24
+ super
228
25
  end
229
26
 
230
- private
231
- def reader_method(name, class_name, mapping, allow_nil, constructor)
232
- define_method(name) do
233
- if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !_read_attribute(key).nil? })
234
- attrs = mapping.collect {|key, _| _read_attribute(key)}
235
- object = constructor.respond_to?(:call) ?
236
- constructor.call(*attrs) :
237
- class_name.constantize.send(constructor, *attrs)
238
- @aggregation_cache[name] = object
239
- end
240
- @aggregation_cache[name]
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(balance amount)
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 +address_street+ equal to "May Street" and +address_city+ equal to "Chicago":
179
+ #
180
+ # Customer.where(address: Address.new("May Street", "Chicago"))
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
+ # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
215
+ # composed_of :gps_location
216
+ # composed_of :gps_location, allow_nil: true
217
+ # composed_of :ip_address,
218
+ # class_name: 'IPAddr',
219
+ # mapping: %w(ip to_i),
220
+ # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
221
+ # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
222
+ #
223
+ def composed_of(part_id, options = {})
224
+ options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
225
+
226
+ unless self < Aggregations
227
+ include Aggregations
241
228
  end
229
+
230
+ name = part_id.id2name
231
+ class_name = options[:class_name] || name.camelize
232
+ mapping = options[:mapping] || [ name, name ]
233
+ mapping = [ mapping ] unless mapping.first.is_a?(Array)
234
+ allow_nil = options[:allow_nil] || false
235
+ constructor = options[:constructor] || :new
236
+ converter = options[:converter]
237
+
238
+ reader_method(name, class_name, mapping, allow_nil, constructor)
239
+ writer_method(name, class_name, mapping, allow_nil, converter)
240
+
241
+ reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self)
242
+ Reflection.add_aggregate_reflection self, part_id, reflection
242
243
  end
243
244
 
244
- def writer_method(name, class_name, mapping, allow_nil, converter)
245
- define_method("#{name}=") do |part|
246
- klass = class_name.constantize
247
- if part.is_a?(Hash)
248
- part = klass.new(*part.values)
245
+ private
246
+ def reader_method(name, class_name, mapping, allow_nil, constructor)
247
+ define_method(name) do
248
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? { |key, _| !_read_attribute(key).nil? })
249
+ attrs = mapping.collect { |key, _| _read_attribute(key) }
250
+ object = constructor.respond_to?(:call) ?
251
+ constructor.call(*attrs) :
252
+ class_name.constantize.send(constructor, *attrs)
253
+ @aggregation_cache[name] = object
254
+ end
255
+ @aggregation_cache[name]
249
256
  end
257
+ end
250
258
 
251
- unless part.is_a?(klass) || converter.nil? || part.nil?
252
- part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
253
- end
259
+ def writer_method(name, class_name, mapping, allow_nil, converter)
260
+ define_method("#{name}=") do |part|
261
+ klass = class_name.constantize
262
+
263
+ unless part.is_a?(klass) || converter.nil? || part.nil?
264
+ part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
265
+ end
266
+
267
+ hash_from_multiparameter_assignment = part.is_a?(Hash) &&
268
+ part.each_key.all? { |k| k.is_a?(Integer) }
269
+ if hash_from_multiparameter_assignment
270
+ raise ArgumentError unless part.size == part.each_key.max
271
+ part = klass.new(*part.sort.map(&:last))
272
+ end
254
273
 
255
- if part.nil? && allow_nil
256
- mapping.each { |key, _| self[key] = nil }
257
- @aggregation_cache[name] = nil
258
- else
259
- mapping.each { |key, value| self[key] = part.send(value) }
260
- @aggregation_cache[name] = part.freeze
274
+ if part.nil? && allow_nil
275
+ mapping.each { |key, _| self[key] = nil }
276
+ @aggregation_cache[name] = nil
277
+ else
278
+ mapping.each { |key, value| self[key] = part.send(value) }
279
+ @aggregation_cache[name] = part.freeze
280
+ end
261
281
  end
262
282
  end
263
- end
264
- end
283
+ end
265
284
  end
266
285
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  class AssociationRelation < Relation
3
- def initialize(klass, table, association)
4
- super(klass, table)
5
+ def initialize(klass, association)
6
+ super(klass)
5
7
  @association = association
6
8
  end
7
9
 
@@ -10,13 +12,29 @@ module ActiveRecord
10
12
  end
11
13
 
12
14
  def ==(other)
13
- other == to_a
15
+ other == records
14
16
  end
15
17
 
16
- private
18
+ def build(*args, &block)
19
+ scoping { @association.build(*args, &block) }
20
+ end
21
+ alias new build
22
+
23
+ def create(*args, &block)
24
+ scoping { @association.create(*args, &block) }
25
+ end
17
26
 
18
- def exec_queries
19
- super.each { |r| @association.set_inverse_instance r }
27
+ def create!(*args, &block)
28
+ scoping { @association.create!(*args, &block) }
20
29
  end
30
+
31
+ private
32
+
33
+ def exec_queries
34
+ super do |record|
35
+ @association.set_inverse_instance_from_queries(record)
36
+ yield record if block_given?
37
+ end
38
+ end
21
39
  end
22
40
  end