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,5 +1,3 @@
1
- require 'active_support/concern'
2
-
3
1
  module ActiveRecord
4
2
  # = Active Record Persistence
5
3
  module Persistence
@@ -9,58 +7,77 @@ module ActiveRecord
9
7
  # Creates an object (or multiple objects) and saves it to the database, if validations pass.
10
8
  # The resulting object is returned whether the object was saved successfully to the database or not.
11
9
  #
12
- # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
10
+ # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
13
11
  # attributes on the objects that are to be created.
14
12
  #
15
- # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
16
- # in the +options+ parameter.
17
- #
18
13
  # ==== Examples
19
14
  # # Create a single new object
20
- # User.create(:first_name => 'Jamie')
21
- #
22
- # # Create a single new object using the :admin mass-assignment security role
23
- # User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
24
- #
25
- # # Create a single new object bypassing mass-assignment security
26
- # User.create({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
15
+ # User.create(first_name: 'Jamie')
27
16
  #
28
17
  # # Create an Array of new objects
29
- # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
18
+ # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
30
19
  #
31
20
  # # Create a single object and pass it into a block to set other attributes.
32
- # User.create(:first_name => 'Jamie') do |u|
21
+ # User.create(first_name: 'Jamie') do |u|
33
22
  # u.is_admin = false
34
23
  # end
35
24
  #
36
25
  # # Creating an Array of new objects using a block, where the block is executed for each object:
37
- # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
26
+ # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
38
27
  # u.is_admin = false
39
28
  # end
40
- def create(attributes = nil, options = {}, &block)
29
+ def create(attributes = nil, &block)
41
30
  if attributes.is_a?(Array)
42
- attributes.collect { |attr| create(attr, options, &block) }
31
+ attributes.collect { |attr| create(attr, &block) }
43
32
  else
44
- object = new(attributes, options, &block)
33
+ object = new(attributes, &block)
45
34
  object.save
46
35
  object
47
36
  end
48
37
  end
38
+
39
+ # Given an attributes hash, +instantiate+ returns a new instance of
40
+ # the appropriate class. Accepts only keys as strings.
41
+ #
42
+ # For example, +Post.all+ may return Comments, Messages, and Emails
43
+ # by storing the record's subclass in a +type+ attribute. By calling
44
+ # +instantiate+ instead of +new+, finder methods ensure they get new
45
+ # instances of the appropriate class for each record.
46
+ #
47
+ # See +ActiveRecord::Inheritance#discriminate_class_for_record+ to see
48
+ # how this "single-table" inheritance mapping is implemented.
49
+ def instantiate(attributes, column_types = {})
50
+ klass = discriminate_class_for_record(attributes)
51
+ column_types = klass.decorate_columns(column_types.dup)
52
+ klass.allocate.init_with('attributes' => attributes, 'column_types' => column_types)
53
+ end
54
+
55
+ private
56
+ # Called by +instantiate+ to decide which class to use for a new
57
+ # record instance.
58
+ #
59
+ # See +ActiveRecord::Inheritance#discriminate_class_for_record+ for
60
+ # the single-table inheritance discriminator.
61
+ def discriminate_class_for_record(record)
62
+ self
63
+ end
49
64
  end
50
65
 
51
66
  # Returns true if this object hasn't been saved yet -- that is, a record
52
- # for the object doesn't exist in the data store yet; otherwise, returns false.
67
+ # for the object doesn't exist in the database yet; otherwise, returns false.
53
68
  def new_record?
69
+ sync_with_transaction_state
54
70
  @new_record
55
71
  end
56
72
 
57
73
  # Returns true if this object has been destroyed, otherwise returns false.
58
74
  def destroyed?
75
+ sync_with_transaction_state
59
76
  @destroyed
60
77
  end
61
78
 
62
- # Returns if the record is persisted, i.e. it's not a new record and it was
63
- # not destroyed.
79
+ # Returns true if the record is persisted, i.e. it's not a new record and it was
80
+ # not destroyed, otherwise returns false.
64
81
  def persisted?
65
82
  !(new_record? || destroyed?)
66
83
  end
@@ -72,19 +89,20 @@ module ActiveRecord
72
89
  #
73
90
  # By default, save always run validations. If any of them fail the action
74
91
  # is cancelled and +save+ returns +false+. However, if you supply
75
- # :validate => false, validations are bypassed altogether. See
92
+ # validate: false, validations are bypassed altogether. See
76
93
  # ActiveRecord::Validations for more information.
77
94
  #
78
95
  # There's a series of callbacks associated with +save+. If any of the
79
96
  # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
80
97
  # +save+ returns +false+. See ActiveRecord::Callbacks for further
81
98
  # details.
99
+ #
100
+ # Attributes marked as readonly are silently ignored if the record is
101
+ # being updated.
82
102
  def save(*)
83
- begin
84
- create_or_update
85
- rescue ActiveRecord::RecordInvalid
86
- false
87
- end
103
+ create_or_update
104
+ rescue ActiveRecord::RecordInvalid
105
+ false
88
106
  end
89
107
 
90
108
  # Saves the model.
@@ -100,6 +118,9 @@ module ActiveRecord
100
118
  # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
101
119
  # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
102
120
  # ActiveRecord::Callbacks for further details.
121
+ #
122
+ # Attributes marked as readonly are silently ignored if the record is
123
+ # being updated.
103
124
  def save!(*)
104
125
  create_or_update || raise(RecordNotSaved)
105
126
  end
@@ -112,45 +133,46 @@ module ActiveRecord
112
133
  # record's primary key, and no callbacks are executed.
113
134
  #
114
135
  # To enforce the object's +before_destroy+ and +after_destroy+
115
- # callbacks, Observer methods, or any <tt>:dependent</tt> association
136
+ # callbacks or any <tt>:dependent</tt> association
116
137
  # options, use <tt>#destroy</tt>.
117
138
  def delete
118
- if persisted?
119
- self.class.delete(id)
120
- IdentityMap.remove(self) if IdentityMap.enabled?
121
- end
139
+ self.class.delete(id) if persisted?
122
140
  @destroyed = true
123
141
  freeze
124
142
  end
125
143
 
126
144
  # Deletes the record in the database and freezes this instance to reflect
127
145
  # that no changes should be made (since they can't be persisted).
146
+ #
147
+ # There's a series of callbacks associated with <tt>destroy</tt>. If
148
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
149
+ # and <tt>destroy</tt> returns +false+. See
150
+ # ActiveRecord::Callbacks for further details.
128
151
  def destroy
152
+ raise ReadOnlyRecord if readonly?
129
153
  destroy_associations
130
-
131
- if persisted?
132
- IdentityMap.remove(self) if IdentityMap.enabled?
133
- pk = self.class.primary_key
134
- column = self.class.columns_hash[pk]
135
- substitute = connection.substitute_at(column, 0)
136
-
137
- relation = self.class.unscoped.where(
138
- self.class.arel_table[pk].eq(substitute))
139
-
140
- relation.bind_values = [[column, id]]
141
- relation.delete_all
142
- end
143
-
154
+ destroy_row if persisted?
144
155
  @destroyed = true
145
156
  freeze
146
157
  end
147
158
 
159
+ # Deletes the record in the database and freezes this instance to reflect
160
+ # that no changes should be made (since they can't be persisted).
161
+ #
162
+ # There's a series of callbacks associated with <tt>destroy!</tt>. If
163
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
164
+ # and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See
165
+ # ActiveRecord::Callbacks for further details.
166
+ def destroy!
167
+ destroy || raise(ActiveRecord::RecordNotDestroyed)
168
+ end
169
+
148
170
  # Returns an instance of the specified +klass+ with the attributes of the
149
171
  # current record. This is mostly useful in relation to single-table
150
172
  # inheritance structures where you want a subclass to appear as the
151
173
  # superclass. This can be used along with record identification in
152
174
  # Action Pack to allow, say, <tt>Client < Company</tt> to do something
153
- # like render <tt>:partial => @client.becomes(Company)</tt> to render that
175
+ # like render <tt>partial: @client.becomes(Company)</tt> to render that
154
176
  # instance using the companies/company partial instead of clients/client.
155
177
  #
156
178
  # Note: The new instance will share a link to the same attributes as the original class.
@@ -162,7 +184,22 @@ module ActiveRecord
162
184
  became.instance_variable_set("@new_record", new_record?)
163
185
  became.instance_variable_set("@destroyed", destroyed?)
164
186
  became.instance_variable_set("@errors", errors)
165
- became.send("#{klass.inheritance_column}=", klass.name) unless self.class.descends_from_active_record?
187
+ became
188
+ end
189
+
190
+ # Wrapper around +becomes+ that also changes the instance's sti column value.
191
+ # This is especially useful if you want to persist the changed class in your
192
+ # database.
193
+ #
194
+ # Note: The old instance's sti column value will be changed too, as both objects
195
+ # share the same set of attributes.
196
+ def becomes!(klass)
197
+ became = becomes(klass)
198
+ sti_type = nil
199
+ if !klass.descends_from_active_record?
200
+ sti_type = klass.sti_name
201
+ end
202
+ became.public_send("#{klass.inheritance_column}=", sti_type)
166
203
  became
167
204
  end
168
205
 
@@ -174,61 +211,78 @@ module ActiveRecord
174
211
  # * updated_at/updated_on column is updated if that column is available.
175
212
  # * Updates all the attributes that are dirty in this object.
176
213
  #
214
+ # This method raises an +ActiveRecord::ActiveRecordError+ if the
215
+ # attribute is marked as readonly.
177
216
  def update_attribute(name, value)
178
217
  name = name.to_s
179
- raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
218
+ verify_readonly_attribute(name)
180
219
  send("#{name}=", value)
181
- save(:validate => false)
182
- end
183
-
184
- # Updates a single attribute of an object, without calling save.
185
- #
186
- # * Validation is skipped.
187
- # * Callbacks are skipped.
188
- # * updated_at/updated_on column is not updated if that column is available.
189
- #
190
- # Raises an +ActiveRecordError+ when called on new objects, or when the +name+
191
- # attribute is marked as readonly.
192
- def update_column(name, value)
193
- name = name.to_s
194
- raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
195
- raise ActiveRecordError, "can not update on a new record object" unless persisted?
196
-
197
- updated_count = self.class.unscoped.update_all({ name => value }, self.class.primary_key => id)
198
-
199
- raw_write_attribute(name, value)
200
-
201
- updated_count == 1
220
+ save(validate: false)
202
221
  end
203
222
 
204
223
  # Updates the attributes of the model from the passed-in hash and saves the
205
224
  # record, all wrapped in a transaction. If the object is invalid, the saving
206
225
  # will fail and false will be returned.
207
- #
208
- # When updating model attributes, mass-assignment security protection is respected.
209
- # If no +:as+ option is supplied then the +:default+ role will be used.
210
- # If you want to bypass the protection given by +attr_protected+ and
211
- # +attr_accessible+ then you can do so using the +:without_protection+ option.
212
- def update_attributes(attributes, options = {})
226
+ def update(attributes)
213
227
  # The following transaction covers any possible database side-effects of the
214
228
  # attributes assignment. For example, setting the IDs of a child collection.
215
229
  with_transaction_returning_status do
216
- self.assign_attributes(attributes, options)
230
+ assign_attributes(attributes)
217
231
  save
218
232
  end
219
233
  end
220
234
 
221
- # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
235
+ alias update_attributes update
236
+
237
+ # Updates its receiver just like +update+ but calls <tt>save!</tt> instead
222
238
  # of +save+, so an exception is raised if the record is invalid.
223
- def update_attributes!(attributes, options = {})
239
+ def update!(attributes)
224
240
  # The following transaction covers any possible database side-effects of the
225
241
  # attributes assignment. For example, setting the IDs of a child collection.
226
242
  with_transaction_returning_status do
227
- self.assign_attributes(attributes, options)
243
+ assign_attributes(attributes)
228
244
  save!
229
245
  end
230
246
  end
231
247
 
248
+ alias update_attributes! update!
249
+
250
+ # Equivalent to <code>update_columns(name => value)</code>.
251
+ def update_column(name, value)
252
+ update_columns(name => value)
253
+ end
254
+
255
+ # Updates the attributes directly in the database issuing an UPDATE SQL
256
+ # statement and sets them in the receiver:
257
+ #
258
+ # user.update_columns(last_request_at: Time.current)
259
+ #
260
+ # This is the fastest way to update attributes because it goes straight to
261
+ # the database, but take into account that in consequence the regular update
262
+ # procedures are totally bypassed. In particular:
263
+ #
264
+ # * Validations are skipped.
265
+ # * Callbacks are skipped.
266
+ # * +updated_at+/+updated_on+ are not updated.
267
+ #
268
+ # This method raises an +ActiveRecord::ActiveRecordError+ when called on new
269
+ # objects, or when at least one of the attributes is marked as readonly.
270
+ def update_columns(attributes)
271
+ raise ActiveRecordError, "can not update on a new record object" unless persisted?
272
+
273
+ attributes.each_key do |key|
274
+ verify_readonly_attribute(key.to_s)
275
+ end
276
+
277
+ updated_count = self.class.unscoped.where(self.class.primary_key => id).update_all(attributes)
278
+
279
+ attributes.each do |k, v|
280
+ raw_write_attribute(k, v)
281
+ end
282
+
283
+ updated_count == 1
284
+ end
285
+
232
286
  # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
233
287
  # The increment is performed directly on the underlying attribute, no setter is invoked.
234
288
  # Only makes sense for number-based attributes. Returns +self+.
@@ -280,25 +334,76 @@ module ActiveRecord
280
334
  toggle(attribute).update_attribute(attribute, self[attribute])
281
335
  end
282
336
 
283
- # Reloads the attributes of this object from the database.
284
- # The optional options argument is passed to find when reloading so you
285
- # may do e.g. record.reload(:lock => true) to reload the same record with
286
- # an exclusive row lock.
337
+ # Reloads the record from the database.
338
+ #
339
+ # This method finds record by its primary key (which could be assigned manually) and
340
+ # modifies the receiver in-place:
341
+ #
342
+ # account = Account.new
343
+ # # => #<Account id: nil, email: nil>
344
+ # account.id = 1
345
+ # account.reload
346
+ # # Account Load (1.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1 [["id", 1]]
347
+ # # => #<Account id: 1, email: 'account@example.com'>
348
+ #
349
+ # Attributes are updated, and caches busted, in particular the associations cache.
350
+ #
351
+ # If the record no longer exists in the database <tt>ActiveRecord::RecordNotFound</tt>
352
+ # is raised. Otherwise, in addition to the in-place modification the method
353
+ # returns +self+ for convenience.
354
+ #
355
+ # The optional <tt>:lock</tt> flag option allows you to lock the reloaded record:
356
+ #
357
+ # reload(lock: true) # reload with pessimistic locking
358
+ #
359
+ # Reloading is commonly used in test suites to test something is actually
360
+ # written to the database, or when some action modifies the corresponding
361
+ # row in the database but not the object in memory:
362
+ #
363
+ # assert account.deposit!(25)
364
+ # assert_equal 25, account.credit # check it is updated in memory
365
+ # assert_equal 25, account.reload.credit # check it is also persisted
366
+ #
367
+ # Another commom use case is optimistic locking handling:
368
+ #
369
+ # def with_optimistic_retry
370
+ # begin
371
+ # yield
372
+ # rescue ActiveRecord::StaleObjectError
373
+ # begin
374
+ # # Reload lock_version in particular.
375
+ # reload
376
+ # rescue ActiveRecord::RecordNotFound
377
+ # # If the record is gone there is nothing to do.
378
+ # else
379
+ # retry
380
+ # end
381
+ # end
382
+ # end
383
+ #
287
384
  def reload(options = nil)
288
385
  clear_aggregation_cache
289
386
  clear_association_cache
290
387
 
291
- IdentityMap.without do
292
- fresh_object = self.class.unscoped { self.class.find(self.id, options) }
293
- @attributes.update(fresh_object.instance_variable_get('@attributes'))
294
- end
388
+ fresh_object =
389
+ if options && options[:lock]
390
+ self.class.unscoped { self.class.lock(options[:lock]).find(id) }
391
+ else
392
+ self.class.unscoped { self.class.find(id) }
393
+ end
394
+
395
+ @attributes.update(fresh_object.instance_variable_get('@attributes'))
295
396
 
296
- @attributes_cache = {}
397
+ @column_types = self.class.column_types
398
+ @column_types_override = fresh_object.instance_variable_get('@column_types_override')
399
+ @attributes_cache = {}
400
+ @new_record = false
297
401
  self
298
402
  end
299
403
 
300
404
  # Saves the record with the updated_at/on attributes set to the current time.
301
- # Please note that no validation is performed and no callbacks are executed.
405
+ # Please note that no validation is performed and only the +after_touch+
406
+ # callback is executed.
302
407
  # If an attribute name is passed, that attribute is updated along with
303
408
  # updated_at/on attributes.
304
409
  #
@@ -308,16 +413,25 @@ module ActiveRecord
308
413
  # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on associated object.
309
414
  #
310
415
  # class Brake < ActiveRecord::Base
311
- # belongs_to :car, :touch => true
416
+ # belongs_to :car, touch: true
312
417
  # end
313
418
  #
314
419
  # class Car < ActiveRecord::Base
315
- # belongs_to :corporation, :touch => true
420
+ # belongs_to :corporation, touch: true
316
421
  # end
317
422
  #
318
423
  # # triggers @brake.car.touch and @brake.car.corporation.touch
319
424
  # @brake.touch
425
+ #
426
+ # Note that +touch+ must be used on a persisted object, or else an
427
+ # ActiveRecordError will be thrown. For example:
428
+ #
429
+ # ball = Ball.new
430
+ # ball.touch(:updated_at) # => raises ActiveRecordError
431
+ #
320
432
  def touch(name = nil)
433
+ raise ActiveRecordError, "can not touch on a new record object" unless persisted?
434
+
321
435
  attributes = timestamp_attributes_for_update_in_model
322
436
  attributes << name if name
323
437
 
@@ -326,14 +440,17 @@ module ActiveRecord
326
440
  changes = {}
327
441
 
328
442
  attributes.each do |column|
329
- changes[column.to_s] = write_attribute(column.to_s, current_time)
443
+ column = column.to_s
444
+ changes[column] = write_attribute(column, current_time)
330
445
  end
331
446
 
332
447
  changes[self.class.locking_column] = increment_lock if locking_enabled?
333
448
 
334
449
  @changed_attributes.except!(*changes.keys)
335
450
  primary_key = self.class.primary_key
336
- self.class.unscoped.update_all(changes, { primary_key => self[primary_key] }) == 1
451
+ self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
452
+ else
453
+ true
337
454
  end
338
455
  end
339
456
 
@@ -343,34 +460,65 @@ module ActiveRecord
343
460
  def destroy_associations
344
461
  end
345
462
 
463
+ def destroy_row
464
+ relation_for_destroy.delete_all
465
+ end
466
+
467
+ def relation_for_destroy
468
+ pk = self.class.primary_key
469
+ column = self.class.columns_hash[pk]
470
+ substitute = self.class.connection.substitute_at(column, 0)
471
+
472
+ relation = self.class.unscoped.where(
473
+ self.class.arel_table[pk].eq(substitute))
474
+
475
+ relation.bind_values = [[column, id]]
476
+ relation
477
+ end
478
+
346
479
  def create_or_update
347
480
  raise ReadOnlyRecord if readonly?
348
- result = new_record? ? create : update
481
+ result = new_record? ? _create_record : _update_record
349
482
  result != false
350
483
  end
351
484
 
352
485
  # Updates the associated record with values matching those of the instance attributes.
353
486
  # Returns the number of affected rows.
354
- def update(attribute_names = @attributes.keys)
355
- attributes_with_values = arel_attributes_values(false, false, attribute_names)
356
- return 0 if attributes_with_values.empty?
357
- klass = self.class
358
- stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
359
- klass.connection.update stmt
487
+ def _update_record(attribute_names = @attributes.keys)
488
+ attributes_with_values = arel_attributes_with_values_for_update(attribute_names)
489
+ if attributes_with_values.empty?
490
+ 0
491
+ else
492
+ klass = self.class
493
+ column_hash = klass.connection.schema_cache.columns_hash klass.table_name
494
+ db_columns_with_values = attributes_with_values.map { |attr,value|
495
+ real_column = column_hash[attr.name]
496
+ [real_column, value]
497
+ }
498
+ bind_attrs = attributes_with_values.dup
499
+ bind_attrs.keys.each_with_index do |column, i|
500
+ real_column = db_columns_with_values[i].first
501
+ bind_attrs[column] = klass.connection.substitute_at(real_column, i)
502
+ end
503
+ stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id_was || id)).arel.compile_update(bind_attrs)
504
+ klass.connection.update stmt, 'SQL', db_columns_with_values
505
+ end
360
506
  end
361
507
 
362
508
  # Creates a record with values matching those of the instance attributes
363
509
  # and returns its id.
364
- def create
365
- attributes_values = arel_attributes_values(!id.nil?)
510
+ def _create_record(attribute_names = @attributes.keys)
511
+ attributes_values = arel_attributes_with_values_for_create(attribute_names)
366
512
 
367
513
  new_id = self.class.unscoped.insert attributes_values
368
-
369
514
  self.id ||= new_id if self.class.primary_key
370
515
 
371
- IdentityMap.add(self) if IdentityMap.enabled?
372
516
  @new_record = false
373
517
  id
374
518
  end
519
+
520
+ def verify_readonly_attribute(name)
521
+ raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
522
+ end
375
523
  end
376
524
  end
@@ -1,10 +1,10 @@
1
- require 'active_support/core_ext/object/blank'
2
1
 
3
2
  module ActiveRecord
4
3
  # = Active Record Query Cache
5
4
  class QueryCache
6
5
  module ClassMethods
7
6
  # Enable the query cache within the block if Active Record is configured.
7
+ # If it's not, it will execute the given block.
8
8
  def cache(&block)
9
9
  if ActiveRecord::Base.connected?
10
10
  connection.cache(&block)
@@ -14,6 +14,7 @@ module ActiveRecord
14
14
  end
15
15
 
16
16
  # Disable the query cache within the block if Active Record is configured.
17
+ # If it's not, it will execute the given block.
17
18
  def uncached(&block)
18
19
  if ActiveRecord::Base.connected?
19
20
  connection.uncached(&block)
@@ -27,48 +28,29 @@ module ActiveRecord
27
28
  @app = app
28
29
  end
29
30
 
30
- class BodyProxy # :nodoc:
31
- def initialize(original_cache_value, target, connection_id)
32
- @original_cache_value = original_cache_value
33
- @target = target
34
- @connection_id = connection_id
35
- end
36
-
37
- def method_missing(method_sym, *arguments, &block)
38
- @target.send(method_sym, *arguments, &block)
39
- end
40
-
41
- def respond_to?(method_sym, include_private = false)
42
- super || @target.respond_to?(method_sym)
43
- end
31
+ def call(env)
32
+ enabled = ActiveRecord::Base.connection.query_cache_enabled
33
+ connection_id = ActiveRecord::Base.connection_id
34
+ ActiveRecord::Base.connection.enable_query_cache!
44
35
 
45
- def each(&block)
46
- @target.each(&block)
36
+ response = @app.call(env)
37
+ response[2] = Rack::BodyProxy.new(response[2]) do
38
+ restore_query_cache_settings(connection_id, enabled)
47
39
  end
48
40
 
49
- def close
50
- @target.close if @target.respond_to?(:close)
51
- ensure
52
- ActiveRecord::Base.connection_id = @connection_id
53
- ActiveRecord::Base.connection.clear_query_cache
54
- unless @original_cache_value
55
- ActiveRecord::Base.connection.disable_query_cache!
56
- end
57
- end
41
+ response
42
+ rescue Exception => e
43
+ restore_query_cache_settings(connection_id, enabled)
44
+ raise e
58
45
  end
59
46
 
60
- def call(env)
61
- old = ActiveRecord::Base.connection.query_cache_enabled
62
- ActiveRecord::Base.connection.enable_query_cache!
47
+ private
63
48
 
64
- status, headers, body = @app.call(env)
65
- [status, headers, BodyProxy.new(old, body, ActiveRecord::Base.connection_id)]
66
- rescue Exception => e
49
+ def restore_query_cache_settings(connection_id, enabled)
50
+ ActiveRecord::Base.connection_id = connection_id
67
51
  ActiveRecord::Base.connection.clear_query_cache
68
- unless old
69
- ActiveRecord::Base.connection.disable_query_cache!
70
- end
71
- raise e
52
+ ActiveRecord::Base.connection.disable_query_cache! unless enabled
72
53
  end
54
+
73
55
  end
74
56
  end