activerecord 3.0.0.beta3 → 3.0.0.beta4

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 (51) hide show
  1. data/CHANGELOG +27 -0
  2. data/lib/active_record.rb +5 -1
  3. data/lib/active_record/aggregations.rb +1 -0
  4. data/lib/active_record/association_preload.rb +1 -1
  5. data/lib/active_record/associations.rb +88 -33
  6. data/lib/active_record/associations/association_collection.rb +12 -11
  7. data/lib/active_record/associations/association_proxy.rb +1 -1
  8. data/lib/active_record/attribute_methods.rb +10 -1
  9. data/lib/active_record/attribute_methods/dirty.rb +50 -50
  10. data/lib/active_record/attribute_methods/primary_key.rb +1 -1
  11. data/lib/active_record/autosave_association.rb +20 -5
  12. data/lib/active_record/base.rb +28 -343
  13. data/lib/active_record/callbacks.rb +23 -34
  14. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1 -1
  15. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -3
  16. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  17. data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -0
  18. data/lib/active_record/connection_adapters/abstract/query_cache.rb +11 -18
  19. data/lib/active_record/connection_adapters/abstract/quoting.rb +3 -7
  20. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +3 -3
  21. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +61 -7
  22. data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
  23. data/lib/active_record/connection_adapters/mysql_adapter.rb +22 -50
  24. data/lib/active_record/connection_adapters/postgresql_adapter.rb +31 -143
  25. data/lib/active_record/connection_adapters/sqlite_adapter.rb +6 -2
  26. data/lib/active_record/counter_cache.rb +105 -0
  27. data/lib/active_record/fixtures.rb +16 -15
  28. data/lib/active_record/locale/en.yml +2 -2
  29. data/lib/active_record/locking/optimistic.rb +37 -16
  30. data/lib/active_record/migration.rb +7 -3
  31. data/lib/active_record/named_scope.rb +1 -5
  32. data/lib/active_record/nested_attributes.rb +13 -2
  33. data/lib/active_record/observer.rb +19 -7
  34. data/lib/active_record/persistence.rb +230 -0
  35. data/lib/active_record/railtie.rb +17 -23
  36. data/lib/active_record/railties/databases.rake +3 -2
  37. data/lib/active_record/relation.rb +5 -2
  38. data/lib/active_record/relation/batches.rb +6 -1
  39. data/lib/active_record/relation/calculations.rb +12 -9
  40. data/lib/active_record/relation/finder_methods.rb +14 -10
  41. data/lib/active_record/relation/query_methods.rb +62 -44
  42. data/lib/active_record/relation/spawn_methods.rb +6 -1
  43. data/lib/active_record/schema_dumper.rb +3 -0
  44. data/lib/active_record/serializers/xml_serializer.rb +30 -56
  45. data/lib/active_record/session_store.rb +1 -1
  46. data/lib/active_record/timestamp.rb +22 -26
  47. data/lib/active_record/transactions.rb +168 -50
  48. data/lib/active_record/validations.rb +33 -49
  49. data/lib/active_record/version.rb +1 -1
  50. data/lib/rails/generators/active_record.rb +6 -9
  51. metadata +27 -10
@@ -3,8 +3,9 @@ require 'yaml'
3
3
  require 'csv'
4
4
  require 'zlib'
5
5
  require 'active_support/dependencies'
6
- require 'active_support/core_ext/logger'
6
+ require 'active_support/core_ext/array/wrap'
7
7
  require 'active_support/core_ext/object/blank'
8
+ require 'active_support/core_ext/logger'
8
9
 
9
10
  if RUBY_VERSION < '1.9'
10
11
  module YAML #:nodoc:
@@ -492,6 +493,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
492
493
 
493
494
  def self.create_fixtures(fixtures_directory, table_names, class_names = {})
494
495
  table_names = [table_names].flatten.map { |n| n.to_s }
496
+ table_names.each { |n| class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/') }
495
497
  connection = block_given? ? yield : ActiveRecord::Base.connection
496
498
 
497
499
  table_names_to_fetch = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) }
@@ -502,7 +504,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
502
504
  fixtures_map = {}
503
505
 
504
506
  fixtures = table_names_to_fetch.map do |table_name|
505
- fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s))
507
+ fixtures_map[table_name] = Fixtures.new(connection, table_name.tr('/', '_'), class_names[table_name.tr('/', '_').to_sym], File.join(fixtures_directory, table_name))
506
508
  end
507
509
 
508
510
  all_loaded_fixtures.update(fixtures_map)
@@ -514,7 +516,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
514
516
  # Cap primary key sequences to max(pk).
515
517
  if connection.respond_to?(:reset_pk_sequence!)
516
518
  table_names.each do |table_name|
517
- connection.reset_pk_sequence!(table_name)
519
+ connection.reset_pk_sequence!(table_name.tr('/', '_'))
518
520
  end
519
521
  end
520
522
  end
@@ -785,16 +787,14 @@ class Fixture #:nodoc:
785
787
  end
786
788
 
787
789
  def key_list
788
- columns = @fixture.keys.collect{ |column_name| @connection.quote_column_name(column_name) }
789
- columns.join(", ")
790
+ @fixture.keys.map { |column_name| @connection.quote_column_name(column_name) }.join(', ')
790
791
  end
791
792
 
792
793
  def value_list
793
- list = @fixture.inject([]) do |fixtures, (key, value)|
794
- col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base)
795
- fixtures << @connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
796
- end
797
- list * ', '
794
+ cols = (model_class && model_class < ActiveRecord::Base) ? model_class.columns_hash : {}
795
+ @fixture.map do |key, value|
796
+ @connection.quote(value, cols[key]).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
797
+ end.join(', ')
798
798
  end
799
799
 
800
800
  def find
@@ -836,8 +836,8 @@ module ActiveRecord
836
836
 
837
837
  def fixtures(*table_names)
838
838
  if table_names.first == :all
839
- table_names = Dir["#{fixture_path}/*.yml"] + Dir["#{fixture_path}/*.csv"]
840
- table_names.map! { |f| File.basename(f).split('.')[0..-2].join('.') }
839
+ table_names = Dir["#{fixture_path}/**/*.{yml,csv}"]
840
+ table_names.map! { |f| f[(fixture_path.size + 1)..-5] }
841
841
  else
842
842
  table_names = table_names.flatten.map { |n| n.to_s }
843
843
  end
@@ -868,9 +868,9 @@ module ActiveRecord
868
868
  end
869
869
 
870
870
  def setup_fixture_accessors(table_names = nil)
871
- table_names = [table_names] if table_names && !table_names.respond_to?(:each)
872
- (table_names || fixture_table_names).each do |table_name|
873
- table_name = table_name.to_s.tr('.', '_')
871
+ table_names = Array.wrap(table_names || fixture_table_names)
872
+ table_names.each do |table_name|
873
+ table_name = table_name.to_s.tr('./', '_')
874
874
 
875
875
  define_method(table_name) do |*fixtures|
876
876
  force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
@@ -889,6 +889,7 @@ module ActiveRecord
889
889
 
890
890
  instances.size == 1 ? instances.first : instances
891
891
  end
892
+ private table_name
892
893
  end
893
894
  end
894
895
 
@@ -9,7 +9,7 @@ en:
9
9
  errors:
10
10
  messages:
11
11
  taken: "has already been taken"
12
- record_invalid: "Validation failed: {{errors}}"
12
+ record_invalid: "Validation failed: %{errors}"
13
13
  # Append your own errors here or at the model/attributes scope.
14
14
 
15
15
  # You can define own errors for models or model attributes.
@@ -18,7 +18,7 @@ en:
18
18
  # For example,
19
19
  # models:
20
20
  # user:
21
- # blank: "This is a custom blank message for {{model}}: {{attribute}}"
21
+ # blank: "This is a custom blank message for %{model}: %{attribute}"
22
22
  # attributes:
23
23
  # login:
24
24
  # blank: "This is a custom blank message for User login"
@@ -23,6 +23,16 @@ module ActiveRecord
23
23
  # p2.first_name = "should fail"
24
24
  # p2.save # Raises a ActiveRecord::StaleObjectError
25
25
  #
26
+ # Optimistic locking will also check for stale data when objects are destroyed. Example:
27
+ #
28
+ # p1 = Person.find(1)
29
+ # p2 = Person.find(1)
30
+ #
31
+ # p1.first_name = "Michael"
32
+ # p1.save
33
+ #
34
+ # p2.destroy # Raises a ActiveRecord::StaleObjectError
35
+ #
26
36
  # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
27
37
  # or otherwise apply the business logic needed to resolve the conflict.
28
38
  #
@@ -38,9 +48,6 @@ module ActiveRecord
38
48
  cattr_accessor :lock_optimistically, :instance_writer => false
39
49
  self.lock_optimistically = true
40
50
 
41
- alias_method_chain :update, :lock
42
- alias_method_chain :attributes_from_column_definition, :lock
43
-
44
51
  class << self
45
52
  alias_method :locking_column=, :set_locking_column
46
53
  end
@@ -51,8 +58,8 @@ module ActiveRecord
51
58
  end
52
59
 
53
60
  private
54
- def attributes_from_column_definition_with_lock
55
- result = attributes_from_column_definition_without_lock
61
+ def attributes_from_column_definition
62
+ result = super
56
63
 
57
64
  # If the locking column has no default value set,
58
65
  # start the lock version at zero. Note we can't use
@@ -66,8 +73,8 @@ module ActiveRecord
66
73
  return result
67
74
  end
68
75
 
69
- def update_with_lock(attribute_names = @attributes.keys) #:nodoc:
70
- return update_without_lock(attribute_names) unless locking_enabled?
76
+ def update(attribute_names = @attributes.keys) #:nodoc:
77
+ return super unless locking_enabled?
71
78
  return 0 if attribute_names.empty?
72
79
 
73
80
  lock_col = self.class.locking_column
@@ -86,9 +93,8 @@ module ActiveRecord
86
93
  )
87
94
  ).arel.update(arel_attributes_values(false, false, attribute_names))
88
95
 
89
-
90
96
  unless affected_rows == 1
91
- raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
97
+ raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
92
98
  end
93
99
 
94
100
  affected_rows
@@ -100,15 +106,30 @@ module ActiveRecord
100
106
  end
101
107
  end
102
108
 
103
- module ClassMethods
104
- DEFAULT_LOCKING_COLUMN = 'lock_version'
109
+ def destroy #:nodoc:
110
+ return super unless locking_enabled?
111
+
112
+ unless new_record?
113
+ lock_col = self.class.locking_column
114
+ previous_value = send(lock_col).to_i
115
+
116
+ table = self.class.arel_table
117
+ predicate = table[self.class.primary_key].eq(id)
118
+ predicate = predicate.and(table[self.class.locking_column].eq(previous_value))
105
119
 
106
- def self.extended(base)
107
- class <<base
108
- alias_method_chain :update_counters, :lock
120
+ affected_rows = self.class.unscoped.where(predicate).delete_all
121
+
122
+ unless affected_rows == 1
123
+ raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}"
124
+ end
109
125
  end
126
+
127
+ freeze
110
128
  end
111
129
 
130
+ module ClassMethods
131
+ DEFAULT_LOCKING_COLUMN = 'lock_version'
132
+
112
133
  # Is optimistic locking enabled for this table? Returns true if the
113
134
  # +lock_optimistically+ flag is set to true (which it is, by default)
114
135
  # and the table includes the +locking_column+ column (defaults to
@@ -140,9 +161,9 @@ module ActiveRecord
140
161
 
141
162
  # Make sure the lock version column gets updated when counters are
142
163
  # updated.
143
- def update_counters_with_lock(id, counters)
164
+ def update_counters(id, counters)
144
165
  counters = counters.merge(locking_column => 1) if locking_enabled?
145
- update_counters_without_lock(id, counters)
166
+ super
146
167
  end
147
168
  end
148
169
  end
@@ -384,9 +384,13 @@ module ActiveRecord
384
384
  class << self
385
385
  def migrate(migrations_path, target_version = nil)
386
386
  case
387
- when target_version.nil? then up(migrations_path, target_version)
388
- when current_version > target_version then down(migrations_path, target_version)
389
- else up(migrations_path, target_version)
387
+ when target_version.nil?
388
+ up(migrations_path, target_version)
389
+ when current_version == 0 && target_version == 0
390
+ when current_version > target_version
391
+ down(migrations_path, target_version)
392
+ else
393
+ up(migrations_path, target_version)
390
394
  end
391
395
  end
392
396
 
@@ -99,11 +99,7 @@ module ActiveRecord
99
99
  block_given? ? relation.extending(Module.new(&block)) : relation
100
100
  end
101
101
 
102
- singleton_class.instance_eval do
103
- define_method name do |*args|
104
- scopes[name].call(*args)
105
- end
106
- end
102
+ singleton_class.send :define_method, name, &scopes[name]
107
103
  end
108
104
 
109
105
  def named_scope(*args, &block)
@@ -1,6 +1,7 @@
1
1
  require 'active_support/core_ext/hash/except'
2
2
  require 'active_support/core_ext/object/try'
3
3
  require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/hash/indifferent_access'
4
5
 
5
6
  module ActiveRecord
6
7
  module NestedAttributes #:nodoc:
@@ -348,14 +349,24 @@ module ActiveRecord
348
349
  attributes_collection = attributes_collection.sort_by { |index, _| index.to_i }.map { |_, attributes| attributes }
349
350
  end
350
351
 
352
+ association = send(association_name)
353
+
354
+ existing_records = if association.loaded?
355
+ association.to_a
356
+ else
357
+ attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
358
+ attribute_ids.present? ? association.all(:conditions => {association.primary_key => attribute_ids}) : []
359
+ end
360
+
351
361
  attributes_collection.each do |attributes|
352
362
  attributes = attributes.with_indifferent_access
353
363
 
354
364
  if attributes['id'].blank?
355
365
  unless reject_new_record?(association_name, attributes)
356
- send(association_name).build(attributes.except(*UNASSIGNABLE_KEYS))
366
+ association.build(attributes.except(*UNASSIGNABLE_KEYS))
357
367
  end
358
- elsif existing_record = send(association_name).detect { |record| record.id.to_s == attributes['id'].to_s }
368
+ elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
369
+ association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
359
370
  assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
360
371
  else
361
372
  raise_nested_attributes_record_not_found(association_name, attributes['id'])
@@ -96,7 +96,8 @@ module ActiveRecord
96
96
  end
97
97
 
98
98
  def self.method_added(method)
99
- self.observed_methods += [method] if ActiveRecord::Callbacks::CALLBACKS.include?(method.to_sym)
99
+ method = method.to_sym
100
+ observed_methods << method if ActiveRecord::Callbacks::CALLBACKS.include?(method)
100
101
  end
101
102
 
102
103
  protected
@@ -104,16 +105,27 @@ module ActiveRecord
104
105
  observed_classes.sum([]) { |klass| klass.send(:subclasses) }
105
106
  end
106
107
 
108
+ def observe_callbacks?
109
+ self.class.observed_methods.any?
110
+ end
111
+
107
112
  def add_observer!(klass)
108
113
  super
114
+ define_callbacks klass if observe_callbacks?
115
+ end
116
+
117
+ def define_callbacks(klass)
118
+ existing_methods = klass.instance_methods.map(&:to_sym)
119
+ observer = self
120
+ observer_name = observer.class.name.underscore.gsub('/', '__')
109
121
 
110
- # Check if a notifier callback was already added to the given class. If
111
- # it was not, add it.
112
122
  self.class.observed_methods.each do |method|
113
- callback = :"_notify_observers_for_#{method}"
114
- if (klass.instance_methods & [callback, callback.to_s]).empty?
115
- klass.class_eval "def #{callback}; notify_observers(:#{method}); end"
116
- klass.send(method, callback)
123
+ callback = :"_notify_#{observer_name}_for_#{method}"
124
+ unless existing_methods.include? callback
125
+ klass.send(:define_method, callback) do # def _notify_user_observer_for_before_save
126
+ observer.update(method, self) # observer.update(:before_save, self)
127
+ end # end
128
+ klass.send(method, callback) # before_save :_notify_user_observer_for_before_save
117
129
  end
118
130
  end
119
131
  end
@@ -0,0 +1,230 @@
1
+ module ActiveRecord
2
+ module Persistence
3
+ # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
4
+ def new_record?
5
+ @new_record
6
+ end
7
+
8
+ # Returns true if this object has been destroyed, otherwise returns false.
9
+ def destroyed?
10
+ @destroyed
11
+ end
12
+
13
+ # Returns if the record is persisted, i.e. it's not a new record and it was not destroyed.
14
+ def persisted?
15
+ !(new_record? || destroyed?)
16
+ end
17
+
18
+ # :call-seq:
19
+ # save(options)
20
+ #
21
+ # Saves the model.
22
+ #
23
+ # If the model is new a record gets created in the database, otherwise
24
+ # the existing record gets updated.
25
+ #
26
+ # By default, save always run validations. If any of them fail the action
27
+ # is cancelled and +save+ returns +false+. However, if you supply
28
+ # :validate => false, validations are bypassed altogether. See
29
+ # ActiveRecord::Validations for more information.
30
+ #
31
+ # There's a series of callbacks associated with +save+. If any of the
32
+ # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
33
+ # +save+ returns +false+. See ActiveRecord::Callbacks for further
34
+ # details.
35
+ def save(*)
36
+ create_or_update
37
+ end
38
+
39
+ # Saves the model.
40
+ #
41
+ # If the model is new a record gets created in the database, otherwise
42
+ # the existing record gets updated.
43
+ #
44
+ # With <tt>save!</tt> validations always run. If any of them fail
45
+ # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
46
+ # for more information.
47
+ #
48
+ # There's a series of callbacks associated with <tt>save!</tt>. If any of
49
+ # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
50
+ # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
51
+ # ActiveRecord::Callbacks for further details.
52
+ def save!(*)
53
+ create_or_update || raise(RecordNotSaved)
54
+ end
55
+
56
+ # Deletes the record in the database and freezes this instance to
57
+ # reflect that no changes should be made (since they can't be
58
+ # persisted). Returns the frozen instance.
59
+ #
60
+ # The row is simply removed with a SQL +DELETE+ statement on the
61
+ # record's primary key, and no callbacks are executed.
62
+ #
63
+ # To enforce the object's +before_destroy+ and +after_destroy+
64
+ # callbacks, Observer methods, or any <tt>:dependent</tt> association
65
+ # options, use <tt>#destroy</tt>.
66
+ def delete
67
+ self.class.delete(id) if persisted?
68
+ @destroyed = true
69
+ freeze
70
+ end
71
+
72
+ # Deletes the record in the database and freezes this instance to reflect that no changes should
73
+ # be made (since they can't be persisted).
74
+ def destroy
75
+ if persisted?
76
+ self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
77
+ end
78
+
79
+ @destroyed = true
80
+ freeze
81
+ end
82
+
83
+ # Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to
84
+ # single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record
85
+ # identification in Action Pack to allow, say, <tt>Client < Company</tt> to do something like render <tt>:partial => @client.becomes(Company)</tt>
86
+ # to render that instance using the companies/company partial instead of clients/client.
87
+ #
88
+ # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
89
+ # instance will affect the other.
90
+ def becomes(klass)
91
+ became = klass.new
92
+ became.instance_variable_set("@attributes", @attributes)
93
+ became.instance_variable_set("@attributes_cache", @attributes_cache)
94
+ became.instance_variable_set("@new_record", new_record?)
95
+ became.instance_variable_set("@destroyed", destroyed?)
96
+ became
97
+ end
98
+
99
+ # Updates a single attribute and saves the record without going through the normal validation procedure.
100
+ # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
101
+ # in Base is replaced with this when the validations module is mixed in, which it is by default.
102
+ def update_attribute(name, value)
103
+ send("#{name}=", value)
104
+ save(:validate => false)
105
+ end
106
+
107
+ # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
108
+ # fail and false will be returned.
109
+ def update_attributes(attributes)
110
+ self.attributes = attributes
111
+ save
112
+ end
113
+
114
+ # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
115
+ def update_attributes!(attributes)
116
+ self.attributes = attributes
117
+ save!
118
+ end
119
+
120
+ # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
121
+ # The increment is performed directly on the underlying attribute, no setter is invoked.
122
+ # Only makes sense for number-based attributes. Returns +self+.
123
+ def increment(attribute, by = 1)
124
+ self[attribute] ||= 0
125
+ self[attribute] += by
126
+ self
127
+ end
128
+
129
+ # Wrapper around +increment+ that saves the record. This method differs from
130
+ # its non-bang version in that it passes through the attribute setter.
131
+ # Saving is not subjected to validation checks. Returns +true+ if the
132
+ # record could be saved.
133
+ def increment!(attribute, by = 1)
134
+ increment(attribute, by).update_attribute(attribute, self[attribute])
135
+ end
136
+
137
+ # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
138
+ # The decrement is performed directly on the underlying attribute, no setter is invoked.
139
+ # Only makes sense for number-based attributes. Returns +self+.
140
+ def decrement(attribute, by = 1)
141
+ self[attribute] ||= 0
142
+ self[attribute] -= by
143
+ self
144
+ end
145
+
146
+ # Wrapper around +decrement+ that saves the record. This method differs from
147
+ # its non-bang version in that it passes through the attribute setter.
148
+ # Saving is not subjected to validation checks. Returns +true+ if the
149
+ # record could be saved.
150
+ def decrement!(attribute, by = 1)
151
+ decrement(attribute, by).update_attribute(attribute, self[attribute])
152
+ end
153
+
154
+ # Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
155
+ # if the predicate returns +true+ the attribute will become +false+. This
156
+ # method toggles directly the underlying value without calling any setter.
157
+ # Returns +self+.
158
+ def toggle(attribute)
159
+ self[attribute] = !send("#{attribute}?")
160
+ self
161
+ end
162
+
163
+ # Wrapper around +toggle+ that saves the record. This method differs from
164
+ # its non-bang version in that it passes through the attribute setter.
165
+ # Saving is not subjected to validation checks. Returns +true+ if the
166
+ # record could be saved.
167
+ def toggle!(attribute)
168
+ toggle(attribute).update_attribute(attribute, self[attribute])
169
+ end
170
+
171
+ # Reloads the attributes of this object from the database.
172
+ # The optional options argument is passed to find when reloading so you
173
+ # may do e.g. record.reload(:lock => true) to reload the same record with
174
+ # an exclusive row lock.
175
+ def reload(options = nil)
176
+ clear_aggregation_cache
177
+ clear_association_cache
178
+ @attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
179
+ @attributes_cache = {}
180
+ self
181
+ end
182
+
183
+ private
184
+ def create_or_update
185
+ raise ReadOnlyRecord if readonly?
186
+ result = new_record? ? create : update
187
+ result != false
188
+ end
189
+
190
+ # Updates the associated record with values matching those of the instance attributes.
191
+ # Returns the number of affected rows.
192
+ def update(attribute_names = @attributes.keys)
193
+ attributes_with_values = arel_attributes_values(false, false, attribute_names)
194
+ return 0 if attributes_with_values.empty?
195
+ self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
196
+ end
197
+
198
+ # Creates a record with values matching those of the instance attributes
199
+ # and returns its id.
200
+ def create
201
+ if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
202
+ self.id = connection.next_sequence_value(self.class.sequence_name)
203
+ end
204
+
205
+ attributes_values = arel_attributes_values
206
+
207
+ new_id = if attributes_values.empty?
208
+ self.class.unscoped.insert connection.empty_insert_statement_value
209
+ else
210
+ self.class.unscoped.insert attributes_values
211
+ end
212
+
213
+ self.id ||= new_id
214
+
215
+ @new_record = false
216
+ id
217
+ end
218
+
219
+ # Initializes the attributes array with keys matching the columns from the linked table and
220
+ # the values matching the corresponding default value of that column, so
221
+ # that a new instance, or one populated from a passed-in Hash, still has all the attributes
222
+ # that instances loaded from the database would.
223
+ def attributes_from_column_definition
224
+ self.class.columns.inject({}) do |attributes, column|
225
+ attributes[column.name] = column.default unless column.name == self.class.primary_key
226
+ attributes
227
+ end
228
+ end
229
+ end
230
+ end