square-activerecord 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/CHANGELOG +6140 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +179 -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 +430 -0
  9. data/lib/active_record/associations.rb +2307 -0
  10. data/lib/active_record/associations/association_collection.rb +572 -0
  11. data/lib/active_record/associations/association_proxy.rb +299 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +82 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +115 -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 +30 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +56 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +145 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +64 -0
  27. data/lib/active_record/attribute_methods/write.rb +43 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1904 -0
  30. data/lib/active_record/callbacks.rb +284 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +364 -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 +333 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +73 -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 +539 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +217 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +657 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1031 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -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 +56 -0
  46. data/lib/active_record/dynamic_scope_match.rb +23 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1006 -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 +419 -0
  56. data/lib/active_record/observer.rb +125 -0
  57. data/lib/active_record/persistence.rb +290 -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 +411 -0
  63. data/lib/active_record/relation.rb +394 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +295 -0
  66. data/lib/active_record/relation/finder_methods.rb +363 -0
  67. data/lib/active_record/relation/predicate_builder.rb +48 -0
  68. data/lib/active_record/relation/query_methods.rb +303 -0
  69. data/lib/active_record/relation/spawn_methods.rb +132 -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 +359 -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 +190 -0
  81. data/lib/active_record/version.rb +10 -0
  82. data/lib/rails/generators/active_record.rb +19 -0
  83. data/lib/rails/generators/active_record/migration.rb +15 -0
  84. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  85. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  86. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  87. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  88. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  89. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  90. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  91. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  92. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  93. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  94. metadata +223 -0
@@ -0,0 +1,145 @@
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, 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
+ # method_name is the same as attr_name except when a non-standard primary key is used,
59
+ # we still define #id as an accessor for the key
60
+ def define_read_method(method_name, attr_name, column)
61
+ cast_code = column.type_cast_code('v') if column
62
+ access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
63
+
64
+ unless attr_name.to_s == self.primary_key.to_s
65
+ access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
66
+ end
67
+
68
+ if cache_attribute?(attr_name)
69
+ access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
70
+ end
71
+
72
+ # Where possible, generate the method by evalling a string, as this will result in
73
+ # faster accesses because it avoids the block eval and then string eval incurred
74
+ # by the second branch.
75
+ #
76
+ # The second, slower, branch is necessary to support instances where the database
77
+ # returns columns with extra stuff in (like 'my_column(omg)').
78
+ if method_name =~ /^[a-zA-Z_]\w*[!?=]?$/
79
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__
80
+ def _#{method_name}
81
+ #{access_code}
82
+ end
83
+
84
+ alias #{method_name} _#{method_name}
85
+ STR
86
+ else
87
+ generated_attribute_methods.module_eval do
88
+ define_method("_#{method_name}") { eval(access_code) }
89
+ alias_method(method_name, "_#{method_name}")
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
96
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
97
+ def read_attribute(attr_name)
98
+ if respond_to? "_#{attr_name}"
99
+ send "_#{attr_name}" if @attributes.has_key?(attr_name.to_s)
100
+ else
101
+ _read_attribute attr_name
102
+ end
103
+ end
104
+
105
+ def _read_attribute(attr_name)
106
+ attr_name = attr_name.to_s
107
+ attr_name = self.class.primary_key if attr_name == 'id'
108
+ value = @attributes[attr_name]
109
+ unless value.nil?
110
+ if column = column_for_attribute(attr_name)
111
+ if unserializable_attribute?(attr_name, column)
112
+ unserialize_attribute(attr_name)
113
+ else
114
+ column.type_cast(value)
115
+ end
116
+ else
117
+ value
118
+ end
119
+ end
120
+ end
121
+
122
+ # Returns true if the attribute is of a text column and marked for serialization.
123
+ def unserializable_attribute?(attr_name, column)
124
+ column.text? && self.class.serialized_attributes[attr_name]
125
+ end
126
+
127
+ # Returns the unserialized object of the attribute.
128
+ def unserialize_attribute(attr_name)
129
+ unserialized_object = object_from_yaml(@attributes[attr_name])
130
+
131
+ if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
132
+ @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
133
+ else
134
+ raise SerializationTypeMismatch,
135
+ "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
136
+ end
137
+ end
138
+
139
+ private
140
+ def attribute(attribute_name)
141
+ read_attribute(attribute_name)
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,64 @@
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
+ alias #{attr_name} _#{attr_name}
29
+ EOV
30
+ generated_attribute_methods.module_eval(method_body, __FILE__, line)
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
37
+ # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
38
+ def define_method_attribute=(attr_name)
39
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
40
+ method_body, line = <<-EOV, __LINE__ + 1
41
+ def #{attr_name}=(original_time)
42
+ time = original_time
43
+ unless time.acts_like?(:time)
44
+ time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
45
+ end
46
+ time = time.in_time_zone rescue nil if time
47
+ write_attribute(:#{attr_name}, original_time)
48
+ @attributes_cache["#{attr_name}"] = time
49
+ end
50
+ EOV
51
+ generated_attribute_methods.module_eval(method_body, __FILE__, line)
52
+ else
53
+ super
54
+ end
55
+ end
56
+
57
+ private
58
+ def create_time_zone_conversion_attribute?(name, column)
59
+ time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,43 @@
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
+ if attr_name =~ /^[a-zA-Z_]\w*[!?=]?$/
14
+ generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
15
+ else
16
+ generated_attribute_methods.send(:define_method, "#{attr_name}=") do |new_value|
17
+ write_attribute(attr_name, new_value)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings
24
+ # for fixnum and float columns are turned into +nil+.
25
+ def write_attribute(attr_name, value)
26
+ attr_name = attr_name.to_s
27
+ attr_name = self.class.primary_key if attr_name == 'id'
28
+ @attributes_cache.delete(attr_name)
29
+ if (column = column_for_attribute(attr_name)) && column.number?
30
+ @attributes[attr_name] = convert_number_column_value(value)
31
+ else
32
+ @attributes[attr_name] = value
33
+ end
34
+ end
35
+
36
+ private
37
+ # Handle *= for method_missing.
38
+ def attribute=(attribute_name, value)
39
+ write_attribute(attribute_name, value)
40
+ end
41
+ end
42
+ end
43
+ 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 body
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