activerecord 3.2.19 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (264) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1715 -604
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +40 -45
  5. data/examples/performance.rb +33 -22
  6. data/examples/simple.rb +3 -4
  7. data/lib/active_record/aggregations.rb +76 -51
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +54 -40
  10. data/lib/active_record/associations/association.rb +76 -56
  11. data/lib/active_record/associations/association_scope.rb +125 -93
  12. data/lib/active_record/associations/belongs_to_association.rb +57 -28
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +120 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +115 -62
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -53
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
  18. data/lib/active_record/associations/builder/has_many.rb +9 -65
  19. data/lib/active_record/associations/builder/has_one.rb +18 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +18 -19
  21. data/lib/active_record/associations/collection_association.rb +268 -186
  22. data/lib/active_record/associations/collection_proxy.rb +1003 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +81 -41
  25. data/lib/active_record/associations/has_many_through_association.rb +76 -55
  26. data/lib/active_record/associations/has_one_association.rb +51 -21
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +239 -155
  32. data/lib/active_record/associations/preloader/association.rb +97 -62
  33. data/lib/active_record/associations/preloader/collection_association.rb +2 -8
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +75 -33
  38. data/lib/active_record/associations/preloader.rb +111 -79
  39. data/lib/active_record/associations/singular_association.rb +35 -13
  40. data/lib/active_record/associations/through_association.rb +41 -19
  41. data/lib/active_record/associations.rb +727 -501
  42. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  43. data/lib/active_record/attribute.rb +213 -0
  44. data/lib/active_record/attribute_assignment.rb +32 -162
  45. data/lib/active_record/attribute_decorators.rb +67 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  47. data/lib/active_record/attribute_methods/dirty.rb +101 -61
  48. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  49. data/lib/active_record/attribute_methods/query.rb +7 -6
  50. data/lib/active_record/attribute_methods/read.rb +56 -117
  51. data/lib/active_record/attribute_methods/serialization.rb +43 -96
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
  53. data/lib/active_record/attribute_methods/write.rb +34 -45
  54. data/lib/active_record/attribute_methods.rb +333 -144
  55. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  56. data/lib/active_record/attribute_set/builder.rb +108 -0
  57. data/lib/active_record/attribute_set.rb +108 -0
  58. data/lib/active_record/attributes.rb +265 -0
  59. data/lib/active_record/autosave_association.rb +285 -223
  60. data/lib/active_record/base.rb +95 -490
  61. data/lib/active_record/callbacks.rb +95 -61
  62. data/lib/active_record/coders/json.rb +13 -0
  63. data/lib/active_record/coders/yaml_column.rb +28 -19
  64. data/lib/active_record/collection_cache_key.rb +40 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
  78. data/lib/active_record/connection_adapters/column.rb +30 -259
  79. data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
  80. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  81. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  82. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  83. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  84. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  89. data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
  90. data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
  91. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
  92. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
  115. data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
  116. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
  117. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
  118. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  119. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
  120. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  121. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  122. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
  123. data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
  124. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  125. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
  129. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  130. data/lib/active_record/connection_handling.rb +155 -0
  131. data/lib/active_record/core.rb +561 -0
  132. data/lib/active_record/counter_cache.rb +146 -105
  133. data/lib/active_record/dynamic_matchers.rb +101 -64
  134. data/lib/active_record/enum.rb +234 -0
  135. data/lib/active_record/errors.rb +153 -56
  136. data/lib/active_record/explain.rb +15 -63
  137. data/lib/active_record/explain_registry.rb +30 -0
  138. data/lib/active_record/explain_subscriber.rb +10 -6
  139. data/lib/active_record/fixture_set/file.rb +77 -0
  140. data/lib/active_record/fixtures.rb +355 -232
  141. data/lib/active_record/gem_version.rb +15 -0
  142. data/lib/active_record/inheritance.rb +144 -79
  143. data/lib/active_record/integration.rb +66 -13
  144. data/lib/active_record/internal_metadata.rb +56 -0
  145. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  146. data/lib/active_record/locale/en.yml +9 -1
  147. data/lib/active_record/locking/optimistic.rb +77 -56
  148. data/lib/active_record/locking/pessimistic.rb +6 -6
  149. data/lib/active_record/log_subscriber.rb +53 -28
  150. data/lib/active_record/migration/command_recorder.rb +166 -33
  151. data/lib/active_record/migration/compatibility.rb +126 -0
  152. data/lib/active_record/migration/join_table.rb +15 -0
  153. data/lib/active_record/migration.rb +792 -264
  154. data/lib/active_record/model_schema.rb +192 -130
  155. data/lib/active_record/nested_attributes.rb +238 -145
  156. data/lib/active_record/no_touching.rb +52 -0
  157. data/lib/active_record/null_relation.rb +89 -0
  158. data/lib/active_record/persistence.rb +357 -157
  159. data/lib/active_record/query_cache.rb +22 -43
  160. data/lib/active_record/querying.rb +34 -23
  161. data/lib/active_record/railtie.rb +88 -48
  162. data/lib/active_record/railties/console_sandbox.rb +3 -4
  163. data/lib/active_record/railties/controller_runtime.rb +5 -4
  164. data/lib/active_record/railties/databases.rake +170 -422
  165. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  166. data/lib/active_record/readonly_attributes.rb +2 -5
  167. data/lib/active_record/reflection.rb +715 -189
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  169. data/lib/active_record/relation/batches.rb +203 -50
  170. data/lib/active_record/relation/calculations.rb +203 -194
  171. data/lib/active_record/relation/delegation.rb +103 -25
  172. data/lib/active_record/relation/finder_methods.rb +457 -261
  173. data/lib/active_record/relation/from_clause.rb +32 -0
  174. data/lib/active_record/relation/merger.rb +167 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  179. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  180. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  181. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  182. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  183. data/lib/active_record/relation/predicate_builder.rb +153 -48
  184. data/lib/active_record/relation/query_attribute.rb +19 -0
  185. data/lib/active_record/relation/query_methods.rb +1019 -194
  186. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  187. data/lib/active_record/relation/spawn_methods.rb +46 -150
  188. data/lib/active_record/relation/where_clause.rb +174 -0
  189. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  190. data/lib/active_record/relation.rb +450 -245
  191. data/lib/active_record/result.rb +104 -12
  192. data/lib/active_record/runtime_registry.rb +22 -0
  193. data/lib/active_record/sanitization.rb +120 -94
  194. data/lib/active_record/schema.rb +28 -18
  195. data/lib/active_record/schema_dumper.rb +141 -74
  196. data/lib/active_record/schema_migration.rb +50 -0
  197. data/lib/active_record/scoping/default.rb +64 -57
  198. data/lib/active_record/scoping/named.rb +93 -108
  199. data/lib/active_record/scoping.rb +73 -121
  200. data/lib/active_record/secure_token.rb +38 -0
  201. data/lib/active_record/serialization.rb +7 -5
  202. data/lib/active_record/statement_cache.rb +113 -0
  203. data/lib/active_record/store.rb +173 -15
  204. data/lib/active_record/suppressor.rb +58 -0
  205. data/lib/active_record/table_metadata.rb +68 -0
  206. data/lib/active_record/tasks/database_tasks.rb +313 -0
  207. data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
  208. data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
  209. data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
  210. data/lib/active_record/timestamp.rb +42 -24
  211. data/lib/active_record/touch_later.rb +58 -0
  212. data/lib/active_record/transactions.rb +233 -105
  213. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  214. data/lib/active_record/type/date.rb +7 -0
  215. data/lib/active_record/type/date_time.rb +7 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  217. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  218. data/lib/active_record/type/internal/timezone.rb +15 -0
  219. data/lib/active_record/type/serialized.rb +63 -0
  220. data/lib/active_record/type/time.rb +20 -0
  221. data/lib/active_record/type/type_map.rb +64 -0
  222. data/lib/active_record/type.rb +72 -0
  223. data/lib/active_record/type_caster/connection.rb +29 -0
  224. data/lib/active_record/type_caster/map.rb +19 -0
  225. data/lib/active_record/type_caster.rb +7 -0
  226. data/lib/active_record/validations/absence.rb +23 -0
  227. data/lib/active_record/validations/associated.rb +33 -18
  228. data/lib/active_record/validations/length.rb +24 -0
  229. data/lib/active_record/validations/presence.rb +66 -0
  230. data/lib/active_record/validations/uniqueness.rb +128 -68
  231. data/lib/active_record/validations.rb +48 -40
  232. data/lib/active_record/version.rb +5 -7
  233. data/lib/active_record.rb +71 -47
  234. data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
  235. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
  236. data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
  237. data/lib/rails/generators/active_record/migration.rb +18 -8
  238. data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
  239. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  240. data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
  241. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  242. data/lib/rails/generators/active_record.rb +3 -11
  243. metadata +188 -134
  244. data/examples/associations.png +0 -0
  245. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  246. data/lib/active_record/associations/join_helper.rb +0 -55
  247. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  248. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  249. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  250. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  251. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  252. data/lib/active_record/dynamic_finder_match.rb +0 -68
  253. data/lib/active_record/dynamic_scope_match.rb +0 -23
  254. data/lib/active_record/fixtures/file.rb +0 -65
  255. data/lib/active_record/identity_map.rb +0 -162
  256. data/lib/active_record/observer.rb +0 -121
  257. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  258. data/lib/active_record/session_store.rb +0 -360
  259. data/lib/active_record/test_case.rb +0 -73
  260. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  261. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  262. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  263. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  264. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,113 @@
1
+ module ActiveRecord
2
+
3
+ # Statement cache is used to cache a single statement in order to avoid creating the AST again.
4
+ # Initializing the cache is done by passing the statement in the create block:
5
+ #
6
+ # cache = StatementCache.create(Book.connection) do |params|
7
+ # Book.where(name: "my book").where("author_id > 3")
8
+ # end
9
+ #
10
+ # The cached statement is executed by using the
11
+ # [connection.execute]{rdoc-ref:ConnectionAdapters::DatabaseStatements#execute} method:
12
+ #
13
+ # cache.execute([], Book, Book.connection)
14
+ #
15
+ # The relation returned by the block is cached, and for each
16
+ # [execute]{rdoc-ref:ConnectionAdapters::DatabaseStatements#execute}
17
+ # call the cached relation gets duped. Database is queried when +to_a+ is called on the relation.
18
+ #
19
+ # If you want to cache the statement without the values you can use the +bind+ method of the
20
+ # block parameter.
21
+ #
22
+ # cache = StatementCache.create(Book.connection) do |params|
23
+ # Book.where(name: params.bind)
24
+ # end
25
+ #
26
+ # And pass the bind values as the first argument of +execute+ call.
27
+ #
28
+ # cache.execute(["my book"], Book, Book.connection)
29
+ class StatementCache # :nodoc:
30
+ class Substitute; end # :nodoc:
31
+
32
+ class Query # :nodoc:
33
+ def initialize(sql)
34
+ @sql = sql
35
+ end
36
+
37
+ def sql_for(binds, connection)
38
+ @sql
39
+ end
40
+ end
41
+
42
+ class PartialQuery < Query # :nodoc:
43
+ def initialize values
44
+ @values = values
45
+ @indexes = values.each_with_index.find_all { |thing,i|
46
+ Arel::Nodes::BindParam === thing
47
+ }.map(&:last)
48
+ end
49
+
50
+ def sql_for(binds, connection)
51
+ val = @values.dup
52
+ binds = connection.prepare_binds_for_database(binds)
53
+ @indexes.each { |i| val[i] = connection.quote(binds.shift) }
54
+ val.join
55
+ end
56
+ end
57
+
58
+ def self.query(visitor, ast)
59
+ Query.new visitor.accept(ast, Arel::Collectors::SQLString.new).value
60
+ end
61
+
62
+ def self.partial_query(visitor, ast, collector)
63
+ collected = visitor.accept(ast, collector).value
64
+ PartialQuery.new collected
65
+ end
66
+
67
+ class Params # :nodoc:
68
+ def bind; Substitute.new; end
69
+ end
70
+
71
+ class BindMap # :nodoc:
72
+ def initialize(bound_attributes)
73
+ @indexes = []
74
+ @bound_attributes = bound_attributes
75
+
76
+ bound_attributes.each_with_index do |attr, i|
77
+ if Substitute === attr.value
78
+ @indexes << i
79
+ end
80
+ end
81
+ end
82
+
83
+ def bind(values)
84
+ bas = @bound_attributes.dup
85
+ @indexes.each_with_index { |offset,i| bas[offset] = bas[offset].with_cast_value(values[i]) }
86
+ bas
87
+ end
88
+ end
89
+
90
+ attr_reader :bind_map, :query_builder
91
+
92
+ def self.create(connection, block = Proc.new)
93
+ relation = block.call Params.new
94
+ bind_map = BindMap.new relation.bound_attributes
95
+ query_builder = connection.cacheable_query relation.arel
96
+ new query_builder, bind_map
97
+ end
98
+
99
+ def initialize(query_builder, bind_map)
100
+ @query_builder = query_builder
101
+ @bind_map = bind_map
102
+ end
103
+
104
+ def execute(params, klass, connection)
105
+ bind_values = bind_map.bind params
106
+
107
+ sql = query_builder.sql_for bind_values, connection
108
+
109
+ klass.find_by_sql(sql, bind_values, preparable: true)
110
+ end
111
+ alias :call :execute
112
+ end
113
+ end
@@ -1,6 +1,8 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+
1
3
  module ActiveRecord
2
4
  # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
3
- # It's like a simple key/value store backed into your record when you don't care about being able to
5
+ # It's like a simple key/value store baked into your record when you don't care about being able to
4
6
  # query that store outside the context of a single record.
5
7
  #
6
8
  # You can then declare accessors to this store that are then accessible just like any other attribute
@@ -10,43 +12,199 @@ module ActiveRecord
10
12
  # Make sure that you declare the database column used for the serialized store as a text, so there's
11
13
  # plenty of room.
12
14
  #
15
+ # You can set custom coder to encode/decode your serialized attributes to/from different formats.
16
+ # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
17
+ #
18
+ # NOTE: If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for
19
+ # the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store].
20
+ # Simply use {.store_accessor}[rdoc-ref:ClassMethods#store_accessor] instead to generate
21
+ # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access
22
+ # using a symbol.
23
+ #
24
+ # NOTE: The default validations with the exception of +uniqueness+ will work.
25
+ # For example, if you want to check for +uniqueness+ with +hstore+ you will
26
+ # need to use a custom validation to handle it.
27
+ #
13
28
  # Examples:
14
29
  #
15
30
  # class User < ActiveRecord::Base
16
- # store :settings, accessors: [ :color, :homepage ]
31
+ # store :settings, accessors: [ :color, :homepage ], coder: JSON
17
32
  # end
18
- #
33
+ #
19
34
  # u = User.new(color: 'black', homepage: '37signals.com')
20
35
  # u.color # Accessor stored attribute
21
36
  # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
22
37
  #
38
+ # # There is no difference between strings and symbols for accessing custom attributes
39
+ # u.settings[:country] # => 'Denmark'
40
+ # u.settings['country'] # => 'Denmark'
41
+ #
23
42
  # # Add additional accessors to an existing store through store_accessor
24
43
  # class SuperUser < User
25
44
  # store_accessor :settings, :privileges, :servants
26
45
  # end
46
+ #
47
+ # The stored attribute names can be retrieved using {.stored_attributes}[rdoc-ref:rdoc-ref:ClassMethods#stored_attributes].
48
+ #
49
+ # User.stored_attributes[:settings] # [:color, :homepage]
50
+ #
51
+ # == Overwriting default accessors
52
+ #
53
+ # All stored values are automatically available through accessors on the Active Record
54
+ # object, but sometimes you want to specialize this behavior. This can be done by overwriting
55
+ # the default accessors (using the same name as the attribute) and calling <tt>super</tt>
56
+ # to actually change things.
57
+ #
58
+ # class Song < ActiveRecord::Base
59
+ # # Uses a stored integer to hold the volume adjustment of the song
60
+ # store :settings, accessors: [:volume_adjustment]
61
+ #
62
+ # def volume_adjustment=(decibels)
63
+ # super(decibels.to_i)
64
+ # end
65
+ #
66
+ # def volume_adjustment
67
+ # super.to_i
68
+ # end
69
+ # end
27
70
  module Store
28
71
  extend ActiveSupport::Concern
29
-
72
+
73
+ included do
74
+ class << self
75
+ attr_accessor :local_stored_attributes
76
+ end
77
+ end
78
+
30
79
  module ClassMethods
31
80
  def store(store_attribute, options = {})
32
- serialize store_attribute, Hash
81
+ serialize store_attribute, IndifferentCoder.new(options[:coder])
33
82
  store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
34
83
  end
35
84
 
36
85
  def store_accessor(store_attribute, *keys)
37
- Array(keys).flatten.each do |key|
38
- define_method("#{key}=") do |value|
39
- send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
40
- send("#{store_attribute}_will_change!")
41
- send(store_attribute)[key] = value
86
+ keys = keys.flatten
87
+
88
+ _store_accessors_module.module_eval do
89
+ keys.each do |key|
90
+ define_method("#{key}=") do |value|
91
+ write_store_attribute(store_attribute, key, value)
92
+ end
93
+
94
+ define_method(key) do
95
+ read_store_attribute(store_attribute, key)
96
+ end
97
+ end
98
+ end
99
+
100
+ # assign new store attribute and create new hash to ensure that each class in the hierarchy
101
+ # has its own hash of stored attributes.
102
+ self.local_stored_attributes ||= {}
103
+ self.local_stored_attributes[store_attribute] ||= []
104
+ self.local_stored_attributes[store_attribute] |= keys
105
+ end
106
+
107
+ def _store_accessors_module # :nodoc:
108
+ @_store_accessors_module ||= begin
109
+ mod = Module.new
110
+ include mod
111
+ mod
112
+ end
113
+ end
114
+
115
+ def stored_attributes
116
+ parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {}
117
+ if self.local_stored_attributes
118
+ parent.merge!(self.local_stored_attributes) { |k, a, b| a | b }
119
+ end
120
+ parent
121
+ end
122
+ end
123
+
124
+ protected
125
+ def read_store_attribute(store_attribute, key)
126
+ accessor = store_accessor_for(store_attribute)
127
+ accessor.read(self, store_attribute, key)
128
+ end
129
+
130
+ def write_store_attribute(store_attribute, key, value)
131
+ accessor = store_accessor_for(store_attribute)
132
+ accessor.write(self, store_attribute, key, value)
133
+ end
134
+
135
+ private
136
+ def store_accessor_for(store_attribute)
137
+ type_for_attribute(store_attribute.to_s).accessor
138
+ end
139
+
140
+ class HashAccessor # :nodoc:
141
+ def self.read(object, attribute, key)
142
+ prepare(object, attribute)
143
+ object.public_send(attribute)[key]
144
+ end
145
+
146
+ def self.write(object, attribute, key, value)
147
+ prepare(object, attribute)
148
+ if value != read(object, attribute, key)
149
+ object.public_send :"#{attribute}_will_change!"
150
+ object.public_send(attribute)[key] = value
151
+ end
152
+ end
153
+
154
+ def self.prepare(object, attribute)
155
+ object.public_send :"#{attribute}=", {} unless object.send(attribute)
156
+ end
157
+ end
158
+
159
+ class StringKeyedHashAccessor < HashAccessor # :nodoc:
160
+ def self.read(object, attribute, key)
161
+ super object, attribute, key.to_s
162
+ end
163
+
164
+ def self.write(object, attribute, key, value)
165
+ super object, attribute, key.to_s, value
166
+ end
167
+ end
168
+
169
+ class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
170
+ def self.prepare(object, store_attribute)
171
+ attribute = object.send(store_attribute)
172
+ unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
173
+ attribute = IndifferentCoder.as_indifferent_hash(attribute)
174
+ object.send :"#{store_attribute}=", attribute
42
175
  end
43
-
44
- define_method(key) do
45
- send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
46
- send(store_attribute)[key]
176
+ attribute
177
+ end
178
+ end
179
+
180
+ class IndifferentCoder # :nodoc:
181
+ def initialize(coder_or_class_name)
182
+ @coder =
183
+ if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)
184
+ coder_or_class_name
185
+ else
186
+ ActiveRecord::Coders::YAMLColumn.new(coder_or_class_name || Object)
47
187
  end
188
+ end
189
+
190
+ def dump(obj)
191
+ @coder.dump self.class.as_indifferent_hash(obj)
192
+ end
193
+
194
+ def load(yaml)
195
+ self.class.as_indifferent_hash(@coder.load(yaml || ''))
196
+ end
197
+
198
+ def self.as_indifferent_hash(obj)
199
+ case obj
200
+ when ActiveSupport::HashWithIndifferentAccess
201
+ obj
202
+ when Hash
203
+ obj.with_indifferent_access
204
+ else
205
+ ActiveSupport::HashWithIndifferentAccess.new
48
206
  end
49
207
  end
50
208
  end
51
209
  end
52
- end
210
+ end
@@ -0,0 +1,58 @@
1
+ module ActiveRecord
2
+ # ActiveRecord::Suppressor prevents the receiver from being saved during
3
+ # a given block.
4
+ #
5
+ # For example, here's a pattern of creating notifications when new comments
6
+ # are posted. (The notification may in turn trigger an email, a push
7
+ # notification, or just appear in the UI somewhere):
8
+ #
9
+ # class Comment < ActiveRecord::Base
10
+ # belongs_to :commentable, polymorphic: true
11
+ # after_create -> { Notification.create! comment: self,
12
+ # recipients: commentable.recipients }
13
+ # end
14
+ #
15
+ # That's what you want the bulk of the time. New comment creates a new
16
+ # Notification. But there may well be off cases, like copying a commentable
17
+ # and its comments, where you don't want that. So you'd have a concern
18
+ # something like this:
19
+ #
20
+ # module Copyable
21
+ # def copy_to(destination)
22
+ # Notification.suppress do
23
+ # # Copy logic that creates new comments that we do not want
24
+ # # triggering notifications.
25
+ # end
26
+ # end
27
+ # end
28
+ module Suppressor
29
+ extend ActiveSupport::Concern
30
+
31
+ module ClassMethods
32
+ def suppress(&block)
33
+ SuppressorRegistry.suppressed[name] = true
34
+ yield
35
+ ensure
36
+ SuppressorRegistry.suppressed[name] = false
37
+ end
38
+ end
39
+
40
+ def save(*) # :nodoc:
41
+ SuppressorRegistry.suppressed[self.class.name] ? true : super
42
+ end
43
+
44
+ def save!(*) # :nodoc:
45
+ SuppressorRegistry.suppressed[self.class.name] ? true : super
46
+ end
47
+ end
48
+
49
+ class SuppressorRegistry # :nodoc:
50
+ extend ActiveSupport::PerThreadRegistry
51
+
52
+ attr_reader :suppressed
53
+
54
+ def initialize
55
+ @suppressed = {}
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,68 @@
1
+ module ActiveRecord
2
+ class TableMetadata # :nodoc:
3
+ delegate :foreign_type, :foreign_key, to: :association, prefix: true
4
+ delegate :association_primary_key, to: :association
5
+
6
+ def initialize(klass, arel_table, association = nil)
7
+ @klass = klass
8
+ @arel_table = arel_table
9
+ @association = association
10
+ end
11
+
12
+ def resolve_column_aliases(hash)
13
+ # This method is a hot spot, so for now, use Hash[] to dup the hash.
14
+ # https://bugs.ruby-lang.org/issues/7166
15
+ new_hash = Hash[hash]
16
+ hash.each do |key, _|
17
+ if (key.is_a?(Symbol)) && klass.attribute_alias?(key)
18
+ new_hash[klass.attribute_alias(key)] = new_hash.delete(key)
19
+ end
20
+ end
21
+ new_hash
22
+ end
23
+
24
+ def arel_attribute(column_name)
25
+ if klass
26
+ klass.arel_attribute(column_name, arel_table)
27
+ else
28
+ arel_table[column_name]
29
+ end
30
+ end
31
+
32
+ def type(column_name)
33
+ if klass
34
+ klass.type_for_attribute(column_name.to_s)
35
+ else
36
+ Type::Value.new
37
+ end
38
+ end
39
+
40
+ def associated_with?(association_name)
41
+ klass && klass._reflect_on_association(association_name)
42
+ end
43
+
44
+ def associated_table(table_name)
45
+ return self if table_name == arel_table.name
46
+
47
+ association = klass._reflect_on_association(table_name)
48
+ if association && !association.polymorphic?
49
+ association_klass = association.klass
50
+ arel_table = association_klass.arel_table.alias(table_name)
51
+ else
52
+ type_caster = TypeCaster::Connection.new(klass, table_name)
53
+ association_klass = nil
54
+ arel_table = Arel::Table.new(table_name, type_caster: type_caster)
55
+ end
56
+
57
+ TableMetadata.new(association_klass, arel_table, association)
58
+ end
59
+
60
+ def polymorphic_association?
61
+ association && association.polymorphic?
62
+ end
63
+
64
+ protected
65
+
66
+ attr_reader :klass, :arel_table, :association
67
+ end
68
+ end