activerecord 3.2.22.5 → 4.0.0.beta1

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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -543
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +20 -29
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +55 -44
  7. data/lib/active_record/aggregations.rb +40 -34
  8. data/lib/active_record/associations.rb +204 -276
  9. data/lib/active_record/associations/alias_tracker.rb +1 -1
  10. data/lib/active_record/associations/association.rb +30 -35
  11. data/lib/active_record/associations/association_scope.rb +40 -40
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -2
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +35 -57
  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 +92 -88
  21. data/lib/active_record/associations/collection_proxy.rb +913 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
  23. data/lib/active_record/associations/has_many_association.rb +35 -9
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -14
  25. data/lib/active_record/associations/has_one_association.rb +33 -13
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +2 -2
  28. data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/join_helper.rb +1 -11
  31. data/lib/active_record/associations/preloader.rb +14 -17
  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 +1 -1
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  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/singular_association.rb +11 -11
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +133 -153
  41. data/lib/active_record/attribute_methods.rb +196 -93
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  43. data/lib/active_record/attribute_methods/dirty.rb +31 -28
  44. data/lib/active_record/attribute_methods/primary_key.rb +38 -30
  45. data/lib/active_record/attribute_methods/query.rb +5 -4
  46. data/lib/active_record/attribute_methods/read.rb +62 -91
  47. data/lib/active_record/attribute_methods/serialization.rb +97 -66
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
  49. data/lib/active_record/attribute_methods/write.rb +32 -39
  50. data/lib/active_record/autosave_association.rb +56 -70
  51. data/lib/active_record/base.rb +53 -450
  52. data/lib/active_record/callbacks.rb +53 -18
  53. data/lib/active_record/coders/yaml_column.rb +11 -9
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
  65. data/lib/active_record/connection_adapters/column.rb +46 -24
  66. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  67. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  68. data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
  69. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  70. data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
  73. data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
  74. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  75. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
  76. data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
  77. data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
  79. data/lib/active_record/connection_handling.rb +98 -0
  80. data/lib/active_record/core.rb +428 -0
  81. data/lib/active_record/counter_cache.rb +106 -108
  82. data/lib/active_record/dynamic_matchers.rb +110 -63
  83. data/lib/active_record/errors.rb +25 -8
  84. data/lib/active_record/explain.rb +8 -58
  85. data/lib/active_record/explain_subscriber.rb +6 -3
  86. data/lib/active_record/fixture_set/file.rb +56 -0
  87. data/lib/active_record/fixtures.rb +146 -148
  88. data/lib/active_record/inheritance.rb +77 -59
  89. data/lib/active_record/integration.rb +5 -5
  90. data/lib/active_record/locale/en.yml +8 -1
  91. data/lib/active_record/locking/optimistic.rb +38 -42
  92. data/lib/active_record/locking/pessimistic.rb +4 -4
  93. data/lib/active_record/log_subscriber.rb +19 -9
  94. data/lib/active_record/migration.rb +318 -153
  95. data/lib/active_record/migration/command_recorder.rb +90 -31
  96. data/lib/active_record/migration/join_table.rb +15 -0
  97. data/lib/active_record/model_schema.rb +69 -92
  98. data/lib/active_record/nested_attributes.rb +113 -148
  99. data/lib/active_record/null_relation.rb +65 -0
  100. data/lib/active_record/persistence.rb +188 -97
  101. data/lib/active_record/query_cache.rb +18 -36
  102. data/lib/active_record/querying.rb +19 -15
  103. data/lib/active_record/railtie.rb +91 -36
  104. data/lib/active_record/railties/console_sandbox.rb +0 -2
  105. data/lib/active_record/railties/controller_runtime.rb +2 -2
  106. data/lib/active_record/railties/databases.rake +90 -309
  107. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  108. data/lib/active_record/readonly_attributes.rb +7 -3
  109. data/lib/active_record/reflection.rb +72 -56
  110. data/lib/active_record/relation.rb +241 -157
  111. data/lib/active_record/relation/batches.rb +25 -22
  112. data/lib/active_record/relation/calculations.rb +143 -121
  113. data/lib/active_record/relation/delegation.rb +96 -18
  114. data/lib/active_record/relation/finder_methods.rb +117 -183
  115. data/lib/active_record/relation/merger.rb +133 -0
  116. data/lib/active_record/relation/predicate_builder.rb +90 -42
  117. data/lib/active_record/relation/query_methods.rb +666 -136
  118. data/lib/active_record/relation/spawn_methods.rb +43 -150
  119. data/lib/active_record/result.rb +33 -6
  120. data/lib/active_record/sanitization.rb +24 -50
  121. data/lib/active_record/schema.rb +19 -12
  122. data/lib/active_record/schema_dumper.rb +31 -39
  123. data/lib/active_record/schema_migration.rb +36 -0
  124. data/lib/active_record/scoping.rb +0 -124
  125. data/lib/active_record/scoping/default.rb +48 -45
  126. data/lib/active_record/scoping/named.rb +74 -103
  127. data/lib/active_record/serialization.rb +6 -2
  128. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  129. data/lib/active_record/store.rb +119 -15
  130. data/lib/active_record/tasks/database_tasks.rb +158 -0
  131. data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
  132. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  133. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  134. data/lib/active_record/test_case.rb +61 -38
  135. data/lib/active_record/timestamp.rb +8 -9
  136. data/lib/active_record/transactions.rb +65 -51
  137. data/lib/active_record/validations.rb +17 -15
  138. data/lib/active_record/validations/associated.rb +20 -14
  139. data/lib/active_record/validations/presence.rb +65 -0
  140. data/lib/active_record/validations/uniqueness.rb +93 -52
  141. data/lib/active_record/version.rb +4 -4
  142. data/lib/rails/generators/active_record.rb +3 -5
  143. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
  144. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  145. data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
  146. data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
  147. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  148. metadata +53 -46
  149. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  150. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  151. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  152. data/lib/active_record/dynamic_finder_match.rb +0 -68
  153. data/lib/active_record/dynamic_scope_match.rb +0 -23
  154. data/lib/active_record/fixtures/file.rb +0 -65
  155. data/lib/active_record/identity_map.rb +0 -162
  156. data/lib/active_record/observer.rb +0 -121
  157. data/lib/active_record/session_store.rb +0 -360
  158. data/lib/rails/generators/active_record/migration.rb +0 -15
  159. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  160. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  161. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  162. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,65 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module ActiveRecord
4
+ module NullRelation # :nodoc:
5
+ def exec_queries
6
+ @records = []
7
+ end
8
+
9
+ def pluck(_column_name)
10
+ []
11
+ end
12
+
13
+ def delete_all(_conditions = nil)
14
+ 0
15
+ end
16
+
17
+ def update_all(_updates, _conditions = nil, _options = {})
18
+ 0
19
+ end
20
+
21
+ def delete(_id_or_array)
22
+ 0
23
+ end
24
+
25
+ def size
26
+ 0
27
+ end
28
+
29
+ def empty?
30
+ true
31
+ end
32
+
33
+ def any?
34
+ false
35
+ end
36
+
37
+ def many?
38
+ false
39
+ end
40
+
41
+ def to_sql
42
+ @to_sql ||= ""
43
+ end
44
+
45
+ def where_values_hash
46
+ {}
47
+ end
48
+
49
+ def count(*)
50
+ 0
51
+ end
52
+
53
+ def sum(*)
54
+ 0
55
+ end
56
+
57
+ def calculate(_operation, _column_name, _options = {})
58
+ nil
59
+ end
60
+
61
+ def exists?(_id = false)
62
+ false
63
+ end
64
+ end
65
+ end
@@ -1,5 +1,3 @@
1
- require 'active_support/concern'
2
-
3
1
  module ActiveRecord
4
2
  # = Active Record Persistence
5
3
  module Persistence
@@ -9,7 +7,7 @@ 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
13
  # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
@@ -17,50 +15,72 @@ module ActiveRecord
17
15
  #
18
16
  # ==== Examples
19
17
  # # 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)
18
+ # User.create(first_name: 'Jamie')
27
19
  #
28
20
  # # Create an Array of new objects
29
- # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
21
+ # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
30
22
  #
31
23
  # # Create a single object and pass it into a block to set other attributes.
32
- # User.create(:first_name => 'Jamie') do |u|
24
+ # User.create(first_name: 'Jamie') do |u|
33
25
  # u.is_admin = false
34
26
  # end
35
27
  #
36
28
  # # 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|
29
+ # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
38
30
  # u.is_admin = false
39
31
  # end
40
- def create(attributes = nil, options = {}, &block)
32
+ def create(attributes = nil, &block)
41
33
  if attributes.is_a?(Array)
42
- attributes.collect { |attr| create(attr, options, &block) }
34
+ attributes.collect { |attr| create(attr, &block) }
43
35
  else
44
- object = new(attributes, options, &block)
36
+ object = new(attributes, &block)
45
37
  object.save
46
38
  object
47
39
  end
48
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)
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
49
67
  end
50
68
 
51
69
  # Returns true if this object hasn't been saved yet -- that is, a record
52
70
  # for the object doesn't exist in the data store yet; otherwise, returns false.
53
71
  def new_record?
72
+ sync_with_transaction_state
54
73
  @new_record
55
74
  end
56
75
 
57
76
  # Returns true if this object has been destroyed, otherwise returns false.
58
77
  def destroyed?
78
+ sync_with_transaction_state
59
79
  @destroyed
60
80
  end
61
81
 
62
- # Returns if the record is persisted, i.e. it's not a new record and it was
63
- # 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.
64
84
  def persisted?
65
85
  !(new_record? || destroyed?)
66
86
  end
@@ -72,7 +92,7 @@ module ActiveRecord
72
92
  #
73
93
  # By default, save always run validations. If any of them fail the action
74
94
  # is cancelled and +save+ returns +false+. However, if you supply
75
- # :validate => false, validations are bypassed altogether. See
95
+ # validate: false, validations are bypassed altogether. See
76
96
  # ActiveRecord::Validations for more information.
77
97
  #
78
98
  # There's a series of callbacks associated with +save+. If any of the
@@ -80,11 +100,9 @@ module ActiveRecord
80
100
  # +save+ returns +false+. See ActiveRecord::Callbacks for further
81
101
  # details.
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.
@@ -112,45 +130,46 @@ module ActiveRecord
112
130
  # record's primary key, and no callbacks are executed.
113
131
  #
114
132
  # To enforce the object's +before_destroy+ and +after_destroy+
115
- # callbacks, Observer methods, or any <tt>:dependent</tt> association
133
+ # callbacks or any <tt>:dependent</tt> association
116
134
  # options, use <tt>#destroy</tt>.
117
135
  def delete
118
- if persisted?
119
- self.class.delete(id)
120
- IdentityMap.remove(self) if IdentityMap.enabled?
121
- end
136
+ self.class.delete(id) if persisted?
122
137
  @destroyed = true
123
138
  freeze
124
139
  end
125
140
 
126
141
  # Deletes the record in the database and freezes this instance to reflect
127
142
  # that no changes should be made (since they can't be persisted).
143
+ #
144
+ # There's a series of callbacks associated with <tt>destroy</tt>. If
145
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
146
+ # and <tt>destroy</tt> returns +false+. See
147
+ # ActiveRecord::Callbacks for further details.
128
148
  def destroy
149
+ raise ReadOnlyRecord if readonly?
129
150
  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
-
151
+ destroy_row if persisted?
144
152
  @destroyed = true
145
153
  freeze
146
154
  end
147
155
 
156
+ # Deletes the record in the database and freezes this instance to reflect
157
+ # that no changes should be made (since they can't be persisted).
158
+ #
159
+ # There's a series of callbacks associated with <tt>destroy!</tt>. If
160
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
161
+ # and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See
162
+ # ActiveRecord::Callbacks for further details.
163
+ def destroy!
164
+ destroy || raise(ActiveRecord::RecordNotDestroyed)
165
+ end
166
+
148
167
  # Returns an instance of the specified +klass+ with the attributes of the
149
168
  # current record. This is mostly useful in relation to single-table
150
169
  # inheritance structures where you want a subclass to appear as the
151
170
  # superclass. This can be used along with record identification in
152
171
  # Action Pack to allow, say, <tt>Client < Company</tt> to do something
153
- # like render <tt>:partial => @client.becomes(Company)</tt> to render that
172
+ # like render <tt>partial: @client.becomes(Company)</tt> to render that
154
173
  # instance using the companies/company partial instead of clients/client.
155
174
  #
156
175
  # Note: The new instance will share a link to the same attributes as the original class.
@@ -162,7 +181,18 @@ module ActiveRecord
162
181
  became.instance_variable_set("@new_record", new_record?)
163
182
  became.instance_variable_set("@destroyed", destroyed?)
164
183
  became.instance_variable_set("@errors", errors)
165
- became.send("#{klass.inheritance_column}=", klass.name) unless self.class.descends_from_active_record?
184
+ became
185
+ end
186
+
187
+ # Wrapper around +becomes+ that also changes the instance's sti column value.
188
+ # This is especially useful if you want to persist the changed class in your
189
+ # database.
190
+ #
191
+ # Note: The old instance's sti column value will be changed too, as both objects
192
+ # share the same set of attributes.
193
+ def becomes!(klass)
194
+ became = becomes(klass)
195
+ became.public_send("#{klass.inheritance_column}=", klass.sti_name) unless self.class.descends_from_active_record?
166
196
  became
167
197
  end
168
198
 
@@ -176,59 +206,74 @@ module ActiveRecord
176
206
  #
177
207
  def update_attribute(name, value)
178
208
  name = name.to_s
179
- raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
209
+ verify_readonly_attribute(name)
180
210
  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
211
+ save(validate: false)
202
212
  end
203
213
 
204
214
  # Updates the attributes of the model from the passed-in hash and saves the
205
215
  # record, all wrapped in a transaction. If the object is invalid, the saving
206
216
  # 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 = {})
217
+ def update(attributes)
213
218
  # The following transaction covers any possible database side-effects of the
214
219
  # attributes assignment. For example, setting the IDs of a child collection.
215
220
  with_transaction_returning_status do
216
- self.assign_attributes(attributes, options)
221
+ assign_attributes(attributes)
217
222
  save
218
223
  end
219
224
  end
220
225
 
221
- # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
226
+ alias update_attributes update
227
+
228
+ # Updates its receiver just like +update+ but calls <tt>save!</tt> instead
222
229
  # of +save+, so an exception is raised if the record is invalid.
223
- def update_attributes!(attributes, options = {})
230
+ def update!(attributes)
224
231
  # The following transaction covers any possible database side-effects of the
225
232
  # attributes assignment. For example, setting the IDs of a child collection.
226
233
  with_transaction_returning_status do
227
- self.assign_attributes(attributes, options)
234
+ assign_attributes(attributes)
228
235
  save!
229
236
  end
230
237
  end
231
238
 
239
+ alias update_attributes! update!
240
+
241
+ # Equivalent to <code>update_columns(name => value)</code>.
242
+ def update_column(name, value)
243
+ update_columns(name => value)
244
+ end
245
+
246
+ # Updates the attributes directly in the database issuing an UPDATE SQL
247
+ # statement and sets them in the receiver:
248
+ #
249
+ # user.update_columns(last_request_at: Time.current)
250
+ #
251
+ # This is the fastest way to update attributes because it goes straight to
252
+ # the database, but take into account that in consequence the regular update
253
+ # procedures are totally bypassed. In particular:
254
+ #
255
+ # * Validations are skipped.
256
+ # * Callbacks are skipped.
257
+ # * +updated_at+/+updated_on+ are not updated.
258
+ #
259
+ # This method raises an +ActiveRecord::ActiveRecordError+ when called on new
260
+ # objects, or when at least one of the attributes is marked as readonly.
261
+ def update_columns(attributes)
262
+ raise ActiveRecordError, "can not update on a new record object" unless persisted?
263
+
264
+ attributes.each_key do |key|
265
+ verify_readonly_attribute(key.to_s)
266
+ end
267
+
268
+ updated_count = self.class.unscoped.where(self.class.primary_key => id).update_all(attributes)
269
+
270
+ attributes.each do |k, v|
271
+ raw_write_attribute(k, v)
272
+ end
273
+
274
+ updated_count == 1
275
+ end
276
+
232
277
  # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
233
278
  # The increment is performed directly on the underlying attribute, no setter is invoked.
234
279
  # Only makes sense for number-based attributes. Returns +self+.
@@ -282,16 +327,21 @@ module ActiveRecord
282
327
 
283
328
  # Reloads the attributes of this object from the database.
284
329
  # 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
330
+ # may do e.g. record.reload(lock: true) to reload the same record with
286
331
  # an exclusive row lock.
287
332
  def reload(options = nil)
288
333
  clear_aggregation_cache
289
334
  clear_association_cache
290
335
 
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
336
+ fresh_object =
337
+ if options && options[:lock]
338
+ self.class.unscoped { self.class.lock.find(id) }
339
+ else
340
+ self.class.unscoped { self.class.find(id) }
341
+ end
342
+
343
+ @attributes.update(fresh_object.instance_variable_get('@attributes'))
344
+ @columns_hash = fresh_object.instance_variable_get('@columns_hash')
295
345
 
296
346
  @attributes_cache = {}
297
347
  self
@@ -308,16 +358,25 @@ module ActiveRecord
308
358
  # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on associated object.
309
359
  #
310
360
  # class Brake < ActiveRecord::Base
311
- # belongs_to :car, :touch => true
361
+ # belongs_to :car, touch: true
312
362
  # end
313
363
  #
314
364
  # class Car < ActiveRecord::Base
315
- # belongs_to :corporation, :touch => true
365
+ # belongs_to :corporation, touch: true
316
366
  # end
317
367
  #
318
368
  # # triggers @brake.car.touch and @brake.car.corporation.touch
319
369
  # @brake.touch
370
+ #
371
+ # Note that +touch+ must be used on a persisted object, or else an
372
+ # ActiveRecordError will be thrown. For example:
373
+ #
374
+ # ball = Ball.new
375
+ # ball.touch(:updated_at) # => raises ActiveRecordError
376
+ #
320
377
  def touch(name = nil)
378
+ raise ActiveRecordError, "can not touch on a new record object" unless persisted?
379
+
321
380
  attributes = timestamp_attributes_for_update_in_model
322
381
  attributes << name if name
323
382
 
@@ -326,14 +385,15 @@ module ActiveRecord
326
385
  changes = {}
327
386
 
328
387
  attributes.each do |column|
329
- changes[column.to_s] = write_attribute(column.to_s, current_time)
388
+ column = column.to_s
389
+ changes[column] = write_attribute(column, current_time)
330
390
  end
331
391
 
332
392
  changes[self.class.locking_column] = increment_lock if locking_enabled?
333
393
 
334
394
  @changed_attributes.except!(*changes.keys)
335
395
  primary_key = self.class.primary_key
336
- self.class.unscoped.update_all(changes, { primary_key => self[primary_key] }) == 1
396
+ self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
337
397
  end
338
398
  end
339
399
 
@@ -343,34 +403,65 @@ module ActiveRecord
343
403
  def destroy_associations
344
404
  end
345
405
 
406
+ def destroy_row
407
+ relation_for_destroy.delete_all
408
+ end
409
+
410
+ def relation_for_destroy
411
+ pk = self.class.primary_key
412
+ column = self.class.columns_hash[pk]
413
+ substitute = connection.substitute_at(column, 0)
414
+
415
+ relation = self.class.unscoped.where(
416
+ self.class.arel_table[pk].eq(substitute))
417
+
418
+ relation.bind_values = [[column, id]]
419
+ relation
420
+ end
421
+
346
422
  def create_or_update
347
423
  raise ReadOnlyRecord if readonly?
348
- result = new_record? ? create : update
424
+ result = new_record? ? create_record : update_record
349
425
  result != false
350
426
  end
351
427
 
352
428
  # Updates the associated record with values matching those of the instance attributes.
353
429
  # 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
430
+ def update_record(attribute_names = @attributes.keys)
431
+ attributes_with_values = arel_attributes_with_values_for_update(attribute_names)
432
+ if attributes_with_values.empty?
433
+ 0
434
+ else
435
+ klass = self.class
436
+ column_hash = klass.connection.schema_cache.columns_hash klass.table_name
437
+ db_columns_with_values = attributes_with_values.map { |attr,value|
438
+ real_column = column_hash[attr.name]
439
+ [real_column, value]
440
+ }
441
+ bind_attrs = attributes_with_values.dup
442
+ bind_attrs.keys.each_with_index do |column, i|
443
+ real_column = db_columns_with_values[i].first
444
+ bind_attrs[column] = klass.connection.substitute_at(real_column, i)
445
+ end
446
+ stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(bind_attrs)
447
+ klass.connection.update stmt, 'SQL', db_columns_with_values
448
+ end
360
449
  end
361
450
 
362
451
  # Creates a record with values matching those of the instance attributes
363
452
  # and returns its id.
364
- def create
365
- attributes_values = arel_attributes_values(!id.nil?)
453
+ def create_record(attribute_names = @attributes.keys)
454
+ attributes_values = arel_attributes_with_values_for_create(attribute_names)
366
455
 
367
456
  new_id = self.class.unscoped.insert attributes_values
368
-
369
457
  self.id ||= new_id if self.class.primary_key
370
458
 
371
- IdentityMap.add(self) if IdentityMap.enabled?
372
459
  @new_record = false
373
460
  id
374
461
  end
462
+
463
+ def verify_readonly_attribute(name)
464
+ raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
465
+ end
375
466
  end
376
467
  end