activerecord 4.1.16 → 4.2.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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +634 -2185
  3. data/README.rdoc +15 -10
  4. data/lib/active_record.rb +2 -1
  5. data/lib/active_record/aggregations.rb +12 -8
  6. data/lib/active_record/associations.rb +58 -33
  7. data/lib/active_record/associations/association.rb +1 -1
  8. data/lib/active_record/associations/association_scope.rb +53 -21
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +16 -5
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -11
  13. data/lib/active_record/associations/builder/has_one.rb +2 -2
  14. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  15. data/lib/active_record/associations/collection_association.rb +32 -44
  16. data/lib/active_record/associations/collection_proxy.rb +1 -10
  17. data/lib/active_record/associations/has_many_association.rb +60 -14
  18. data/lib/active_record/associations/has_many_through_association.rb +34 -23
  19. data/lib/active_record/associations/has_one_association.rb +0 -1
  20. data/lib/active_record/associations/join_dependency.rb +7 -9
  21. data/lib/active_record/associations/join_dependency/join_association.rb +18 -14
  22. data/lib/active_record/associations/preloader.rb +2 -2
  23. data/lib/active_record/associations/preloader/association.rb +9 -5
  24. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  25. data/lib/active_record/associations/singular_association.rb +16 -1
  26. data/lib/active_record/associations/through_association.rb +6 -22
  27. data/lib/active_record/attribute.rb +131 -0
  28. data/lib/active_record/attribute_assignment.rb +19 -11
  29. data/lib/active_record/attribute_decorators.rb +66 -0
  30. data/lib/active_record/attribute_methods.rb +53 -90
  31. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  32. data/lib/active_record/attribute_methods/dirty.rb +85 -42
  33. data/lib/active_record/attribute_methods/primary_key.rb +6 -8
  34. data/lib/active_record/attribute_methods/read.rb +14 -57
  35. data/lib/active_record/attribute_methods/serialization.rb +12 -146
  36. data/lib/active_record/attribute_methods/time_zone_conversion.rb +32 -40
  37. data/lib/active_record/attribute_methods/write.rb +8 -23
  38. data/lib/active_record/attribute_set.rb +77 -0
  39. data/lib/active_record/attribute_set/builder.rb +32 -0
  40. data/lib/active_record/attributes.rb +122 -0
  41. data/lib/active_record/autosave_association.rb +11 -21
  42. data/lib/active_record/base.rb +9 -19
  43. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +69 -45
  44. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -42
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -60
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +37 -2
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +102 -21
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +9 -33
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +178 -55
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +120 -115
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +143 -57
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +156 -107
  53. data/lib/active_record/connection_adapters/column.rb +13 -244
  54. data/lib/active_record/connection_adapters/connection_specification.rb +6 -20
  55. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -15
  56. data/lib/active_record/connection_adapters/mysql_adapter.rb +55 -143
  57. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  58. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  59. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -20
  60. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +96 -0
  62. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  64. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  67. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +76 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +85 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  86. data/lib/active_record/connection_adapters/postgresql/quoting.rb +42 -122
  87. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  88. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +154 -0
  89. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +86 -34
  90. data/lib/active_record/connection_adapters/postgresql/utils.rb +66 -0
  91. data/lib/active_record/connection_adapters/postgresql_adapter.rb +188 -452
  92. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -47
  94. data/lib/active_record/connection_handling.rb +1 -1
  95. data/lib/active_record/core.rb +119 -22
  96. data/lib/active_record/counter_cache.rb +60 -6
  97. data/lib/active_record/enum.rb +9 -10
  98. data/lib/active_record/errors.rb +27 -26
  99. data/lib/active_record/explain.rb +1 -1
  100. data/lib/active_record/fixtures.rb +52 -45
  101. data/lib/active_record/gem_version.rb +3 -3
  102. data/lib/active_record/inheritance.rb +33 -8
  103. data/lib/active_record/integration.rb +4 -4
  104. data/lib/active_record/locking/optimistic.rb +34 -16
  105. data/lib/active_record/migration.rb +22 -32
  106. data/lib/active_record/migration/command_recorder.rb +19 -2
  107. data/lib/active_record/migration/join_table.rb +1 -1
  108. data/lib/active_record/model_schema.rb +39 -48
  109. data/lib/active_record/nested_attributes.rb +8 -18
  110. data/lib/active_record/persistence.rb +39 -22
  111. data/lib/active_record/query_cache.rb +3 -3
  112. data/lib/active_record/querying.rb +1 -8
  113. data/lib/active_record/railtie.rb +17 -10
  114. data/lib/active_record/railties/databases.rake +47 -42
  115. data/lib/active_record/readonly_attributes.rb +0 -1
  116. data/lib/active_record/reflection.rb +225 -92
  117. data/lib/active_record/relation.rb +35 -11
  118. data/lib/active_record/relation/batches.rb +0 -2
  119. data/lib/active_record/relation/calculations.rb +28 -32
  120. data/lib/active_record/relation/delegation.rb +1 -1
  121. data/lib/active_record/relation/finder_methods.rb +42 -20
  122. data/lib/active_record/relation/merger.rb +0 -1
  123. data/lib/active_record/relation/predicate_builder.rb +1 -22
  124. data/lib/active_record/relation/predicate_builder/array_handler.rb +16 -11
  125. data/lib/active_record/relation/predicate_builder/relation_handler.rb +0 -4
  126. data/lib/active_record/relation/query_methods.rb +98 -62
  127. data/lib/active_record/relation/spawn_methods.rb +6 -7
  128. data/lib/active_record/result.rb +16 -9
  129. data/lib/active_record/sanitization.rb +8 -1
  130. data/lib/active_record/schema.rb +0 -1
  131. data/lib/active_record/schema_dumper.rb +51 -9
  132. data/lib/active_record/schema_migration.rb +4 -0
  133. data/lib/active_record/scoping/default.rb +5 -4
  134. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  135. data/lib/active_record/statement_cache.rb +79 -5
  136. data/lib/active_record/store.rb +5 -5
  137. data/lib/active_record/tasks/database_tasks.rb +37 -5
  138. data/lib/active_record/tasks/mysql_database_tasks.rb +10 -16
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -2
  140. data/lib/active_record/timestamp.rb +9 -7
  141. data/lib/active_record/transactions.rb +35 -21
  142. data/lib/active_record/type.rb +20 -0
  143. data/lib/active_record/type/binary.rb +40 -0
  144. data/lib/active_record/type/boolean.rb +19 -0
  145. data/lib/active_record/type/date.rb +46 -0
  146. data/lib/active_record/type/date_time.rb +43 -0
  147. data/lib/active_record/type/decimal.rb +40 -0
  148. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  149. data/lib/active_record/type/float.rb +19 -0
  150. data/lib/active_record/type/hash_lookup_type_map.rb +19 -0
  151. data/lib/active_record/type/integer.rb +23 -0
  152. data/lib/active_record/type/mutable.rb +16 -0
  153. data/lib/active_record/type/numeric.rb +36 -0
  154. data/lib/active_record/type/serialized.rb +51 -0
  155. data/lib/active_record/type/string.rb +36 -0
  156. data/lib/active_record/type/text.rb +11 -0
  157. data/lib/active_record/type/time.rb +26 -0
  158. data/lib/active_record/type/time_value.rb +38 -0
  159. data/lib/active_record/type/type_map.rb +48 -0
  160. data/lib/active_record/type/value.rb +101 -0
  161. data/lib/active_record/validations.rb +21 -16
  162. data/lib/active_record/validations/uniqueness.rb +9 -23
  163. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  164. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  165. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  166. metadata +71 -14
  167. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -1,18 +1,27 @@
1
1
  module ActiveRecord
2
2
  module AttributeMethods
3
3
  module TimeZoneConversion
4
- class Type # :nodoc:
5
- def initialize(column)
6
- @column = column
4
+ class TimeZoneConverter < SimpleDelegator # :nodoc:
5
+ def type_cast_from_database(value)
6
+ convert_time_to_time_zone(super)
7
7
  end
8
8
 
9
- def type_cast(value)
10
- value = @column.type_cast(value)
11
- value.acts_like?(:time) ? value.in_time_zone : value
9
+ def type_cast_from_user(value)
10
+ if value.is_a?(Array)
11
+ value.map { |v| type_cast_from_user(v) }
12
+ elsif value.respond_to?(:in_time_zone)
13
+ value.in_time_zone
14
+ end
12
15
  end
13
16
 
14
- def type
15
- @column.type
17
+ def convert_time_to_time_zone(value)
18
+ if value.is_a?(Array)
19
+ value.map { |v| convert_time_to_time_zone(v) }
20
+ elsif value.acts_like?(:time)
21
+ value.in_time_zone
22
+ else
23
+ value
24
+ end
16
25
  end
17
26
  end
18
27
 
@@ -27,43 +36,26 @@ module ActiveRecord
27
36
  end
28
37
 
29
38
  module ClassMethods
30
- protected
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 = convert_value_to_time_zone("#{attr_name}", time)
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
39
+ private
40
+
41
+ def inherited(subclass)
42
+ # We need to apply this decorator here, rather than on module inclusion. The closure
43
+ # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
44
+ # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or
45
+ # `skip_time_zone_conversion_for_attributes` would not be picked up.
46
+ subclass.class_eval do
47
+ matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
48
+ decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type|
49
+ TimeZoneConverter.new(type)
50
+ end
47
51
  end
52
+ super
48
53
  end
49
54
 
50
- private
51
- def create_time_zone_conversion_attribute?(name, column)
55
+ def create_time_zone_conversion_attribute?(name, cast_type)
52
56
  time_zone_aware_attributes &&
53
57
  !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
54
- (:datetime == column.type || :timestamp == column.type)
55
- end
56
- end
57
-
58
- private
59
-
60
- def convert_value_to_time_zone(attr_name, value)
61
- if value.is_a?(Array)
62
- value.map { |v| convert_value_to_time_zone(attr_name, v) }
63
- elsif value.respond_to?(:in_time_zone)
64
- value.in_time_zone || self.class.columns_hash[attr_name].type_cast(value)
65
- else
66
- nil
58
+ (:datetime == cast_type.type)
67
59
  end
68
60
  end
69
61
  end
@@ -26,8 +26,6 @@ module ActiveRecord
26
26
  protected
27
27
 
28
28
  if Module.methods_transplantable?
29
- # See define_method_attribute in read.rb for an explanation of
30
- # this code.
31
29
  def define_method_attribute=(name)
32
30
  method = WriterMethodCache[name]
33
31
  generated_attribute_methods.module_eval {
@@ -55,11 +53,11 @@ module ActiveRecord
55
53
  # specified +value+. Empty strings for fixnum and float columns are
56
54
  # turned into +nil+.
57
55
  def write_attribute(attr_name, value)
58
- write_attribute_with_type_cast(attr_name, value, :type_cast_attribute_for_write)
56
+ write_attribute_with_type_cast(attr_name, value, true)
59
57
  end
60
58
 
61
59
  def raw_write_attribute(attr_name, value)
62
- write_attribute_with_type_cast(attr_name, value, :raw_type_cast_attribute_for_write)
60
+ write_attribute_with_type_cast(attr_name, value, false)
63
61
  end
64
62
 
65
63
  private
@@ -68,30 +66,17 @@ module ActiveRecord
68
66
  write_attribute(attribute_name, value)
69
67
  end
70
68
 
71
- def type_cast_attribute_for_write(column, value)
72
- return value unless column
73
-
74
- column.type_cast_for_write value
75
- end
76
- alias_method :raw_type_cast_attribute_for_write, :type_cast_attribute_for_write
77
-
78
- def write_attribute_with_type_cast(attr_name, value, type_cast_method)
69
+ def write_attribute_with_type_cast(attr_name, value, should_type_cast)
79
70
  attr_name = attr_name.to_s
80
71
  attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
81
- @attributes_cache.delete(attr_name)
82
- column = column_for_attribute(attr_name)
83
72
 
84
- # If we're dealing with a binary column, write the data to the cache
85
- # so we don't attempt to typecast multiple times.
86
- if column && column.binary?
87
- @attributes_cache[attr_name] = value
88
- end
89
-
90
- if column || @attributes.has_key?(attr_name)
91
- @attributes[attr_name] = send(type_cast_method, column, value)
73
+ if should_type_cast
74
+ @attributes.write_from_user(attr_name, value)
92
75
  else
93
- raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
76
+ @attributes.write_from_database(attr_name, value)
94
77
  end
78
+
79
+ value
95
80
  end
96
81
  end
97
82
  end
@@ -0,0 +1,77 @@
1
+ require 'active_record/attribute_set/builder'
2
+
3
+ module ActiveRecord
4
+ class AttributeSet # :nodoc:
5
+ delegate :keys, to: :initialized_attributes
6
+
7
+ def initialize(attributes)
8
+ @attributes = attributes
9
+ end
10
+
11
+ def [](name)
12
+ attributes[name] || Attribute.null(name)
13
+ end
14
+
15
+ def values_before_type_cast
16
+ attributes.transform_values(&:value_before_type_cast)
17
+ end
18
+
19
+ def to_hash
20
+ initialized_attributes.transform_values(&:value)
21
+ end
22
+ alias_method :to_h, :to_hash
23
+
24
+ def key?(name)
25
+ attributes.key?(name) && self[name].initialized?
26
+ end
27
+
28
+ def fetch_value(name, &block)
29
+ self[name].value(&block)
30
+ end
31
+
32
+ def write_from_database(name, value)
33
+ attributes[name] = self[name].with_value_from_database(value)
34
+ end
35
+
36
+ def write_from_user(name, value)
37
+ attributes[name] = self[name].with_value_from_user(value)
38
+ end
39
+
40
+ def freeze
41
+ @attributes.freeze
42
+ super
43
+ end
44
+
45
+ def initialize_dup(_)
46
+ @attributes = attributes.transform_values(&:dup)
47
+ super
48
+ end
49
+
50
+ def initialize_clone(_)
51
+ @attributes = attributes.clone
52
+ super
53
+ end
54
+
55
+ def reset(key)
56
+ if key?(key)
57
+ write_from_database(key, nil)
58
+ end
59
+ end
60
+
61
+ def ensure_initialized(key)
62
+ unless self[key].initialized?
63
+ write_from_database(key, nil)
64
+ end
65
+ end
66
+
67
+ protected
68
+
69
+ attr_reader :attributes
70
+
71
+ private
72
+
73
+ def initialized_attributes
74
+ attributes.select { |_, attr| attr.initialized? }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,32 @@
1
+ module ActiveRecord
2
+ class AttributeSet # :nodoc:
3
+ class Builder # :nodoc:
4
+ attr_reader :types
5
+
6
+ def initialize(types)
7
+ @types = types
8
+ end
9
+
10
+ def build_from_database(values = {}, additional_types = {})
11
+ attributes = build_attributes_from_values(values, additional_types)
12
+ add_uninitialized_attributes(attributes)
13
+ AttributeSet.new(attributes)
14
+ end
15
+
16
+ private
17
+
18
+ def build_attributes_from_values(values, additional_types)
19
+ values.each_with_object({}) do |(name, value), hash|
20
+ type = additional_types.fetch(name, types[name])
21
+ hash[name] = Attribute.from_database(name, value, type)
22
+ end
23
+ end
24
+
25
+ def add_uninitialized_attributes(attributes)
26
+ types.except(*attributes.keys).each do |name, type|
27
+ attributes[name] = Attribute.uninitialized(name, type)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,122 @@
1
+ module ActiveRecord
2
+ module Attributes # :nodoc:
3
+ extend ActiveSupport::Concern
4
+
5
+ Type = ActiveRecord::Type
6
+
7
+ included do
8
+ class_attribute :user_provided_columns, instance_accessor: false # :internal:
9
+ self.user_provided_columns = {}
10
+ end
11
+
12
+ module ClassMethods # :nodoc:
13
+ # Defines or overrides a attribute on this model. This allows customization of
14
+ # Active Record's type casting behavior, as well as adding support for user defined
15
+ # types.
16
+ #
17
+ # +name+ The name of the methods to define attribute methods for, and the column which
18
+ # this will persist to.
19
+ #
20
+ # +cast_type+ A type object that contains information about how to type cast the value.
21
+ # See the examples section for more information.
22
+ #
23
+ # ==== Options
24
+ # The options hash accepts the following options:
25
+ #
26
+ # +default+ is the default value that the column should use on a new record.
27
+ #
28
+ # ==== Examples
29
+ #
30
+ # The type detected by Active Record can be overridden.
31
+ #
32
+ # # db/schema.rb
33
+ # create_table :store_listings, force: true do |t|
34
+ # t.decimal :price_in_cents
35
+ # end
36
+ #
37
+ # # app/models/store_listing.rb
38
+ # class StoreListing < ActiveRecord::Base
39
+ # end
40
+ #
41
+ # store_listing = StoreListing.new(price_in_cents: '10.1')
42
+ #
43
+ # # before
44
+ # store_listing.price_in_cents # => BigDecimal.new(10.1)
45
+ #
46
+ # class StoreListing < ActiveRecord::Base
47
+ # attribute :price_in_cents, Type::Integer.new
48
+ # end
49
+ #
50
+ # # after
51
+ # store_listing.price_in_cents # => 10
52
+ #
53
+ # Users may also define their own custom types, as long as they respond to the methods
54
+ # defined on the value type. The `type_cast` method on your type object will be called
55
+ # with values both from the database, and from your controllers. See
56
+ # `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your
57
+ # type objects inherit from an existing type, or the base value type.
58
+ #
59
+ # class MoneyType < ActiveRecord::Type::Integer
60
+ # def type_cast(value)
61
+ # if value.include?('$')
62
+ # price_in_dollars = value.gsub(/\$/, '').to_f
63
+ # price_in_dollars * 100
64
+ # else
65
+ # value.to_i
66
+ # end
67
+ # end
68
+ # end
69
+ #
70
+ # class StoreListing < ActiveRecord::Base
71
+ # attribute :price_in_cents, MoneyType.new
72
+ # end
73
+ #
74
+ # store_listing = StoreListing.new(price_in_cents: '$10.00')
75
+ # store_listing.price_in_cents # => 1000
76
+ def attribute(name, cast_type, options = {})
77
+ name = name.to_s
78
+ clear_caches_calculated_from_columns
79
+ # Assign a new hash to ensure that subclasses do not share a hash
80
+ self.user_provided_columns = user_provided_columns.merge(name => connection.new_column(name, options[:default], cast_type))
81
+ end
82
+
83
+ # Returns an array of column objects for the table associated with this class.
84
+ def columns
85
+ @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name))
86
+ end
87
+
88
+ # Returns a hash of column objects for the table associated with this class.
89
+ def columns_hash
90
+ @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
91
+ end
92
+
93
+ def reset_column_information # :nodoc:
94
+ super
95
+ clear_caches_calculated_from_columns
96
+ end
97
+
98
+ private
99
+
100
+ def add_user_provided_columns(schema_columns)
101
+ existing_columns = schema_columns.map do |column|
102
+ user_provided_columns[column.name] || column
103
+ end
104
+
105
+ existing_column_names = existing_columns.map(&:name)
106
+ new_columns = user_provided_columns.except(*existing_column_names).values
107
+
108
+ existing_columns + new_columns
109
+ end
110
+
111
+ def clear_caches_calculated_from_columns
112
+ @attributes_builder = nil
113
+ @column_names = nil
114
+ @column_types = nil
115
+ @columns = nil
116
+ @columns_hash = nil
117
+ @content_columns = nil
118
+ @default_attributes = nil
119
+ end
120
+ end
121
+ end
122
+ end
@@ -177,15 +177,15 @@ module ActiveRecord
177
177
  # before actually defining them.
178
178
  def add_autosave_association_callbacks(reflection)
179
179
  save_method = :"autosave_associated_records_for_#{reflection.name}"
180
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
181
+ collection = reflection.collection?
180
182
 
181
- if reflection.collection?
183
+ if collection
182
184
  before_save :before_save_collection_association
183
185
 
184
186
  define_non_cyclic_method(save_method) { save_collection_association(reflection) }
185
- # Doesn't use after_save as that would save associations added in after_create/after_update twice
186
- after_create save_method
187
- after_update save_method
188
- elsif reflection.macro == :has_one
187
+ after_save save_method
188
+ elsif reflection.has_one?
189
189
  define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method)
190
190
  # Configures two callbacks instead of a single after_save so that
191
191
  # the model may rely on their execution order relative to its
@@ -202,22 +202,9 @@ module ActiveRecord
202
202
  before_save save_method
203
203
  end
204
204
 
205
- define_autosave_validation_callbacks(reflection)
206
- end
207
-
208
- def define_autosave_validation_callbacks(reflection)
209
- validation_method = :"validate_associated_records_for_#{reflection.name}"
210
205
  if reflection.validate? && !method_defined?(validation_method)
211
- if reflection.collection?
212
- method = :validate_collection_association
213
- else
214
- method = :validate_single_association
215
- end
216
-
217
- define_non_cyclic_method(validation_method) do
218
- send(method, reflection)
219
- true
220
- end
206
+ method = (collection ? :validate_collection_association : :validate_single_association)
207
+ define_non_cyclic_method(validation_method) { send(method, reflection) }
221
208
  validate validation_method
222
209
  end
223
210
  end
@@ -316,7 +303,8 @@ module ActiveRecord
316
303
  def association_valid?(reflection, record)
317
304
  return true if record.destroyed? || record.marked_for_destruction?
318
305
 
319
- unless valid = record.valid?
306
+ validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
307
+ unless valid = record.valid?(validation_context)
320
308
  if reflection.options[:autosave]
321
309
  record.errors.each do |attribute, message|
322
310
  attribute = "#{reflection.name}.#{attribute}"
@@ -350,6 +338,7 @@ module ActiveRecord
350
338
  autosave = reflection.options[:autosave]
351
339
 
352
340
  if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
341
+
353
342
  if autosave
354
343
  records_to_destroy = records.select(&:marked_for_destruction?)
355
344
  records_to_destroy.each { |record| association.destroy(record) }
@@ -373,6 +362,7 @@ module ActiveRecord
373
362
 
374
363
  raise ActiveRecord::Rollback unless saved
375
364
  end
365
+ @new_record_before_save = false
376
366
  end
377
367
 
378
368
  # reconstruct the scope now that we know the owner's id