activerecord 4.0.13 → 4.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +745 -2700
  3. data/README.rdoc +2 -2
  4. data/examples/performance.rb +30 -18
  5. data/examples/simple.rb +4 -4
  6. data/lib/active_record.rb +2 -6
  7. data/lib/active_record/aggregations.rb +2 -1
  8. data/lib/active_record/association_relation.rb +0 -4
  9. data/lib/active_record/associations.rb +87 -43
  10. data/lib/active_record/associations/alias_tracker.rb +1 -3
  11. data/lib/active_record/associations/association.rb +8 -16
  12. data/lib/active_record/associations/association_scope.rb +5 -16
  13. data/lib/active_record/associations/belongs_to_association.rb +34 -25
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  15. data/lib/active_record/associations/builder/association.rb +78 -54
  16. data/lib/active_record/associations/builder/belongs_to.rb +91 -58
  17. data/lib/active_record/associations/builder/collection_association.rb +47 -45
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -25
  19. data/lib/active_record/associations/builder/has_many.rb +2 -2
  20. data/lib/active_record/associations/builder/has_one.rb +5 -7
  21. data/lib/active_record/associations/builder/singular_association.rb +6 -7
  22. data/lib/active_record/associations/collection_association.rb +68 -105
  23. data/lib/active_record/associations/collection_proxy.rb +12 -15
  24. data/lib/active_record/associations/has_many_association.rb +11 -9
  25. data/lib/active_record/associations/has_many_through_association.rb +16 -12
  26. data/lib/active_record/associations/has_one_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +204 -165
  28. data/lib/active_record/associations/join_dependency/join_association.rb +43 -101
  29. data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
  30. data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
  31. data/lib/active_record/associations/join_helper.rb +2 -11
  32. data/lib/active_record/associations/preloader.rb +89 -34
  33. data/lib/active_record/associations/preloader/association.rb +43 -25
  34. data/lib/active_record/associations/preloader/collection_association.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +58 -26
  38. data/lib/active_record/associations/singular_association.rb +6 -5
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +5 -2
  41. data/lib/active_record/attribute_methods.rb +45 -40
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
  43. data/lib/active_record/attribute_methods/dirty.rb +8 -22
  44. data/lib/active_record/attribute_methods/primary_key.rb +1 -7
  45. data/lib/active_record/attribute_methods/read.rb +55 -28
  46. data/lib/active_record/attribute_methods/serialization.rb +12 -33
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -13
  48. data/lib/active_record/attribute_methods/write.rb +37 -12
  49. data/lib/active_record/autosave_association.rb +207 -207
  50. data/lib/active_record/base.rb +5 -1
  51. data/lib/active_record/callbacks.rb +2 -2
  52. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -7
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +11 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -14
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -5
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +84 -0
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +52 -83
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +0 -5
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +14 -97
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +58 -60
  63. data/lib/active_record/connection_adapters/column.rb +1 -35
  64. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +3 -4
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +16 -15
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +24 -18
  68. data/lib/active_record/connection_adapters/postgresql/cast.rb +20 -16
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +23 -43
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +19 -12
  71. data/lib/active_record/connection_adapters/postgresql/quoting.rb +28 -23
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +8 -30
  73. data/lib/active_record/connection_adapters/postgresql_adapter.rb +92 -75
  74. data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +31 -64
  76. data/lib/active_record/connection_handling.rb +2 -2
  77. data/lib/active_record/core.rb +22 -43
  78. data/lib/active_record/counter_cache.rb +7 -7
  79. data/lib/active_record/enum.rb +100 -0
  80. data/lib/active_record/errors.rb +10 -5
  81. data/lib/active_record/fixture_set/file.rb +2 -1
  82. data/lib/active_record/fixtures.rb +171 -74
  83. data/lib/active_record/inheritance.rb +16 -22
  84. data/lib/active_record/integration.rb +52 -1
  85. data/lib/active_record/locking/optimistic.rb +7 -2
  86. data/lib/active_record/locking/pessimistic.rb +1 -1
  87. data/lib/active_record/log_subscriber.rb +5 -12
  88. data/lib/active_record/migration.rb +62 -46
  89. data/lib/active_record/migration/command_recorder.rb +7 -13
  90. data/lib/active_record/model_schema.rb +7 -14
  91. data/lib/active_record/nested_attributes.rb +10 -8
  92. data/lib/active_record/no_touching.rb +52 -0
  93. data/lib/active_record/null_relation.rb +3 -3
  94. data/lib/active_record/persistence.rb +16 -34
  95. data/lib/active_record/querying.rb +14 -12
  96. data/lib/active_record/railtie.rb +0 -50
  97. data/lib/active_record/railties/databases.rake +12 -15
  98. data/lib/active_record/readonly_attributes.rb +0 -6
  99. data/lib/active_record/reflection.rb +189 -75
  100. data/lib/active_record/relation.rb +69 -94
  101. data/lib/active_record/relation/batches.rb +57 -23
  102. data/lib/active_record/relation/calculations.rb +36 -43
  103. data/lib/active_record/relation/delegation.rb +54 -39
  104. data/lib/active_record/relation/finder_methods.rb +107 -62
  105. data/lib/active_record/relation/merger.rb +7 -20
  106. data/lib/active_record/relation/predicate_builder.rb +57 -38
  107. data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
  108. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  109. data/lib/active_record/relation/query_methods.rb +110 -98
  110. data/lib/active_record/relation/spawn_methods.rb +1 -2
  111. data/lib/active_record/result.rb +45 -6
  112. data/lib/active_record/runtime_registry.rb +5 -0
  113. data/lib/active_record/sanitization.rb +6 -8
  114. data/lib/active_record/schema_dumper.rb +16 -5
  115. data/lib/active_record/schema_migration.rb +24 -25
  116. data/lib/active_record/scoping/default.rb +5 -18
  117. data/lib/active_record/scoping/named.rb +8 -29
  118. data/lib/active_record/store.rb +56 -28
  119. data/lib/active_record/tasks/database_tasks.rb +8 -4
  120. data/lib/active_record/timestamp.rb +4 -4
  121. data/lib/active_record/transactions.rb +8 -10
  122. data/lib/active_record/validations/presence.rb +1 -1
  123. data/lib/active_record/validations/uniqueness.rb +1 -6
  124. data/lib/active_record/version.rb +1 -1
  125. data/lib/rails/generators/active_record.rb +2 -8
  126. data/lib/rails/generators/active_record/migration.rb +18 -0
  127. data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
  128. data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
  129. metadata +32 -45
  130. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
  131. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  132. data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
  133. data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
  134. data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
  135. data/lib/active_record/test_case.rb +0 -102
@@ -41,8 +41,9 @@ module ActiveRecord
41
41
  # task.read_attribute_before_type_cast('id') # => '1'
42
42
  # task.read_attribute('completed_on') # => Sun, 21 Oct 2012
43
43
  # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
44
+ # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
44
45
  def read_attribute_before_type_cast(attr_name)
45
- @attributes[attr_name]
46
+ @attributes[attr_name.to_s]
46
47
  end
47
48
 
48
49
  # Returns a hash of attributes before typecasting and deserialization.
@@ -14,24 +14,12 @@ module ActiveRecord
14
14
 
15
15
  class_attribute :partial_writes, instance_writer: false
16
16
  self.partial_writes = true
17
-
18
- def self.partial_updates=(v); self.partial_writes = v; end
19
- def self.partial_updates?; partial_writes?; end
20
- def self.partial_updates; partial_writes; end
21
-
22
- ActiveSupport::Deprecation.deprecate_methods(
23
- singleton_class,
24
- :partial_updates= => :partial_writes=,
25
- :partial_updates? => :partial_writes?,
26
- :partial_updates => :partial_writes
27
- )
28
17
  end
29
18
 
30
19
  # Attempts to +save+ the record and clears changed attributes if successful.
31
20
  def save(*)
32
21
  if status = super
33
- @previously_changed = changes
34
- @changed_attributes.clear
22
+ changes_applied
35
23
  end
36
24
  status
37
25
  end
@@ -39,16 +27,14 @@ module ActiveRecord
39
27
  # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
40
28
  def save!(*)
41
29
  super.tap do
42
- @previously_changed = changes
43
- @changed_attributes.clear
30
+ changes_applied
44
31
  end
45
32
  end
46
33
 
47
34
  # <tt>reload</tt> the record and clears changed attributes.
48
35
  def reload(*)
49
36
  super.tap do
50
- @previously_changed.clear
51
- @changed_attributes.clear
37
+ reset_changes
52
38
  end
53
39
  end
54
40
 
@@ -59,22 +45,22 @@ module ActiveRecord
59
45
 
60
46
  # The attribute already has an unsaved change.
61
47
  if attribute_changed?(attr)
62
- old = @changed_attributes[attr]
63
- @changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
48
+ old = changed_attributes[attr]
49
+ changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
64
50
  else
65
51
  old = clone_attribute_value(:read_attribute, attr)
66
- @changed_attributes[attr] = old if _field_changed?(attr, old, value)
52
+ changed_attributes[attr] = old if _field_changed?(attr, old, value)
67
53
  end
68
54
 
69
55
  # Carry on.
70
56
  super(attr, value)
71
57
  end
72
58
 
73
- def _update_record(*)
59
+ def update_record(*)
74
60
  partial_writes? ? super(keys_for_partial_write) : super
75
61
  end
76
62
 
77
- def _create_record(*)
63
+ def create_record(*)
78
64
  partial_writes? ? super(keys_for_partial_write) : super
79
65
  end
80
66
 
@@ -37,12 +37,6 @@ module ActiveRecord
37
37
  read_attribute_before_type_cast(self.class.primary_key)
38
38
  end
39
39
 
40
- # Returns the primary key previous value.
41
- def id_was
42
- sync_with_transaction_state
43
- attribute_was(self.class.primary_key)
44
- end
45
-
46
40
  protected
47
41
 
48
42
  def attribute_method?(attr_name)
@@ -58,7 +52,7 @@ module ActiveRecord
58
52
  end
59
53
  end
60
54
 
61
- ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set
55
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast).to_set
62
56
 
63
57
  def dangerous_attribute_method?(method_name)
64
58
  super && !ID_ATTRIBUTE_METHODS.include?(method_name)
@@ -1,6 +1,38 @@
1
+ require 'active_support/core_ext/module/method_transplanting'
2
+
1
3
  module ActiveRecord
2
4
  module AttributeMethods
3
5
  module Read
6
+ ReaderMethodCache = Class.new(AttributeMethodCache) {
7
+ private
8
+ # We want to generate the methods via module_eval rather than
9
+ # define_method, because define_method is slower on dispatch.
10
+ # Evaluating many similar methods may use more memory as the instruction
11
+ # sequences are duplicated and cached (in MRI). define_method may
12
+ # be slower on dispatch, but if you're careful about the closure
13
+ # created, then define_method will consume much less memory.
14
+ #
15
+ # But sometimes the database might return columns with
16
+ # characters that are not allowed in normal method names (like
17
+ # 'my_column(omg)'. So to work around this we first define with
18
+ # the __temp__ identifier, and then use alias method to rename
19
+ # it to what we want.
20
+ #
21
+ # We are also defining a constant to hold the frozen string of
22
+ # the attribute name. Using a constant means that we do not have
23
+ # to allocate an object on each call to the attribute method.
24
+ # Making it frozen means that it doesn't get duped when used to
25
+ # key the @attributes_cache in read_attribute.
26
+ def method_body(method_name, const_name)
27
+ <<-EOMETHOD
28
+ def #{method_name}
29
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
30
+ read_attribute(name) { |n| missing_attribute(n, caller) }
31
+ end
32
+ EOMETHOD
33
+ end
34
+ }.new
35
+
4
36
  extend ActiveSupport::Concern
5
37
 
6
38
  ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
@@ -32,35 +64,30 @@ module ActiveRecord
32
64
 
33
65
  protected
34
66
 
35
- # We want to generate the methods via module_eval rather than
36
- # define_method, because define_method is slower on dispatch.
37
- # Evaluating many similar methods may use more memory as the instruction
38
- # sequences are duplicated and cached (in MRI). define_method may
39
- # be slower on dispatch, but if you're careful about the closure
40
- # created, then define_method will consume much less memory.
41
- #
42
- # But sometimes the database might return columns with
43
- # characters that are not allowed in normal method names (like
44
- # 'my_column(omg)'. So to work around this we first define with
45
- # the __temp__ identifier, and then use alias method to rename
46
- # it to what we want.
47
- #
48
- # We are also defining a constant to hold the frozen string of
49
- # the attribute name. Using a constant means that we do not have
50
- # to allocate an object on each call to the attribute method.
51
- # Making it frozen means that it doesn't get duped when used to
52
- # key the @attributes_cache in read_attribute.
53
- def define_method_attribute(name)
54
- safe_name = name.unpack('h*').first
55
- generated_attribute_methods::AttrNames.set_name_cache safe_name, name
56
-
57
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
58
- def __temp__#{safe_name}
59
- read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) }
67
+ if Module.methods_transplantable?
68
+ def define_method_attribute(name)
69
+ method = ReaderMethodCache[name]
70
+ generated_attribute_methods.module_eval { define_method name, method }
71
+ end
72
+ else
73
+ def define_method_attribute(name)
74
+ safe_name = name.unpack('h*').first
75
+ temp_method = "__temp__#{safe_name}"
76
+
77
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
78
+
79
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
80
+ def #{temp_method}
81
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
82
+ read_attribute(name) { |n| missing_attribute(n, caller) }
83
+ end
84
+ STR
85
+
86
+ generated_attribute_methods.module_eval do
87
+ alias_method name, temp_method
88
+ undef_method temp_method
60
89
  end
61
- alias_method #{name.inspect}, :__temp__#{safe_name}
62
- undef_method :__temp__#{safe_name}
63
- STR
90
+ end
64
91
  end
65
92
 
66
93
  private
@@ -24,11 +24,13 @@ module ActiveRecord
24
24
  # serialized object must be of that class on retrieval or
25
25
  # <tt>SerializationTypeMismatch</tt> will be raised.
26
26
  #
27
+ # A notable side effect of serialized attributes is that the model will
28
+ # be updated on every save, even if it is not dirty.
29
+ #
27
30
  # ==== Parameters
28
31
  #
29
32
  # * +attr_name+ - The field name that should be serialized.
30
- # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump`
31
- # or a class name that the object type should be equal to.
33
+ # * +class_name+ - Optional, class name that the object type should be equal to.
32
34
  #
33
35
  # ==== Example
34
36
  #
@@ -36,23 +38,13 @@ module ActiveRecord
36
38
  # class User < ActiveRecord::Base
37
39
  # serialize :preferences
38
40
  # end
39
- #
40
- # # Serialize preferences using JSON as coder.
41
- # class User < ActiveRecord::Base
42
- # serialize :preferences, JSON
43
- # end
44
- #
45
- # # Serialize preferences as Hash using YAML coder.
46
- # class User < ActiveRecord::Base
47
- # serialize :preferences, Hash
48
- # end
49
- def serialize(attr_name, class_name_or_coder = Object)
41
+ def serialize(attr_name, class_name = Object)
50
42
  include Behavior
51
43
 
52
- coder = if [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
53
- class_name_or_coder
44
+ coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
45
+ class_name
54
46
  else
55
- Coders::YAMLColumn.new(class_name_or_coder)
47
+ Coders::YAMLColumn.new(class_name)
56
48
  end
57
49
 
58
50
  # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
@@ -61,13 +53,6 @@ module ActiveRecord
61
53
  end
62
54
  end
63
55
 
64
- # *DEPRECATED*: Use ActiveRecord::AttributeMethods::Serialization::ClassMethods#serialized_attributes class level method instead.
65
- def serialized_attributes
66
- message = "Instance level serialized_attributes method is deprecated, please use class level method."
67
- ActiveSupport::Deprecation.warn message
68
- defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes
69
- end
70
-
71
56
  class Type # :nodoc:
72
57
  def initialize(column)
73
58
  @column = column
@@ -84,6 +69,10 @@ module ActiveRecord
84
69
  def type
85
70
  @column.type
86
71
  end
72
+
73
+ def accessor
74
+ ActiveRecord::Store::IndifferentHashAccessor
75
+ end
87
76
  end
88
77
 
89
78
  class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
@@ -167,16 +156,6 @@ module ActiveRecord
167
156
  super
168
157
  end
169
158
  end
170
-
171
- def attributes_for_coder
172
- attribute_names.each_with_object({}) do |name, attrs|
173
- attrs[name] = if self.class.serialized_attributes.include?(name)
174
- @attributes[name].serialized_value
175
- else
176
- read_attribute(name)
177
- end
178
- end
179
- end
180
159
  end
181
160
  end
182
161
  end
@@ -34,7 +34,7 @@ module ActiveRecord
34
34
  if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
35
35
  method_body, line = <<-EOV, __LINE__ + 1
36
36
  def #{attr_name}=(time)
37
- time_with_zone = convert_value_to_time_zone(time)
37
+ time_with_zone = time.respond_to?(:in_time_zone) ? time.in_time_zone : nil
38
38
  previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
39
39
  write_attribute(:#{attr_name}, time)
40
40
  #{attr_name}_will_change! if previous_time != time_with_zone
@@ -54,18 +54,6 @@ module ActiveRecord
54
54
  (:datetime == column.type || :timestamp == column.type)
55
55
  end
56
56
  end
57
-
58
- private
59
-
60
- def convert_value_to_time_zone(value)
61
- if value.is_a?(Array)
62
- value.map { |v| convert_value_to_time_zone(v) }
63
- elsif value.respond_to?(:in_time_zone)
64
- value.in_time_zone
65
- else
66
- nil
67
- end
68
- end
69
57
  end
70
58
  end
71
59
  end
@@ -1,6 +1,21 @@
1
+ require 'active_support/core_ext/module/method_transplanting'
2
+
1
3
  module ActiveRecord
2
4
  module AttributeMethods
3
5
  module Write
6
+ WriterMethodCache = Class.new(AttributeMethodCache) {
7
+ private
8
+
9
+ def method_body(method_name, const_name)
10
+ <<-EOMETHOD
11
+ def #{method_name}(value)
12
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
13
+ write_attribute(name, value)
14
+ end
15
+ EOMETHOD
16
+ end
17
+ }.new
18
+
4
19
  extend ActiveSupport::Concern
5
20
 
6
21
  included do
@@ -10,19 +25,29 @@ module ActiveRecord
10
25
  module ClassMethods
11
26
  protected
12
27
 
13
- # See define_method_attribute in read.rb for an explanation of
14
- # this code.
15
- def define_method_attribute=(name)
16
- safe_name = name.unpack('h*').first
17
- generated_attribute_methods::AttrNames.set_name_cache safe_name, name
28
+ if Module.methods_transplantable?
29
+ # See define_method_attribute in read.rb for an explanation of
30
+ # this code.
31
+ def define_method_attribute=(name)
32
+ method = WriterMethodCache[name]
33
+ generated_attribute_methods.module_eval {
34
+ define_method "#{name}=", method
35
+ }
36
+ end
37
+ else
38
+ def define_method_attribute=(name)
39
+ safe_name = name.unpack('h*').first
40
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
18
41
 
19
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
20
- def __temp__#{safe_name}=(value)
21
- write_attribute(AttrNames::ATTR_#{safe_name}, value)
22
- end
23
- alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
24
- undef_method :__temp__#{safe_name}=
25
- STR
42
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
43
+ def __temp__#{safe_name}=(value)
44
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
45
+ write_attribute(name, value)
46
+ end
47
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
48
+ undef_method :__temp__#{safe_name}=
49
+ STR
50
+ end
26
51
  end
27
52
  end
28
53
 
@@ -127,87 +127,87 @@ module ActiveRecord
127
127
  extend ActiveSupport::Concern
128
128
 
129
129
  module AssociationBuilderExtension #:nodoc:
130
- def build
130
+ def self.build(model, reflection)
131
131
  model.send(:add_autosave_association_callbacks, reflection)
132
- super
132
+ end
133
+
134
+ def self.valid_options
135
+ [ :autosave ]
133
136
  end
134
137
  end
135
138
 
136
139
  included do
137
- Associations::Builder::Association.class_eval do
138
- self.valid_options << :autosave
139
- include AssociationBuilderExtension
140
- end
140
+ Associations::Builder::Association.extensions << AssociationBuilderExtension
141
141
  end
142
142
 
143
143
  module ClassMethods
144
144
  private
145
145
 
146
- def define_non_cyclic_method(name, reflection, &block)
147
- define_method(name) do |*args|
148
- result = true; @_already_called ||= {}
149
- # Loop prevention for validation of associations
150
- unless @_already_called[[name, reflection.name]]
151
- begin
152
- @_already_called[[name, reflection.name]]=true
153
- result = instance_eval(&block)
154
- ensure
155
- @_already_called[[name, reflection.name]]=false
146
+ def define_non_cyclic_method(name, &block)
147
+ define_method(name) do |*args|
148
+ result = true; @_already_called ||= {}
149
+ # Loop prevention for validation of associations
150
+ unless @_already_called[name]
151
+ begin
152
+ @_already_called[name]=true
153
+ result = instance_eval(&block)
154
+ ensure
155
+ @_already_called[name]=false
156
+ end
156
157
  end
157
- end
158
158
 
159
- result
159
+ result
160
+ end
160
161
  end
161
- end
162
162
 
163
- # Adds validation and save callbacks for the association as specified by
164
- # the +reflection+.
165
- #
166
- # For performance reasons, we don't check whether to validate at runtime.
167
- # However the validation and callback methods are lazy and those methods
168
- # get created when they are invoked for the very first time. However,
169
- # this can change, for instance, when using nested attributes, which is
170
- # called _after_ the association has been defined. Since we don't want
171
- # the callbacks to get defined multiple times, there are guards that
172
- # check if the save or validation methods have already been defined
173
- # before actually defining them.
174
- def add_autosave_association_callbacks(reflection)
175
- save_method = :"autosave_associated_records_for_#{reflection.name}"
176
- validation_method = :"validate_associated_records_for_#{reflection.name}"
177
- collection = reflection.collection?
178
-
179
- unless method_defined?(save_method)
180
- if collection
181
- before_save :before_save_collection_association
182
-
183
- define_non_cyclic_method(save_method, reflection) { save_collection_association(reflection) }
184
- # Doesn't use after_save as that would save associations added in after_create/after_update twice
185
- after_create save_method
186
- after_update save_method
187
- elsif reflection.macro == :has_one
188
- define_method(save_method) { save_has_one_association(reflection) }
189
- # Configures two callbacks instead of a single after_save so that
190
- # the model may rely on their execution order relative to its
191
- # own callbacks.
192
- #
193
- # For example, given that after_creates run before after_saves, if
194
- # we configured instead an after_save there would be no way to fire
195
- # a custom after_create callback after the child association gets
196
- # created.
197
- after_create save_method
198
- after_update save_method
199
- else
200
- define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
201
- before_save save_method
163
+ # Adds validation and save callbacks for the association as specified by
164
+ # the +reflection+.
165
+ #
166
+ # For performance reasons, we don't check whether to validate at runtime.
167
+ # However the validation and callback methods are lazy and those methods
168
+ # get created when they are invoked for the very first time. However,
169
+ # this can change, for instance, when using nested attributes, which is
170
+ # called _after_ the association has been defined. Since we don't want
171
+ # the callbacks to get defined multiple times, there are guards that
172
+ # check if the save or validation methods have already been defined
173
+ # before actually defining them.
174
+ def add_autosave_association_callbacks(reflection)
175
+ save_method = :"autosave_associated_records_for_#{reflection.name}"
176
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
177
+ collection = reflection.collection?
178
+
179
+ unless method_defined?(save_method)
180
+ if collection
181
+ before_save :before_save_collection_association
182
+
183
+ define_non_cyclic_method(save_method) { save_collection_association(reflection) }
184
+ # Doesn't use after_save as that would save associations added in after_create/after_update twice
185
+ after_create save_method
186
+ after_update save_method
187
+ elsif reflection.macro == :has_one
188
+ define_method(save_method) { save_has_one_association(reflection) }
189
+ # Configures two callbacks instead of a single after_save so that
190
+ # the model may rely on their execution order relative to its
191
+ # own callbacks.
192
+ #
193
+ # For example, given that after_creates run before after_saves, if
194
+ # we configured instead an after_save there would be no way to fire
195
+ # a custom after_create callback after the child association gets
196
+ # created.
197
+ after_create save_method
198
+ after_update save_method
199
+ else
200
+ define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
201
+ before_save save_method
202
+ end
202
203
  end
203
- end
204
204
 
205
- if reflection.validate? && !method_defined?(validation_method)
206
- method = (collection ? :validate_collection_association : :validate_single_association)
207
- define_non_cyclic_method(validation_method, reflection) { send(method, reflection) }
208
- validate validation_method
205
+ if reflection.validate? && !method_defined?(validation_method)
206
+ method = (collection ? :validate_collection_association : :validate_single_association)
207
+ define_non_cyclic_method(validation_method) { send(method, reflection) }
208
+ validate validation_method
209
+ end
209
210
  end
210
- end
211
211
  end
212
212
 
213
213
  # Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
@@ -254,179 +254,179 @@ module ActiveRecord
254
254
 
255
255
  private
256
256
 
257
- # Returns the record for an association collection that should be validated
258
- # or saved. If +autosave+ is +false+ only new records will be returned,
259
- # unless the parent is/was a new record itself.
260
- def associated_records_to_validate_or_save(association, new_record, autosave)
261
- if new_record
262
- association && association.target
263
- elsif autosave
264
- association.target.find_all { |record| record.changed_for_autosave? }
265
- else
266
- association.target.find_all { |record| record.new_record? }
257
+ # Returns the record for an association collection that should be validated
258
+ # or saved. If +autosave+ is +false+ only new records will be returned,
259
+ # unless the parent is/was a new record itself.
260
+ def associated_records_to_validate_or_save(association, new_record, autosave)
261
+ if new_record
262
+ association && association.target
263
+ elsif autosave
264
+ association.target.find_all { |record| record.changed_for_autosave? }
265
+ else
266
+ association.target.find_all { |record| record.new_record? }
267
+ end
267
268
  end
268
- end
269
269
 
270
- # go through nested autosave associations that are loaded in memory (without loading
271
- # any new ones), and return true if is changed for autosave
272
- def nested_records_changed_for_autosave?
273
- self.class.reflect_on_all_autosave_associations.any? do |reflection|
274
- association = association_instance_get(reflection.name)
275
- association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
270
+ # go through nested autosave associations that are loaded in memory (without loading
271
+ # any new ones), and return true if is changed for autosave
272
+ def nested_records_changed_for_autosave?
273
+ self.class.reflect_on_all_autosave_associations.any? do |reflection|
274
+ association = association_instance_get(reflection.name)
275
+ association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
276
+ end
276
277
  end
277
- end
278
278
 
279
- # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
280
- # turned on for the association.
281
- def validate_single_association(reflection)
282
- association = association_instance_get(reflection.name)
283
- record = association && association.reader
284
- association_valid?(reflection, record) if record
285
- end
279
+ # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
280
+ # turned on for the association.
281
+ def validate_single_association(reflection)
282
+ association = association_instance_get(reflection.name)
283
+ record = association && association.reader
284
+ association_valid?(reflection, record) if record
285
+ end
286
286
 
287
- # Validate the associated records if <tt>:validate</tt> or
288
- # <tt>:autosave</tt> is turned on for the association specified by
289
- # +reflection+.
290
- def validate_collection_association(reflection)
291
- if association = association_instance_get(reflection.name)
292
- if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
293
- records.each { |record| association_valid?(reflection, record) }
287
+ # Validate the associated records if <tt>:validate</tt> or
288
+ # <tt>:autosave</tt> is turned on for the association specified by
289
+ # +reflection+.
290
+ def validate_collection_association(reflection)
291
+ if association = association_instance_get(reflection.name)
292
+ if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
293
+ records.each { |record| association_valid?(reflection, record) }
294
+ end
294
295
  end
295
296
  end
296
- end
297
297
 
298
- # Returns whether or not the association is valid and applies any errors to
299
- # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
300
- # enabled records if they're marked_for_destruction? or destroyed.
301
- def association_valid?(reflection, record)
302
- return true if record.destroyed? || record.marked_for_destruction?
303
-
304
- unless valid = record.valid?
305
- if reflection.options[:autosave]
306
- record.errors.each do |attribute, message|
307
- attribute = "#{reflection.name}.#{attribute}"
308
- errors[attribute] << message
309
- errors[attribute].uniq!
298
+ # Returns whether or not the association is valid and applies any errors to
299
+ # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
300
+ # enabled records if they're marked_for_destruction? or destroyed.
301
+ def association_valid?(reflection, record)
302
+ return true if record.destroyed? || record.marked_for_destruction?
303
+
304
+ unless valid = record.valid?
305
+ if reflection.options[:autosave]
306
+ record.errors.each do |attribute, message|
307
+ attribute = "#{reflection.name}.#{attribute}"
308
+ errors[attribute] << message
309
+ errors[attribute].uniq!
310
+ end
311
+ else
312
+ errors.add(reflection.name)
310
313
  end
311
- else
312
- errors.add(reflection.name)
313
314
  end
315
+ valid
314
316
  end
315
- valid
316
- end
317
317
 
318
- # Is used as a before_save callback to check while saving a collection
319
- # association whether or not the parent was a new record before saving.
320
- def before_save_collection_association
321
- @new_record_before_save = new_record?
322
- true
323
- end
318
+ # Is used as a before_save callback to check while saving a collection
319
+ # association whether or not the parent was a new record before saving.
320
+ def before_save_collection_association
321
+ @new_record_before_save = new_record?
322
+ true
323
+ end
324
324
 
325
- # Saves any new associated records, or all loaded autosave associations if
326
- # <tt>:autosave</tt> is enabled on the association.
327
- #
328
- # In addition, it destroys all children that were marked for destruction
329
- # with mark_for_destruction.
330
- #
331
- # This all happens inside a transaction, _if_ the Transactions module is included into
332
- # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
333
- def save_collection_association(reflection)
334
- if association = association_instance_get(reflection.name)
335
- autosave = reflection.options[:autosave]
336
-
337
- if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
338
-
339
- if autosave
340
- records_to_destroy = records.select(&:marked_for_destruction?)
341
- records_to_destroy.each { |record| association.destroy(record) }
342
- records -= records_to_destroy
343
- end
325
+ # Saves any new associated records, or all loaded autosave associations if
326
+ # <tt>:autosave</tt> is enabled on the association.
327
+ #
328
+ # In addition, it destroys all children that were marked for destruction
329
+ # with mark_for_destruction.
330
+ #
331
+ # This all happens inside a transaction, _if_ the Transactions module is included into
332
+ # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
333
+ def save_collection_association(reflection)
334
+ if association = association_instance_get(reflection.name)
335
+ autosave = reflection.options[:autosave]
336
+
337
+ if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
338
+
339
+ if autosave
340
+ records_to_destroy = records.select(&:marked_for_destruction?)
341
+ records_to_destroy.each { |record| association.destroy(record) }
342
+ records -= records_to_destroy
343
+ end
344
344
 
345
- records.each do |record|
346
- next if record.destroyed?
345
+ records.each do |record|
346
+ next if record.destroyed?
347
347
 
348
- saved = true
348
+ saved = true
349
349
 
350
- if autosave != false && (@new_record_before_save || record.new_record?)
351
- if autosave
352
- saved = association.insert_record(record, false)
353
- else
354
- association.insert_record(record) unless reflection.nested?
350
+ if autosave != false && (@new_record_before_save || record.new_record?)
351
+ if autosave
352
+ saved = association.insert_record(record, false)
353
+ else
354
+ association.insert_record(record) unless reflection.nested?
355
+ end
356
+ elsif autosave
357
+ saved = record.save(:validate => false)
355
358
  end
356
- elsif autosave
357
- saved = record.save(:validate => false)
358
- end
359
359
 
360
- raise ActiveRecord::Rollback unless saved
360
+ raise ActiveRecord::Rollback unless saved
361
+ end
361
362
  end
362
- end
363
363
 
364
- # reconstruct the scope now that we know the owner's id
365
- association.reset_scope if association.respond_to?(:reset_scope)
364
+ # reconstruct the scope now that we know the owner's id
365
+ association.reset_scope if association.respond_to?(:reset_scope)
366
+ end
366
367
  end
367
- end
368
368
 
369
- # Saves the associated record if it's new or <tt>:autosave</tt> is enabled
370
- # on the association.
371
- #
372
- # In addition, it will destroy the association if it was marked for
373
- # destruction with mark_for_destruction.
374
- #
375
- # This all happens inside a transaction, _if_ the Transactions module is included into
376
- # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
377
- def save_has_one_association(reflection)
378
- association = association_instance_get(reflection.name)
379
- record = association && association.load_target
380
- if record && !record.destroyed?
381
- autosave = reflection.options[:autosave]
382
-
383
- if autosave && record.marked_for_destruction?
384
- record.destroy
385
- elsif autosave != false
386
- key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
387
-
388
- if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
389
- unless reflection.through_reflection
390
- record[reflection.foreign_key] = key
391
- end
369
+ # Saves the associated record if it's new or <tt>:autosave</tt> is enabled
370
+ # on the association.
371
+ #
372
+ # In addition, it will destroy the association if it was marked for
373
+ # destruction with mark_for_destruction.
374
+ #
375
+ # This all happens inside a transaction, _if_ the Transactions module is included into
376
+ # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
377
+ def save_has_one_association(reflection)
378
+ association = association_instance_get(reflection.name)
379
+ record = association && association.load_target
380
+ if record && !record.destroyed?
381
+ autosave = reflection.options[:autosave]
392
382
 
393
- saved = record.save(:validate => !autosave)
394
- raise ActiveRecord::Rollback if !saved && autosave
395
- saved
383
+ if autosave && record.marked_for_destruction?
384
+ record.destroy
385
+ else
386
+ key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
387
+ if autosave != false && (autosave || new_record? || record_changed?(reflection, record, key))
388
+
389
+ unless reflection.through_reflection
390
+ record[reflection.foreign_key] = key
391
+ end
392
+
393
+ saved = record.save(:validate => !autosave)
394
+ raise ActiveRecord::Rollback if !saved && autosave
395
+ saved
396
+ end
396
397
  end
397
398
  end
398
399
  end
399
- end
400
400
 
401
- # If the record is new or it has changed, returns true.
402
- def record_changed?(reflection, record, key)
403
- record.new_record? || record[reflection.foreign_key] != key || record.changed_attributes.include?(reflection.foreign_key)
404
- end
401
+ # If the record is new or it has changed, returns true.
402
+ def record_changed?(reflection, record, key)
403
+ record.new_record? || record[reflection.foreign_key] != key || record.attribute_changed?(reflection.foreign_key)
404
+ end
405
405
 
406
- # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
407
- #
408
- # In addition, it will destroy the association if it was marked for destruction.
409
- def save_belongs_to_association(reflection)
410
- association = association_instance_get(reflection.name)
411
- record = association && association.load_target
412
- if record && !record.destroyed?
413
- autosave = reflection.options[:autosave]
414
-
415
- if autosave && record.marked_for_destruction?
416
- self[reflection.foreign_key] = nil
417
- record.destroy
418
- elsif autosave != false
419
- saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
420
-
421
- if association.updated?
422
- association_id = record.send(reflection.options[:primary_key] || :id)
423
- self[reflection.foreign_key] = association_id
424
- association.loaded!
425
- end
406
+ # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
407
+ #
408
+ # In addition, it will destroy the association if it was marked for destruction.
409
+ def save_belongs_to_association(reflection)
410
+ association = association_instance_get(reflection.name)
411
+ record = association && association.load_target
412
+ if record && !record.destroyed?
413
+ autosave = reflection.options[:autosave]
414
+
415
+ if autosave && record.marked_for_destruction?
416
+ self[reflection.foreign_key] = nil
417
+ record.destroy
418
+ elsif autosave != false
419
+ saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
420
+
421
+ if association.updated?
422
+ association_id = record.send(reflection.options[:primary_key] || :id)
423
+ self[reflection.foreign_key] = association_id
424
+ association.loaded!
425
+ end
426
426
 
427
- saved if autosave
427
+ saved if autosave
428
+ end
428
429
  end
429
430
  end
430
- end
431
431
  end
432
432
  end