activerecord 3.2.22.4 → 4.0.13

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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2799 -617
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +23 -32
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +40 -34
  7. data/lib/active_record/association_relation.rb +22 -0
  8. data/lib/active_record/associations/alias_tracker.rb +4 -2
  9. data/lib/active_record/associations/association.rb +60 -46
  10. data/lib/active_record/associations/association_scope.rb +46 -40
  11. data/lib/active_record/associations/belongs_to_association.rb +17 -4
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +73 -56
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +130 -96
  21. data/lib/active_record/associations/collection_proxy.rb +916 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
  23. data/lib/active_record/associations/has_many_association.rb +35 -8
  24. data/lib/active_record/associations/has_many_through_association.rb +37 -17
  25. data/lib/active_record/associations/has_one_association.rb +42 -19
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
  28. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  29. data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
  30. data/lib/active_record/associations/join_dependency.rb +30 -9
  31. data/lib/active_record/associations/join_helper.rb +1 -11
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/preloader.rb +20 -43
  39. data/lib/active_record/associations/singular_association.rb +11 -11
  40. data/lib/active_record/associations/through_association.rb +3 -3
  41. data/lib/active_record/associations.rb +223 -282
  42. data/lib/active_record/attribute_assignment.rb +134 -154
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  44. data/lib/active_record/attribute_methods/dirty.rb +36 -29
  45. data/lib/active_record/attribute_methods/primary_key.rb +45 -31
  46. data/lib/active_record/attribute_methods/query.rb +5 -4
  47. data/lib/active_record/attribute_methods/read.rb +67 -90
  48. data/lib/active_record/attribute_methods/serialization.rb +133 -70
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
  50. data/lib/active_record/attribute_methods/write.rb +34 -39
  51. data/lib/active_record/attribute_methods.rb +268 -108
  52. data/lib/active_record/autosave_association.rb +80 -73
  53. data/lib/active_record/base.rb +54 -451
  54. data/lib/active_record/callbacks.rb +60 -22
  55. data/lib/active_record/coders/yaml_column.rb +18 -21
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
  67. data/lib/active_record/connection_adapters/column.rb +67 -36
  68. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
  70. data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
  71. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
  72. data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
  79. data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
  80. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
  81. data/lib/active_record/connection_handling.rb +98 -0
  82. data/lib/active_record/core.rb +472 -0
  83. data/lib/active_record/counter_cache.rb +107 -108
  84. data/lib/active_record/dynamic_matchers.rb +115 -63
  85. data/lib/active_record/errors.rb +36 -18
  86. data/lib/active_record/explain.rb +15 -63
  87. data/lib/active_record/explain_registry.rb +30 -0
  88. data/lib/active_record/explain_subscriber.rb +8 -4
  89. data/lib/active_record/fixture_set/file.rb +55 -0
  90. data/lib/active_record/fixtures.rb +159 -155
  91. data/lib/active_record/inheritance.rb +93 -59
  92. data/lib/active_record/integration.rb +8 -8
  93. data/lib/active_record/locale/en.yml +8 -1
  94. data/lib/active_record/locking/optimistic.rb +39 -43
  95. data/lib/active_record/locking/pessimistic.rb +4 -4
  96. data/lib/active_record/log_subscriber.rb +19 -9
  97. data/lib/active_record/migration/command_recorder.rb +102 -33
  98. data/lib/active_record/migration/join_table.rb +15 -0
  99. data/lib/active_record/migration.rb +411 -173
  100. data/lib/active_record/model_schema.rb +81 -94
  101. data/lib/active_record/nested_attributes.rb +173 -131
  102. data/lib/active_record/null_relation.rb +67 -0
  103. data/lib/active_record/persistence.rb +254 -106
  104. data/lib/active_record/query_cache.rb +18 -36
  105. data/lib/active_record/querying.rb +19 -15
  106. data/lib/active_record/railtie.rb +113 -38
  107. data/lib/active_record/railties/console_sandbox.rb +3 -4
  108. data/lib/active_record/railties/controller_runtime.rb +4 -3
  109. data/lib/active_record/railties/databases.rake +115 -368
  110. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  111. data/lib/active_record/readonly_attributes.rb +7 -3
  112. data/lib/active_record/reflection.rb +110 -61
  113. data/lib/active_record/relation/batches.rb +29 -29
  114. data/lib/active_record/relation/calculations.rb +155 -125
  115. data/lib/active_record/relation/delegation.rb +94 -18
  116. data/lib/active_record/relation/finder_methods.rb +151 -203
  117. data/lib/active_record/relation/merger.rb +188 -0
  118. data/lib/active_record/relation/predicate_builder.rb +85 -42
  119. data/lib/active_record/relation/query_methods.rb +793 -146
  120. data/lib/active_record/relation/spawn_methods.rb +43 -150
  121. data/lib/active_record/relation.rb +293 -173
  122. data/lib/active_record/result.rb +48 -7
  123. data/lib/active_record/runtime_registry.rb +17 -0
  124. data/lib/active_record/sanitization.rb +41 -54
  125. data/lib/active_record/schema.rb +19 -12
  126. data/lib/active_record/schema_dumper.rb +41 -41
  127. data/lib/active_record/schema_migration.rb +46 -0
  128. data/lib/active_record/scoping/default.rb +56 -52
  129. data/lib/active_record/scoping/named.rb +78 -103
  130. data/lib/active_record/scoping.rb +54 -124
  131. data/lib/active_record/serialization.rb +6 -2
  132. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  133. data/lib/active_record/statement_cache.rb +26 -0
  134. data/lib/active_record/store.rb +131 -15
  135. data/lib/active_record/tasks/database_tasks.rb +204 -0
  136. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
  138. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  140. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  141. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  142. data/lib/active_record/test_case.rb +67 -38
  143. data/lib/active_record/timestamp.rb +16 -11
  144. data/lib/active_record/transactions.rb +73 -51
  145. data/lib/active_record/validations/associated.rb +19 -13
  146. data/lib/active_record/validations/presence.rb +65 -0
  147. data/lib/active_record/validations/uniqueness.rb +110 -57
  148. data/lib/active_record/validations.rb +18 -17
  149. data/lib/active_record/version.rb +7 -6
  150. data/lib/active_record.rb +63 -45
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
  152. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  154. data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
  155. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  156. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  157. data/lib/rails/generators/active_record.rb +3 -5
  158. metadata +43 -29
  159. data/examples/associations.png +0 -0
  160. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  161. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  162. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  163. data/lib/active_record/dynamic_finder_match.rb +0 -68
  164. data/lib/active_record/dynamic_scope_match.rb +0 -23
  165. data/lib/active_record/fixtures/file.rb +0 -65
  166. data/lib/active_record/identity_map.rb +0 -162
  167. data/lib/active_record/observer.rb +0 -121
  168. data/lib/active_record/session_store.rb +0 -360
  169. data/lib/rails/generators/active_record/migration.rb +0 -15
  170. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  171. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  172. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  173. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,4 +1,4 @@
1
- require 'active_support/concern'
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
2
 
3
3
  module ActiveRecord
4
4
  module Inheritance
@@ -6,14 +6,36 @@ module ActiveRecord
6
6
 
7
7
  included do
8
8
  # Determine whether to store the full constant name including namespace when using STI
9
- class_attribute :store_full_sti_class
9
+ class_attribute :store_full_sti_class, instance_writer: false
10
10
  self.store_full_sti_class = true
11
11
  end
12
12
 
13
13
  module ClassMethods
14
+ # Determines if one of the attributes passed in is the inheritance column,
15
+ # and if the inheritance column is attr accessible, it initializes an
16
+ # instance of the given subclass instead of the base class
17
+ def new(*args, &block)
18
+ if abstract_class? || self == Base
19
+ raise NotImplementedError, "#{self} is an abstract class and can not be instantiated."
20
+ end
21
+
22
+ attrs = args.first
23
+ if subclass_from_attributes?(attrs)
24
+ subclass = subclass_from_attributes(attrs)
25
+ end
26
+
27
+ if subclass
28
+ subclass.new(*args, &block)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
14
34
  # True if this isn't a concrete subclass needing a STI type condition.
15
35
  def descends_from_active_record?
16
- if superclass.abstract_class?
36
+ if self == Base
37
+ false
38
+ elsif superclass.abstract_class?
17
39
  superclass.descends_from_active_record?
18
40
  else
19
41
  superclass == Base || !columns_hash.include?(inheritance_column)
@@ -33,17 +55,41 @@ module ActiveRecord
33
55
  @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
34
56
  end
35
57
 
36
- # Returns the base AR subclass that this class descends from. If A
37
- # extends AR::Base, A.base_class will return A. If B descends from A
58
+ # Returns the class descending directly from ActiveRecord::Base, or
59
+ # an abstract class, if any, in the inheritance hierarchy.
60
+ #
61
+ # If A extends AR::Base, A.base_class will return A. If B descends from A
38
62
  # through some arbitrarily deep hierarchy, B.base_class will return A.
39
63
  #
40
64
  # If B < A and C < B and if A is an abstract_class then both B.base_class
41
65
  # and C.base_class would return B as the answer since A is an abstract_class.
42
66
  def base_class
43
- class_of_active_record_descendant(self)
67
+ unless self < Base
68
+ raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
69
+ end
70
+
71
+ if superclass == Base || superclass.abstract_class?
72
+ self
73
+ else
74
+ superclass.base_class
75
+ end
44
76
  end
45
77
 
46
78
  # Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
79
+ # If you are using inheritance with ActiveRecord and don't want child classes
80
+ # to utilize the implied STI table name of the parent class, this will need to be true.
81
+ # For example, given the following:
82
+ #
83
+ # class SuperClass < ActiveRecord::Base
84
+ # self.abstract_class = true
85
+ # end
86
+ # class Child < SuperClass
87
+ # self.table_name = 'the_table_i_really_want'
88
+ # end
89
+ #
90
+ #
91
+ # <tt>self.abstract_class = true</tt> is required to make <tt>Child<.find,.create, or any Arel method></tt> use <tt>the_table_i_really_want</tt> instead of a table called <tt>super_classes</tt>
92
+ #
47
93
  attr_accessor :abstract_class
48
94
 
49
95
  # Returns whether this class is an abstract class or not.
@@ -55,36 +101,8 @@ module ActiveRecord
55
101
  store_full_sti_class ? name : name.demodulize
56
102
  end
57
103
 
58
- # Finder methods must instantiate through this method to work with the
59
- # single-table inheritance model that makes it possible to create
60
- # objects of different types from the same table.
61
- def instantiate(record)
62
- sti_class = find_sti_class(record[inheritance_column])
63
- record_id = sti_class.primary_key && record[sti_class.primary_key]
64
-
65
- if ActiveRecord::IdentityMap.enabled? && record_id
66
- instance = use_identity_map(sti_class, record_id, record)
67
- else
68
- instance = sti_class.allocate.init_with('attributes' => record)
69
- end
70
-
71
- instance
72
- end
73
-
74
104
  protected
75
105
 
76
- # Returns the class descending directly from ActiveRecord::Base or an
77
- # abstract class, if any, in the inheritance hierarchy.
78
- def class_of_active_record_descendant(klass)
79
- if klass == Base || klass.superclass == Base || klass.superclass.abstract_class?
80
- klass
81
- elsif klass.superclass.nil?
82
- raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
83
- else
84
- class_of_active_record_descendant(klass.superclass)
85
- end
86
- end
87
-
88
106
  # Returns the class type of the record using the current module as a prefix. So descendants of
89
107
  # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
90
108
  def compute_type(type_name)
@@ -114,39 +132,33 @@ module ActiveRecord
114
132
 
115
133
  private
116
134
 
117
- def use_identity_map(sti_class, record_id, record)
118
- if (column = sti_class.columns_hash[sti_class.primary_key]) && column.number?
119
- record_id = record_id.to_i
120
- end
121
-
122
- if instance = IdentityMap.get(sti_class, record_id)
123
- instance.reinit_with('attributes' => record)
135
+ # Called by +instantiate+ to decide which class to use for a new
136
+ # record instance. For single-table inheritance, we check the record
137
+ # for a +type+ column and return the corresponding class.
138
+ def discriminate_class_for_record(record)
139
+ if using_single_table_inheritance?(record)
140
+ find_sti_class(record[inheritance_column])
124
141
  else
125
- instance = sti_class.allocate.init_with('attributes' => record)
126
- IdentityMap.add(instance)
142
+ super
127
143
  end
144
+ end
128
145
 
129
- instance
146
+ def using_single_table_inheritance?(record)
147
+ record[inheritance_column].present? && columns_hash.include?(inheritance_column)
130
148
  end
131
149
 
132
150
  def find_sti_class(type_name)
133
- if type_name.blank? || !columns_hash.include?(inheritance_column)
134
- self
151
+ if store_full_sti_class
152
+ ActiveSupport::Dependencies.constantize(type_name)
135
153
  else
136
- begin
137
- if store_full_sti_class
138
- ActiveSupport::Dependencies.constantize(type_name)
139
- else
140
- compute_type(type_name)
141
- end
142
- rescue NameError
143
- raise SubclassNotFound,
144
- "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
145
- "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
146
- "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
147
- "or overwrite #{name}.inheritance_column to use another column for that information."
148
- end
154
+ compute_type(type_name)
149
155
  end
156
+ rescue NameError
157
+ raise SubclassNotFound,
158
+ "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
159
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
160
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
161
+ "or overwrite #{name}.inheritance_column to use another column for that information."
150
162
  end
151
163
 
152
164
  def type_condition(table = arel_table)
@@ -155,6 +167,28 @@ module ActiveRecord
155
167
 
156
168
  sti_column.in(sti_names)
157
169
  end
170
+
171
+ # Detect the subclass from the inheritance column of attrs. If the inheritance column value
172
+ # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
173
+ # If this is a StrongParameters hash, and access to inheritance_column is not permitted,
174
+ # this will ignore the inheritance column and return nil
175
+ def subclass_from_attributes?(attrs)
176
+ columns_hash.include?(inheritance_column) && attrs.is_a?(Hash)
177
+ end
178
+
179
+ def subclass_from_attributes(attrs)
180
+ subclass_name = attrs.with_indifferent_access[inheritance_column]
181
+
182
+ if subclass_name.present? && subclass_name != self.name
183
+ subclass = subclass_name.safe_constantize
184
+
185
+ unless descendants.include?(subclass)
186
+ raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
187
+ end
188
+
189
+ subclass
190
+ end
191
+ end
158
192
  end
159
193
 
160
194
  private
@@ -5,10 +5,12 @@ module ActiveRecord
5
5
  included do
6
6
  ##
7
7
  # :singleton-method:
8
- # Indicates the format used to generate the timestamp format in the cache key.
9
- # This is +:number+, by default.
8
+ # Indicates the format used to generate the timestamp in the cache key.
9
+ # Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
10
+ #
11
+ # This is +:nsec+, by default.
10
12
  class_attribute :cache_timestamp_format, :instance_writer => false
11
- self.cache_timestamp_format = :number
13
+ self.cache_timestamp_format = :nsec
12
14
  end
13
15
 
14
16
  # Returns a String, which Action Pack uses for constructing an URL to this
@@ -19,7 +21,7 @@ module ActiveRecord
19
21
  # <tt>resources :users</tt> route. Normally, +user_path+ will
20
22
  # construct a path with the user object's 'id' in it:
21
23
  #
22
- # user = User.find_by_name('Phusion')
24
+ # user = User.find_by(name: 'Phusion')
23
25
  # user_path(user) # => "/users/1"
24
26
  #
25
27
  # You can override +to_param+ in your model to make +user_path+ construct
@@ -31,7 +33,7 @@ module ActiveRecord
31
33
  # end
32
34
  # end
33
35
  #
34
- # user = User.find_by_name('Phusion')
36
+ # user = User.find_by(name: 'Phusion')
35
37
  # user_path(user) # => "/users/Phusion"
36
38
  def to_param
37
39
  # We can't use alias_method here, because method 'id' optimizes itself on the fly.
@@ -40,8 +42,6 @@ module ActiveRecord
40
42
 
41
43
  # Returns a cache key that can be used to identify this record.
42
44
  #
43
- # ==== Examples
44
- #
45
45
  # Product.new.cache_key # => "products/new"
46
46
  # Product.find(5).cache_key # => "products/5" (updated_at not available)
47
47
  # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
@@ -49,7 +49,7 @@ module ActiveRecord
49
49
  case
50
50
  when new_record?
51
51
  "#{self.class.model_name.cache_key}/new"
52
- when timestamp = self[:updated_at]
52
+ when timestamp = max_updated_column_timestamp
53
53
  timestamp = timestamp.utc.to_s(cache_timestamp_format)
54
54
  "#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
55
55
  else
@@ -4,12 +4,19 @@ en:
4
4
  #created_at: "Created at"
5
5
  #updated_at: "Updated at"
6
6
 
7
+ # Default error messages
8
+ errors:
9
+ messages:
10
+ taken: "has already been taken"
11
+
7
12
  # Active Record models configuration
8
13
  activerecord:
9
14
  errors:
10
15
  messages:
11
- taken: "has already been taken"
12
16
  record_invalid: "Validation failed: %{errors}"
17
+ restrict_dependent_destroy:
18
+ one: "Cannot delete record because a dependent %{record} exists"
19
+ many: "Cannot delete record because dependent %{record} exist"
13
20
  # Append your own errors here or at the model/attributes scope.
14
21
 
15
22
  # You can define own errors for models or model attributes.
@@ -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,7 @@ 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 _update_record(attribute_names = @attributes.keys) #:nodoc:
68
70
  return super unless locking_enabled?
69
71
  return 0 if attribute_names.empty?
70
72
 
@@ -80,11 +82,11 @@ module ActiveRecord
80
82
 
81
83
  stmt = relation.where(
82
84
  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]))
85
+ relation.table[lock_col].eq(self.class.quote_value(previous_lock_value, column_for_attribute(lock_col)))
84
86
  )
85
- ).arel.compile_update(arel_attributes_values(false, false, attribute_names))
87
+ ).arel.compile_update(arel_attributes_with_values_for_update(attribute_names))
86
88
 
87
- affected_rows = connection.update stmt
89
+ affected_rows = self.class.connection.update stmt
88
90
 
89
91
  unless affected_rows == 1
90
92
  raise ActiveRecord::StaleObjectError.new(self, "update")
@@ -99,26 +101,29 @@ module ActiveRecord
99
101
  end
100
102
  end
101
103
 
102
- def destroy #:nodoc:
103
- return super unless locking_enabled?
104
+ def destroy_row
105
+ affected_rows = super
106
+
107
+ if locking_enabled? && affected_rows != 1
108
+ raise ActiveRecord::StaleObjectError.new(self, "destroy")
109
+ end
104
110
 
105
- destroy_associations
111
+ affected_rows
112
+ end
106
113
 
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))
114
+ def relation_for_destroy
115
+ relation = super
112
116
 
113
- affected_rows = self.class.unscoped.where(predicate).delete_all
117
+ if locking_enabled?
118
+ column_name = self.class.locking_column
119
+ column = self.class.columns_hash[column_name]
120
+ substitute = self.class.connection.substitute_at(column, relation.bind_values.length)
114
121
 
115
- unless affected_rows == 1
116
- raise ActiveRecord::StaleObjectError.new(self, "destroy")
117
- end
122
+ relation = relation.where(self.class.arel_table[column_name].eq(substitute))
123
+ relation.bind_values << [column, self[column_name].to_i]
118
124
  end
119
125
 
120
- @destroyed = true
121
- freeze
126
+ relation
122
127
  end
123
128
 
124
129
  module ClassMethods
@@ -131,14 +136,9 @@ module ActiveRecord
131
136
  lock_optimistically && columns_hash[locking_column]
132
137
  end
133
138
 
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
139
  # 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
140
+ def locking_column=(value)
141
+ @locking_column = value.to_s
142
142
  end
143
143
 
144
144
  # The version column used for optimistic locking. Defaults to +lock_version+.
@@ -147,10 +147,6 @@ module ActiveRecord
147
147
  @locking_column
148
148
  end
149
149
 
150
- def original_locking_column #:nodoc:
151
- deprecated_original_property_getter :locking_column
152
- end
153
-
154
150
  # Quote the column name used for optimistic locking.
155
151
  def quoted_locking_column
156
152
  connection.quote_column_name(locking_column)
@@ -168,16 +164,16 @@ module ActiveRecord
168
164
  super
169
165
  end
170
166
 
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
178
- end
167
+ def column_defaults
168
+ @column_defaults ||= begin
169
+ defaults = super
170
+
171
+ if defaults.key?(locking_column) && lock_optimistically
172
+ defaults[locking_column] ||= 0
173
+ end
179
174
 
180
- attributes
175
+ defaults
176
+ end
181
177
  end
182
178
  end
183
179
  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
@@ -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
@@ -18,25 +20,33 @@ module ActiveRecord
18
20
  @odd_or_even = false
19
21
  end
20
22
 
23
+ def render_bind(column, value)
24
+ if column
25
+ if column.binary?
26
+ value = "<#{value.bytesize} bytes of binary data>"
27
+ end
28
+
29
+ [column.name, value]
30
+ else
31
+ [nil, value]
32
+ end
33
+ end
34
+
21
35
  def sql(event)
22
36
  self.class.runtime += event.duration
23
37
  return unless logger.debug?
24
38
 
25
39
  payload = event.payload
26
40
 
27
- return if 'SCHEMA' == payload[:name]
41
+ return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
28
42
 
29
- name = '%s (%.1fms)' % [payload[:name], event.duration]
43
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
30
44
  sql = payload[:sql].squeeze(' ')
31
45
  binds = nil
32
46
 
33
47
  unless (payload[:binds] || []).empty?
34
48
  binds = " " + payload[:binds].map { |col,v|
35
- if col
36
- [col.name, v]
37
- else
38
- [nil, v]
39
- end
49
+ render_bind(col, v)
40
50
  }.inspect
41
51
  end
42
52