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,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/scoping/default"
4
+ require "active_record/scoping/named"
5
+
6
+ module ActiveRecord
7
+ # This class is used to create a table that keeps track of values and keys such
8
+ # as which environment migrations were run in.
9
+ class InternalMetadata < ActiveRecord::Base # :nodoc:
10
+ class << self
11
+ def primary_key
12
+ "key"
13
+ end
14
+
15
+ def table_name
16
+ "#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}"
17
+ end
18
+
19
+ def []=(key, value)
20
+ find_or_initialize_by(key: key).update_attributes!(value: value)
21
+ end
22
+
23
+ def [](key)
24
+ where(key: key).pluck(:value).first
25
+ end
26
+
27
+ def table_exists?
28
+ connection.table_exists?(table_name)
29
+ end
30
+
31
+ # Creates an internal metadata table with columns +key+ and +value+
32
+ def create_table
33
+ unless table_exists?
34
+ key_options = connection.internal_string_options_for_primary_key
35
+
36
+ connection.create_table(table_name, id: false) do |t|
37
+ t.string :key, key_options
38
+ t.string :value
39
+ t.timestamps
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module LegacyYamlAdapter
5
+ def self.convert(klass, coder)
6
+ return coder unless coder.is_a?(Psych::Coder)
7
+
8
+ case coder["active_record_yaml_version"]
9
+ when 1, 2 then coder
10
+ else
11
+ if coder["attributes"].is_a?(ActiveModel::AttributeSet)
12
+ Rails420.convert(klass, coder)
13
+ else
14
+ Rails41.convert(klass, coder)
15
+ end
16
+ end
17
+ end
18
+
19
+ module Rails420
20
+ def self.convert(klass, coder)
21
+ attribute_set = coder["attributes"]
22
+
23
+ klass.attribute_names.each do |attr_name|
24
+ attribute = attribute_set[attr_name]
25
+ if attribute.type.is_a?(Delegator)
26
+ type_from_klass = klass.type_for_attribute(attr_name)
27
+ attribute_set[attr_name] = attribute.with_type(type_from_klass)
28
+ end
29
+ end
30
+
31
+ coder
32
+ end
33
+ end
34
+
35
+ module Rails41
36
+ def self.convert(klass, coder)
37
+ attributes = klass.attributes_builder
38
+ .build_from_database(coder["attributes"])
39
+ new_record = coder["attributes"][klass.primary_key].blank?
40
+
41
+ {
42
+ "attributes" => attributes,
43
+ "new_record" => new_record,
44
+ }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,48 @@
1
+ en:
2
+ # Attributes names common to most models
3
+ #attributes:
4
+ #created_at: "Created at"
5
+ #updated_at: "Updated at"
6
+
7
+ # Default error messages
8
+ errors:
9
+ messages:
10
+ required: "must exist"
11
+ taken: "has already been taken"
12
+
13
+ # Active Record models configuration
14
+ activerecord:
15
+ errors:
16
+ messages:
17
+ record_invalid: "Validation failed: %{errors}"
18
+ restrict_dependent_destroy:
19
+ has_one: "Cannot delete record because a dependent %{record} exists"
20
+ has_many: "Cannot delete record because dependent %{record} exist"
21
+ # Append your own errors here or at the model/attributes scope.
22
+
23
+ # You can define own errors for models or model attributes.
24
+ # The values :model, :attribute and :value are always available for interpolation.
25
+ #
26
+ # For example,
27
+ # models:
28
+ # user:
29
+ # blank: "This is a custom blank message for %{model}: %{attribute}"
30
+ # attributes:
31
+ # login:
32
+ # blank: "This is a custom blank message for User login"
33
+ # Will define custom blank validation message for User model and
34
+ # custom blank validation message for login attribute of User model.
35
+ #models:
36
+
37
+ # Translate model names. Used in Model.human_name().
38
+ #models:
39
+ # For example,
40
+ # user: "Dude"
41
+ # will translate User model name to "Dude"
42
+
43
+ # Translate model attribute names. Used in Model.human_attribute_name(attribute).
44
+ #attributes:
45
+ # For example,
46
+ # user:
47
+ # login: "Handle"
48
+ # will translate User attribute "login" as "Handle"
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Locking
5
+ # == What is Optimistic Locking
6
+ #
7
+ # Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
8
+ # conflicts with the data. It does this by checking whether another process has made changes to a record since
9
+ # it was opened, an <tt>ActiveRecord::StaleObjectError</tt> exception is thrown if that has occurred
10
+ # and the update is ignored.
11
+ #
12
+ # Check out <tt>ActiveRecord::Locking::Pessimistic</tt> for an alternative.
13
+ #
14
+ # == Usage
15
+ #
16
+ # Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the
17
+ # record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
18
+ # will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
19
+ #
20
+ # p1 = Person.find(1)
21
+ # p2 = Person.find(1)
22
+ #
23
+ # p1.first_name = "Michael"
24
+ # p1.save
25
+ #
26
+ # p2.first_name = "should fail"
27
+ # p2.save # Raises an ActiveRecord::StaleObjectError
28
+ #
29
+ # Optimistic locking will also check for stale data when objects are destroyed. Example:
30
+ #
31
+ # p1 = Person.find(1)
32
+ # p2 = Person.find(1)
33
+ #
34
+ # p1.first_name = "Michael"
35
+ # p1.save
36
+ #
37
+ # p2.destroy # Raises an ActiveRecord::StaleObjectError
38
+ #
39
+ # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
40
+ # or otherwise apply the business logic needed to resolve the conflict.
41
+ #
42
+ # This locking mechanism will function inside a single Ruby process. To make it work across all
43
+ # web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
44
+ #
45
+ # This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
46
+ # To override the name of the +lock_version+ column, set the <tt>locking_column</tt> class attribute:
47
+ #
48
+ # class Person < ActiveRecord::Base
49
+ # self.locking_column = :lock_person
50
+ # end
51
+ #
52
+ module Optimistic
53
+ extend ActiveSupport::Concern
54
+
55
+ included do
56
+ class_attribute :lock_optimistically, instance_writer: false, default: true
57
+ end
58
+
59
+ def locking_enabled? #:nodoc:
60
+ self.class.locking_enabled?
61
+ end
62
+
63
+ private
64
+ def _create_record(attribute_names = self.attribute_names, *)
65
+ if locking_enabled?
66
+ # We always want to persist the locking version, even if we don't detect
67
+ # a change from the default, since the database might have no default
68
+ attribute_names |= [self.class.locking_column]
69
+ end
70
+ super
71
+ end
72
+
73
+ def _touch_row(attribute_names, time)
74
+ super
75
+ ensure
76
+ clear_attribute_change(self.class.locking_column) if locking_enabled?
77
+ end
78
+
79
+ def _update_row(attribute_names, attempted_action = "update")
80
+ return super unless locking_enabled?
81
+
82
+ begin
83
+ locking_column = self.class.locking_column
84
+ previous_lock_value = read_attribute_before_type_cast(locking_column)
85
+ attribute_names << locking_column
86
+
87
+ self[locking_column] += 1
88
+
89
+ affected_rows = self.class._update_record(
90
+ attributes_with_values(attribute_names),
91
+ self.class.primary_key => id_in_database,
92
+ locking_column => previous_lock_value
93
+ )
94
+
95
+ if affected_rows != 1
96
+ raise ActiveRecord::StaleObjectError.new(self, attempted_action)
97
+ end
98
+
99
+ affected_rows
100
+
101
+ # If something went wrong, revert the locking_column value.
102
+ rescue Exception
103
+ self[locking_column] = previous_lock_value.to_i
104
+ raise
105
+ end
106
+ end
107
+
108
+ def destroy_row
109
+ return super unless locking_enabled?
110
+
111
+ locking_column = self.class.locking_column
112
+
113
+ affected_rows = self.class._delete_record(
114
+ self.class.primary_key => id_in_database,
115
+ locking_column => read_attribute_before_type_cast(locking_column)
116
+ )
117
+
118
+ if affected_rows != 1
119
+ raise ActiveRecord::StaleObjectError.new(self, "destroy")
120
+ end
121
+
122
+ affected_rows
123
+ end
124
+
125
+ module ClassMethods
126
+ DEFAULT_LOCKING_COLUMN = "lock_version"
127
+
128
+ # Returns true if the +lock_optimistically+ flag is set to true
129
+ # (which it is, by default) and the table includes the
130
+ # +locking_column+ column (defaults to +lock_version+).
131
+ def locking_enabled?
132
+ lock_optimistically && columns_hash[locking_column]
133
+ end
134
+
135
+ # Set the column to use for optimistic locking. Defaults to +lock_version+.
136
+ def locking_column=(value)
137
+ reload_schema_from_cache
138
+ @locking_column = value.to_s
139
+ end
140
+
141
+ # The version column used for optimistic locking. Defaults to +lock_version+.
142
+ def locking_column
143
+ @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
144
+ @locking_column
145
+ end
146
+
147
+ # Reset the column used for optimistic locking back to the +lock_version+ default.
148
+ def reset_locking_column
149
+ self.locking_column = DEFAULT_LOCKING_COLUMN
150
+ end
151
+
152
+ # Make sure the lock version column gets updated when counters are
153
+ # updated.
154
+ def update_counters(id, counters)
155
+ counters = counters.merge(locking_column => 1) if locking_enabled?
156
+ super
157
+ end
158
+
159
+ private
160
+
161
+ # We need to apply this decorator here, rather than on module inclusion. The closure
162
+ # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
163
+ # sub class being decorated. As such, changes to `lock_optimistically`, or
164
+ # `locking_column` would not be picked up.
165
+ def inherited(subclass)
166
+ subclass.class_eval do
167
+ is_lock_column = ->(name, _) { lock_optimistically && name == locking_column }
168
+ decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type|
169
+ LockingType.new(type)
170
+ end
171
+ end
172
+ super
173
+ end
174
+ end
175
+ end
176
+
177
+ # In de/serialize we change `nil` to 0, so that we can allow passing
178
+ # `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError`
179
+ # during update record.
180
+ class LockingType < DelegateClass(Type::Value) # :nodoc:
181
+ def deserialize(value)
182
+ super.to_i
183
+ end
184
+
185
+ def serialize(value)
186
+ super.to_i
187
+ end
188
+
189
+ def init_with(coder)
190
+ __setobj__(coder["subtype"])
191
+ end
192
+
193
+ def encode_with(coder)
194
+ coder["subtype"] = __getobj__
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Locking
5
+ # Locking::Pessimistic provides support for row-level locking using
6
+ # SELECT ... FOR UPDATE and other lock types.
7
+ #
8
+ # Chain <tt>ActiveRecord::Base#find</tt> to <tt>ActiveRecord::QueryMethods#lock</tt> to obtain an exclusive
9
+ # lock on the selected rows:
10
+ # # select * from accounts where id=1 for update
11
+ # Account.lock.find(1)
12
+ #
13
+ # Call <tt>lock('some locking clause')</tt> to use a database-specific locking clause
14
+ # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
15
+ #
16
+ # Account.transaction do
17
+ # # select * from accounts where name = 'shugo' limit 1 for update
18
+ # shugo = Account.where("name = 'shugo'").lock(true).first
19
+ # yuko = Account.where("name = 'yuko'").lock(true).first
20
+ # shugo.balance -= 100
21
+ # shugo.save!
22
+ # yuko.balance += 100
23
+ # yuko.save!
24
+ # end
25
+ #
26
+ # You can also use <tt>ActiveRecord::Base#lock!</tt> method to lock one record by id.
27
+ # This may be better if you don't need to lock every row. Example:
28
+ #
29
+ # Account.transaction do
30
+ # # select * from accounts where ...
31
+ # accounts = Account.where(...)
32
+ # account1 = accounts.detect { |account| ... }
33
+ # account2 = accounts.detect { |account| ... }
34
+ # # select * from accounts where id=? for update
35
+ # account1.lock!
36
+ # account2.lock!
37
+ # account1.balance -= 100
38
+ # account1.save!
39
+ # account2.balance += 100
40
+ # account2.save!
41
+ # end
42
+ #
43
+ # You can start a transaction and acquire the lock in one go by calling
44
+ # <tt>with_lock</tt> with a block. The block is called from within
45
+ # a transaction, the object is already locked. Example:
46
+ #
47
+ # account = Account.first
48
+ # account.with_lock do
49
+ # # This block is called within a transaction,
50
+ # # account is already locked.
51
+ # account.balance -= 100
52
+ # account.save!
53
+ # end
54
+ #
55
+ # Database-specific information on row locking:
56
+ # MySQL: https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
57
+ # PostgreSQL: https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
58
+ module Pessimistic
59
+ # Obtain a row lock on this record. Reloads the record to obtain the requested
60
+ # lock. Pass an SQL locking clause to append the end of the SELECT statement
61
+ # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
62
+ # the locked record.
63
+ def lock!(lock = true)
64
+ if persisted?
65
+ if has_changes_to_save?
66
+ raise(<<-MSG.squish)
67
+ Locking a record with unpersisted changes is not supported. Use
68
+ `save` to persist the changes, or `reload` to discard them
69
+ explicitly.
70
+ MSG
71
+ end
72
+
73
+ reload(lock: lock)
74
+ end
75
+ self
76
+ end
77
+
78
+ # Wraps the passed block in a transaction, locking the object
79
+ # before yielding. You can pass the SQL locking clause
80
+ # as argument (see <tt>lock!</tt>).
81
+ def with_lock(lock = true)
82
+ transaction do
83
+ lock!(lock)
84
+ yield
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class LogSubscriber < ActiveSupport::LogSubscriber
5
+ IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
6
+
7
+ def self.runtime=(value)
8
+ ActiveRecord::RuntimeRegistry.sql_runtime = value
9
+ end
10
+
11
+ def self.runtime
12
+ ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
13
+ end
14
+
15
+ def self.reset_runtime
16
+ rt, self.runtime = runtime, 0
17
+ rt
18
+ end
19
+
20
+ def sql(event)
21
+ self.class.runtime += event.duration
22
+ return unless logger.debug?
23
+
24
+ payload = event.payload
25
+
26
+ return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
27
+
28
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
29
+ name = "CACHE #{name}" if payload[:cached]
30
+ sql = payload[:sql]
31
+ binds = nil
32
+
33
+ unless (payload[:binds] || []).empty?
34
+ casted_params = type_casted_binds(payload[:type_casted_binds])
35
+ binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
36
+ render_bind(attr, value)
37
+ }.inspect
38
+ end
39
+
40
+ name = colorize_payload_name(name, payload[:name])
41
+ sql = color(sql, sql_color(sql), true)
42
+
43
+ debug " #{name} #{sql}#{binds}"
44
+ end
45
+
46
+ private
47
+ def type_casted_binds(casted_binds)
48
+ casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
49
+ end
50
+
51
+ def render_bind(attr, value)
52
+ if attr.is_a?(Array)
53
+ attr = attr.first
54
+ elsif attr.type.binary? && attr.value
55
+ value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
56
+ end
57
+
58
+ [attr && attr.name, value]
59
+ end
60
+
61
+ def colorize_payload_name(name, payload_name)
62
+ if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
63
+ color(name, MAGENTA, true)
64
+ else
65
+ color(name, CYAN, true)
66
+ end
67
+ end
68
+
69
+ def sql_color(sql)
70
+ case sql
71
+ when /\A\s*rollback/mi
72
+ RED
73
+ when /select .*for update/mi, /\A\s*lock/mi
74
+ WHITE
75
+ when /\A\s*select/i
76
+ BLUE
77
+ when /\A\s*insert/i
78
+ GREEN
79
+ when /\A\s*update/i
80
+ YELLOW
81
+ when /\A\s*delete/i
82
+ RED
83
+ when /transaction\s*\Z/i
84
+ CYAN
85
+ else
86
+ MAGENTA
87
+ end
88
+ end
89
+
90
+ def logger
91
+ ActiveRecord::Base.logger
92
+ end
93
+
94
+ def debug(progname = nil, &block)
95
+ return unless super
96
+
97
+ if ActiveRecord::Base.verbose_query_logs
98
+ log_query_source
99
+ end
100
+ end
101
+
102
+ def log_query_source
103
+ source_line, line_number = extract_callstack(caller_locations)
104
+
105
+ if source_line
106
+ if defined?(::Rails.root)
107
+ app_root = "#{::Rails.root.to_s}/".freeze
108
+ source_line = source_line.sub(app_root, "")
109
+ end
110
+
111
+ logger.debug(" ↳ #{ source_line }:#{ line_number }")
112
+ end
113
+ end
114
+
115
+ def extract_callstack(callstack)
116
+ line = callstack.find do |frame|
117
+ frame.absolute_path && !ignored_callstack(frame.absolute_path)
118
+ end
119
+
120
+ offending_line = line || callstack.first
121
+
122
+ [
123
+ offending_line.path,
124
+ offending_line.lineno
125
+ ]
126
+ end
127
+
128
+ RAILS_GEM_ROOT = File.expand_path("../../..", __dir__) + "/"
129
+
130
+ def ignored_callstack(path)
131
+ path.start_with?(RAILS_GEM_ROOT) ||
132
+ path.start_with?(RbConfig::CONFIG["rubylibdir"])
133
+ end
134
+ end
135
+ end
136
+
137
+ ActiveRecord::LogSubscriber.attach_to :active_record