activerecord 5.2.3

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 (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +937 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +217 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +188 -0
  8. data/lib/active_record/aggregations.rb +283 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1860 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +299 -0
  13. data/lib/active_record/associations/association_scope.rb +168 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +130 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +140 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +163 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +82 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
  20. data/lib/active_record/associations/builder/has_many.rb +17 -0
  21. data/lib/active_record/associations/builder/has_one.rb +30 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +42 -0
  23. data/lib/active_record/associations/collection_association.rb +513 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1131 -0
  25. data/lib/active_record/associations/foreign_association.rb +13 -0
  26. data/lib/active_record/associations/has_many_association.rb +144 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +227 -0
  28. data/lib/active_record/associations/has_one_association.rb +120 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +262 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +193 -0
  35. data/lib/active_record/associations/preloader/association.rb +131 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +107 -0
  37. data/lib/active_record/associations/singular_association.rb +73 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +88 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +492 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +150 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +143 -0
  45. data/lib/active_record/attribute_methods/query.rb +42 -0
  46. data/lib/active_record/attribute_methods/read.rb +85 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +68 -0
  50. data/lib/active_record/attributes.rb +266 -0
  51. data/lib/active_record/autosave_association.rb +498 -0
  52. data/lib/active_record/base.rb +329 -0
  53. data/lib/active_record/callbacks.rb +353 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/collection_cache_key.rb +53 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
  70. data/lib/active_record/connection_adapters/column.rb +91 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  73. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
  118. data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +145 -0
  129. data/lib/active_record/core.rb +559 -0
  130. data/lib/active_record/counter_cache.rb +218 -0
  131. data/lib/active_record/define_callbacks.rb +22 -0
  132. data/lib/active_record/dynamic_matchers.rb +122 -0
  133. data/lib/active_record/enum.rb +244 -0
  134. data/lib/active_record/errors.rb +380 -0
  135. data/lib/active_record/explain.rb +50 -0
  136. data/lib/active_record/explain_registry.rb +32 -0
  137. data/lib/active_record/explain_subscriber.rb +34 -0
  138. data/lib/active_record/fixture_set/file.rb +82 -0
  139. data/lib/active_record/fixtures.rb +1065 -0
  140. data/lib/active_record/gem_version.rb +17 -0
  141. data/lib/active_record/inheritance.rb +283 -0
  142. data/lib/active_record/integration.rb +155 -0
  143. data/lib/active_record/internal_metadata.rb +45 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  145. data/lib/active_record/locale/en.yml +48 -0
  146. data/lib/active_record/locking/optimistic.rb +198 -0
  147. data/lib/active_record/locking/pessimistic.rb +89 -0
  148. data/lib/active_record/log_subscriber.rb +137 -0
  149. data/lib/active_record/migration.rb +1378 -0
  150. data/lib/active_record/migration/command_recorder.rb +240 -0
  151. data/lib/active_record/migration/compatibility.rb +217 -0
  152. data/lib/active_record/migration/join_table.rb +17 -0
  153. data/lib/active_record/model_schema.rb +521 -0
  154. data/lib/active_record/nested_attributes.rb +600 -0
  155. data/lib/active_record/no_touching.rb +58 -0
  156. data/lib/active_record/null_relation.rb +68 -0
  157. data/lib/active_record/persistence.rb +763 -0
  158. data/lib/active_record/query_cache.rb +45 -0
  159. data/lib/active_record/querying.rb +70 -0
  160. data/lib/active_record/railtie.rb +226 -0
  161. data/lib/active_record/railties/console_sandbox.rb +7 -0
  162. data/lib/active_record/railties/controller_runtime.rb +56 -0
  163. data/lib/active_record/railties/databases.rake +377 -0
  164. data/lib/active_record/readonly_attributes.rb +24 -0
  165. data/lib/active_record/reflection.rb +1044 -0
  166. data/lib/active_record/relation.rb +629 -0
  167. data/lib/active_record/relation/batches.rb +287 -0
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  169. data/lib/active_record/relation/calculations.rb +417 -0
  170. data/lib/active_record/relation/delegation.rb +147 -0
  171. data/lib/active_record/relation/finder_methods.rb +565 -0
  172. data/lib/active_record/relation/from_clause.rb +26 -0
  173. data/lib/active_record/relation/merger.rb +193 -0
  174. data/lib/active_record/relation/predicate_builder.rb +152 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  182. data/lib/active_record/relation/query_attribute.rb +45 -0
  183. data/lib/active_record/relation/query_methods.rb +1231 -0
  184. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  185. data/lib/active_record/relation/spawn_methods.rb +77 -0
  186. data/lib/active_record/relation/where_clause.rb +186 -0
  187. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  188. data/lib/active_record/result.rb +149 -0
  189. data/lib/active_record/runtime_registry.rb +24 -0
  190. data/lib/active_record/sanitization.rb +222 -0
  191. data/lib/active_record/schema.rb +70 -0
  192. data/lib/active_record/schema_dumper.rb +255 -0
  193. data/lib/active_record/schema_migration.rb +56 -0
  194. data/lib/active_record/scoping.rb +106 -0
  195. data/lib/active_record/scoping/default.rb +152 -0
  196. data/lib/active_record/scoping/named.rb +213 -0
  197. data/lib/active_record/secure_token.rb +40 -0
  198. data/lib/active_record/serialization.rb +22 -0
  199. data/lib/active_record/statement_cache.rb +121 -0
  200. data/lib/active_record/store.rb +211 -0
  201. data/lib/active_record/suppressor.rb +61 -0
  202. data/lib/active_record/table_metadata.rb +82 -0
  203. data/lib/active_record/tasks/database_tasks.rb +337 -0
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  207. data/lib/active_record/timestamp.rb +153 -0
  208. data/lib/active_record/touch_later.rb +64 -0
  209. data/lib/active_record/transactions.rb +502 -0
  210. data/lib/active_record/translation.rb +24 -0
  211. data/lib/active_record/type.rb +79 -0
  212. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  213. data/lib/active_record/type/date.rb +9 -0
  214. data/lib/active_record/type/date_time.rb +9 -0
  215. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  217. data/lib/active_record/type/internal/timezone.rb +17 -0
  218. data/lib/active_record/type/json.rb +30 -0
  219. data/lib/active_record/type/serialized.rb +71 -0
  220. data/lib/active_record/type/text.rb +11 -0
  221. data/lib/active_record/type/time.rb +21 -0
  222. data/lib/active_record/type/type_map.rb +62 -0
  223. data/lib/active_record/type/unsigned_integer.rb +17 -0
  224. data/lib/active_record/type_caster.rb +9 -0
  225. data/lib/active_record/type_caster/connection.rb +33 -0
  226. data/lib/active_record/type_caster/map.rb +23 -0
  227. data/lib/active_record/validations.rb +93 -0
  228. data/lib/active_record/validations/absence.rb +25 -0
  229. data/lib/active_record/validations/associated.rb +60 -0
  230. data/lib/active_record/validations/length.rb +26 -0
  231. data/lib/active_record/validations/presence.rb +68 -0
  232. data/lib/active_record/validations/uniqueness.rb +238 -0
  233. data/lib/active_record/version.rb +10 -0
  234. data/lib/rails/generators/active_record.rb +19 -0
  235. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  236. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  237. data/lib/rails/generators/active_record/migration.rb +35 -0
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
  239. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  240. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  241. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  242. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  243. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  244. metadata +333 -0
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module SecureToken
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ # Example using #has_secure_token
9
+ #
10
+ # # Schema: User(token:string, auth_token:string)
11
+ # class User < ActiveRecord::Base
12
+ # has_secure_token
13
+ # has_secure_token :auth_token
14
+ # end
15
+ #
16
+ # user = User.new
17
+ # user.save
18
+ # user.token # => "pX27zsMN2ViQKta1bGfLmVJE"
19
+ # user.auth_token # => "77TMHrHJFvFDwodq8w7Ev2m7"
20
+ # user.regenerate_token # => true
21
+ # user.regenerate_auth_token # => true
22
+ #
23
+ # <tt>SecureRandom::base58</tt> is used to generate the 24-character unique token, so collisions are highly unlikely.
24
+ #
25
+ # Note that it's still possible to generate a race condition in the database in the same way that
26
+ # {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
27
+ # You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
28
+ def has_secure_token(attribute = :token)
29
+ # Load securerandom only when has_secure_token is used.
30
+ require "active_support/core_ext/securerandom"
31
+ define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token }
32
+ before_create { send("#{attribute}=", self.class.generate_unique_secure_token) unless send("#{attribute}?") }
33
+ end
34
+
35
+ def generate_unique_secure_token
36
+ SecureRandom.base58(24)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord #:nodoc:
4
+ # = Active Record \Serialization
5
+ module Serialization
6
+ extend ActiveSupport::Concern
7
+ include ActiveModel::Serializers::JSON
8
+
9
+ included do
10
+ self.include_root_in_json = false
11
+ end
12
+
13
+ def serializable_hash(options = nil)
14
+ options = options.try(:dup) || {}
15
+
16
+ options[:except] = Array(options[:except]).map(&:to_s)
17
+ options[:except] |= Array(self.class.inheritance_column)
18
+
19
+ super(options)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ # Statement cache is used to cache a single statement in order to avoid creating the AST again.
5
+ # Initializing the cache is done by passing the statement in the create block:
6
+ #
7
+ # cache = StatementCache.create(Book.connection) do |params|
8
+ # Book.where(name: "my book").where("author_id > 3")
9
+ # end
10
+ #
11
+ # The cached statement is executed by using the
12
+ # {connection.execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] method:
13
+ #
14
+ # cache.execute([], Book.connection)
15
+ #
16
+ # The relation returned by the block is cached, and for each
17
+ # {execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute]
18
+ # call the cached relation gets duped. Database is queried when +to_a+ is called on the relation.
19
+ #
20
+ # If you want to cache the statement without the values you can use the +bind+ method of the
21
+ # block parameter.
22
+ #
23
+ # cache = StatementCache.create(Book.connection) do |params|
24
+ # Book.where(name: params.bind)
25
+ # end
26
+ #
27
+ # And pass the bind values as the first argument of +execute+ call.
28
+ #
29
+ # cache.execute(["my book"], Book.connection)
30
+ class StatementCache # :nodoc:
31
+ class Substitute; end # :nodoc:
32
+
33
+ class Query # :nodoc:
34
+ def initialize(sql)
35
+ @sql = sql
36
+ end
37
+
38
+ def sql_for(binds, connection)
39
+ @sql
40
+ end
41
+ end
42
+
43
+ class PartialQuery < Query # :nodoc:
44
+ def initialize(values)
45
+ @values = values
46
+ @indexes = values.each_with_index.find_all { |thing, i|
47
+ Arel::Nodes::BindParam === thing
48
+ }.map(&:last)
49
+ end
50
+
51
+ def sql_for(binds, connection)
52
+ val = @values.dup
53
+ casted_binds = binds.map(&:value_for_database)
54
+ @indexes.each { |i| val[i] = connection.quote(casted_binds.shift) }
55
+ val.join
56
+ end
57
+ end
58
+
59
+ def self.query(sql)
60
+ Query.new(sql)
61
+ end
62
+
63
+ def self.partial_query(values)
64
+ PartialQuery.new(values)
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
+ def self.create(connection, block = Proc.new)
91
+ relation = block.call Params.new
92
+ query_builder, binds = connection.cacheable_query(self, relation.arel)
93
+ bind_map = BindMap.new(binds)
94
+ new(query_builder, bind_map, relation.klass)
95
+ end
96
+
97
+ def initialize(query_builder, bind_map, klass)
98
+ @query_builder = query_builder
99
+ @bind_map = bind_map
100
+ @klass = klass
101
+ end
102
+
103
+ def execute(params, connection, &block)
104
+ bind_values = bind_map.bind params
105
+
106
+ sql = query_builder.sql_for bind_values, connection
107
+
108
+ klass.find_by_sql(sql, bind_values, preparable: true, &block)
109
+ end
110
+
111
+ def self.unsupported_value?(value)
112
+ case value
113
+ when NilClass, Array, Range, Hash, Relation, Base then true
114
+ end
115
+ end
116
+
117
+ protected
118
+
119
+ attr_reader :query_builder, :bind_map, :klass
120
+ end
121
+ end
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/indifferent_access"
4
+
5
+ module ActiveRecord
6
+ # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
7
+ # It's like a simple key/value store baked into your record when you don't care about being able to
8
+ # query that store outside the context of a single record.
9
+ #
10
+ # You can then declare accessors to this store that are then accessible just like any other attribute
11
+ # of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's
12
+ # already built around just accessing attributes on the model.
13
+ #
14
+ # Make sure that you declare the database column used for the serialized store as a text, so there's
15
+ # plenty of room.
16
+ #
17
+ # You can set custom coder to encode/decode your serialized attributes to/from different formats.
18
+ # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
19
+ #
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].
22
+ # Simply use {.store_accessor}[rdoc-ref:ClassMethods#store_accessor] instead to generate
23
+ # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access
24
+ # using a symbol.
25
+ #
26
+ # NOTE: The default validations with the exception of +uniqueness+ will work.
27
+ # For example, if you want to check for +uniqueness+ with +hstore+ you will
28
+ # need to use a custom validation to handle it.
29
+ #
30
+ # Examples:
31
+ #
32
+ # class User < ActiveRecord::Base
33
+ # store :settings, accessors: [ :color, :homepage ], coder: JSON
34
+ # end
35
+ #
36
+ # u = User.new(color: 'black', homepage: '37signals.com')
37
+ # u.color # Accessor stored attribute
38
+ # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
39
+ #
40
+ # # There is no difference between strings and symbols for accessing custom attributes
41
+ # u.settings[:country] # => 'Denmark'
42
+ # u.settings['country'] # => 'Denmark'
43
+ #
44
+ # # Add additional accessors to an existing store through store_accessor
45
+ # class SuperUser < User
46
+ # store_accessor :settings, :privileges, :servants
47
+ # end
48
+ #
49
+ # The stored attribute names can be retrieved using {.stored_attributes}[rdoc-ref:rdoc-ref:ClassMethods#stored_attributes].
50
+ #
51
+ # User.stored_attributes[:settings] # [:color, :homepage]
52
+ #
53
+ # == Overwriting default accessors
54
+ #
55
+ # All stored values are automatically available through accessors on the Active Record
56
+ # object, but sometimes you want to specialize this behavior. This can be done by overwriting
57
+ # the default accessors (using the same name as the attribute) and calling <tt>super</tt>
58
+ # to actually change things.
59
+ #
60
+ # class Song < ActiveRecord::Base
61
+ # # Uses a stored integer to hold the volume adjustment of the song
62
+ # store :settings, accessors: [:volume_adjustment]
63
+ #
64
+ # def volume_adjustment=(decibels)
65
+ # super(decibels.to_i)
66
+ # end
67
+ #
68
+ # def volume_adjustment
69
+ # super.to_i
70
+ # end
71
+ # end
72
+ module Store
73
+ extend ActiveSupport::Concern
74
+
75
+ included do
76
+ class << self
77
+ attr_accessor :local_stored_attributes
78
+ end
79
+ end
80
+
81
+ module ClassMethods
82
+ def store(store_attribute, options = {})
83
+ serialize store_attribute, IndifferentCoder.new(store_attribute, options[:coder])
84
+ store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
85
+ end
86
+
87
+ def store_accessor(store_attribute, *keys)
88
+ keys = keys.flatten
89
+
90
+ _store_accessors_module.module_eval do
91
+ keys.each do |key|
92
+ define_method("#{key}=") do |value|
93
+ write_store_attribute(store_attribute, key, value)
94
+ end
95
+
96
+ define_method(key) do
97
+ read_store_attribute(store_attribute, key)
98
+ end
99
+ end
100
+ end
101
+
102
+ # assign new store attribute and create new hash to ensure that each class in the hierarchy
103
+ # has its own hash of stored attributes.
104
+ self.local_stored_attributes ||= {}
105
+ self.local_stored_attributes[store_attribute] ||= []
106
+ self.local_stored_attributes[store_attribute] |= keys
107
+ end
108
+
109
+ def _store_accessors_module # :nodoc:
110
+ @_store_accessors_module ||= begin
111
+ mod = Module.new
112
+ include mod
113
+ mod
114
+ end
115
+ end
116
+
117
+ def stored_attributes
118
+ parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {}
119
+ if local_stored_attributes
120
+ parent.merge!(local_stored_attributes) { |k, a, b| a | b }
121
+ end
122
+ parent
123
+ end
124
+ end
125
+
126
+ private
127
+ def read_store_attribute(store_attribute, key) # :doc:
128
+ accessor = store_accessor_for(store_attribute)
129
+ accessor.read(self, store_attribute, key)
130
+ end
131
+
132
+ def write_store_attribute(store_attribute, key, value) # :doc:
133
+ accessor = store_accessor_for(store_attribute)
134
+ accessor.write(self, store_attribute, key, value)
135
+ end
136
+
137
+ def store_accessor_for(store_attribute)
138
+ type_for_attribute(store_attribute).accessor
139
+ end
140
+
141
+ class HashAccessor # :nodoc:
142
+ def self.read(object, attribute, key)
143
+ prepare(object, attribute)
144
+ object.public_send(attribute)[key]
145
+ end
146
+
147
+ def self.write(object, attribute, key, value)
148
+ prepare(object, attribute)
149
+ if value != read(object, attribute, key)
150
+ object.public_send :"#{attribute}_will_change!"
151
+ object.public_send(attribute)[key] = value
152
+ end
153
+ end
154
+
155
+ def self.prepare(object, attribute)
156
+ object.public_send :"#{attribute}=", {} unless object.send(attribute)
157
+ end
158
+ end
159
+
160
+ class StringKeyedHashAccessor < HashAccessor # :nodoc:
161
+ def self.read(object, attribute, key)
162
+ super object, attribute, key.to_s
163
+ end
164
+
165
+ def self.write(object, attribute, key, value)
166
+ super object, attribute, key.to_s, value
167
+ end
168
+ end
169
+
170
+ class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
171
+ def self.prepare(object, store_attribute)
172
+ attribute = object.send(store_attribute)
173
+ unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
174
+ attribute = IndifferentCoder.as_indifferent_hash(attribute)
175
+ object.send :"#{store_attribute}=", attribute
176
+ end
177
+ attribute
178
+ end
179
+ end
180
+
181
+ class IndifferentCoder # :nodoc:
182
+ def initialize(attr_name, coder_or_class_name)
183
+ @coder =
184
+ if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)
185
+ coder_or_class_name
186
+ else
187
+ ActiveRecord::Coders::YAMLColumn.new(attr_name, coder_or_class_name || Object)
188
+ end
189
+ end
190
+
191
+ def dump(obj)
192
+ @coder.dump self.class.as_indifferent_hash(obj)
193
+ end
194
+
195
+ def load(yaml)
196
+ self.class.as_indifferent_hash(@coder.load(yaml || ""))
197
+ end
198
+
199
+ def self.as_indifferent_hash(obj)
200
+ case obj
201
+ when ActiveSupport::HashWithIndifferentAccess
202
+ obj
203
+ when Hash
204
+ obj.with_indifferent_access
205
+ else
206
+ ActiveSupport::HashWithIndifferentAccess.new
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ # ActiveRecord::Suppressor prevents the receiver from being saved during
5
+ # a given block.
6
+ #
7
+ # For example, here's a pattern of creating notifications when new comments
8
+ # are posted. (The notification may in turn trigger an email, a push
9
+ # notification, or just appear in the UI somewhere):
10
+ #
11
+ # class Comment < ActiveRecord::Base
12
+ # belongs_to :commentable, polymorphic: true
13
+ # after_create -> { Notification.create! comment: self,
14
+ # recipients: commentable.recipients }
15
+ # end
16
+ #
17
+ # That's what you want the bulk of the time. New comment creates a new
18
+ # Notification. But there may well be off cases, like copying a commentable
19
+ # and its comments, where you don't want that. So you'd have a concern
20
+ # something like this:
21
+ #
22
+ # module Copyable
23
+ # def copy_to(destination)
24
+ # Notification.suppress do
25
+ # # Copy logic that creates new comments that we do not want
26
+ # # triggering notifications.
27
+ # end
28
+ # end
29
+ # end
30
+ module Suppressor
31
+ extend ActiveSupport::Concern
32
+
33
+ module ClassMethods
34
+ def suppress(&block)
35
+ previous_state = SuppressorRegistry.suppressed[name]
36
+ SuppressorRegistry.suppressed[name] = true
37
+ yield
38
+ ensure
39
+ SuppressorRegistry.suppressed[name] = previous_state
40
+ end
41
+ end
42
+
43
+ def save(*) # :nodoc:
44
+ SuppressorRegistry.suppressed[self.class.name] ? true : super
45
+ end
46
+
47
+ def save!(*) # :nodoc:
48
+ SuppressorRegistry.suppressed[self.class.name] ? true : super
49
+ end
50
+ end
51
+
52
+ class SuppressorRegistry # :nodoc:
53
+ extend ActiveSupport::PerThreadRegistry
54
+
55
+ attr_reader :suppressed
56
+
57
+ def initialize
58
+ @suppressed = {}
59
+ end
60
+ end
61
+ end