activerecord 5.2.3 → 6.0.0.rc2

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 (266) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +605 -554
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +4 -2
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +9 -2
  7. data/lib/active_record/aggregations.rb +4 -2
  8. data/lib/active_record/associations.rb +19 -14
  9. data/lib/active_record/associations/association.rb +52 -19
  10. data/lib/active_record/associations/association_scope.rb +4 -6
  11. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  13. data/lib/active_record/associations/builder/association.rb +14 -18
  14. data/lib/active_record/associations/builder/belongs_to.rb +19 -52
  15. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  17. data/lib/active_record/associations/builder/has_many.rb +2 -0
  18. data/lib/active_record/associations/builder/has_one.rb +35 -1
  19. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  20. data/lib/active_record/associations/collection_association.rb +6 -21
  21. data/lib/active_record/associations/collection_proxy.rb +12 -15
  22. data/lib/active_record/associations/foreign_association.rb +7 -0
  23. data/lib/active_record/associations/has_many_association.rb +2 -10
  24. data/lib/active_record/associations/has_many_through_association.rb +18 -25
  25. data/lib/active_record/associations/has_one_association.rb +28 -30
  26. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  27. data/lib/active_record/associations/join_dependency.rb +24 -28
  28. data/lib/active_record/associations/join_dependency/join_association.rb +27 -7
  29. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  30. data/lib/active_record/associations/preloader.rb +39 -31
  31. data/lib/active_record/associations/preloader/association.rb +38 -36
  32. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  33. data/lib/active_record/associations/singular_association.rb +2 -16
  34. data/lib/active_record/attribute_assignment.rb +7 -10
  35. data/lib/active_record/attribute_methods.rb +28 -100
  36. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  37. data/lib/active_record/attribute_methods/dirty.rb +111 -40
  38. data/lib/active_record/attribute_methods/primary_key.rb +15 -22
  39. data/lib/active_record/attribute_methods/query.rb +2 -3
  40. data/lib/active_record/attribute_methods/read.rb +15 -53
  41. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  42. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  43. data/lib/active_record/attribute_methods/write.rb +17 -24
  44. data/lib/active_record/attributes.rb +13 -0
  45. data/lib/active_record/autosave_association.rb +15 -5
  46. data/lib/active_record/base.rb +2 -3
  47. data/lib/active_record/callbacks.rb +5 -19
  48. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +111 -19
  49. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +95 -123
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +20 -11
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +19 -12
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +76 -48
  55. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +132 -53
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -56
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +172 -41
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +128 -194
  60. data/lib/active_record/connection_adapters/column.rb +17 -13
  61. data/lib/active_record/connection_adapters/connection_specification.rb +52 -42
  62. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +73 -13
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  67. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  68. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +129 -13
  69. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  70. data/lib/active_record/connection_adapters/mysql2_adapter.rb +24 -7
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +20 -1
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  75. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  76. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  79. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  80. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  81. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
  82. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +12 -1
  83. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  84. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +55 -53
  85. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +160 -74
  87. data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
  88. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  89. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  90. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -6
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +42 -11
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +127 -143
  93. data/lib/active_record/connection_handling.rb +149 -27
  94. data/lib/active_record/core.rb +100 -60
  95. data/lib/active_record/counter_cache.rb +4 -29
  96. data/lib/active_record/database_configurations.rb +204 -0
  97. data/lib/active_record/database_configurations/database_config.rb +37 -0
  98. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  99. data/lib/active_record/database_configurations/url_config.rb +79 -0
  100. data/lib/active_record/dynamic_matchers.rb +1 -1
  101. data/lib/active_record/enum.rb +28 -7
  102. data/lib/active_record/errors.rb +15 -7
  103. data/lib/active_record/explain.rb +1 -1
  104. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  105. data/lib/active_record/fixture_set/render_context.rb +17 -0
  106. data/lib/active_record/fixture_set/table_row.rb +153 -0
  107. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  108. data/lib/active_record/fixtures.rb +140 -472
  109. data/lib/active_record/gem_version.rb +4 -4
  110. data/lib/active_record/inheritance.rb +13 -3
  111. data/lib/active_record/insert_all.rb +180 -0
  112. data/lib/active_record/integration.rb +68 -16
  113. data/lib/active_record/internal_metadata.rb +10 -2
  114. data/lib/active_record/locking/optimistic.rb +5 -6
  115. data/lib/active_record/locking/pessimistic.rb +3 -3
  116. data/lib/active_record/log_subscriber.rb +7 -26
  117. data/lib/active_record/middleware/database_selector.rb +75 -0
  118. data/lib/active_record/middleware/database_selector/resolver.rb +90 -0
  119. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  120. data/lib/active_record/migration.rb +87 -76
  121. data/lib/active_record/migration/command_recorder.rb +50 -6
  122. data/lib/active_record/migration/compatibility.rb +76 -49
  123. data/lib/active_record/model_schema.rb +30 -9
  124. data/lib/active_record/nested_attributes.rb +2 -2
  125. data/lib/active_record/no_touching.rb +7 -0
  126. data/lib/active_record/persistence.rb +228 -24
  127. data/lib/active_record/query_cache.rb +11 -4
  128. data/lib/active_record/querying.rb +32 -20
  129. data/lib/active_record/railtie.rb +80 -43
  130. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  131. data/lib/active_record/railties/controller_runtime.rb +30 -35
  132. data/lib/active_record/railties/databases.rake +193 -46
  133. data/lib/active_record/reflection.rb +42 -44
  134. data/lib/active_record/relation.rb +310 -80
  135. data/lib/active_record/relation/batches.rb +13 -10
  136. data/lib/active_record/relation/calculations.rb +58 -49
  137. data/lib/active_record/relation/delegation.rb +26 -43
  138. data/lib/active_record/relation/finder_methods.rb +14 -27
  139. data/lib/active_record/relation/merger.rb +11 -20
  140. data/lib/active_record/relation/predicate_builder.rb +4 -6
  141. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  142. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  143. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  144. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  145. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  146. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  147. data/lib/active_record/relation/query_attribute.rb +13 -8
  148. data/lib/active_record/relation/query_methods.rb +182 -68
  149. data/lib/active_record/relation/spawn_methods.rb +1 -1
  150. data/lib/active_record/relation/where_clause.rb +14 -10
  151. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  152. data/lib/active_record/result.rb +30 -11
  153. data/lib/active_record/sanitization.rb +32 -40
  154. data/lib/active_record/schema.rb +2 -11
  155. data/lib/active_record/schema_dumper.rb +22 -7
  156. data/lib/active_record/schema_migration.rb +5 -1
  157. data/lib/active_record/scoping.rb +8 -8
  158. data/lib/active_record/scoping/default.rb +6 -7
  159. data/lib/active_record/scoping/named.rb +19 -15
  160. data/lib/active_record/statement_cache.rb +32 -5
  161. data/lib/active_record/store.rb +87 -8
  162. data/lib/active_record/table_metadata.rb +10 -17
  163. data/lib/active_record/tasks/database_tasks.rb +159 -25
  164. data/lib/active_record/tasks/mysql_database_tasks.rb +5 -5
  165. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  166. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  167. data/lib/active_record/test_databases.rb +38 -0
  168. data/lib/active_record/test_fixtures.rb +224 -0
  169. data/lib/active_record/timestamp.rb +39 -25
  170. data/lib/active_record/touch_later.rb +4 -2
  171. data/lib/active_record/transactions.rb +56 -65
  172. data/lib/active_record/translation.rb +1 -1
  173. data/lib/active_record/type.rb +3 -4
  174. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  175. data/lib/active_record/type_caster/connection.rb +15 -14
  176. data/lib/active_record/type_caster/map.rb +1 -4
  177. data/lib/active_record/validations/uniqueness.rb +15 -27
  178. data/lib/arel.rb +51 -0
  179. data/lib/arel/alias_predication.rb +9 -0
  180. data/lib/arel/attributes.rb +22 -0
  181. data/lib/arel/attributes/attribute.rb +37 -0
  182. data/lib/arel/collectors/bind.rb +24 -0
  183. data/lib/arel/collectors/composite.rb +31 -0
  184. data/lib/arel/collectors/plain_string.rb +20 -0
  185. data/lib/arel/collectors/sql_string.rb +20 -0
  186. data/lib/arel/collectors/substitute_binds.rb +28 -0
  187. data/lib/arel/crud.rb +42 -0
  188. data/lib/arel/delete_manager.rb +18 -0
  189. data/lib/arel/errors.rb +9 -0
  190. data/lib/arel/expressions.rb +29 -0
  191. data/lib/arel/factory_methods.rb +49 -0
  192. data/lib/arel/insert_manager.rb +49 -0
  193. data/lib/arel/math.rb +45 -0
  194. data/lib/arel/nodes.rb +68 -0
  195. data/lib/arel/nodes/and.rb +32 -0
  196. data/lib/arel/nodes/ascending.rb +23 -0
  197. data/lib/arel/nodes/binary.rb +52 -0
  198. data/lib/arel/nodes/bind_param.rb +36 -0
  199. data/lib/arel/nodes/case.rb +55 -0
  200. data/lib/arel/nodes/casted.rb +50 -0
  201. data/lib/arel/nodes/comment.rb +29 -0
  202. data/lib/arel/nodes/count.rb +12 -0
  203. data/lib/arel/nodes/delete_statement.rb +45 -0
  204. data/lib/arel/nodes/descending.rb +23 -0
  205. data/lib/arel/nodes/equality.rb +18 -0
  206. data/lib/arel/nodes/extract.rb +24 -0
  207. data/lib/arel/nodes/false.rb +16 -0
  208. data/lib/arel/nodes/full_outer_join.rb +8 -0
  209. data/lib/arel/nodes/function.rb +44 -0
  210. data/lib/arel/nodes/grouping.rb +8 -0
  211. data/lib/arel/nodes/in.rb +8 -0
  212. data/lib/arel/nodes/infix_operation.rb +80 -0
  213. data/lib/arel/nodes/inner_join.rb +8 -0
  214. data/lib/arel/nodes/insert_statement.rb +37 -0
  215. data/lib/arel/nodes/join_source.rb +20 -0
  216. data/lib/arel/nodes/matches.rb +18 -0
  217. data/lib/arel/nodes/named_function.rb +23 -0
  218. data/lib/arel/nodes/node.rb +50 -0
  219. data/lib/arel/nodes/node_expression.rb +13 -0
  220. data/lib/arel/nodes/outer_join.rb +8 -0
  221. data/lib/arel/nodes/over.rb +15 -0
  222. data/lib/arel/nodes/regexp.rb +16 -0
  223. data/lib/arel/nodes/right_outer_join.rb +8 -0
  224. data/lib/arel/nodes/select_core.rb +67 -0
  225. data/lib/arel/nodes/select_statement.rb +41 -0
  226. data/lib/arel/nodes/sql_literal.rb +16 -0
  227. data/lib/arel/nodes/string_join.rb +11 -0
  228. data/lib/arel/nodes/table_alias.rb +27 -0
  229. data/lib/arel/nodes/terminal.rb +16 -0
  230. data/lib/arel/nodes/true.rb +16 -0
  231. data/lib/arel/nodes/unary.rb +45 -0
  232. data/lib/arel/nodes/unary_operation.rb +20 -0
  233. data/lib/arel/nodes/unqualified_column.rb +22 -0
  234. data/lib/arel/nodes/update_statement.rb +41 -0
  235. data/lib/arel/nodes/values_list.rb +9 -0
  236. data/lib/arel/nodes/window.rb +126 -0
  237. data/lib/arel/nodes/with.rb +11 -0
  238. data/lib/arel/order_predications.rb +13 -0
  239. data/lib/arel/predications.rb +257 -0
  240. data/lib/arel/select_manager.rb +271 -0
  241. data/lib/arel/table.rb +110 -0
  242. data/lib/arel/tree_manager.rb +72 -0
  243. data/lib/arel/update_manager.rb +34 -0
  244. data/lib/arel/visitors.rb +20 -0
  245. data/lib/arel/visitors/depth_first.rb +204 -0
  246. data/lib/arel/visitors/dot.rb +297 -0
  247. data/lib/arel/visitors/ibm_db.rb +34 -0
  248. data/lib/arel/visitors/informix.rb +62 -0
  249. data/lib/arel/visitors/mssql.rb +157 -0
  250. data/lib/arel/visitors/mysql.rb +83 -0
  251. data/lib/arel/visitors/oracle.rb +159 -0
  252. data/lib/arel/visitors/oracle12.rb +66 -0
  253. data/lib/arel/visitors/postgresql.rb +110 -0
  254. data/lib/arel/visitors/sqlite.rb +39 -0
  255. data/lib/arel/visitors/to_sql.rb +889 -0
  256. data/lib/arel/visitors/visitor.rb +46 -0
  257. data/lib/arel/visitors/where_sql.rb +23 -0
  258. data/lib/arel/window_predications.rb +9 -0
  259. data/lib/rails/generators/active_record/migration.rb +14 -1
  260. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  261. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  262. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  263. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  264. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  265. metadata +109 -27
  266. data/lib/active_record/collection_cache_key.rb +0 -53
@@ -44,7 +44,7 @@ module ActiveRecord
44
44
  def initialize(values)
45
45
  @values = values
46
46
  @indexes = values.each_with_index.find_all { |thing, i|
47
- Arel::Nodes::BindParam === thing
47
+ Substitute === thing
48
48
  }.map(&:last)
49
49
  end
50
50
 
@@ -56,6 +56,28 @@ module ActiveRecord
56
56
  end
57
57
  end
58
58
 
59
+ class PartialQueryCollector
60
+ def initialize
61
+ @parts = []
62
+ @binds = []
63
+ end
64
+
65
+ def <<(str)
66
+ @parts << str
67
+ self
68
+ end
69
+
70
+ def add_bind(obj)
71
+ @binds << obj
72
+ @parts << Substitute.new
73
+ self
74
+ end
75
+
76
+ def value
77
+ [@parts, @binds]
78
+ end
79
+ end
80
+
59
81
  def self.query(sql)
60
82
  Query.new(sql)
61
83
  end
@@ -64,6 +86,10 @@ module ActiveRecord
64
86
  PartialQuery.new(values)
65
87
  end
66
88
 
89
+ def self.partial_query_collector
90
+ PartialQueryCollector.new
91
+ end
92
+
67
93
  class Params # :nodoc:
68
94
  def bind; Substitute.new; end
69
95
  end
@@ -87,8 +113,8 @@ module ActiveRecord
87
113
  end
88
114
  end
89
115
 
90
- def self.create(connection, block = Proc.new)
91
- relation = block.call Params.new
116
+ def self.create(connection, callable = nil, &block)
117
+ relation = (callable || block).call Params.new
92
118
  query_builder, binds = connection.cacheable_query(self, relation.arel)
93
119
  bind_map = BindMap.new(binds)
94
120
  new(query_builder, bind_map, relation.klass)
@@ -106,6 +132,8 @@ module ActiveRecord
106
132
  sql = query_builder.sql_for bind_values, connection
107
133
 
108
134
  klass.find_by_sql(sql, bind_values, preparable: true, &block)
135
+ rescue ::RangeError
136
+ nil
109
137
  end
110
138
 
111
139
  def self.unsupported_value?(value)
@@ -114,8 +142,7 @@ module ActiveRecord
114
142
  end
115
143
  end
116
144
 
117
- protected
118
-
145
+ private
119
146
  attr_reader :query_builder, :bind_map, :klass
120
147
  end
121
148
  end
@@ -11,14 +11,20 @@ module ActiveRecord
11
11
  # of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's
12
12
  # already built around just accessing attributes on the model.
13
13
  #
14
+ # Every accessor comes with dirty tracking methods (+key_changed?+, +key_was+ and +key_change+) and
15
+ # methods to access the changes made during the last save (+saved_change_to_key?+, +saved_change_to_key+ and
16
+ # +key_before_last_save+).
17
+ #
18
+ # NOTE: There is no +key_will_change!+ method for accessors, use +store_will_change!+ instead.
19
+ #
14
20
  # Make sure that you declare the database column used for the serialized store as a text, so there's
15
21
  # plenty of room.
16
22
  #
17
23
  # You can set custom coder to encode/decode your serialized attributes to/from different formats.
18
24
  # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
19
25
  #
20
- # NOTE: If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for
21
- # the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store].
26
+ # NOTE: If you are using structured database data types (eg. PostgreSQL +hstore+/+json+, or MySQL 5.7+
27
+ # +json+) there is no need for the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store].
22
28
  # Simply use {.store_accessor}[rdoc-ref:ClassMethods#store_accessor] instead to generate
23
29
  # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access
24
30
  # using a symbol.
@@ -31,24 +37,40 @@ module ActiveRecord
31
37
  #
32
38
  # class User < ActiveRecord::Base
33
39
  # store :settings, accessors: [ :color, :homepage ], coder: JSON
40
+ # store :parent, accessors: [ :name ], coder: JSON, prefix: true
41
+ # store :spouse, accessors: [ :name ], coder: JSON, prefix: :partner
42
+ # store :settings, accessors: [ :two_factor_auth ], suffix: true
43
+ # store :settings, accessors: [ :login_retry ], suffix: :config
34
44
  # end
35
45
  #
36
- # u = User.new(color: 'black', homepage: '37signals.com')
46
+ # u = User.new(color: 'black', homepage: '37signals.com', parent_name: 'Mary', partner_name: 'Lily')
37
47
  # u.color # Accessor stored attribute
48
+ # u.parent_name # Accessor stored attribute with prefix
49
+ # u.partner_name # Accessor stored attribute with custom prefix
50
+ # u.two_factor_auth_settings # Accessor stored attribute with suffix
51
+ # u.login_retry_config # Accessor stored attribute with custom suffix
38
52
  # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
39
53
  #
40
54
  # # There is no difference between strings and symbols for accessing custom attributes
41
55
  # u.settings[:country] # => 'Denmark'
42
56
  # u.settings['country'] # => 'Denmark'
43
57
  #
58
+ # # Dirty tracking
59
+ # u.color = 'green'
60
+ # u.color_changed? # => true
61
+ # u.color_was # => 'black'
62
+ # u.color_change # => ['black', 'red']
63
+ #
44
64
  # # Add additional accessors to an existing store through store_accessor
45
65
  # class SuperUser < User
46
66
  # store_accessor :settings, :privileges, :servants
67
+ # store_accessor :parent, :birthday, prefix: true
68
+ # store_accessor :settings, :secret_question, suffix: :config
47
69
  # end
48
70
  #
49
71
  # The stored attribute names can be retrieved using {.stored_attributes}[rdoc-ref:rdoc-ref:ClassMethods#stored_attributes].
50
72
  #
51
- # User.stored_attributes[:settings] # [:color, :homepage]
73
+ # User.stored_attributes[:settings] # [:color, :homepage, :two_factor_auth, :login_retry]
52
74
  #
53
75
  # == Overwriting default accessors
54
76
  #
@@ -81,21 +103,78 @@ module ActiveRecord
81
103
  module ClassMethods
82
104
  def store(store_attribute, options = {})
83
105
  serialize store_attribute, IndifferentCoder.new(store_attribute, options[:coder])
84
- store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
106
+ store_accessor(store_attribute, options[:accessors], options.slice(:prefix, :suffix)) if options.has_key? :accessors
85
107
  end
86
108
 
87
- def store_accessor(store_attribute, *keys)
109
+ def store_accessor(store_attribute, *keys, prefix: nil, suffix: nil)
88
110
  keys = keys.flatten
89
111
 
112
+ accessor_prefix =
113
+ case prefix
114
+ when String, Symbol
115
+ "#{prefix}_"
116
+ when TrueClass
117
+ "#{store_attribute}_"
118
+ else
119
+ ""
120
+ end
121
+ accessor_suffix =
122
+ case suffix
123
+ when String, Symbol
124
+ "_#{suffix}"
125
+ when TrueClass
126
+ "_#{store_attribute}"
127
+ else
128
+ ""
129
+ end
130
+
90
131
  _store_accessors_module.module_eval do
91
132
  keys.each do |key|
92
- define_method("#{key}=") do |value|
133
+ accessor_key = "#{accessor_prefix}#{key}#{accessor_suffix}"
134
+
135
+ define_method("#{accessor_key}=") do |value|
93
136
  write_store_attribute(store_attribute, key, value)
94
137
  end
95
138
 
96
- define_method(key) do
139
+ define_method(accessor_key) do
97
140
  read_store_attribute(store_attribute, key)
98
141
  end
142
+
143
+ define_method("#{accessor_key}_changed?") do
144
+ return false unless attribute_changed?(store_attribute)
145
+ prev_store, new_store = changes[store_attribute]
146
+ prev_store&.dig(key) != new_store&.dig(key)
147
+ end
148
+
149
+ define_method("#{accessor_key}_change") do
150
+ return unless attribute_changed?(store_attribute)
151
+ prev_store, new_store = changes[store_attribute]
152
+ [prev_store&.dig(key), new_store&.dig(key)]
153
+ end
154
+
155
+ define_method("#{accessor_key}_was") do
156
+ return unless attribute_changed?(store_attribute)
157
+ prev_store, _new_store = changes[store_attribute]
158
+ prev_store&.dig(key)
159
+ end
160
+
161
+ define_method("saved_change_to_#{accessor_key}?") do
162
+ return false unless saved_change_to_attribute?(store_attribute)
163
+ prev_store, new_store = saved_change_to_attribute(store_attribute)
164
+ prev_store&.dig(key) != new_store&.dig(key)
165
+ end
166
+
167
+ define_method("saved_change_to_#{accessor_key}") do
168
+ return unless saved_change_to_attribute?(store_attribute)
169
+ prev_store, new_store = saved_change_to_attribute(store_attribute)
170
+ [prev_store&.dig(key), new_store&.dig(key)]
171
+ end
172
+
173
+ define_method("#{accessor_key}_before_last_save") do
174
+ return unless saved_change_to_attribute?(store_attribute)
175
+ prev_store, _new_store = saved_change_to_attribute(store_attribute)
176
+ prev_store&.dig(key)
177
+ end
99
178
  end
100
179
  end
101
180
 
@@ -4,17 +4,18 @@ module ActiveRecord
4
4
  class TableMetadata # :nodoc:
5
5
  delegate :foreign_type, :foreign_key, :join_primary_key, :join_foreign_key, to: :association, prefix: true
6
6
 
7
- def initialize(klass, arel_table, association = nil)
7
+ def initialize(klass, arel_table, association = nil, types = klass)
8
8
  @klass = klass
9
+ @types = types
9
10
  @arel_table = arel_table
10
11
  @association = association
11
12
  end
12
13
 
13
14
  def resolve_column_aliases(hash)
14
15
  new_hash = hash.dup
15
- hash.each do |key, _|
16
- if (key.is_a?(Symbol)) && klass.attribute_alias?(key)
17
- new_hash[klass.attribute_alias(key)] = new_hash.delete(key)
16
+ hash.each_key do |key|
17
+ if key.is_a?(Symbol) && new_key = klass.attribute_aliases[key.to_s]
18
+ new_hash[new_key] = new_hash.delete(key)
18
19
  end
19
20
  end
20
21
  new_hash
@@ -29,11 +30,7 @@ module ActiveRecord
29
30
  end
30
31
 
31
32
  def type(column_name)
32
- if klass
33
- klass.type_for_attribute(column_name)
34
- else
35
- Type.default_value
36
- end
33
+ types.type_for_attribute(column_name)
37
34
  end
38
35
 
39
36
  def has_column?(column_name)
@@ -52,13 +49,12 @@ module ActiveRecord
52
49
  elsif association && !association.polymorphic?
53
50
  association_klass = association.klass
54
51
  arel_table = association_klass.arel_table.alias(table_name)
52
+ TableMetadata.new(association_klass, arel_table, association)
55
53
  else
56
54
  type_caster = TypeCaster::Connection.new(klass, table_name)
57
- association_klass = nil
58
55
  arel_table = Arel::Table.new(table_name, type_caster: type_caster)
56
+ TableMetadata.new(nil, arel_table, association, type_caster)
59
57
  end
60
-
61
- TableMetadata.new(association_klass, arel_table, association)
62
58
  end
63
59
 
64
60
  def polymorphic_association?
@@ -73,10 +69,7 @@ module ActiveRecord
73
69
  klass.reflect_on_aggregation(aggregation_name)
74
70
  end
75
71
 
76
- # TODO Change this to private once we've dropped Ruby 2.2 support.
77
- # Workaround for Ruby 2.2 "private attribute?" warning.
78
- protected
79
-
80
- attr_reader :klass, :arel_table, :association
72
+ private
73
+ attr_reader :klass, :types, :arel_table, :association
81
74
  end
82
75
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_record/database_configurations"
4
+
3
5
  module ActiveRecord
4
6
  module Tasks # :nodoc:
5
7
  class DatabaseAlreadyExists < StandardError; end # :nodoc:
@@ -8,7 +10,7 @@ module ActiveRecord
8
10
  # ActiveRecord::Tasks::DatabaseTasks is a utility class, which encapsulates
9
11
  # logic behind common tasks used to manage database and migrations.
10
12
  #
11
- # The tasks defined here are used with Rake tasks provided by Active Record.
13
+ # The tasks defined here are used with Rails commands provided by Active Record.
12
14
  #
13
15
  # In order to use DatabaseTasks, a few config values need to be set. All the needed
14
16
  # config values are set by Rails already, so it's necessary to do it only if you
@@ -101,25 +103,30 @@ module ActiveRecord
101
103
  @env ||= Rails.env
102
104
  end
103
105
 
106
+ def spec
107
+ @spec ||= "primary"
108
+ end
109
+
104
110
  def seed_loader
105
111
  @seed_loader ||= Rails.application
106
112
  end
107
113
 
108
114
  def current_config(options = {})
109
115
  options.reverse_merge! env: env
116
+ options[:spec] ||= "primary"
110
117
  if options.has_key?(:config)
111
118
  @current_config = options[:config]
112
119
  else
113
- @current_config ||= ActiveRecord::Base.configurations[options[:env]]
120
+ @current_config ||= ActiveRecord::Base.configurations.configs_for(env_name: options[:env], spec_name: options[:spec]).config
114
121
  end
115
122
  end
116
123
 
117
124
  def create(*arguments)
118
125
  configuration = arguments.first
119
126
  class_for_adapter(configuration["adapter"]).new(*arguments).create
120
- $stdout.puts "Created database '#{configuration['database']}'"
127
+ $stdout.puts "Created database '#{configuration['database']}'" if verbose?
121
128
  rescue DatabaseAlreadyExists
122
- $stderr.puts "Database '#{configuration['database']}' already exists"
129
+ $stderr.puts "Database '#{configuration['database']}' already exists" if verbose?
123
130
  rescue Exception => error
124
131
  $stderr.puts error
125
132
  $stderr.puts "Couldn't create '#{configuration['database']}' database. Please check your configuration."
@@ -134,8 +141,45 @@ module ActiveRecord
134
141
  end
135
142
  end
136
143
 
137
- def create_current(environment = env)
138
- each_current_configuration(environment) { |configuration|
144
+ def setup_initial_database_yaml
145
+ return {} unless defined?(Rails)
146
+
147
+ begin
148
+ Rails.application.config.load_database_yaml
149
+ rescue
150
+ $stderr.puts "Rails couldn't infer whether you are using multiple databases from your database.yml and can't generate the tasks for the non-primary databases. If you'd like to use this feature, please simplify your ERB."
151
+
152
+ {}
153
+ end
154
+ end
155
+
156
+ def for_each(databases)
157
+ database_configs = ActiveRecord::DatabaseConfigurations.new(databases).configs_for(env_name: Rails.env)
158
+
159
+ # if this is a single database application we don't want tasks for each primary database
160
+ return if database_configs.count == 1
161
+
162
+ database_configs.each do |db_config|
163
+ yield db_config.spec_name
164
+ end
165
+ end
166
+
167
+ def raise_for_multi_db(environment = env, command:)
168
+ db_configs = ActiveRecord::Base.configurations.configs_for(env_name: environment)
169
+
170
+ if db_configs.count > 1
171
+ dbs_list = []
172
+
173
+ db_configs.each do |db|
174
+ dbs_list << "#{command}:#{db.spec_name}"
175
+ end
176
+
177
+ raise "You're using a multiple database application. To use `#{command}` you must run the namespaced task with a VERSION. Available tasks are #{dbs_list.to_sentence}."
178
+ end
179
+ end
180
+
181
+ def create_current(environment = env, spec_name = nil)
182
+ each_current_configuration(environment, spec_name) { |configuration|
139
183
  create configuration
140
184
  }
141
185
  ActiveRecord::Base.establish_connection(environment.to_sym)
@@ -144,7 +188,7 @@ module ActiveRecord
144
188
  def drop(*arguments)
145
189
  configuration = arguments.first
146
190
  class_for_adapter(configuration["adapter"]).new(*arguments).drop
147
- $stdout.puts "Dropped database '#{configuration['database']}'"
191
+ $stdout.puts "Dropped database '#{configuration['database']}'" if verbose?
148
192
  rescue ActiveRecord::NoDatabaseError
149
193
  $stderr.puts "Database '#{configuration['database']}' does not exist"
150
194
  rescue Exception => error
@@ -163,20 +207,56 @@ module ActiveRecord
163
207
  }
164
208
  end
165
209
 
210
+ def truncate_tables(configuration)
211
+ ActiveRecord::Base.connected_to(database: { truncation: configuration }) do
212
+ conn = ActiveRecord::Base.connection
213
+ table_names = conn.tables
214
+ table_names -= [
215
+ conn.schema_migration.table_name,
216
+ InternalMetadata.table_name
217
+ ]
218
+
219
+ ActiveRecord::Base.connection.truncate_tables(*table_names)
220
+ end
221
+ end
222
+ private :truncate_tables
223
+
224
+ def truncate_all(environment = env)
225
+ ActiveRecord::Base.configurations.configs_for(env_name: environment).each do |db_config|
226
+ truncate_tables db_config.config
227
+ end
228
+ end
229
+
166
230
  def migrate
167
231
  check_target_version
168
232
 
169
- verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true
170
233
  scope = ENV["SCOPE"]
171
- verbose_was, Migration.verbose = Migration.verbose, verbose
234
+ verbose_was, Migration.verbose = Migration.verbose, verbose?
235
+
172
236
  Base.connection.migration_context.migrate(target_version) do |migration|
173
237
  scope.blank? || scope == migration.scope
174
238
  end
239
+
175
240
  ActiveRecord::Base.clear_cache!
176
241
  ensure
177
242
  Migration.verbose = verbose_was
178
243
  end
179
244
 
245
+ def migrate_status
246
+ unless ActiveRecord::Base.connection.schema_migration.table_exists?
247
+ Kernel.abort "Schema migrations table does not exist yet."
248
+ end
249
+
250
+ # output
251
+ puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n"
252
+ puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
253
+ puts "-" * 50
254
+ ActiveRecord::Base.connection.migration_context.migrations_status.each do |status, version, name|
255
+ puts "#{status.center(8)} #{version.ljust(14)} #{name}"
256
+ end
257
+ puts
258
+ end
259
+
180
260
  def check_target_version
181
261
  if target_version && !(Migration::MigrationFilenameRegexp.match?(ENV["VERSION"]) || /\A\d+\z/.match?(ENV["VERSION"]))
182
262
  raise "Invalid format of target version: `VERSION=#{ENV['VERSION']}`"
@@ -187,8 +267,8 @@ module ActiveRecord
187
267
  ENV["VERSION"].to_i if ENV["VERSION"] && !ENV["VERSION"].empty?
188
268
  end
189
269
 
190
- def charset_current(environment = env)
191
- charset ActiveRecord::Base.configurations[environment]
270
+ def charset_current(environment = env, specification_name = spec)
271
+ charset ActiveRecord::Base.configurations.configs_for(env_name: environment, spec_name: specification_name).config
192
272
  end
193
273
 
194
274
  def charset(*arguments)
@@ -196,8 +276,8 @@ module ActiveRecord
196
276
  class_for_adapter(configuration["adapter"]).new(*arguments).charset
197
277
  end
198
278
 
199
- def collation_current(environment = env)
200
- collation ActiveRecord::Base.configurations[environment]
279
+ def collation_current(environment = env, specification_name = spec)
280
+ collation ActiveRecord::Base.configurations.configs_for(env_name: environment, spec_name: specification_name).config
201
281
  end
202
282
 
203
283
  def collation(*arguments)
@@ -234,9 +314,10 @@ module ActiveRecord
234
314
  class_for_adapter(configuration["adapter"]).new(*arguments).structure_load(filename, structure_load_flags)
235
315
  end
236
316
 
237
- def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = env) # :nodoc:
238
- file ||= schema_file(format)
317
+ def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = env, spec_name = "primary") # :nodoc:
318
+ file ||= dump_filename(spec_name, format)
239
319
 
320
+ verbose_was, Migration.verbose = Migration.verbose, verbose? && ENV["VERBOSE"]
240
321
  check_schema_file(file)
241
322
  ActiveRecord::Base.establish_connection(configuration)
242
323
 
@@ -250,27 +331,74 @@ module ActiveRecord
250
331
  end
251
332
  ActiveRecord::InternalMetadata.create_table
252
333
  ActiveRecord::InternalMetadata[:environment] = environment
334
+ ensure
335
+ Migration.verbose = verbose_was
336
+ end
337
+
338
+ def dump_schema(configuration, format = ActiveRecord::Base.schema_format, spec_name = "primary") # :nodoc:
339
+ require "active_record/schema_dumper"
340
+ filename = dump_filename(spec_name, format)
341
+ connection = ActiveRecord::Base.connection
342
+
343
+ case format
344
+ when :ruby
345
+ File.open(filename, "w:utf-8") do |file|
346
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
347
+ end
348
+ when :sql
349
+ structure_dump(configuration, filename)
350
+ if connection.schema_migration.table_exists?
351
+ File.open(filename, "a") do |f|
352
+ f.puts connection.dump_schema_information
353
+ f.print "\n"
354
+ end
355
+ end
356
+ end
253
357
  end
254
358
 
255
359
  def schema_file(format = ActiveRecord::Base.schema_format)
360
+ File.join(db_dir, schema_file_type(format))
361
+ end
362
+
363
+ def schema_file_type(format = ActiveRecord::Base.schema_format)
256
364
  case format
257
365
  when :ruby
258
- File.join(db_dir, "schema.rb")
366
+ "schema.rb"
259
367
  when :sql
260
- File.join(db_dir, "structure.sql")
368
+ "structure.sql"
369
+ end
370
+ end
371
+
372
+ def dump_filename(namespace, format = ActiveRecord::Base.schema_format)
373
+ filename = if namespace == "primary"
374
+ schema_file_type(format)
375
+ else
376
+ "#{namespace}_#{schema_file_type(format)}"
261
377
  end
378
+
379
+ ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
380
+ end
381
+
382
+ def cache_dump_filename(namespace)
383
+ filename = if namespace == "primary"
384
+ "schema_cache.yml"
385
+ else
386
+ "#{namespace}_schema_cache.yml"
387
+ end
388
+
389
+ ENV["SCHEMA_CACHE"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
262
390
  end
263
391
 
264
392
  def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
265
- each_current_configuration(environment) { |configuration, configuration_environment|
266
- load_schema configuration, format, file, configuration_environment
393
+ each_current_configuration(environment) { |configuration, spec_name, env|
394
+ load_schema(configuration, format, file, env, spec_name)
267
395
  }
268
396
  ActiveRecord::Base.establish_connection(environment.to_sym)
269
397
  end
270
398
 
271
399
  def check_schema_file(filename)
272
400
  unless File.exist?(filename)
273
- message = %{#{filename} doesn't exist yet. Run `rails db:migrate` to create it, then try again.}.dup
401
+ message = +%{#{filename} doesn't exist yet. Run `rails db:migrate` to create it, then try again.}
274
402
  message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails.root)
275
403
  Kernel.abort message
276
404
  end
@@ -297,6 +425,9 @@ module ActiveRecord
297
425
  end
298
426
 
299
427
  private
428
+ def verbose?
429
+ ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true
430
+ end
300
431
 
301
432
  def class_for_adapter(adapter)
302
433
  _key, task = @tasks.each_pair.detect { |pattern, _task| adapter[pattern] }
@@ -306,19 +437,22 @@ module ActiveRecord
306
437
  task.is_a?(String) ? task.constantize : task
307
438
  end
308
439
 
309
- def each_current_configuration(environment)
440
+ def each_current_configuration(environment, spec_name = nil)
310
441
  environments = [environment]
311
442
  environments << "test" if environment == "development"
312
443
 
313
- ActiveRecord::Base.configurations.slice(*environments).each do |configuration_environment, configuration|
314
- next unless configuration["database"]
444
+ environments.each do |env|
445
+ ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config|
446
+ next if spec_name && spec_name != db_config.spec_name
315
447
 
316
- yield configuration, configuration_environment
448
+ yield db_config.config, db_config.spec_name, env
449
+ end
317
450
  end
318
451
  end
319
452
 
320
453
  def each_local_configuration
321
- ActiveRecord::Base.configurations.each_value do |configuration|
454
+ ActiveRecord::Base.configurations.configs_for.each do |db_config|
455
+ configuration = db_config.config
322
456
  next unless configuration["database"]
323
457
 
324
458
  if local_database?(configuration)