activerecord 3.0.0 → 4.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 (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,26 +1,90 @@
1
1
  module ActiveRecord
2
2
  # = Active Record Persistence
3
3
  module Persistence
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ # Creates an object (or multiple objects) and saves it to the database, if validations pass.
8
+ # The resulting object is returned whether the object was saved successfully to the database or not.
9
+ #
10
+ # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
11
+ # attributes on the objects that are to be created.
12
+ #
13
+ # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
14
+ # in the +options+ parameter.
15
+ #
16
+ # ==== Examples
17
+ # # Create a single new object
18
+ # User.create(first_name: 'Jamie')
19
+ #
20
+ # # Create an Array of new objects
21
+ # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
22
+ #
23
+ # # Create a single object and pass it into a block to set other attributes.
24
+ # User.create(first_name: 'Jamie') do |u|
25
+ # u.is_admin = false
26
+ # end
27
+ #
28
+ # # Creating an Array of new objects using a block, where the block is executed for each object:
29
+ # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
30
+ # u.is_admin = false
31
+ # end
32
+ def create(attributes = nil, &block)
33
+ if attributes.is_a?(Array)
34
+ attributes.collect { |attr| create(attr, &block) }
35
+ else
36
+ object = new(attributes, &block)
37
+ object.save
38
+ object
39
+ end
40
+ end
41
+
42
+ # Given an attributes hash, +instantiate+ returns a new instance of
43
+ # the appropriate class.
44
+ #
45
+ # For example, +Post.all+ may return Comments, Messages, and Emails
46
+ # by storing the record's subclass in a +type+ attribute. By calling
47
+ # +instantiate+ instead of +new+, finder methods ensure they get new
48
+ # instances of the appropriate class for each record.
49
+ #
50
+ # See +ActiveRecord::Inheritance#discriminate_class_for_record+ to see
51
+ # how this "single-table" inheritance mapping is implemented.
52
+ def instantiate(record, column_types = {})
53
+ klass = discriminate_class_for_record(record)
54
+ column_types = klass.decorate_columns(column_types.dup)
55
+ klass.allocate.init_with('attributes' => record, 'column_types' => column_types)
56
+ end
57
+
58
+ private
59
+ # Called by +instantiate+ to decide which class to use for a new
60
+ # record instance.
61
+ #
62
+ # See +ActiveRecord::Inheritance#discriminate_class_for_record+ for
63
+ # the single-table inheritance discriminator.
64
+ def discriminate_class_for_record(record)
65
+ self
66
+ end
67
+ end
68
+
4
69
  # Returns true if this object hasn't been saved yet -- that is, a record
5
70
  # for the object doesn't exist in the data store yet; otherwise, returns false.
6
71
  def new_record?
72
+ sync_with_transaction_state
7
73
  @new_record
8
74
  end
9
75
 
10
76
  # Returns true if this object has been destroyed, otherwise returns false.
11
77
  def destroyed?
78
+ sync_with_transaction_state
12
79
  @destroyed
13
80
  end
14
81
 
15
- # Returns if the record is persisted, i.e. it's not a new record and it was
16
- # not destroyed.
82
+ # Returns true if the record is persisted, i.e. it's not a new record and it was
83
+ # not destroyed, otherwise returns false.
17
84
  def persisted?
18
85
  !(new_record? || destroyed?)
19
86
  end
20
87
 
21
- # :call-seq:
22
- # save(options)
23
- #
24
88
  # Saves the model.
25
89
  #
26
90
  # If the model is new a record gets created in the database, otherwise
@@ -28,15 +92,20 @@ module ActiveRecord
28
92
  #
29
93
  # By default, save always run validations. If any of them fail the action
30
94
  # is cancelled and +save+ returns +false+. However, if you supply
31
- # :validate => false, validations are bypassed altogether. See
95
+ # validate: false, validations are bypassed altogether. See
32
96
  # ActiveRecord::Validations for more information.
33
97
  #
34
98
  # There's a series of callbacks associated with +save+. If any of the
35
99
  # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
36
100
  # +save+ returns +false+. See ActiveRecord::Callbacks for further
37
101
  # details.
102
+ #
103
+ # Attributes marked as readonly are silently ignored if the record is
104
+ # being updated.
38
105
  def save(*)
39
106
  create_or_update
107
+ rescue ActiveRecord::RecordInvalid
108
+ false
40
109
  end
41
110
 
42
111
  # Saves the model.
@@ -52,6 +121,9 @@ module ActiveRecord
52
121
  # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
53
122
  # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
54
123
  # ActiveRecord::Callbacks for further details.
124
+ #
125
+ # Attributes marked as readonly are silently ignored if the record is
126
+ # being updated.
55
127
  def save!(*)
56
128
  create_or_update || raise(RecordNotSaved)
57
129
  end
@@ -64,7 +136,7 @@ module ActiveRecord
64
136
  # record's primary key, and no callbacks are executed.
65
137
  #
66
138
  # To enforce the object's +before_destroy+ and +after_destroy+
67
- # callbacks, Observer methods, or any <tt>:dependent</tt> association
139
+ # callbacks or any <tt>:dependent</tt> association
68
140
  # options, use <tt>#destroy</tt>.
69
141
  def delete
70
142
  self.class.delete(id) if persisted?
@@ -74,21 +146,36 @@ module ActiveRecord
74
146
 
75
147
  # Deletes the record in the database and freezes this instance to reflect
76
148
  # that no changes should be made (since they can't be persisted).
149
+ #
150
+ # There's a series of callbacks associated with <tt>destroy</tt>. If
151
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
152
+ # and <tt>destroy</tt> returns +false+. See
153
+ # ActiveRecord::Callbacks for further details.
77
154
  def destroy
78
- if persisted?
79
- self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
80
- end
81
-
155
+ raise ReadOnlyRecord if readonly?
156
+ destroy_associations
157
+ destroy_row if persisted?
82
158
  @destroyed = true
83
159
  freeze
84
160
  end
85
161
 
162
+ # Deletes the record in the database and freezes this instance to reflect
163
+ # that no changes should be made (since they can't be persisted).
164
+ #
165
+ # There's a series of callbacks associated with <tt>destroy!</tt>. If
166
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
167
+ # and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See
168
+ # ActiveRecord::Callbacks for further details.
169
+ def destroy!
170
+ destroy || raise(ActiveRecord::RecordNotDestroyed)
171
+ end
172
+
86
173
  # Returns an instance of the specified +klass+ with the attributes of the
87
174
  # current record. This is mostly useful in relation to single-table
88
175
  # inheritance structures where you want a subclass to appear as the
89
176
  # superclass. This can be used along with record identification in
90
177
  # Action Pack to allow, say, <tt>Client < Company</tt> to do something
91
- # like render <tt>:partial => @client.becomes(Company)</tt> to render that
178
+ # like render <tt>partial: @client.becomes(Company)</tt> to render that
92
179
  # instance using the companies/company partial instead of clients/client.
93
180
  #
94
181
  # Note: The new instance will share a link to the same attributes as the original class.
@@ -99,6 +186,19 @@ module ActiveRecord
99
186
  became.instance_variable_set("@attributes_cache", @attributes_cache)
100
187
  became.instance_variable_set("@new_record", new_record?)
101
188
  became.instance_variable_set("@destroyed", destroyed?)
189
+ became.instance_variable_set("@errors", errors)
190
+ became
191
+ end
192
+
193
+ # Wrapper around +becomes+ that also changes the instance's sti column value.
194
+ # This is especially useful if you want to persist the changed class in your
195
+ # database.
196
+ #
197
+ # Note: The old instance's sti column value will be changed too, as both objects
198
+ # share the same set of attributes.
199
+ def becomes!(klass)
200
+ became = becomes(klass)
201
+ became.public_send("#{klass.inheritance_column}=", klass.sti_name) unless self.class.descends_from_active_record?
102
202
  became
103
203
  end
104
204
 
@@ -110,36 +210,78 @@ module ActiveRecord
110
210
  # * updated_at/updated_on column is updated if that column is available.
111
211
  # * Updates all the attributes that are dirty in this object.
112
212
  #
213
+ # This method raises an +ActiveRecord::ActiveRecordError+ if the
214
+ # attribute is marked as readonly.
113
215
  def update_attribute(name, value)
114
216
  name = name.to_s
115
- raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
217
+ verify_readonly_attribute(name)
116
218
  send("#{name}=", value)
117
- save(:validate => false)
219
+ save(validate: false)
118
220
  end
119
221
 
120
222
  # Updates the attributes of the model from the passed-in hash and saves the
121
223
  # record, all wrapped in a transaction. If the object is invalid, the saving
122
224
  # will fail and false will be returned.
123
- def update_attributes(attributes)
225
+ def update(attributes)
124
226
  # The following transaction covers any possible database side-effects of the
125
227
  # attributes assignment. For example, setting the IDs of a child collection.
126
228
  with_transaction_returning_status do
127
- self.attributes = attributes
229
+ assign_attributes(attributes)
128
230
  save
129
231
  end
130
232
  end
131
233
 
132
- # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
234
+ alias update_attributes update
235
+
236
+ # Updates its receiver just like +update+ but calls <tt>save!</tt> instead
133
237
  # of +save+, so an exception is raised if the record is invalid.
134
- def update_attributes!(attributes)
238
+ def update!(attributes)
135
239
  # The following transaction covers any possible database side-effects of the
136
240
  # attributes assignment. For example, setting the IDs of a child collection.
137
241
  with_transaction_returning_status do
138
- self.attributes = attributes
242
+ assign_attributes(attributes)
139
243
  save!
140
244
  end
141
245
  end
142
246
 
247
+ alias update_attributes! update!
248
+
249
+ # Equivalent to <code>update_columns(name => value)</code>.
250
+ def update_column(name, value)
251
+ update_columns(name => value)
252
+ end
253
+
254
+ # Updates the attributes directly in the database issuing an UPDATE SQL
255
+ # statement and sets them in the receiver:
256
+ #
257
+ # user.update_columns(last_request_at: Time.current)
258
+ #
259
+ # This is the fastest way to update attributes because it goes straight to
260
+ # the database, but take into account that in consequence the regular update
261
+ # procedures are totally bypassed. In particular:
262
+ #
263
+ # * Validations are skipped.
264
+ # * Callbacks are skipped.
265
+ # * +updated_at+/+updated_on+ are not updated.
266
+ #
267
+ # This method raises an +ActiveRecord::ActiveRecordError+ when called on new
268
+ # objects, or when at least one of the attributes is marked as readonly.
269
+ def update_columns(attributes)
270
+ raise ActiveRecordError, "can not update on a new record object" unless persisted?
271
+
272
+ attributes.each_key do |key|
273
+ verify_readonly_attribute(key.to_s)
274
+ end
275
+
276
+ updated_count = self.class.unscoped.where(self.class.primary_key => id).update_all(attributes)
277
+
278
+ attributes.each do |k, v|
279
+ raw_write_attribute(k, v)
280
+ end
281
+
282
+ updated_count == 1
283
+ end
284
+
143
285
  # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
144
286
  # The increment is performed directly on the underlying attribute, no setter is invoked.
145
287
  # Only makes sense for number-based attributes. Returns +self+.
@@ -191,14 +333,58 @@ module ActiveRecord
191
333
  toggle(attribute).update_attribute(attribute, self[attribute])
192
334
  end
193
335
 
194
- # Reloads the attributes of this object from the database.
195
- # The optional options argument is passed to find when reloading so you
196
- # may do e.g. record.reload(:lock => true) to reload the same record with
197
- # an exclusive row lock.
336
+ # Reloads the record from the database.
337
+ #
338
+ # This method modifies the receiver in-place. Attributes are updated, and
339
+ # caches busted, in particular the associations cache.
340
+ #
341
+ # If the record no longer exists in the database <tt>ActiveRecord::RecordNotFound</tt>
342
+ # is raised. Otherwise, in addition to the in-place modification the method
343
+ # returns +self+ for convenience.
344
+ #
345
+ # The optional <tt>:lock</tt> flag option allows you to lock the reloaded record:
346
+ #
347
+ # reload(lock: true) # reload with pessimistic locking
348
+ #
349
+ # Reloading is commonly used in test suites to test something is actually
350
+ # written to the database, or when some action modifies the corresponding
351
+ # row in the database but not the object in memory:
352
+ #
353
+ # assert account.deposit!(25)
354
+ # assert_equal 25, account.credit # check it is updated in memory
355
+ # assert_equal 25, account.reload.credit # check it is also persisted
356
+ #
357
+ # Another commom use case is optimistic locking handling:
358
+ #
359
+ # def with_optimistic_retry
360
+ # begin
361
+ # yield
362
+ # rescue ActiveRecord::StaleObjectError
363
+ # begin
364
+ # # Reload lock_version in particular.
365
+ # reload
366
+ # rescue ActiveRecord::RecordNotFound
367
+ # # If the record is gone there is nothing to do.
368
+ # else
369
+ # retry
370
+ # end
371
+ # end
372
+ # end
373
+ #
198
374
  def reload(options = nil)
199
375
  clear_aggregation_cache
200
376
  clear_association_cache
201
- @attributes.update(self.class.unscoped { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
377
+
378
+ fresh_object =
379
+ if options && options[:lock]
380
+ self.class.unscoped { self.class.lock.find(id) }
381
+ else
382
+ self.class.unscoped { self.class.find(id) }
383
+ end
384
+
385
+ @attributes.update(fresh_object.instance_variable_get('@attributes'))
386
+ @columns_hash = fresh_object.instance_variable_get('@columns_hash')
387
+
202
388
  @attributes_cache = {}
203
389
  self
204
390
  end
@@ -214,78 +400,110 @@ module ActiveRecord
214
400
  # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on associated object.
215
401
  #
216
402
  # class Brake < ActiveRecord::Base
217
- # belongs_to :car, :touch => true
403
+ # belongs_to :car, touch: true
218
404
  # end
219
405
  #
220
406
  # class Car < ActiveRecord::Base
221
- # belongs_to :corporation, :touch => true
407
+ # belongs_to :corporation, touch: true
222
408
  # end
223
409
  #
224
410
  # # triggers @brake.car.touch and @brake.car.corporation.touch
225
411
  # @brake.touch
412
+ #
413
+ # Note that +touch+ must be used on a persisted object, or else an
414
+ # ActiveRecordError will be thrown. For example:
415
+ #
416
+ # ball = Ball.new
417
+ # ball.touch(:updated_at) # => raises ActiveRecordError
418
+ #
226
419
  def touch(name = nil)
420
+ raise ActiveRecordError, "can not touch on a new record object" unless persisted?
421
+
227
422
  attributes = timestamp_attributes_for_update_in_model
228
- unless attributes.blank?
229
- attributes << name if name
423
+ attributes << name if name
230
424
 
425
+ unless attributes.empty?
231
426
  current_time = current_time_from_proper_timezone
232
427
  changes = {}
233
428
 
234
429
  attributes.each do |column|
235
- changes[column.to_s] = write_attribute(column.to_s, current_time)
430
+ column = column.to_s
431
+ changes[column] = write_attribute(column, current_time)
236
432
  end
237
433
 
434
+ changes[self.class.locking_column] = increment_lock if locking_enabled?
435
+
238
436
  @changed_attributes.except!(*changes.keys)
239
437
  primary_key = self.class.primary_key
240
- self.class.update_all(changes, { primary_key => self[primary_key] }) == 1
438
+ self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
241
439
  end
242
440
  end
243
441
 
244
442
  private
443
+
444
+ # A hook to be overridden by association modules.
445
+ def destroy_associations
446
+ end
447
+
448
+ def destroy_row
449
+ relation_for_destroy.delete_all
450
+ end
451
+
452
+ def relation_for_destroy
453
+ pk = self.class.primary_key
454
+ column = self.class.columns_hash[pk]
455
+ substitute = self.class.connection.substitute_at(column, 0)
456
+
457
+ relation = self.class.unscoped.where(
458
+ self.class.arel_table[pk].eq(substitute))
459
+
460
+ relation.bind_values = [[column, id]]
461
+ relation
462
+ end
463
+
245
464
  def create_or_update
246
465
  raise ReadOnlyRecord if readonly?
247
- result = new_record? ? create : update
466
+ result = new_record? ? create_record : update_record
248
467
  result != false
249
468
  end
250
469
 
251
470
  # Updates the associated record with values matching those of the instance attributes.
252
471
  # Returns the number of affected rows.
253
- def update(attribute_names = @attributes.keys)
254
- attributes_with_values = arel_attributes_values(false, false, attribute_names)
255
- return 0 if attributes_with_values.empty?
256
- self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
472
+ def update_record(attribute_names = @attributes.keys)
473
+ attributes_with_values = arel_attributes_with_values_for_update(attribute_names)
474
+ if attributes_with_values.empty?
475
+ 0
476
+ else
477
+ klass = self.class
478
+ column_hash = klass.connection.schema_cache.columns_hash klass.table_name
479
+ db_columns_with_values = attributes_with_values.map { |attr,value|
480
+ real_column = column_hash[attr.name]
481
+ [real_column, value]
482
+ }
483
+ bind_attrs = attributes_with_values.dup
484
+ bind_attrs.keys.each_with_index do |column, i|
485
+ real_column = db_columns_with_values[i].first
486
+ bind_attrs[column] = klass.connection.substitute_at(real_column, i)
487
+ end
488
+ stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id_was || id)).arel.compile_update(bind_attrs)
489
+ klass.connection.update stmt, 'SQL', db_columns_with_values
490
+ end
257
491
  end
258
492
 
259
493
  # Creates a record with values matching those of the instance attributes
260
494
  # and returns its id.
261
- def create
262
- if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
263
- self.id = connection.next_sequence_value(self.class.sequence_name)
264
- end
265
-
266
- attributes_values = arel_attributes_values
495
+ def create_record(attribute_names = @attributes.keys)
496
+ attributes_values = arel_attributes_with_values_for_create(attribute_names)
267
497
 
268
- new_id = if attributes_values.empty?
269
- self.class.unscoped.insert connection.empty_insert_statement_value
270
- else
271
- self.class.unscoped.insert attributes_values
272
- end
273
-
274
- self.id ||= new_id
498
+ new_id = self.class.unscoped.insert attributes_values
499
+ self.id ||= new_id if self.class.primary_key
275
500
 
276
501
  @new_record = false
277
502
  id
278
503
  end
279
504
 
280
- # Initializes the attributes array with keys matching the columns from the linked table and
281
- # the values matching the corresponding default value of that column, so
282
- # that a new instance, or one populated from a passed-in Hash, still has all the attributes
283
- # that instances loaded from the database would.
284
- def attributes_from_column_definition
285
- self.class.columns.inject({}) do |attributes, column|
286
- attributes[column.name] = column.default unless column.name == self.class.primary_key
287
- attributes
288
- end
505
+ def verify_readonly_attribute(name)
506
+ raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
289
507
  end
290
508
  end
291
509
  end
@@ -1,24 +1,25 @@
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
- if ActiveRecord::Base.configurations.blank?
10
- yield
11
- else
9
+ if ActiveRecord::Base.connected?
12
10
  connection.cache(&block)
11
+ else
12
+ yield
13
13
  end
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
- if ActiveRecord::Base.configurations.blank?
19
- yield
20
- else
19
+ if ActiveRecord::Base.connected?
21
20
  connection.uncached(&block)
21
+ else
22
+ yield
22
23
  end
23
24
  end
24
25
  end
@@ -28,9 +29,28 @@ module ActiveRecord
28
29
  end
29
30
 
30
31
  def call(env)
31
- ActiveRecord::Base.cache do
32
- @app.call(env)
32
+ enabled = ActiveRecord::Base.connection.query_cache_enabled
33
+ connection_id = ActiveRecord::Base.connection_id
34
+ ActiveRecord::Base.connection.enable_query_cache!
35
+
36
+ response = @app.call(env)
37
+ response[2] = Rack::BodyProxy.new(response[2]) do
38
+ restore_query_cache_settings(connection_id, enabled)
33
39
  end
40
+
41
+ response
42
+ rescue Exception => e
43
+ restore_query_cache_settings(connection_id, enabled)
44
+ raise e
34
45
  end
46
+
47
+ private
48
+
49
+ def restore_query_cache_settings(connection_id, enabled)
50
+ ActiveRecord::Base.connection_id = connection_id
51
+ ActiveRecord::Base.connection.clear_query_cache
52
+ ActiveRecord::Base.connection.disable_query_cache! unless enabled
53
+ end
54
+
35
55
  end
36
56
  end
@@ -0,0 +1,62 @@
1
+ module ActiveRecord
2
+ module Querying
3
+ delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :to => :all
4
+ delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :all
5
+ delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, :to => :all
6
+ delegate :find_by, :find_by!, :to => :all
7
+ delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :all
8
+ delegate :find_each, :find_in_batches, :to => :all
9
+ delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
10
+ :where, :preload, :eager_load, :includes, :from, :lock, :readonly,
11
+ :having, :create_with, :uniq, :distinct, :references, :none, :unscope, :to => :all
12
+ delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :all
13
+
14
+ # Executes a custom SQL query against your database and returns all the results. The results will
15
+ # be returned as an array with columns requested encapsulated as attributes of the model you call
16
+ # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
17
+ # a Product object with the attributes you specified in the SQL query.
18
+ #
19
+ # If you call a complicated SQL query which spans multiple tables the columns specified by the
20
+ # SELECT will be attributes of the model, whether or not they are columns of the corresponding
21
+ # table.
22
+ #
23
+ # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
24
+ # no database agnostic conversions performed. This should be a last resort because using, for example,
25
+ # MySQL specific terms will lock you to using that particular database engine or require you to
26
+ # change your call if you switch engines.
27
+ #
28
+ # # A simple SQL query spanning multiple tables
29
+ # Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
30
+ # # => [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
31
+ #
32
+ # # You can use the same string replacement techniques as you can with ActiveRecord#find
33
+ # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
34
+ # # => [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
35
+ def find_by_sql(sql, binds = [])
36
+ result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
37
+ column_types = {}
38
+
39
+ if result_set.respond_to? :column_types
40
+ column_types = result_set.column_types
41
+ else
42
+ ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`"
43
+ end
44
+
45
+ result_set.map { |record| instantiate(record, column_types) }
46
+ end
47
+
48
+ # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
49
+ # The use of this method should be restricted to complicated SQL queries that can't be executed
50
+ # using the ActiveRecord::Calculations class methods. Look into those before using this.
51
+ #
52
+ # ==== Parameters
53
+ #
54
+ # * +sql+ - An SQL statement which should return a count query from the database, see the example below.
55
+ #
56
+ # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
57
+ def count_by_sql(sql)
58
+ sql = sanitize_conditions(sql)
59
+ connection.select_value(sql, "#{name} Count").to_i
60
+ end
61
+ end
62
+ end