sskirby-activerecord 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. data/CHANGELOG.md +6749 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +222 -0
  4. data/examples/associations.png +0 -0
  5. data/examples/performance.rb +177 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +147 -0
  8. data/lib/active_record/aggregations.rb +255 -0
  9. data/lib/active_record/associations.rb +1604 -0
  10. data/lib/active_record/associations/alias_tracker.rb +79 -0
  11. data/lib/active_record/associations/association.rb +239 -0
  12. data/lib/active_record/associations/association_scope.rb +119 -0
  13. data/lib/active_record/associations/belongs_to_association.rb +79 -0
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
  15. data/lib/active_record/associations/builder/association.rb +55 -0
  16. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  17. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  19. data/lib/active_record/associations/builder/has_many.rb +71 -0
  20. data/lib/active_record/associations/builder/has_one.rb +62 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  22. data/lib/active_record/associations/collection_association.rb +574 -0
  23. data/lib/active_record/associations/collection_proxy.rb +132 -0
  24. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
  25. data/lib/active_record/associations/has_many_association.rb +108 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +180 -0
  27. data/lib/active_record/associations/has_one_association.rb +73 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +214 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  33. data/lib/active_record/associations/join_helper.rb +55 -0
  34. data/lib/active_record/associations/preloader.rb +177 -0
  35. data/lib/active_record/associations/preloader/association.rb +127 -0
  36. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  37. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  38. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  39. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  40. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  41. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  42. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  43. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  44. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  45. data/lib/active_record/associations/singular_association.rb +64 -0
  46. data/lib/active_record/associations/through_association.rb +83 -0
  47. data/lib/active_record/attribute_assignment.rb +221 -0
  48. data/lib/active_record/attribute_methods.rb +272 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  50. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  51. data/lib/active_record/attribute_methods/dirty.rb +101 -0
  52. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  53. data/lib/active_record/attribute_methods/query.rb +39 -0
  54. data/lib/active_record/attribute_methods/read.rb +135 -0
  55. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  56. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
  57. data/lib/active_record/attribute_methods/write.rb +69 -0
  58. data/lib/active_record/autosave_association.rb +422 -0
  59. data/lib/active_record/base.rb +716 -0
  60. data/lib/active_record/callbacks.rb +275 -0
  61. data/lib/active_record/coders/yaml_column.rb +41 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
  70. data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
  71. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  72. data/lib/active_record/connection_adapters/column.rb +270 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
  75. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
  76. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
  78. data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
  79. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  80. data/lib/active_record/counter_cache.rb +119 -0
  81. data/lib/active_record/dynamic_finder_match.rb +56 -0
  82. data/lib/active_record/dynamic_matchers.rb +79 -0
  83. data/lib/active_record/dynamic_scope_match.rb +23 -0
  84. data/lib/active_record/errors.rb +195 -0
  85. data/lib/active_record/explain.rb +85 -0
  86. data/lib/active_record/explain_subscriber.rb +21 -0
  87. data/lib/active_record/fixtures.rb +906 -0
  88. data/lib/active_record/fixtures/file.rb +65 -0
  89. data/lib/active_record/identity_map.rb +156 -0
  90. data/lib/active_record/inheritance.rb +167 -0
  91. data/lib/active_record/integration.rb +49 -0
  92. data/lib/active_record/locale/en.yml +40 -0
  93. data/lib/active_record/locking/optimistic.rb +183 -0
  94. data/lib/active_record/locking/pessimistic.rb +77 -0
  95. data/lib/active_record/log_subscriber.rb +68 -0
  96. data/lib/active_record/migration.rb +765 -0
  97. data/lib/active_record/migration/command_recorder.rb +105 -0
  98. data/lib/active_record/model_schema.rb +366 -0
  99. data/lib/active_record/nested_attributes.rb +469 -0
  100. data/lib/active_record/observer.rb +121 -0
  101. data/lib/active_record/persistence.rb +372 -0
  102. data/lib/active_record/query_cache.rb +74 -0
  103. data/lib/active_record/querying.rb +58 -0
  104. data/lib/active_record/railtie.rb +119 -0
  105. data/lib/active_record/railties/console_sandbox.rb +6 -0
  106. data/lib/active_record/railties/controller_runtime.rb +49 -0
  107. data/lib/active_record/railties/databases.rake +620 -0
  108. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  109. data/lib/active_record/readonly_attributes.rb +26 -0
  110. data/lib/active_record/reflection.rb +534 -0
  111. data/lib/active_record/relation.rb +534 -0
  112. data/lib/active_record/relation/batches.rb +90 -0
  113. data/lib/active_record/relation/calculations.rb +354 -0
  114. data/lib/active_record/relation/delegation.rb +49 -0
  115. data/lib/active_record/relation/finder_methods.rb +398 -0
  116. data/lib/active_record/relation/predicate_builder.rb +58 -0
  117. data/lib/active_record/relation/query_methods.rb +417 -0
  118. data/lib/active_record/relation/spawn_methods.rb +148 -0
  119. data/lib/active_record/result.rb +34 -0
  120. data/lib/active_record/sanitization.rb +194 -0
  121. data/lib/active_record/schema.rb +58 -0
  122. data/lib/active_record/schema_dumper.rb +204 -0
  123. data/lib/active_record/scoping.rb +152 -0
  124. data/lib/active_record/scoping/default.rb +142 -0
  125. data/lib/active_record/scoping/named.rb +202 -0
  126. data/lib/active_record/serialization.rb +18 -0
  127. data/lib/active_record/serializers/xml_serializer.rb +202 -0
  128. data/lib/active_record/session_store.rb +358 -0
  129. data/lib/active_record/store.rb +50 -0
  130. data/lib/active_record/test_case.rb +73 -0
  131. data/lib/active_record/timestamp.rb +113 -0
  132. data/lib/active_record/transactions.rb +360 -0
  133. data/lib/active_record/translation.rb +22 -0
  134. data/lib/active_record/validations.rb +83 -0
  135. data/lib/active_record/validations/associated.rb +43 -0
  136. data/lib/active_record/validations/uniqueness.rb +180 -0
  137. data/lib/active_record/version.rb +10 -0
  138. data/lib/rails/generators/active_record.rb +25 -0
  139. data/lib/rails/generators/active_record/migration.rb +15 -0
  140. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  141. data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
  142. data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
  143. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  144. data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
  145. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  146. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  147. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  148. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  149. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  150. metadata +242 -0
@@ -0,0 +1,121 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+
3
+ module ActiveRecord
4
+ # = Active Record Observer
5
+ #
6
+ # Observer classes respond to life cycle callbacks to implement trigger-like
7
+ # behavior outside the original class. This is a great way to reduce the
8
+ # clutter that normally comes when the model class is burdened with
9
+ # functionality that doesn't pertain to the core responsibility of the
10
+ # class. Example:
11
+ #
12
+ # class CommentObserver < ActiveRecord::Observer
13
+ # def after_save(comment)
14
+ # Notifications.comment("admin@do.com", "New comment was posted", comment).deliver
15
+ # end
16
+ # end
17
+ #
18
+ # This Observer sends an email when a Comment#save is finished.
19
+ #
20
+ # class ContactObserver < ActiveRecord::Observer
21
+ # def after_create(contact)
22
+ # contact.logger.info('New contact added!')
23
+ # end
24
+ #
25
+ # def after_destroy(contact)
26
+ # contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
27
+ # end
28
+ # end
29
+ #
30
+ # This Observer uses logger to log when specific callbacks are triggered.
31
+ #
32
+ # == Observing a class that can't be inferred
33
+ #
34
+ # Observers will by default be mapped to the class with which they share a name. So CommentObserver will
35
+ # be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer
36
+ # differently than the class you're interested in observing, you can use the Observer.observe class method which takes
37
+ # either the concrete class (Product) or a symbol for that class (:product):
38
+ #
39
+ # class AuditObserver < ActiveRecord::Observer
40
+ # observe :account
41
+ #
42
+ # def after_update(account)
43
+ # AuditTrail.new(account, "UPDATED")
44
+ # end
45
+ # end
46
+ #
47
+ # If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments:
48
+ #
49
+ # class AuditObserver < ActiveRecord::Observer
50
+ # observe :account, :balance
51
+ #
52
+ # def after_update(record)
53
+ # AuditTrail.new(record, "UPDATED")
54
+ # end
55
+ # end
56
+ #
57
+ # The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
58
+ #
59
+ # == Available callback methods
60
+ #
61
+ # The observer can implement callback methods for each of the methods described in the Callbacks module.
62
+ #
63
+ # == Storing Observers in Rails
64
+ #
65
+ # If you're using Active Record within Rails, observer classes are usually stored in app/models with the
66
+ # naming convention of app/models/audit_observer.rb.
67
+ #
68
+ # == Configuration
69
+ #
70
+ # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration
71
+ # setting in your <tt>config/application.rb</tt> file.
72
+ #
73
+ # config.active_record.observers = :comment_observer, :signup_observer
74
+ #
75
+ # Observers will not be invoked unless you define these in your application configuration.
76
+ #
77
+ # == Loading
78
+ #
79
+ # Observers register themselves in the model class they observe, since it is the class that
80
+ # notifies them of events when they occur. As a side-effect, when an observer is loaded its
81
+ # corresponding model class is loaded.
82
+ #
83
+ # Up to (and including) Rails 2.0.2 observers were instantiated between plugins and
84
+ # application initializers. Now observers are loaded after application initializers,
85
+ # so observed models can make use of extensions.
86
+ #
87
+ # If by any chance you are using observed models in the initialization you can still
88
+ # load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are
89
+ # singletons and that call instantiates and registers them.
90
+ #
91
+ class Observer < ActiveModel::Observer
92
+
93
+ protected
94
+
95
+ def observed_classes
96
+ klasses = super
97
+ klasses + klasses.map { |klass| klass.descendants }.flatten
98
+ end
99
+
100
+ def add_observer!(klass)
101
+ super
102
+ define_callbacks klass
103
+ end
104
+
105
+ def define_callbacks(klass)
106
+ observer = self
107
+ observer_name = observer.class.name.underscore.gsub('/', '__')
108
+
109
+ ActiveRecord::Callbacks::CALLBACKS.each do |callback|
110
+ next unless respond_to?(callback)
111
+ callback_meth = :"_notify_#{observer_name}_for_#{callback}"
112
+ unless klass.respond_to?(callback_meth)
113
+ klass.send(:define_method, callback_meth) do |&block|
114
+ observer.update(callback, self, &block)
115
+ end
116
+ klass.send(callback, callback_meth)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,372 @@
1
+ require 'active_support/concern'
2
+
3
+ module ActiveRecord
4
+ # = Active Record Persistence
5
+ module Persistence
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ # Creates an object (or multiple objects) and saves it to the database, if validations pass.
10
+ # The resulting object is returned whether the object was saved successfully to the database or not.
11
+ #
12
+ # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
13
+ # attributes on the objects that are to be created.
14
+ #
15
+ # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
16
+ # in the +options+ parameter.
17
+ #
18
+ # ==== Examples
19
+ # # 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)
27
+ #
28
+ # # Create an Array of new objects
29
+ # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
30
+ #
31
+ # # Create a single object and pass it into a block to set other attributes.
32
+ # User.create(:first_name => 'Jamie') do |u|
33
+ # u.is_admin = false
34
+ # end
35
+ #
36
+ # # 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|
38
+ # u.is_admin = false
39
+ # end
40
+ def create(attributes = nil, options = {}, &block)
41
+ if attributes.is_a?(Array)
42
+ attributes.collect { |attr| create(attr, options, &block) }
43
+ else
44
+ object = new(attributes, options, &block)
45
+ object.save
46
+ object
47
+ end
48
+ end
49
+ end
50
+
51
+ # 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.
53
+ def new_record?
54
+ @new_record
55
+ end
56
+
57
+ # Returns true if this object has been destroyed, otherwise returns false.
58
+ def destroyed?
59
+ @destroyed
60
+ end
61
+
62
+ # Returns if the record is persisted, i.e. it's not a new record and it was
63
+ # not destroyed.
64
+ def persisted?
65
+ !(new_record? || destroyed?)
66
+ end
67
+
68
+ # Saves the model.
69
+ #
70
+ # If the model is new a record gets created in the database, otherwise
71
+ # the existing record gets updated.
72
+ #
73
+ # By default, save always run validations. If any of them fail the action
74
+ # is cancelled and +save+ returns +false+. However, if you supply
75
+ # :validate => false, validations are bypassed altogether. See
76
+ # ActiveRecord::Validations for more information.
77
+ #
78
+ # There's a series of callbacks associated with +save+. If any of the
79
+ # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
80
+ # +save+ returns +false+. See ActiveRecord::Callbacks for further
81
+ # details.
82
+ def save(*)
83
+ begin
84
+ create_or_update
85
+ rescue ActiveRecord::RecordInvalid
86
+ false
87
+ end
88
+ end
89
+
90
+ # Saves the model.
91
+ #
92
+ # If the model is new a record gets created in the database, otherwise
93
+ # the existing record gets updated.
94
+ #
95
+ # With <tt>save!</tt> validations always run. If any of them fail
96
+ # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
97
+ # for more information.
98
+ #
99
+ # There's a series of callbacks associated with <tt>save!</tt>. If any of
100
+ # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
101
+ # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
102
+ # ActiveRecord::Callbacks for further details.
103
+ def save!(*)
104
+ create_or_update || raise(RecordNotSaved)
105
+ end
106
+
107
+ # Deletes the record in the database and freezes this instance to
108
+ # reflect that no changes should be made (since they can't be
109
+ # persisted). Returns the frozen instance.
110
+ #
111
+ # The row is simply removed with an SQL +DELETE+ statement on the
112
+ # record's primary key, and no callbacks are executed.
113
+ #
114
+ # To enforce the object's +before_destroy+ and +after_destroy+
115
+ # callbacks, Observer methods, or any <tt>:dependent</tt> association
116
+ # options, use <tt>#destroy</tt>.
117
+ def delete
118
+ if persisted?
119
+ self.class.delete(id)
120
+ IdentityMap.remove(self) if IdentityMap.enabled?
121
+ end
122
+ @destroyed = true
123
+ freeze
124
+ end
125
+
126
+ # Deletes the record in the database and freezes this instance to reflect
127
+ # that no changes should be made (since they can't be persisted).
128
+ def destroy
129
+ 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
+
144
+ @destroyed = true
145
+ freeze
146
+ end
147
+
148
+ # Returns an instance of the specified +klass+ with the attributes of the
149
+ # current record. This is mostly useful in relation to single-table
150
+ # inheritance structures where you want a subclass to appear as the
151
+ # superclass. This can be used along with record identification in
152
+ # Action Pack to allow, say, <tt>Client < Company</tt> to do something
153
+ # like render <tt>:partial => @client.becomes(Company)</tt> to render that
154
+ # instance using the companies/company partial instead of clients/client.
155
+ #
156
+ # Note: The new instance will share a link to the same attributes as the original class.
157
+ # So any change to the attributes in either instance will affect the other.
158
+ def becomes(klass)
159
+ became = klass.new
160
+ became.instance_variable_set("@attributes", @attributes)
161
+ became.instance_variable_set("@attributes_cache", @attributes_cache)
162
+ became.instance_variable_set("@new_record", new_record?)
163
+ became.instance_variable_set("@destroyed", destroyed?)
164
+ became.instance_variable_set("@errors", errors)
165
+ became.type = klass.name unless self.class.descends_from_active_record?
166
+ became
167
+ end
168
+
169
+ # Updates a single attribute and saves the record.
170
+ # This is especially useful for boolean flags on existing records. Also note that
171
+ #
172
+ # * Validation is skipped.
173
+ # * Callbacks are invoked.
174
+ # * updated_at/updated_on column is updated if that column is available.
175
+ # * Updates all the attributes that are dirty in this object.
176
+ #
177
+ def update_attribute(name, value)
178
+ name = name.to_s
179
+ raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
180
+ 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
+ raw_write_attribute(name, value)
197
+ self.class.update_all({ name => value }, self.class.primary_key => id) == 1
198
+ end
199
+
200
+ # Updates the attributes of the model from the passed-in hash and saves the
201
+ # record, all wrapped in a transaction. If the object is invalid, the saving
202
+ # will fail and false will be returned.
203
+ #
204
+ # When updating model attributes, mass-assignment security protection is respected.
205
+ # If no +:as+ option is supplied then the +:default+ role will be used.
206
+ # If you want to bypass the protection given by +attr_protected+ and
207
+ # +attr_accessible+ then you can do so using the +:without_protection+ option.
208
+ def update_attributes(attributes, options = {})
209
+ # The following transaction covers any possible database side-effects of the
210
+ # attributes assignment. For example, setting the IDs of a child collection.
211
+ with_transaction_returning_status do
212
+ self.assign_attributes(attributes, options)
213
+ save
214
+ end
215
+ end
216
+
217
+ # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
218
+ # of +save+, so an exception is raised if the record is invalid.
219
+ def update_attributes!(attributes, options = {})
220
+ # The following transaction covers any possible database side-effects of the
221
+ # attributes assignment. For example, setting the IDs of a child collection.
222
+ with_transaction_returning_status do
223
+ self.assign_attributes(attributes, options)
224
+ save!
225
+ end
226
+ end
227
+
228
+ # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
229
+ # The increment is performed directly on the underlying attribute, no setter is invoked.
230
+ # Only makes sense for number-based attributes. Returns +self+.
231
+ def increment(attribute, by = 1)
232
+ self[attribute] ||= 0
233
+ self[attribute] += by
234
+ self
235
+ end
236
+
237
+ # Wrapper around +increment+ that saves the record. This method differs from
238
+ # its non-bang version in that it passes through the attribute setter.
239
+ # Saving is not subjected to validation checks. Returns +true+ if the
240
+ # record could be saved.
241
+ def increment!(attribute, by = 1)
242
+ increment(attribute, by).update_attribute(attribute, self[attribute])
243
+ end
244
+
245
+ # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
246
+ # The decrement is performed directly on the underlying attribute, no setter is invoked.
247
+ # Only makes sense for number-based attributes. Returns +self+.
248
+ def decrement(attribute, by = 1)
249
+ self[attribute] ||= 0
250
+ self[attribute] -= by
251
+ self
252
+ end
253
+
254
+ # Wrapper around +decrement+ that saves the record. This method differs from
255
+ # its non-bang version in that it passes through the attribute setter.
256
+ # Saving is not subjected to validation checks. Returns +true+ if the
257
+ # record could be saved.
258
+ def decrement!(attribute, by = 1)
259
+ decrement(attribute, by).update_attribute(attribute, self[attribute])
260
+ end
261
+
262
+ # Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
263
+ # if the predicate returns +true+ the attribute will become +false+. This
264
+ # method toggles directly the underlying value without calling any setter.
265
+ # Returns +self+.
266
+ def toggle(attribute)
267
+ self[attribute] = !send("#{attribute}?")
268
+ self
269
+ end
270
+
271
+ # Wrapper around +toggle+ that saves the record. This method differs from
272
+ # its non-bang version in that it passes through the attribute setter.
273
+ # Saving is not subjected to validation checks. Returns +true+ if the
274
+ # record could be saved.
275
+ def toggle!(attribute)
276
+ toggle(attribute).update_attribute(attribute, self[attribute])
277
+ end
278
+
279
+ # Reloads the attributes of this object from the database.
280
+ # The optional options argument is passed to find when reloading so you
281
+ # may do e.g. record.reload(:lock => true) to reload the same record with
282
+ # an exclusive row lock.
283
+ def reload(options = nil)
284
+ clear_aggregation_cache
285
+ clear_association_cache
286
+
287
+ IdentityMap.without do
288
+ fresh_object = self.class.unscoped { self.class.find(self.id, options) }
289
+ @attributes.update(fresh_object.instance_variable_get('@attributes'))
290
+ end
291
+
292
+ @attributes_cache = {}
293
+ self
294
+ end
295
+
296
+ # Saves the record with the updated_at/on attributes set to the current time.
297
+ # Please note that no validation is performed and no callbacks are executed.
298
+ # If an attribute name is passed, that attribute is updated along with
299
+ # updated_at/on attributes.
300
+ #
301
+ # product.touch # updates updated_at/on
302
+ # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
303
+ #
304
+ # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on associated object.
305
+ #
306
+ # class Brake < ActiveRecord::Base
307
+ # belongs_to :car, :touch => true
308
+ # end
309
+ #
310
+ # class Car < ActiveRecord::Base
311
+ # belongs_to :corporation, :touch => true
312
+ # end
313
+ #
314
+ # # triggers @brake.car.touch and @brake.car.corporation.touch
315
+ # @brake.touch
316
+ def touch(name = nil)
317
+ attributes = timestamp_attributes_for_update_in_model
318
+ attributes << name if name
319
+
320
+ unless attributes.empty?
321
+ current_time = current_time_from_proper_timezone
322
+ changes = {}
323
+
324
+ attributes.each do |column|
325
+ changes[column.to_s] = write_attribute(column.to_s, current_time)
326
+ end
327
+
328
+ changes[self.class.locking_column] = increment_lock if locking_enabled?
329
+
330
+ @changed_attributes.except!(*changes.keys)
331
+ primary_key = self.class.primary_key
332
+ self.class.unscoped.update_all(changes, { primary_key => self[primary_key] }) == 1
333
+ end
334
+ end
335
+
336
+ private
337
+
338
+ # A hook to be overridden by association modules.
339
+ def destroy_associations
340
+ end
341
+
342
+ def create_or_update
343
+ raise ReadOnlyRecord if readonly?
344
+ result = new_record? ? create : update
345
+ result != false
346
+ end
347
+
348
+ # Updates the associated record with values matching those of the instance attributes.
349
+ # Returns the number of affected rows.
350
+ def update(attribute_names = @attributes.keys)
351
+ attributes_with_values = arel_attributes_values(false, false, attribute_names)
352
+ return 0 if attributes_with_values.empty?
353
+ klass = self.class
354
+ stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
355
+ klass.connection.update stmt
356
+ end
357
+
358
+ # Creates a record with values matching those of the instance attributes
359
+ # and returns its id.
360
+ def create
361
+ attributes_values = arel_attributes_values(!id.nil?)
362
+
363
+ new_id = self.class.unscoped.insert attributes_values
364
+
365
+ self.id ||= new_id if self.class.primary_key
366
+
367
+ IdentityMap.add(self) if IdentityMap.enabled?
368
+ @new_record = false
369
+ id
370
+ end
371
+ end
372
+ end