activerecord 3.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 (93) hide show
  1. data/CHANGELOG +6023 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +162 -0
  5. data/examples/simple.rb +14 -0
  6. data/lib/active_record.rb +124 -0
  7. data/lib/active_record/aggregations.rb +277 -0
  8. data/lib/active_record/association_preload.rb +403 -0
  9. data/lib/active_record/associations.rb +2254 -0
  10. data/lib/active_record/associations/association_collection.rb +562 -0
  11. data/lib/active_record/associations/association_proxy.rb +295 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +137 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  17. data/lib/active_record/associations/has_one_association.rb +143 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  19. data/lib/active_record/associations/through_association_scope.rb +154 -0
  20. data/lib/active_record/attribute_methods.rb +60 -0
  21. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +116 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  27. data/lib/active_record/attribute_methods/write.rb +37 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1867 -0
  30. data/lib/active_record/callbacks.rb +288 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  33. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +212 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +643 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1030 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  43. data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
  44. data/lib/active_record/counter_cache.rb +115 -0
  45. data/lib/active_record/dynamic_finder_match.rb +53 -0
  46. data/lib/active_record/dynamic_scope_match.rb +32 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1008 -0
  49. data/lib/active_record/locale/en.yml +40 -0
  50. data/lib/active_record/locking/optimistic.rb +172 -0
  51. data/lib/active_record/locking/pessimistic.rb +55 -0
  52. data/lib/active_record/log_subscriber.rb +48 -0
  53. data/lib/active_record/migration.rb +617 -0
  54. data/lib/active_record/named_scope.rb +138 -0
  55. data/lib/active_record/nested_attributes.rb +417 -0
  56. data/lib/active_record/observer.rb +140 -0
  57. data/lib/active_record/persistence.rb +291 -0
  58. data/lib/active_record/query_cache.rb +36 -0
  59. data/lib/active_record/railtie.rb +91 -0
  60. data/lib/active_record/railties/controller_runtime.rb +38 -0
  61. data/lib/active_record/railties/databases.rake +512 -0
  62. data/lib/active_record/reflection.rb +403 -0
  63. data/lib/active_record/relation.rb +393 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +286 -0
  66. data/lib/active_record/relation/finder_methods.rb +355 -0
  67. data/lib/active_record/relation/predicate_builder.rb +41 -0
  68. data/lib/active_record/relation/query_methods.rb +261 -0
  69. data/lib/active_record/relation/spawn_methods.rb +112 -0
  70. data/lib/active_record/schema.rb +59 -0
  71. data/lib/active_record/schema_dumper.rb +195 -0
  72. data/lib/active_record/serialization.rb +60 -0
  73. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  74. data/lib/active_record/session_store.rb +340 -0
  75. data/lib/active_record/test_case.rb +67 -0
  76. data/lib/active_record/timestamp.rb +88 -0
  77. data/lib/active_record/transactions.rb +356 -0
  78. data/lib/active_record/validations.rb +84 -0
  79. data/lib/active_record/validations/associated.rb +48 -0
  80. data/lib/active_record/validations/uniqueness.rb +185 -0
  81. data/lib/active_record/version.rb +9 -0
  82. data/lib/rails/generators/active_record.rb +27 -0
  83. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  84. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  85. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  86. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  87. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  88. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  89. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  90. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  91. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  92. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  93. metadata +224 -0
@@ -0,0 +1,116 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Read
4
+ extend ActiveSupport::Concern
5
+
6
+ ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
7
+
8
+ included do
9
+ attribute_method_suffix ""
10
+
11
+ cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
12
+ self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
13
+
14
+ # Undefine id so it can be used as an attribute name
15
+ undef_method(:id) if method_defined?(:id)
16
+ end
17
+
18
+ module ClassMethods
19
+ # +cache_attributes+ allows you to declare which converted attribute values should
20
+ # be cached. Usually caching only pays off for attributes with expensive conversion
21
+ # methods, like time related columns (e.g. +created_at+, +updated_at+).
22
+ def cache_attributes(*attribute_names)
23
+ attribute_names.each {|attr| cached_attributes << attr.to_s}
24
+ end
25
+
26
+ # Returns the attributes which are cached. By default time related columns
27
+ # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
28
+ def cached_attributes
29
+ @cached_attributes ||=
30
+ columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map{|col| col.name}.to_set
31
+ end
32
+
33
+ # Returns +true+ if the provided attribute is being cached.
34
+ def cache_attribute?(attr_name)
35
+ cached_attributes.include?(attr_name)
36
+ end
37
+
38
+ protected
39
+ def define_method_attribute(attr_name)
40
+ if self.serialized_attributes[attr_name]
41
+ define_read_method_for_serialized_attribute(attr_name)
42
+ else
43
+ define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name])
44
+ end
45
+
46
+ if attr_name == primary_key && attr_name != "id"
47
+ define_read_method(:id, attr_name, columns_hash[attr_name])
48
+ end
49
+ end
50
+
51
+ private
52
+ # Define read method for serialized attribute.
53
+ def define_read_method_for_serialized_attribute(attr_name)
54
+ generated_attribute_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__)
55
+ end
56
+
57
+ # Define an attribute reader method. Cope with nil column.
58
+ def define_read_method(symbol, attr_name, column)
59
+ cast_code = column.type_cast_code('v') if column
60
+ access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
61
+
62
+ unless attr_name.to_s == self.primary_key.to_s
63
+ access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
64
+ end
65
+
66
+ if cache_attribute?(attr_name)
67
+ access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
68
+ end
69
+ generated_attribute_methods.module_eval("def #{symbol}; #{access_code}; end", __FILE__, __LINE__)
70
+ end
71
+ end
72
+
73
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
74
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
75
+ def read_attribute(attr_name)
76
+ attr_name = attr_name.to_s
77
+ attr_name = self.class.primary_key if attr_name == 'id'
78
+ if !(value = @attributes[attr_name]).nil?
79
+ if column = column_for_attribute(attr_name)
80
+ if unserializable_attribute?(attr_name, column)
81
+ unserialize_attribute(attr_name)
82
+ else
83
+ column.type_cast(value)
84
+ end
85
+ else
86
+ value
87
+ end
88
+ else
89
+ nil
90
+ end
91
+ end
92
+
93
+ # Returns true if the attribute is of a text column and marked for serialization.
94
+ def unserializable_attribute?(attr_name, column)
95
+ column.text? && self.class.serialized_attributes[attr_name]
96
+ end
97
+
98
+ # Returns the unserialized object of the attribute.
99
+ def unserialize_attribute(attr_name)
100
+ unserialized_object = object_from_yaml(@attributes[attr_name])
101
+
102
+ if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
103
+ @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
104
+ else
105
+ raise SerializationTypeMismatch,
106
+ "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
107
+ end
108
+ end
109
+
110
+ private
111
+ def attribute(attribute_name)
112
+ read_attribute(attribute_name)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,61 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module TimeZoneConversion
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ cattr_accessor :time_zone_aware_attributes, :instance_writer => false
8
+ self.time_zone_aware_attributes = false
9
+
10
+ class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
11
+ self.skip_time_zone_conversion_for_attributes = []
12
+ end
13
+
14
+ module ClassMethods
15
+ protected
16
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
17
+ # This enhanced read method automatically converts the UTC time stored in the database to the time
18
+ # zone stored in Time.zone.
19
+ def define_method_attribute(attr_name)
20
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
21
+ method_body, line = <<-EOV, __LINE__ + 1
22
+ def #{attr_name}(reload = false)
23
+ cached = @attributes_cache['#{attr_name}']
24
+ return cached if cached && !reload
25
+ time = read_attribute('#{attr_name}')
26
+ @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
27
+ end
28
+ EOV
29
+ generated_attribute_methods.module_eval(method_body, __FILE__, line)
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
36
+ # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
37
+ def define_method_attribute=(attr_name)
38
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
39
+ method_body, line = <<-EOV, __LINE__ + 1
40
+ def #{attr_name}=(time)
41
+ unless time.acts_like?(:time)
42
+ time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
43
+ end
44
+ time = time.in_time_zone rescue nil if time
45
+ write_attribute(:#{attr_name}, time)
46
+ end
47
+ EOV
48
+ generated_attribute_methods.module_eval(method_body, __FILE__, line)
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ private
55
+ def create_time_zone_conversion_attribute?(name, column)
56
+ time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,37 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Write
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attribute_method_suffix "="
8
+ end
9
+
10
+ module ClassMethods
11
+ protected
12
+ def define_method_attribute=(attr_name)
13
+ generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
14
+ end
15
+ end
16
+
17
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings
18
+ # for fixnum and float columns are turned into +nil+.
19
+ def write_attribute(attr_name, value)
20
+ attr_name = attr_name.to_s
21
+ attr_name = self.class.primary_key if attr_name == 'id'
22
+ @attributes_cache.delete(attr_name)
23
+ if (column = column_for_attribute(attr_name)) && column.number?
24
+ @attributes[attr_name] = convert_number_column_value(value)
25
+ else
26
+ @attributes[attr_name] = value
27
+ end
28
+ end
29
+
30
+ private
31
+ # Handle *= for method_missing.
32
+ def attribute=(attribute_name, value)
33
+ write_attribute(attribute_name, value)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,369 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+
3
+ module ActiveRecord
4
+ # = Active Record Autosave Association
5
+ #
6
+ # +AutosaveAssociation+ is a module that takes care of automatically saving
7
+ # associacted records when their parent is saved. In addition to saving, it
8
+ # also destroys any associated records that were marked for destruction.
9
+ # (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
10
+ #
11
+ # Saving of the parent, its associations, and the destruction of marked
12
+ # associations, all happen inside a transaction. This should never leave the
13
+ # database in an inconsistent state.
14
+ #
15
+ # If validations for any of the associations fail, their error messages will
16
+ # be applied to the parent.
17
+ #
18
+ # Note that it also means that associations marked for destruction won't
19
+ # be destroyed directly. They will however still be marked for destruction.
20
+ #
21
+ # Note that <tt>:autosave => false</tt> is not same as not declaring <tt>:autosave</tt>.
22
+ # When the <tt>:autosave</tt> option is not present new associations are saved.
23
+ #
24
+ # === One-to-one Example
25
+ #
26
+ # class Post
27
+ # has_one :author, :autosave => true
28
+ # end
29
+ #
30
+ # Saving changes to the parent and its associated model can now be performed
31
+ # automatically _and_ atomically:
32
+ #
33
+ # post = Post.find(1)
34
+ # post.title # => "The current global position of migrating ducks"
35
+ # post.author.name # => "alloy"
36
+ #
37
+ # post.title = "On the migration of ducks"
38
+ # post.author.name = "Eloy Duran"
39
+ #
40
+ # post.save
41
+ # post.reload
42
+ # post.title # => "On the migration of ducks"
43
+ # post.author.name # => "Eloy Duran"
44
+ #
45
+ # Destroying an associated model, as part of the parent's save action, is as
46
+ # simple as marking it for destruction:
47
+ #
48
+ # post.author.mark_for_destruction
49
+ # post.author.marked_for_destruction? # => true
50
+ #
51
+ # Note that the model is _not_ yet removed from the database:
52
+ #
53
+ # id = post.author.id
54
+ # Author.find_by_id(id).nil? # => false
55
+ #
56
+ # post.save
57
+ # post.reload.author # => nil
58
+ #
59
+ # Now it _is_ removed from the database:
60
+ #
61
+ # Author.find_by_id(id).nil? # => true
62
+ #
63
+ # === One-to-many Example
64
+ #
65
+ # When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
66
+ #
67
+ # class Post
68
+ # has_many :comments # :autosave option is no declared
69
+ # end
70
+ #
71
+ # post = Post.new(:title => 'ruby rocks')
72
+ # post.comments.build(:body => 'hello world')
73
+ # post.save # => saves both post and comment
74
+ #
75
+ # post = Post.create(:title => 'ruby rocks')
76
+ # post.comments.build(:body => 'hello world')
77
+ # post.save # => saves both post and comment
78
+ #
79
+ # post = Post.create(:title => 'ruby rocks')
80
+ # post.comments.create(:body => 'hello world')
81
+ # post.save # => saves both post and comment
82
+ #
83
+ # When <tt>:autosave</tt> is true all children is saved, no matter whether they are new records:
84
+ #
85
+ # class Post
86
+ # has_many :comments, :autosave => true
87
+ # end
88
+ #
89
+ # post = Post.create(:title => 'ruby rocks')
90
+ # post.comments.create(:body => 'hello world')
91
+ # post.comments[0].body = 'hi everyone'
92
+ # post.save # => saves both post and comment, with 'hi everyone' as title
93
+ #
94
+ # Destroying one of the associated models as part of the parent's save action
95
+ # is as simple as marking it for destruction:
96
+ #
97
+ # post.comments.last.mark_for_destruction
98
+ # post.comments.last.marked_for_destruction? # => true
99
+ # post.comments.length # => 2
100
+ #
101
+ # Note that the model is _not_ yet removed from the database:
102
+ #
103
+ # id = post.comments.last.id
104
+ # Comment.find_by_id(id).nil? # => false
105
+ #
106
+ # post.save
107
+ # post.reload.comments.length # => 1
108
+ #
109
+ # Now it _is_ removed from the database:
110
+ #
111
+ # Comment.find_by_id(id).nil? # => true
112
+ #
113
+ # === Validation
114
+ #
115
+ # Children records are validated unless <tt>:validate</tt> is +false+.
116
+ module AutosaveAssociation
117
+ extend ActiveSupport::Concern
118
+
119
+ ASSOCIATION_TYPES = %w{ has_one belongs_to has_many has_and_belongs_to_many }
120
+
121
+ included do
122
+ ASSOCIATION_TYPES.each do |type|
123
+ send("valid_keys_for_#{type}_association") << :autosave
124
+ end
125
+ end
126
+
127
+ module ClassMethods
128
+ private
129
+
130
+ # def belongs_to(name, options = {})
131
+ # super
132
+ # add_autosave_association_callbacks(reflect_on_association(name))
133
+ # end
134
+ ASSOCIATION_TYPES.each do |type|
135
+ module_eval <<-CODE, __FILE__, __LINE__ + 1
136
+ def #{type}(name, options = {})
137
+ super
138
+ add_autosave_association_callbacks(reflect_on_association(name))
139
+ end
140
+ CODE
141
+ end
142
+
143
+ # Adds validation and save callbacks for the association as specified by
144
+ # the +reflection+.
145
+ #
146
+ # For performance reasons, we don't check whether to validate at runtime.
147
+ # However the validation and callback methods are lazy and those methods
148
+ # get created when they are invoked for the very first time. However,
149
+ # this can change, for instance, when using nested attributes, which is
150
+ # called _after_ the association has been defined. Since we don't want
151
+ # the callbacks to get defined multiple times, there are guards that
152
+ # check if the save or validation methods have already been defined
153
+ # before actually defining them.
154
+ def add_autosave_association_callbacks(reflection)
155
+ save_method = :"autosave_associated_records_for_#{reflection.name}"
156
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
157
+ collection = reflection.collection?
158
+
159
+ unless method_defined?(save_method)
160
+ if collection
161
+ before_save :before_save_collection_association
162
+
163
+ define_method(save_method) { save_collection_association(reflection) }
164
+ # Doesn't use after_save as that would save associations added in after_create/after_update twice
165
+ after_create save_method
166
+ after_update save_method
167
+ else
168
+ if reflection.macro == :has_one
169
+ define_method(save_method) { save_has_one_association(reflection) }
170
+ after_save save_method
171
+ else
172
+ define_method(save_method) { save_belongs_to_association(reflection) }
173
+ before_save save_method
174
+ end
175
+ end
176
+ end
177
+
178
+ if reflection.validate? && !method_defined?(validation_method)
179
+ method = (collection ? :validate_collection_association : :validate_single_association)
180
+ define_method(validation_method) { send(method, reflection) }
181
+ validate validation_method
182
+ end
183
+ end
184
+ end
185
+
186
+ # Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
187
+ def reload(options = nil)
188
+ @marked_for_destruction = false
189
+ super
190
+ end
191
+
192
+ # Marks this record to be destroyed as part of the parents save transaction.
193
+ # This does _not_ actually destroy the record instantly, rather child record will be destroyed
194
+ # when <tt>parent.save</tt> is called.
195
+ #
196
+ # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
197
+ def mark_for_destruction
198
+ @marked_for_destruction = true
199
+ end
200
+
201
+ # Returns whether or not this record will be destroyed as part of the parents save transaction.
202
+ #
203
+ # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
204
+ def marked_for_destruction?
205
+ @marked_for_destruction
206
+ end
207
+
208
+ # Returns whether or not this record has been changed in any way (including whether
209
+ # any of its nested autosave associations are likewise changed)
210
+ def changed_for_autosave?
211
+ new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
212
+ end
213
+
214
+ private
215
+
216
+ # Returns the record for an association collection that should be validated
217
+ # or saved. If +autosave+ is +false+ only new records will be returned,
218
+ # unless the parent is/was a new record itself.
219
+ def associated_records_to_validate_or_save(association, new_record, autosave)
220
+ if new_record
221
+ association
222
+ elsif autosave
223
+ association.target.find_all { |record| record.changed_for_autosave? }
224
+ else
225
+ association.target.find_all { |record| record.new_record? }
226
+ end
227
+ end
228
+
229
+ # go through nested autosave associations that are loaded in memory (without loading
230
+ # any new ones), and return true if is changed for autosave
231
+ def nested_records_changed_for_autosave?
232
+ self.class.reflect_on_all_autosave_associations.any? do |reflection|
233
+ association = association_instance_get(reflection.name)
234
+ association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
235
+ end
236
+ end
237
+
238
+ # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
239
+ # turned on for the association.
240
+ def validate_single_association(reflection)
241
+ if (association = association_instance_get(reflection.name)) && !association.target.nil?
242
+ association_valid?(reflection, association)
243
+ end
244
+ end
245
+
246
+ # Validate the associated records if <tt>:validate</tt> or
247
+ # <tt>:autosave</tt> is turned on for the association specified by
248
+ # +reflection+.
249
+ def validate_collection_association(reflection)
250
+ if association = association_instance_get(reflection.name)
251
+ if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
252
+ records.each { |record| association_valid?(reflection, record) }
253
+ end
254
+ end
255
+ end
256
+
257
+ # Returns whether or not the association is valid and applies any errors to
258
+ # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
259
+ # enabled records if they're marked_for_destruction? or destroyed.
260
+ def association_valid?(reflection, association)
261
+ return true if association.destroyed? || association.marked_for_destruction?
262
+
263
+ unless valid = association.valid?
264
+ if reflection.options[:autosave]
265
+ association.errors.each do |attribute, message|
266
+ attribute = "#{reflection.name}.#{attribute}"
267
+ errors[attribute] << message
268
+ errors[attribute].uniq!
269
+ end
270
+ else
271
+ errors.add(reflection.name)
272
+ end
273
+ end
274
+ valid
275
+ end
276
+
277
+ # Is used as a before_save callback to check while saving a collection
278
+ # association whether or not the parent was a new record before saving.
279
+ def before_save_collection_association
280
+ @new_record_before_save = new_record?
281
+ true
282
+ end
283
+
284
+ # Saves any new associated records, or all loaded autosave associations if
285
+ # <tt>:autosave</tt> is enabled on the association.
286
+ #
287
+ # In addition, it destroys all children that were marked for destruction
288
+ # with mark_for_destruction.
289
+ #
290
+ # This all happens inside a transaction, _if_ the Transactions module is included into
291
+ # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
292
+ def save_collection_association(reflection)
293
+ if association = association_instance_get(reflection.name)
294
+ autosave = reflection.options[:autosave]
295
+
296
+ if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
297
+ records.each do |record|
298
+ next if record.destroyed?
299
+
300
+ if autosave && record.marked_for_destruction?
301
+ association.destroy(record)
302
+ elsif autosave != false && (@new_record_before_save || record.new_record?)
303
+ if autosave
304
+ saved = association.send(:insert_record, record, false, false)
305
+ else
306
+ association.send(:insert_record, record)
307
+ end
308
+ elsif autosave
309
+ saved = record.save(:validate => false)
310
+ end
311
+
312
+ raise ActiveRecord::Rollback if saved == false
313
+ end
314
+ end
315
+
316
+ # reconstruct the SQL queries now that we know the owner's id
317
+ association.send(:construct_sql) if association.respond_to?(:construct_sql)
318
+ end
319
+ end
320
+
321
+ # Saves the associated record if it's new or <tt>:autosave</tt> is enabled
322
+ # on the association.
323
+ #
324
+ # In addition, it will destroy the association if it was marked for
325
+ # destruction with mark_for_destruction.
326
+ #
327
+ # This all happens inside a transaction, _if_ the Transactions module is included into
328
+ # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
329
+ def save_has_one_association(reflection)
330
+ if (association = association_instance_get(reflection.name)) && !association.target.nil? && !association.destroyed?
331
+ autosave = reflection.options[:autosave]
332
+
333
+ if autosave && association.marked_for_destruction?
334
+ association.destroy
335
+ else
336
+ key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
337
+ if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave)
338
+ association[reflection.primary_key_name] = key
339
+ saved = association.save(:validate => !autosave)
340
+ raise ActiveRecord::Rollback if !saved && autosave
341
+ saved
342
+ end
343
+ end
344
+ end
345
+ end
346
+
347
+ # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
348
+ #
349
+ # In addition, it will destroy the association if it was marked for destruction.
350
+ def save_belongs_to_association(reflection)
351
+ if (association = association_instance_get(reflection.name)) && !association.destroyed?
352
+ autosave = reflection.options[:autosave]
353
+
354
+ if autosave && association.marked_for_destruction?
355
+ association.destroy
356
+ elsif autosave != false
357
+ saved = association.save(:validate => !autosave) if association.new_record? || autosave
358
+
359
+ if association.updated?
360
+ association_id = association.send(reflection.options[:primary_key] || :id)
361
+ self[reflection.primary_key_name] = association_id
362
+ end
363
+
364
+ saved if autosave
365
+ end
366
+ end
367
+ end
368
+ end
369
+ end