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
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  #
12
12
  # == Usage
13
13
  #
14
- # Active Records support optimistic locking if the field +lock_version+ is present. Each update to the
14
+ # Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the
15
15
  # record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
16
16
  # will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
17
17
  #
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  # p1.save
23
23
  #
24
24
  # p2.first_name = "should fail"
25
- # p2.save # Raises a ActiveRecord::StaleObjectError
25
+ # p2.save # Raises an ActiveRecord::StaleObjectError
26
26
  #
27
27
  # Optimistic locking will also check for stale data when objects are destroyed. Example:
28
28
  #
@@ -32,7 +32,7 @@ module ActiveRecord
32
32
  # p1.first_name = "Michael"
33
33
  # p1.save
34
34
  #
35
- # p2.destroy # Raises a ActiveRecord::StaleObjectError
35
+ # p2.destroy # Raises an ActiveRecord::StaleObjectError
36
36
  #
37
37
  # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
38
38
  # or otherwise apply the business logic needed to resolve the conflict.
@@ -40,16 +40,18 @@ module ActiveRecord
40
40
  # This locking mechanism will function inside a single Ruby process. To make it work across all
41
41
  # web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
42
42
  #
43
- # You must ensure that your database schema defaults the +lock_version+ column to 0.
44
- #
45
43
  # 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, invoke the <tt>set_locking_column</tt> method.
47
- # This method uses the same syntax as <tt>set_table_name</tt>
44
+ # To override the name of the +lock_version+ column, set the <tt>locking_column</tt> class attribute:
45
+ #
46
+ # class Person < ActiveRecord::Base
47
+ # self.locking_column = :lock_person
48
+ # end
49
+ #
48
50
  module Optimistic
49
51
  extend ActiveSupport::Concern
50
52
 
51
53
  included do
52
- cattr_accessor :lock_optimistically, :instance_writer => false
54
+ class_attribute :lock_optimistically, instance_writer: false
53
55
  self.lock_optimistically = true
54
56
  end
55
57
 
@@ -64,7 +66,16 @@ module ActiveRecord
64
66
  send(lock_col + '=', previous_lock_value + 1)
65
67
  end
66
68
 
67
- def update(attribute_names = @attributes.keys) #:nodoc:
69
+ def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
70
+ if locking_enabled?
71
+ # We always want to persist the locking version, even if we don't detect
72
+ # a change from the default, since the database might have no default
73
+ attribute_names |= [self.class.locking_column]
74
+ end
75
+ super
76
+ end
77
+
78
+ def _update_record(attribute_names = self.attribute_names) #:nodoc:
68
79
  return super unless locking_enabled?
69
80
  return 0 if attribute_names.empty?
70
81
 
@@ -78,13 +89,14 @@ module ActiveRecord
78
89
  begin
79
90
  relation = self.class.unscoped
80
91
 
81
- stmt = relation.where(
82
- relation.table[self.class.primary_key].eq(id).and(
83
- relation.table[lock_col].eq(quote_value(previous_lock_value, self.class.columns_hash[lock_col]))
84
- )
85
- ).arel.compile_update(arel_attributes_values(false, false, attribute_names))
86
-
87
- affected_rows = connection.update stmt
92
+ affected_rows = relation.where(
93
+ self.class.primary_key => id,
94
+ lock_col => previous_lock_value,
95
+ ).update_all(
96
+ attributes_for_update(attribute_names).map do |name|
97
+ [name, _read_attribute(name)]
98
+ end.to_h
99
+ )
88
100
 
89
101
  unless affected_rows == 1
90
102
  raise ActiveRecord::StaleObjectError.new(self, "update")
@@ -99,26 +111,25 @@ module ActiveRecord
99
111
  end
100
112
  end
101
113
 
102
- def destroy #:nodoc:
103
- return super unless locking_enabled?
114
+ def destroy_row
115
+ affected_rows = super
104
116
 
105
- destroy_associations
117
+ if locking_enabled? && affected_rows != 1
118
+ raise ActiveRecord::StaleObjectError.new(self, "destroy")
119
+ end
106
120
 
107
- if persisted?
108
- table = self.class.arel_table
109
- lock_col = self.class.locking_column
110
- predicate = table[self.class.primary_key].eq(id).
111
- and(table[lock_col].eq(send(lock_col).to_i))
121
+ affected_rows
122
+ end
112
123
 
113
- affected_rows = self.class.unscoped.where(predicate).delete_all
124
+ def relation_for_destroy
125
+ relation = super
114
126
 
115
- unless affected_rows == 1
116
- raise ActiveRecord::StaleObjectError.new(self, "destroy")
117
- end
127
+ if locking_enabled?
128
+ locking_column = self.class.locking_column
129
+ relation = relation.where(locking_column => _read_attribute(locking_column))
118
130
  end
119
131
 
120
- @destroyed = true
121
- freeze
132
+ relation
122
133
  end
123
134
 
124
135
  module ClassMethods
@@ -131,31 +142,18 @@ module ActiveRecord
131
142
  lock_optimistically && columns_hash[locking_column]
132
143
  end
133
144
 
134
- def locking_column=(value)
135
- @original_locking_column = @locking_column if defined?(@locking_column)
136
- @locking_column = value.to_s
137
- end
138
-
139
145
  # Set the column to use for optimistic locking. Defaults to +lock_version+.
140
- def set_locking_column(value = nil, &block)
141
- deprecated_property_setter :locking_column, value, block
146
+ def locking_column=(value)
147
+ reload_schema_from_cache
148
+ @locking_column = value.to_s
142
149
  end
143
150
 
144
151
  # The version column used for optimistic locking. Defaults to +lock_version+.
145
152
  def locking_column
146
- reset_locking_column unless defined?(@locking_column)
153
+ @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
147
154
  @locking_column
148
155
  end
149
156
 
150
- def original_locking_column #:nodoc:
151
- deprecated_original_property_getter :locking_column
152
- end
153
-
154
- # Quote the column name used for optimistic locking.
155
- def quoted_locking_column
156
- connection.quote_column_name(locking_column)
157
- end
158
-
159
157
  # Reset the column used for optimistic locking back to the +lock_version+ default.
160
158
  def reset_locking_column
161
159
  self.locking_column = DEFAULT_LOCKING_COLUMN
@@ -168,18 +166,41 @@ module ActiveRecord
168
166
  super
169
167
  end
170
168
 
171
- # If the locking column has no default value set,
172
- # start the lock version at zero. Note we can't use
173
- # <tt>locking_enabled?</tt> at this point as
174
- # <tt>@attributes</tt> may not have been initialized yet.
175
- def initialize_attributes(attributes, options = {}) #:nodoc:
176
- if attributes.key?(locking_column) && lock_optimistically
177
- attributes[locking_column] ||= 0
169
+ private
170
+
171
+ # We need to apply this decorator here, rather than on module inclusion. The closure
172
+ # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
173
+ # sub class being decorated. As such, changes to `lock_optimistically`, or
174
+ # `locking_column` would not be picked up.
175
+ def inherited(subclass)
176
+ subclass.class_eval do
177
+ is_lock_column = ->(name, _) { lock_optimistically && name == locking_column }
178
+ decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type|
179
+ LockingType.new(type)
180
+ end
178
181
  end
179
-
180
- attributes
182
+ super
181
183
  end
182
184
  end
183
185
  end
186
+
187
+ class LockingType < DelegateClass(Type::Value) # :nodoc:
188
+ def deserialize(value)
189
+ # `nil` *should* be changed to 0
190
+ super.to_i
191
+ end
192
+
193
+ def serialize(value)
194
+ super.to_i
195
+ end
196
+
197
+ def init_with(coder)
198
+ __setobj__(coder['subtype'])
199
+ end
200
+
201
+ def encode_with(coder)
202
+ coder['subtype'] = __getobj__
203
+ end
204
+ end
184
205
  end
185
206
  end
@@ -3,12 +3,12 @@ module ActiveRecord
3
3
  # Locking::Pessimistic provides support for row-level locking using
4
4
  # SELECT ... FOR UPDATE and other lock types.
5
5
  #
6
- # Pass <tt>:lock => true</tt> to <tt>ActiveRecord::Base.find</tt> to obtain an exclusive
6
+ # Chain <tt>ActiveRecord::Base#find</tt> to <tt>ActiveRecord::QueryMethods#lock</tt> to obtain an exclusive
7
7
  # lock on the selected rows:
8
8
  # # select * from accounts where id=1 for update
9
- # Account.find(1, :lock => true)
9
+ # Account.lock.find(1)
10
10
  #
11
- # Pass <tt>:lock => 'some locking clause'</tt> to give a database-specific locking clause
11
+ # Call <tt>lock('some locking clause')</tt> to use a database-specific locking clause
12
12
  # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
13
13
  #
14
14
  # Account.transaction do
@@ -26,7 +26,7 @@ module ActiveRecord
26
26
  #
27
27
  # Account.transaction do
28
28
  # # select * from accounts where ...
29
- # accounts = Account.where(...).all
29
+ # accounts = Account.where(...)
30
30
  # account1 = accounts.detect { |account| ... }
31
31
  # account2 = accounts.detect { |account| ... }
32
32
  # # select * from accounts where id=? for update
@@ -51,7 +51,7 @@ module ActiveRecord
51
51
  # end
52
52
  #
53
53
  # Database-specific information on row locking:
54
- # MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
54
+ # MySQL: http://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
55
55
  # PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
56
56
  module Pessimistic
57
57
  # Obtain a row lock on this record. Reloads the record to obtain the requested
@@ -64,7 +64,7 @@ module ActiveRecord
64
64
  end
65
65
 
66
66
  # Wraps the passed block in a transaction, locking the object
67
- # before yielding. You pass can the SQL locking clause
67
+ # before yielding. You can pass the SQL locking clause
68
68
  # as argument (see <tt>lock!</tt>).
69
69
  def with_lock(lock = true)
70
70
  transaction do
@@ -1,11 +1,13 @@
1
1
  module ActiveRecord
2
2
  class LogSubscriber < ActiveSupport::LogSubscriber
3
+ IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
4
+
3
5
  def self.runtime=(value)
4
- Thread.current["active_record_sql_runtime"] = value
6
+ ActiveRecord::RuntimeRegistry.sql_runtime = value
5
7
  end
6
8
 
7
9
  def self.runtime
8
- Thread.current["active_record_sql_runtime"] ||= 0
10
+ ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
9
11
  end
10
12
 
11
13
  def self.reset_runtime
@@ -15,52 +17,75 @@ module ActiveRecord
15
17
 
16
18
  def initialize
17
19
  super
18
- @odd_or_even = false
20
+ @odd = false
21
+ end
22
+
23
+ def render_bind(attribute)
24
+ value = if attribute.type.binary? && attribute.value
25
+ if attribute.value.is_a?(Hash)
26
+ "<#{attribute.value_for_database.to_s.bytesize} bytes of binary data>"
27
+ else
28
+ "<#{attribute.value.bytesize} bytes of binary data>"
29
+ end
30
+ else
31
+ attribute.value_for_database
32
+ end
33
+
34
+ [attribute.name, value]
19
35
  end
20
36
 
21
37
  def sql(event)
22
- self.class.runtime += event.duration
23
38
  return unless logger.debug?
24
39
 
40
+ self.class.runtime += event.duration
41
+
25
42
  payload = event.payload
26
43
 
27
- return if 'SCHEMA' == payload[:name]
44
+ return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
28
45
 
29
- name = '%s (%.1fms)' % [payload[:name], event.duration]
30
- sql = payload[:sql].squeeze(' ')
46
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
47
+ sql = payload[:sql]
31
48
  binds = nil
32
49
 
33
50
  unless (payload[:binds] || []).empty?
34
- binds = " " + payload[:binds].map { |col,v|
35
- if col
36
- [col.name, v]
37
- else
38
- [nil, v]
39
- end
40
- }.inspect
51
+ binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect
41
52
  end
42
53
 
43
- if odd?
44
- name = color(name, CYAN, true)
45
- sql = color(sql, nil, true)
46
- else
47
- name = color(name, MAGENTA, true)
48
- end
54
+ name = colorize_payload_name(name, payload[:name])
55
+ sql = color(sql, sql_color(sql), true)
49
56
 
50
57
  debug " #{name} #{sql}#{binds}"
51
58
  end
52
59
 
53
- def identity(event)
54
- return unless logger.debug?
55
-
56
- name = color(event.payload[:name], odd? ? CYAN : MAGENTA, true)
57
- line = odd? ? color(event.payload[:line], nil, true) : event.payload[:line]
60
+ private
58
61
 
59
- debug " #{name} #{line}"
62
+ def colorize_payload_name(name, payload_name)
63
+ if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
64
+ color(name, MAGENTA, true)
65
+ else
66
+ color(name, CYAN, true)
67
+ end
60
68
  end
61
69
 
62
- def odd?
63
- @odd_or_even = !@odd_or_even
70
+ def sql_color(sql)
71
+ case sql
72
+ when /\A\s*rollback/mi
73
+ RED
74
+ when /select .*for update/mi, /\A\s*lock/mi
75
+ WHITE
76
+ when /\A\s*select/i
77
+ BLUE
78
+ when /\A\s*insert/i
79
+ GREEN
80
+ when /\A\s*update/i
81
+ YELLOW
82
+ when /\A\s*delete/i
83
+ RED
84
+ when /transaction\s*\Z/i
85
+ CYAN
86
+ else
87
+ MAGENTA
88
+ end
64
89
  end
65
90
 
66
91
  def logger
@@ -5,69 +5,150 @@ module ActiveRecord
5
5
  # knows how to invert the following commands:
6
6
  #
7
7
  # * add_column
8
+ # * add_foreign_key
8
9
  # * add_index
10
+ # * add_reference
9
11
  # * add_timestamps
12
+ # * change_column
13
+ # * change_column_default (must supply a :from and :to option)
14
+ # * change_column_null
15
+ # * create_join_table
10
16
  # * create_table
17
+ # * disable_extension
18
+ # * drop_join_table
19
+ # * drop_table (must supply a block)
20
+ # * enable_extension
21
+ # * remove_column (must supply a type)
22
+ # * remove_columns (must specify at least one column name or more)
23
+ # * remove_foreign_key (must supply a second table)
24
+ # * remove_index
25
+ # * remove_reference
11
26
  # * remove_timestamps
12
27
  # * rename_column
13
28
  # * rename_index
14
29
  # * rename_table
15
30
  class CommandRecorder
16
- attr_accessor :commands, :delegate
31
+ ReversibleAndIrreversibleMethods = [:create_table, :create_join_table, :rename_table, :add_column, :remove_column,
32
+ :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps,
33
+ :change_column_default, :add_reference, :remove_reference, :transaction,
34
+ :drop_join_table, :drop_table, :execute_block, :enable_extension, :disable_extension,
35
+ :change_column, :execute, :remove_columns, :change_column_null,
36
+ :add_foreign_key, :remove_foreign_key
37
+ ]
38
+ include JoinTable
39
+
40
+ attr_accessor :commands, :delegate, :reverting
17
41
 
18
42
  def initialize(delegate = nil)
19
43
  @commands = []
20
44
  @delegate = delegate
45
+ @reverting = false
21
46
  end
22
47
 
23
- # record +command+. +command+ should be a method name and arguments.
48
+ # While executing the given block, the recorded will be in reverting mode.
49
+ # All commands recorded will end up being recorded reverted
50
+ # and in reverse order.
51
+ # For example:
52
+ #
53
+ # recorder.revert{ recorder.record(:rename_table, [:old, :new]) }
54
+ # # same effect as recorder.record(:rename_table, [:new, :old])
55
+ def revert
56
+ @reverting = !@reverting
57
+ previous = @commands
58
+ @commands = []
59
+ yield
60
+ ensure
61
+ @commands = previous.concat(@commands.reverse)
62
+ @reverting = !@reverting
63
+ end
64
+
65
+ # Record +command+. +command+ should be a method name and arguments.
24
66
  # For example:
25
67
  #
26
68
  # recorder.record(:method_name, [:arg1, :arg2])
27
- def record(*command)
28
- @commands << command
69
+ def record(*command, &block)
70
+ if @reverting
71
+ @commands << inverse_of(*command, &block)
72
+ else
73
+ @commands << (command << block)
74
+ end
29
75
  end
30
76
 
31
- # Returns a list that represents commands that are the inverse of the
32
- # commands stored in +commands+. For example:
77
+ # Returns the inverse of the given command. For example:
33
78
  #
34
- # recorder.record(:rename_table, [:old, :new])
35
- # recorder.inverse # => [:rename_table, [:new, :old]]
79
+ # recorder.inverse_of(:rename_table, [:old, :new])
80
+ # # => [:rename_table, [:new, :old]]
36
81
  #
37
82
  # This method will raise an +IrreversibleMigration+ exception if it cannot
38
- # invert the +commands+.
39
- def inverse
40
- @commands.reverse.map { |name, args|
41
- method = :"invert_#{name}"
42
- raise IrreversibleMigration unless respond_to?(method, true)
43
- send(method, args)
44
- }
83
+ # invert the +command+.
84
+ def inverse_of(command, args, &block)
85
+ method = :"invert_#{command}"
86
+ raise IrreversibleMigration, <<-MSG.strip_heredoc unless respond_to?(method, true)
87
+ This migration uses #{command}, which is not automatically reversible.
88
+ To make the migration reversible you can either:
89
+ 1. Define #up and #down methods in place of the #change method.
90
+ 2. Use the #reversible method to define reversible behavior.
91
+ MSG
92
+ send(method, args, &block)
45
93
  end
46
94
 
47
95
  def respond_to?(*args) # :nodoc:
48
96
  super || delegate.respond_to?(*args)
49
97
  end
50
98
 
51
- [:create_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method|
99
+ ReversibleAndIrreversibleMethods.each do |method|
52
100
  class_eval <<-EOV, __FILE__, __LINE__ + 1
53
- def #{method}(*args) # def create_table(*args)
54
- record(:"#{method}", args) # record(:create_table, args)
55
- end # end
101
+ def #{method}(*args, &block) # def create_table(*args, &block)
102
+ record(:"#{method}", args, &block) # record(:create_table, args, &block)
103
+ end # end
56
104
  EOV
57
105
  end
106
+ alias :add_belongs_to :add_reference
107
+ alias :remove_belongs_to :remove_reference
108
+
109
+ def change_table(table_name, options = {}) # :nodoc:
110
+ yield delegate.update_table_definition(table_name, self)
111
+ end
58
112
 
59
113
  private
60
114
 
61
- def invert_create_table(args)
62
- [:drop_table, [args.first]]
115
+ module StraightReversions
116
+ private
117
+ { transaction: :transaction,
118
+ execute_block: :execute_block,
119
+ create_table: :drop_table,
120
+ create_join_table: :drop_join_table,
121
+ add_column: :remove_column,
122
+ add_timestamps: :remove_timestamps,
123
+ add_reference: :remove_reference,
124
+ enable_extension: :disable_extension
125
+ }.each do |cmd, inv|
126
+ [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse|
127
+ class_eval <<-EOV, __FILE__, __LINE__ + 1
128
+ def invert_#{method}(args, &block) # def invert_create_table(args, &block)
129
+ [:#{inverse}, args, block] # [:drop_table, args, block]
130
+ end # end
131
+ EOV
132
+ end
133
+ end
134
+ end
135
+
136
+ include StraightReversions
137
+
138
+ def invert_drop_table(args, &block)
139
+ if args.size == 1 && block == nil
140
+ raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)."
141
+ end
142
+ super
63
143
  end
64
144
 
65
145
  def invert_rename_table(args)
66
146
  [:rename_table, args.reverse]
67
147
  end
68
148
 
69
- def invert_add_column(args)
70
- [:remove_column, args.first(2)]
149
+ def invert_remove_column(args)
150
+ raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2
151
+ super
71
152
  end
72
153
 
73
154
  def invert_rename_index(args)
@@ -80,26 +161,78 @@ module ActiveRecord
80
161
 
81
162
  def invert_add_index(args)
82
163
  table, columns, options = *args
83
- index_name = options.try(:[], :name)
84
- options_hash = index_name ? {:name => index_name} : {:column => columns}
164
+ options ||= {}
165
+
166
+ index_name = options[:name]
167
+ options_hash = index_name ? { name: index_name } : { column: columns }
168
+
85
169
  [:remove_index, [table, options_hash]]
86
170
  end
87
171
 
88
- def invert_remove_timestamps(args)
89
- [:add_timestamps, args]
172
+ def invert_remove_index(args)
173
+ table, options_or_column = *args
174
+ if (options = options_or_column).is_a?(Hash)
175
+ unless options[:column]
176
+ raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option."
177
+ end
178
+ options = options.dup
179
+ [:add_index, [table, options.delete(:column), options]]
180
+ elsif (column = options_or_column).present?
181
+ [:add_index, [table, column]]
182
+ end
90
183
  end
91
184
 
92
- def invert_add_timestamps(args)
93
- [:remove_timestamps, args]
185
+ alias :invert_add_belongs_to :invert_add_reference
186
+ alias :invert_remove_belongs_to :invert_remove_reference
187
+
188
+ def invert_change_column_default(args)
189
+ table, column, options = *args
190
+
191
+ unless options && options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
192
+ raise ActiveRecord::IrreversibleMigration, "change_column_default is only reversible if given a :from and :to option."
193
+ end
194
+
195
+ [:change_column_default, [table, column, from: options[:to], to: options[:from]]]
196
+ end
197
+
198
+ def invert_change_column_null(args)
199
+ args[2] = !args[2]
200
+ [:change_column_null, args]
201
+ end
202
+
203
+ def invert_add_foreign_key(args)
204
+ from_table, to_table, add_options = args
205
+ add_options ||= {}
206
+
207
+ if add_options[:name]
208
+ options = { name: add_options[:name] }
209
+ elsif add_options[:column]
210
+ options = { column: add_options[:column] }
211
+ else
212
+ options = to_table
213
+ end
214
+
215
+ [:remove_foreign_key, [from_table, options]]
216
+ end
217
+
218
+ def invert_remove_foreign_key(args)
219
+ from_table, to_table, remove_options = args
220
+ raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil? || to_table.is_a?(Hash)
221
+
222
+ reversed_args = [from_table, to_table]
223
+ reversed_args << remove_options if remove_options
224
+
225
+ [:add_foreign_key, reversed_args]
94
226
  end
95
227
 
96
228
  # Forwards any missing method call to the \target.
97
229
  def method_missing(method, *args, &block)
98
- @delegate.send(method, *args, &block)
99
- rescue NoMethodError => e
100
- raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@delegate}")
230
+ if @delegate.respond_to?(method)
231
+ @delegate.send(method, *args, &block)
232
+ else
233
+ super
234
+ end
101
235
  end
102
-
103
236
  end
104
237
  end
105
238
  end