activerecord 3.2.22.5 → 4.0.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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -543
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +20 -29
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +55 -44
  7. data/lib/active_record/aggregations.rb +40 -34
  8. data/lib/active_record/associations.rb +204 -276
  9. data/lib/active_record/associations/alias_tracker.rb +1 -1
  10. data/lib/active_record/associations/association.rb +30 -35
  11. data/lib/active_record/associations/association_scope.rb +40 -40
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -2
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +35 -57
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +92 -88
  21. data/lib/active_record/associations/collection_proxy.rb +913 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
  23. data/lib/active_record/associations/has_many_association.rb +35 -9
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -14
  25. data/lib/active_record/associations/has_one_association.rb +33 -13
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +2 -2
  28. data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/join_helper.rb +1 -11
  31. data/lib/active_record/associations/preloader.rb +14 -17
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/singular_association.rb +11 -11
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +133 -153
  41. data/lib/active_record/attribute_methods.rb +196 -93
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  43. data/lib/active_record/attribute_methods/dirty.rb +31 -28
  44. data/lib/active_record/attribute_methods/primary_key.rb +38 -30
  45. data/lib/active_record/attribute_methods/query.rb +5 -4
  46. data/lib/active_record/attribute_methods/read.rb +62 -91
  47. data/lib/active_record/attribute_methods/serialization.rb +97 -66
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
  49. data/lib/active_record/attribute_methods/write.rb +32 -39
  50. data/lib/active_record/autosave_association.rb +56 -70
  51. data/lib/active_record/base.rb +53 -450
  52. data/lib/active_record/callbacks.rb +53 -18
  53. data/lib/active_record/coders/yaml_column.rb +11 -9
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
  65. data/lib/active_record/connection_adapters/column.rb +46 -24
  66. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  67. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  68. data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
  69. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  70. data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
  73. data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
  74. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  75. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
  76. data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
  77. data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
  79. data/lib/active_record/connection_handling.rb +98 -0
  80. data/lib/active_record/core.rb +428 -0
  81. data/lib/active_record/counter_cache.rb +106 -108
  82. data/lib/active_record/dynamic_matchers.rb +110 -63
  83. data/lib/active_record/errors.rb +25 -8
  84. data/lib/active_record/explain.rb +8 -58
  85. data/lib/active_record/explain_subscriber.rb +6 -3
  86. data/lib/active_record/fixture_set/file.rb +56 -0
  87. data/lib/active_record/fixtures.rb +146 -148
  88. data/lib/active_record/inheritance.rb +77 -59
  89. data/lib/active_record/integration.rb +5 -5
  90. data/lib/active_record/locale/en.yml +8 -1
  91. data/lib/active_record/locking/optimistic.rb +38 -42
  92. data/lib/active_record/locking/pessimistic.rb +4 -4
  93. data/lib/active_record/log_subscriber.rb +19 -9
  94. data/lib/active_record/migration.rb +318 -153
  95. data/lib/active_record/migration/command_recorder.rb +90 -31
  96. data/lib/active_record/migration/join_table.rb +15 -0
  97. data/lib/active_record/model_schema.rb +69 -92
  98. data/lib/active_record/nested_attributes.rb +113 -148
  99. data/lib/active_record/null_relation.rb +65 -0
  100. data/lib/active_record/persistence.rb +188 -97
  101. data/lib/active_record/query_cache.rb +18 -36
  102. data/lib/active_record/querying.rb +19 -15
  103. data/lib/active_record/railtie.rb +91 -36
  104. data/lib/active_record/railties/console_sandbox.rb +0 -2
  105. data/lib/active_record/railties/controller_runtime.rb +2 -2
  106. data/lib/active_record/railties/databases.rake +90 -309
  107. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  108. data/lib/active_record/readonly_attributes.rb +7 -3
  109. data/lib/active_record/reflection.rb +72 -56
  110. data/lib/active_record/relation.rb +241 -157
  111. data/lib/active_record/relation/batches.rb +25 -22
  112. data/lib/active_record/relation/calculations.rb +143 -121
  113. data/lib/active_record/relation/delegation.rb +96 -18
  114. data/lib/active_record/relation/finder_methods.rb +117 -183
  115. data/lib/active_record/relation/merger.rb +133 -0
  116. data/lib/active_record/relation/predicate_builder.rb +90 -42
  117. data/lib/active_record/relation/query_methods.rb +666 -136
  118. data/lib/active_record/relation/spawn_methods.rb +43 -150
  119. data/lib/active_record/result.rb +33 -6
  120. data/lib/active_record/sanitization.rb +24 -50
  121. data/lib/active_record/schema.rb +19 -12
  122. data/lib/active_record/schema_dumper.rb +31 -39
  123. data/lib/active_record/schema_migration.rb +36 -0
  124. data/lib/active_record/scoping.rb +0 -124
  125. data/lib/active_record/scoping/default.rb +48 -45
  126. data/lib/active_record/scoping/named.rb +74 -103
  127. data/lib/active_record/serialization.rb +6 -2
  128. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  129. data/lib/active_record/store.rb +119 -15
  130. data/lib/active_record/tasks/database_tasks.rb +158 -0
  131. data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
  132. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  133. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  134. data/lib/active_record/test_case.rb +61 -38
  135. data/lib/active_record/timestamp.rb +8 -9
  136. data/lib/active_record/transactions.rb +65 -51
  137. data/lib/active_record/validations.rb +17 -15
  138. data/lib/active_record/validations/associated.rb +20 -14
  139. data/lib/active_record/validations/presence.rb +65 -0
  140. data/lib/active_record/validations/uniqueness.rb +93 -52
  141. data/lib/active_record/version.rb +4 -4
  142. data/lib/rails/generators/active_record.rb +3 -5
  143. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
  144. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  145. data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
  146. data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
  147. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  148. metadata +53 -46
  149. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  150. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  151. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  152. data/lib/active_record/dynamic_finder_match.rb +0 -68
  153. data/lib/active_record/dynamic_scope_match.rb +0 -23
  154. data/lib/active_record/fixtures/file.rb +0 -65
  155. data/lib/active_record/identity_map.rb +0 -162
  156. data/lib/active_record/observer.rb +0 -121
  157. data/lib/active_record/session_store.rb +0 -360
  158. data/lib/rails/generators/active_record/migration.rb +0 -15
  159. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  160. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  161. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  162. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -4,37 +4,19 @@ module ActiveRecord
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- # Returns a hash of all the attributes that have been specified for serialization as
8
- # keys and their class restriction as values.
9
- class_attribute :serialized_attributes
7
+ # Returns a hash of all the attributes that have been specified for
8
+ # serialization as keys and their class restriction as values.
9
+ class_attribute :serialized_attributes, instance_accessor: false
10
10
  self.serialized_attributes = {}
11
11
  end
12
12
 
13
- class Attribute < Struct.new(:coder, :value, :state)
14
- def unserialized_value
15
- state == :serialized ? unserialize : value
16
- end
17
-
18
- def serialized_value
19
- state == :unserialized ? serialize : value
20
- end
21
-
22
- def unserialize
23
- self.state = :unserialized
24
- self.value = coder.load(value)
25
- end
26
-
27
- def serialize
28
- self.state = :serialized
29
- self.value = coder.dump(value)
30
- end
31
- end
32
-
33
13
  module ClassMethods
34
- # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
35
- # then specify the name of that attribute using this method and it will be handled automatically.
36
- # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
37
- # class on retrieval or SerializationTypeMismatch will be raised.
14
+ # If you have an attribute that needs to be saved to the database as an
15
+ # object, and retrieved as the same object, then specify the name of that
16
+ # attribute using this method and it will be handled automatically. The
17
+ # serialization is done through YAML. If +class_name+ is specified, the
18
+ # serialized object must be of that class on retrieval or
19
+ # <tt>SerializationTypeMismatch</tt> will be raised.
38
20
  #
39
21
  # ==== Parameters
40
22
  #
@@ -42,11 +24,14 @@ module ActiveRecord
42
24
  # * +class_name+ - Optional, class name that the object type should be equal to.
43
25
  #
44
26
  # ==== Example
45
- # # Serialize a preferences attribute
27
+ #
28
+ # # Serialize a preferences attribute.
46
29
  # class User < ActiveRecord::Base
47
30
  # serialize :preferences
48
31
  # end
49
32
  def serialize(attr_name, class_name = Object)
33
+ include Behavior
34
+
50
35
  coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
51
36
  class_name
52
37
  else
@@ -57,61 +42,107 @@ module ActiveRecord
57
42
  # has its own hash of own serialized attributes
58
43
  self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
59
44
  end
45
+ end
60
46
 
61
- def initialize_attributes(attributes, options = {}) #:nodoc:
62
- serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
63
- super(attributes, options)
47
+ def serialized_attributes
48
+ message = "Instance level serialized_attributes method is deprecated, please use class level method."
49
+ ActiveSupport::Deprecation.warn message
50
+ defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes
51
+ end
64
52
 
65
- serialized_attributes.each do |key, coder|
66
- if attributes.key?(key)
67
- attributes[key] = Attribute.new(coder, attributes[key], serialized)
68
- end
69
- end
53
+ class Type # :nodoc:
54
+ def initialize(column)
55
+ @column = column
56
+ end
70
57
 
71
- attributes
58
+ def type_cast(value)
59
+ value.unserialized_value
72
60
  end
73
61
 
74
- private
62
+ def type
63
+ @column.type
64
+ end
65
+ end
66
+
67
+ class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
68
+ def unserialized_value
69
+ state == :serialized ? unserialize : value
70
+ end
75
71
 
76
- def attribute_cast_code(attr_name)
77
- if serialized_attributes.include?(attr_name)
78
- "v.unserialized_value"
72
+ def serialized_value
73
+ state == :unserialized ? serialize : value
74
+ end
75
+
76
+ def unserialize
77
+ self.state = :unserialized
78
+ self.value = coder.load(value)
79
+ end
80
+
81
+ def serialize
82
+ self.state = :serialized
83
+ self.value = coder.dump(value)
84
+ end
85
+ end
86
+
87
+ # This is only added to the model when serialize is called, which
88
+ # ensures we do not make things slower when serialization is not used.
89
+ module Behavior #:nodoc:
90
+ extend ActiveSupport::Concern
91
+
92
+ module ClassMethods
93
+ def initialize_attributes(attributes, options = {})
94
+ serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
95
+ super(attributes, options)
96
+
97
+ serialized_attributes.each do |key, coder|
98
+ if attributes.key?(key)
99
+ attributes[key] = Attribute.new(coder, attributes[key], serialized)
100
+ end
101
+ end
102
+
103
+ attributes
104
+ end
105
+ end
106
+
107
+ def type_cast_attribute_for_write(column, value)
108
+ if column && coder = self.class.serialized_attributes[column.name]
109
+ Attribute.new(coder, value, :unserialized)
79
110
  else
80
111
  super
81
112
  end
82
113
  end
83
- end
84
114
 
85
- def type_cast_attribute_for_write(column, value)
86
- if column && coder = self.class.serialized_attributes[column.name]
87
- Attribute.new(coder, value, :unserialized)
88
- else
89
- super
115
+ def _field_changed?(attr, old, value)
116
+ if self.class.serialized_attributes.include?(attr)
117
+ old != value
118
+ else
119
+ super
120
+ end
90
121
  end
91
- end
92
122
 
93
- def _field_changed?(attr, old, value)
94
- if self.class.serialized_attributes.include?(attr)
95
- old != value
96
- else
97
- super
123
+ def read_attribute_before_type_cast(attr_name)
124
+ if self.class.serialized_attributes.include?(attr_name)
125
+ super.unserialized_value
126
+ else
127
+ super
128
+ end
98
129
  end
99
- end
100
130
 
101
- def read_attribute_before_type_cast(attr_name)
102
- if serialized_attributes.include?(attr_name)
103
- super.unserialized_value
104
- else
105
- super
131
+ def attributes_before_type_cast
132
+ super.dup.tap do |attributes|
133
+ self.class.serialized_attributes.each_key do |key|
134
+ if attributes.key?(key)
135
+ attributes[key] = attributes[key].unserialized_value
136
+ end
137
+ end
138
+ end
106
139
  end
107
- end
108
140
 
109
- def attributes_before_type_cast
110
- super.dup.tap do |attributes|
111
- self.class.serialized_attributes.each_key do |key|
112
- if attributes.key?(key)
113
- attributes[key] = attributes[key].unserialized_value
114
- end
141
+ def typecasted_attribute_value(name)
142
+ if self.class.serialized_attributes.include?(name)
143
+ @attributes[name].serialized_value
144
+ else
145
+ super
115
146
  end
116
147
  end
117
148
  end
@@ -1,64 +1,58 @@
1
- require 'active_support/core_ext/class/attribute'
2
- require 'active_support/core_ext/object/inclusion'
3
-
4
1
  module ActiveRecord
5
2
  module AttributeMethods
6
3
  module TimeZoneConversion
4
+ class Type # :nodoc:
5
+ def initialize(column)
6
+ @column = column
7
+ end
8
+
9
+ def type_cast(value)
10
+ value = @column.type_cast(value)
11
+ value.acts_like?(:time) ? value.in_time_zone : value
12
+ end
13
+
14
+ def type
15
+ @column.type
16
+ end
17
+ end
18
+
7
19
  extend ActiveSupport::Concern
8
20
 
9
21
  included do
10
- cattr_accessor :time_zone_aware_attributes, :instance_writer => false
22
+ mattr_accessor :time_zone_aware_attributes, instance_writer: false
11
23
  self.time_zone_aware_attributes = false
12
24
 
13
- class_attribute :skip_time_zone_conversion_for_attributes, :instance_writer => false
25
+ class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
14
26
  self.skip_time_zone_conversion_for_attributes = []
15
27
  end
16
28
 
17
29
  module ClassMethods
18
30
  protected
19
- # The enhanced read method automatically converts the UTC time stored in the database to the time
20
- # zone stored in Time.zone.
21
- def attribute_cast_code(attr_name)
22
- column = columns_hash[attr_name]
23
-
24
- if create_time_zone_conversion_attribute?(attr_name, column)
25
- typecast = "v = #{super}"
26
- time_zone_conversion = "v.acts_like?(:time) ? v.in_time_zone : v"
27
-
28
- "((#{typecast}) && (#{time_zone_conversion}))"
29
- else
30
- super
31
- end
32
- end
33
-
34
- # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
35
- # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
36
- def define_method_attribute=(attr_name)
37
- if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
38
- method_body, line = <<-EOV, __LINE__ + 1
39
- def #{attr_name}=(original_time)
40
- original_time = nil if original_time.blank?
41
- time = original_time
42
- unless time.acts_like?(:time)
43
- time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
44
- end
45
- time = time.in_time_zone rescue nil if time
46
- previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
47
- write_attribute(:#{attr_name}, original_time)
48
- #{attr_name}_will_change! if previous_time != time
49
- @attributes_cache["#{attr_name}"] = time
50
- end
51
- EOV
52
- generated_attribute_methods.module_eval(method_body, __FILE__, line)
53
- else
54
- super
55
- end
31
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
32
+ # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
33
+ def define_method_attribute=(attr_name)
34
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
35
+ method_body, line = <<-EOV, __LINE__ + 1
36
+ def #{attr_name}=(time)
37
+ time_with_zone = time.respond_to?(:in_time_zone) ? time.in_time_zone : nil
38
+ previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
39
+ write_attribute(:#{attr_name}, time)
40
+ #{attr_name}_will_change! if previous_time != time_with_zone
41
+ @attributes_cache["#{attr_name}"] = time_with_zone
42
+ end
43
+ EOV
44
+ generated_attribute_methods.module_eval(method_body, __FILE__, line)
45
+ else
46
+ super
56
47
  end
48
+ end
57
49
 
58
50
  private
59
- def create_time_zone_conversion_attribute?(name, column)
60
- time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && column.type.in?([:datetime, :timestamp])
61
- end
51
+ def create_time_zone_conversion_attribute?(name, column)
52
+ time_zone_aware_attributes &&
53
+ !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
54
+ [:datetime, :timestamp].include?(column.type)
55
+ end
62
56
  end
63
57
  end
64
58
  end
@@ -9,62 +9,55 @@ module ActiveRecord
9
9
 
10
10
  module ClassMethods
11
11
  protected
12
- def define_method_attribute=(attr_name)
13
- if attr_name =~ ActiveModel::AttributeMethods::NAME_COMPILABLE_REGEXP
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
12
+
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.module_eval <<-STR, __FILE__, __LINE__ + 1
18
+ def __temp__#{safe_name}=(value)
19
+ write_attribute(AttrNames::ATTR_#{safe_name}, value)
19
20
  end
20
- end
21
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
22
+ undef_method :__temp__#{safe_name}=
23
+ STR
24
+ end
21
25
  end
22
26
 
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+.
27
+ # Updates the attribute identified by <tt>attr_name</tt> with the
28
+ # specified +value+. Empty strings for fixnum and float columns are
29
+ # turned into +nil+.
25
30
  def write_attribute(attr_name, value)
26
31
  attr_name = attr_name.to_s
27
32
  attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
28
33
  @attributes_cache.delete(attr_name)
29
34
  column = column_for_attribute(attr_name)
30
35
 
31
- unless column || @attributes.has_key?(attr_name)
32
- ActiveSupport::Deprecation.warn(
33
- "You're trying to create an attribute `#{attr_name}'. Writing arbitrary " \
34
- "attributes on a model is deprecated. Please just use `attr_writer` etc."
35
- )
36
+ # If we're dealing with a binary column, write the data to the cache
37
+ # so we don't attempt to typecast multiple times.
38
+ if column && column.binary?
39
+ @attributes_cache[attr_name] = value
36
40
  end
37
41
 
38
- @attributes[attr_name] = type_cast_attribute_for_write(column, value)
42
+ if column || @attributes.has_key?(attr_name)
43
+ @attributes[attr_name] = type_cast_attribute_for_write(column, value)
44
+ else
45
+ raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
46
+ end
39
47
  end
40
48
  alias_method :raw_write_attribute, :write_attribute
41
49
 
42
50
  private
43
- # Handle *= for method_missing.
44
- def attribute=(attribute_name, value)
45
- write_attribute(attribute_name, value)
46
- end
51
+ # Handle *= for method_missing.
52
+ def attribute=(attribute_name, value)
53
+ write_attribute(attribute_name, value)
54
+ end
47
55
 
48
- def type_cast_attribute_for_write(column, value)
49
- if column && column.number?
50
- convert_number_column_value(value)
51
- else
52
- value
53
- end
54
- end
56
+ def type_cast_attribute_for_write(column, value)
57
+ return value unless column
55
58
 
56
- def convert_number_column_value(value)
57
- case value
58
- when FalseClass
59
- 0
60
- when TrueClass
61
- 1
62
- when String
63
- value.presence
64
- else
65
- value
66
- end
67
- end
59
+ column.type_cast_for_write value
60
+ end
68
61
  end
69
62
  end
70
63
  end
@@ -1,5 +1,3 @@
1
- require 'active_support/core_ext/array/wrap'
2
-
3
1
  module ActiveRecord
4
2
  # = Active Record Autosave Association
5
3
  #
@@ -18,7 +16,7 @@ module ActiveRecord
18
16
  # Note that it also means that associations marked for destruction won't
19
17
  # be destroyed directly. They will however still be marked for destruction.
20
18
  #
21
- # Note that <tt>:autosave => false</tt> is not same as not declaring <tt>:autosave</tt>.
19
+ # Note that <tt>autosave: false</tt> is not same as not declaring <tt>:autosave</tt>.
22
20
  # When the <tt>:autosave</tt> option is not present new associations are saved.
23
21
  #
24
22
  # == Validation
@@ -30,16 +28,14 @@ module ActiveRecord
30
28
  # Association with autosave option defines several callbacks on your
31
29
  # model (before_save, after_create, after_update). Please note that
32
30
  # callbacks are executed in the order they were defined in
33
- # model. You should avoid modyfing the association content, before
31
+ # model. You should avoid modifying the association content, before
34
32
  # autosave callbacks are executed. Placing your callbacks after
35
33
  # associations is usually a good practice.
36
34
  #
37
- # == Examples
38
- #
39
35
  # === One-to-one Example
40
36
  #
41
37
  # class Post
42
- # has_one :author, :autosave => true
38
+ # has_one :author, autosave: true
43
39
  # end
44
40
  #
45
41
  # Saving changes to the parent and its associated model can now be performed
@@ -80,29 +76,30 @@ module ActiveRecord
80
76
  # When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
81
77
  #
82
78
  # class Post
83
- # has_many :comments # :autosave option is no declared
79
+ # has_many :comments # :autosave option is not declared
84
80
  # end
85
81
  #
86
- # post = Post.new(:title => 'ruby rocks')
87
- # post.comments.build(:body => 'hello world')
82
+ # post = Post.new(title: 'ruby rocks')
83
+ # post.comments.build(body: 'hello world')
88
84
  # post.save # => saves both post and comment
89
85
  #
90
- # post = Post.create(:title => 'ruby rocks')
91
- # post.comments.build(:body => 'hello world')
86
+ # post = Post.create(title: 'ruby rocks')
87
+ # post.comments.build(body: 'hello world')
92
88
  # post.save # => saves both post and comment
93
89
  #
94
- # post = Post.create(:title => 'ruby rocks')
95
- # post.comments.create(:body => 'hello world')
90
+ # post = Post.create(title: 'ruby rocks')
91
+ # post.comments.create(body: 'hello world')
96
92
  # post.save # => saves both post and comment
97
93
  #
98
- # When <tt>:autosave</tt> is true all children is saved, no matter whether they are new records:
94
+ # When <tt>:autosave</tt> is true all children are saved, no matter whether they
95
+ # are new records or not:
99
96
  #
100
97
  # class Post
101
- # has_many :comments, :autosave => true
98
+ # has_many :comments, autosave: true
102
99
  # end
103
100
  #
104
- # post = Post.create(:title => 'ruby rocks')
105
- # post.comments.create(:body => 'hello world')
101
+ # post = Post.create(title: 'ruby rocks')
102
+ # post.comments.create(body: 'hello world')
106
103
  # post.comments[0].body = 'hi everyone'
107
104
  # post.save # => saves both post and comment, with 'hi everyone' as body
108
105
  #
@@ -128,23 +125,17 @@ module ActiveRecord
128
125
  module AutosaveAssociation
129
126
  extend ActiveSupport::Concern
130
127
 
131
- ASSOCIATION_TYPES = %w{ HasOne HasMany BelongsTo HasAndBelongsToMany }
132
-
133
128
  module AssociationBuilderExtension #:nodoc:
134
- def self.included(base)
135
- base.valid_options << :autosave
136
- end
137
-
138
129
  def build
139
- reflection = super
140
130
  model.send(:add_autosave_association_callbacks, reflection)
141
- reflection
131
+ super
142
132
  end
143
133
  end
144
134
 
145
135
  included do
146
- ASSOCIATION_TYPES.each do |type|
147
- Associations::Builder.const_get(type).send(:include, AssociationBuilderExtension)
136
+ Associations::Builder::Association.class_eval do
137
+ self.valid_options << :autosave
138
+ include AssociationBuilderExtension
148
139
  end
149
140
  end
150
141
 
@@ -192,23 +183,21 @@ module ActiveRecord
192
183
  # Doesn't use after_save as that would save associations added in after_create/after_update twice
193
184
  after_create save_method
194
185
  after_update save_method
186
+ elsif reflection.macro == :has_one
187
+ define_method(save_method) { save_has_one_association(reflection) }
188
+ # Configures two callbacks instead of a single after_save so that
189
+ # the model may rely on their execution order relative to its
190
+ # own callbacks.
191
+ #
192
+ # For example, given that after_creates run before after_saves, if
193
+ # we configured instead an after_save there would be no way to fire
194
+ # a custom after_create callback after the child association gets
195
+ # created.
196
+ after_create save_method
197
+ after_update save_method
195
198
  else
196
- if reflection.macro == :has_one
197
- define_method(save_method) { save_has_one_association(reflection) }
198
- # Configures two callbacks instead of a single after_save so that
199
- # the model may rely on their execution order relative to its
200
- # own callbacks.
201
- #
202
- # For example, given that after_creates run before after_saves, if
203
- # we configured instead an after_save there would be no way to fire
204
- # a custom after_create callback after the child association gets
205
- # created.
206
- after_create save_method
207
- after_update save_method
208
- else
209
- define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
210
- before_save save_method
211
- end
199
+ define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
200
+ before_save save_method
212
201
  end
213
202
  end
214
203
 
@@ -297,7 +286,7 @@ module ActiveRecord
297
286
  def association_valid?(reflection, record)
298
287
  return true if record.destroyed? || record.marked_for_destruction?
299
288
 
300
- unless valid = record.valid?
289
+ unless valid = record.valid?(validation_context)
301
290
  if reflection.options[:autosave]
302
291
  record.errors.each do |attribute, message|
303
292
  attribute = "#{reflection.name}.#{attribute}"
@@ -331,38 +320,34 @@ module ActiveRecord
331
320
  autosave = reflection.options[:autosave]
332
321
 
333
322
  if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
334
- begin
335
- if autosave
336
- records_to_destroy = records.select(&:marked_for_destruction?)
337
- records_to_destroy.each { |record| association.proxy.destroy(record) }
338
- records -= records_to_destroy
323
+ records_to_destroy = []
324
+ records.each do |record|
325
+ next if record.destroyed?
326
+
327
+ saved = true
328
+
329
+ if autosave && record.marked_for_destruction?
330
+ records_to_destroy << record
331
+ elsif autosave != false && (@new_record_before_save || record.new_record?)
332
+ if autosave
333
+ saved = association.insert_record(record, false)
334
+ else
335
+ association.insert_record(record) unless reflection.nested?
336
+ end
337
+ elsif autosave
338
+ saved = record.save(:validate => false)
339
339
  end
340
340
 
341
- records.each do |record|
342
- next if record.destroyed?
343
-
344
- saved = true
345
-
346
- if autosave != false && (@new_record_before_save || record.new_record?)
347
- if autosave
348
- saved = association.insert_record(record, false)
349
- else
350
- association.insert_record(record) unless reflection.nested?
351
- end
352
- elsif autosave
353
- saved = record.save(:validate => false)
354
- end
341
+ raise ActiveRecord::Rollback unless saved
342
+ end
355
343
 
356
- raise ActiveRecord::Rollback unless saved
357
- end
358
- rescue
359
- records.each {|x| IdentityMap.remove(x) } if IdentityMap.enabled?
360
- raise
344
+ records_to_destroy.each do |record|
345
+ association.destroy(record)
361
346
  end
362
347
  end
363
348
 
364
349
  # reconstruct the scope now that we know the owner's id
365
- association.reset_scope if association.respond_to?(:reset_scope)
350
+ association.send(:reset_scope) if association.respond_to?(:reset_scope)
366
351
  end
367
352
  end
368
353
 
@@ -407,6 +392,7 @@ module ActiveRecord
407
392
  autosave = reflection.options[:autosave]
408
393
 
409
394
  if autosave && record.marked_for_destruction?
395
+ self[reflection.foreign_key] = nil
410
396
  record.destroy
411
397
  elsif autosave != false
412
398
  saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)