activerecord 4.1.15 → 4.2.11.3

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 (185) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1162 -1792
  3. data/README.rdoc +15 -10
  4. data/lib/active_record.rb +4 -0
  5. data/lib/active_record/aggregations.rb +15 -8
  6. data/lib/active_record/association_relation.rb +13 -0
  7. data/lib/active_record/associations.rb +158 -49
  8. data/lib/active_record/associations/alias_tracker.rb +3 -12
  9. data/lib/active_record/associations/association.rb +16 -4
  10. data/lib/active_record/associations/association_scope.rb +83 -38
  11. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  12. data/lib/active_record/associations/builder/association.rb +15 -4
  13. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  14. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
  16. data/lib/active_record/associations/builder/has_many.rb +1 -1
  17. data/lib/active_record/associations/builder/has_one.rb +2 -2
  18. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  19. data/lib/active_record/associations/collection_association.rb +63 -27
  20. data/lib/active_record/associations/collection_proxy.rb +29 -35
  21. data/lib/active_record/associations/foreign_association.rb +11 -0
  22. data/lib/active_record/associations/has_many_association.rb +83 -22
  23. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  24. data/lib/active_record/associations/has_one_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -13
  26. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  27. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  28. data/lib/active_record/associations/preloader.rb +36 -26
  29. data/lib/active_record/associations/preloader/association.rb +14 -11
  30. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  31. data/lib/active_record/associations/singular_association.rb +17 -2
  32. data/lib/active_record/associations/through_association.rb +5 -12
  33. data/lib/active_record/attribute.rb +163 -0
  34. data/lib/active_record/attribute_assignment.rb +19 -11
  35. data/lib/active_record/attribute_decorators.rb +66 -0
  36. data/lib/active_record/attribute_methods.rb +56 -94
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  38. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  39. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  40. data/lib/active_record/attribute_methods/query.rb +1 -1
  41. data/lib/active_record/attribute_methods/read.rb +22 -59
  42. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
  44. data/lib/active_record/attribute_methods/write.rb +9 -24
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attribute_set/builder.rb +106 -0
  47. data/lib/active_record/attributes.rb +147 -0
  48. data/lib/active_record/autosave_association.rb +19 -12
  49. data/lib/active_record/base.rb +13 -24
  50. data/lib/active_record/callbacks.rb +6 -6
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  55. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
  63. data/lib/active_record/connection_adapters/column.rb +29 -240
  64. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  68. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  71. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  99. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
  100. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  101. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  102. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  103. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  104. data/lib/active_record/connection_handling.rb +1 -1
  105. data/lib/active_record/core.rb +163 -39
  106. data/lib/active_record/counter_cache.rb +60 -6
  107. data/lib/active_record/enum.rb +9 -11
  108. data/lib/active_record/errors.rb +53 -30
  109. data/lib/active_record/explain.rb +1 -1
  110. data/lib/active_record/explain_subscriber.rb +1 -1
  111. data/lib/active_record/fixtures.rb +55 -69
  112. data/lib/active_record/gem_version.rb +4 -4
  113. data/lib/active_record/inheritance.rb +35 -10
  114. data/lib/active_record/integration.rb +4 -4
  115. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  116. data/lib/active_record/locking/optimistic.rb +46 -26
  117. data/lib/active_record/migration.rb +71 -46
  118. data/lib/active_record/migration/command_recorder.rb +19 -2
  119. data/lib/active_record/migration/join_table.rb +1 -1
  120. data/lib/active_record/model_schema.rb +52 -58
  121. data/lib/active_record/nested_attributes.rb +5 -5
  122. data/lib/active_record/no_touching.rb +1 -1
  123. data/lib/active_record/persistence.rb +46 -26
  124. data/lib/active_record/query_cache.rb +3 -3
  125. data/lib/active_record/querying.rb +10 -7
  126. data/lib/active_record/railtie.rb +18 -11
  127. data/lib/active_record/railties/databases.rake +50 -51
  128. data/lib/active_record/readonly_attributes.rb +0 -1
  129. data/lib/active_record/reflection.rb +273 -114
  130. data/lib/active_record/relation.rb +57 -25
  131. data/lib/active_record/relation/batches.rb +0 -2
  132. data/lib/active_record/relation/calculations.rb +41 -37
  133. data/lib/active_record/relation/finder_methods.rb +70 -47
  134. data/lib/active_record/relation/merger.rb +39 -29
  135. data/lib/active_record/relation/predicate_builder.rb +16 -8
  136. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  137. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
  138. data/lib/active_record/relation/query_methods.rb +114 -65
  139. data/lib/active_record/relation/spawn_methods.rb +3 -0
  140. data/lib/active_record/result.rb +18 -7
  141. data/lib/active_record/sanitization.rb +12 -2
  142. data/lib/active_record/schema.rb +0 -1
  143. data/lib/active_record/schema_dumper.rb +59 -28
  144. data/lib/active_record/schema_migration.rb +5 -4
  145. data/lib/active_record/scoping/default.rb +6 -4
  146. data/lib/active_record/scoping/named.rb +4 -0
  147. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  148. data/lib/active_record/statement_cache.rb +95 -10
  149. data/lib/active_record/store.rb +5 -5
  150. data/lib/active_record/tasks/database_tasks.rb +61 -6
  151. data/lib/active_record/tasks/mysql_database_tasks.rb +32 -17
  152. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  153. data/lib/active_record/timestamp.rb +9 -7
  154. data/lib/active_record/transactions.rb +53 -27
  155. data/lib/active_record/type.rb +23 -0
  156. data/lib/active_record/type/big_integer.rb +13 -0
  157. data/lib/active_record/type/binary.rb +50 -0
  158. data/lib/active_record/type/boolean.rb +31 -0
  159. data/lib/active_record/type/date.rb +50 -0
  160. data/lib/active_record/type/date_time.rb +54 -0
  161. data/lib/active_record/type/decimal.rb +64 -0
  162. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  163. data/lib/active_record/type/decorator.rb +14 -0
  164. data/lib/active_record/type/float.rb +19 -0
  165. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  166. data/lib/active_record/type/integer.rb +59 -0
  167. data/lib/active_record/type/mutable.rb +16 -0
  168. data/lib/active_record/type/numeric.rb +36 -0
  169. data/lib/active_record/type/serialized.rb +62 -0
  170. data/lib/active_record/type/string.rb +40 -0
  171. data/lib/active_record/type/text.rb +11 -0
  172. data/lib/active_record/type/time.rb +26 -0
  173. data/lib/active_record/type/time_value.rb +38 -0
  174. data/lib/active_record/type/type_map.rb +64 -0
  175. data/lib/active_record/type/unsigned_integer.rb +15 -0
  176. data/lib/active_record/type/value.rb +110 -0
  177. data/lib/active_record/validations.rb +25 -19
  178. data/lib/active_record/validations/associated.rb +5 -3
  179. data/lib/active_record/validations/presence.rb +5 -3
  180. data/lib/active_record/validations/uniqueness.rb +25 -29
  181. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  182. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +66 -11
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -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 {
@@ -52,14 +50,14 @@ module ActiveRecord
52
50
  end
53
51
 
54
52
  # Updates the attribute identified by <tt>attr_name</tt> with the
55
- # specified +value+. Empty strings for fixnum and float columns are
53
+ # specified +value+. Empty strings for Integer 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_cast_value(attr_name, value)
94
77
  end
78
+
79
+ value
95
80
  end
96
81
  end
97
82
  end
@@ -0,0 +1,81 @@
1
+ require 'active_record/attribute_set/builder'
2
+
3
+ module ActiveRecord
4
+ class AttributeSet # :nodoc:
5
+ def initialize(attributes)
6
+ @attributes = attributes
7
+ end
8
+
9
+ def [](name)
10
+ attributes[name] || Attribute.null(name)
11
+ end
12
+
13
+ def values_before_type_cast
14
+ attributes.transform_values(&:value_before_type_cast)
15
+ end
16
+
17
+ def to_hash
18
+ initialized_attributes.transform_values(&:value)
19
+ end
20
+ alias_method :to_h, :to_hash
21
+
22
+ def key?(name)
23
+ attributes.key?(name) && self[name].initialized?
24
+ end
25
+
26
+ def keys
27
+ attributes.initialized_keys
28
+ end
29
+
30
+ def fetch_value(name)
31
+ self[name].value { |n| yield n if block_given? }
32
+ end
33
+
34
+ def write_from_database(name, value)
35
+ attributes[name] = self[name].with_value_from_database(value)
36
+ end
37
+
38
+ def write_from_user(name, value)
39
+ attributes[name] = self[name].with_value_from_user(value)
40
+ end
41
+
42
+ def write_cast_value(name, value)
43
+ attributes[name] = self[name].with_cast_value(value)
44
+ end
45
+
46
+ def freeze
47
+ @attributes.freeze
48
+ super
49
+ end
50
+
51
+ def initialize_dup(_)
52
+ @attributes = attributes.dup
53
+ super
54
+ end
55
+
56
+ def initialize_clone(_)
57
+ @attributes = attributes.clone
58
+ super
59
+ end
60
+
61
+ def reset(key)
62
+ if key?(key)
63
+ write_from_database(key, nil)
64
+ end
65
+ end
66
+
67
+ def ==(other)
68
+ attributes == other.attributes
69
+ end
70
+
71
+ protected
72
+
73
+ attr_reader :attributes
74
+
75
+ private
76
+
77
+ def initialized_attributes
78
+ attributes.select { |_, attr| attr.initialized? }
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,106 @@
1
+ require 'active_record/attribute'
2
+
3
+ module ActiveRecord
4
+ class AttributeSet # :nodoc:
5
+ class Builder # :nodoc:
6
+ attr_reader :types, :always_initialized
7
+
8
+ def initialize(types, always_initialized = nil)
9
+ @types = types
10
+ @always_initialized = always_initialized
11
+ end
12
+
13
+ def build_from_database(values = {}, additional_types = {})
14
+ if always_initialized && !values.key?(always_initialized)
15
+ values[always_initialized] = nil
16
+ end
17
+
18
+ attributes = LazyAttributeHash.new(types, values, additional_types)
19
+ AttributeSet.new(attributes)
20
+ end
21
+ end
22
+ end
23
+
24
+ class LazyAttributeHash # :nodoc:
25
+ delegate :transform_values, to: :materialize
26
+
27
+ def initialize(types, values, additional_types)
28
+ @types = types
29
+ @values = values
30
+ @additional_types = additional_types
31
+ @materialized = false
32
+ @delegate_hash = {}
33
+ end
34
+
35
+ def key?(key)
36
+ delegate_hash.key?(key) || values.key?(key) || types.key?(key)
37
+ end
38
+
39
+ def [](key)
40
+ delegate_hash[key] || assign_default_value(key)
41
+ end
42
+
43
+ def []=(key, value)
44
+ if frozen?
45
+ raise RuntimeError, "Can't modify frozen hash"
46
+ end
47
+ delegate_hash[key] = value
48
+ end
49
+
50
+ def initialized_keys
51
+ delegate_hash.keys | values.keys
52
+ end
53
+
54
+ def initialize_dup(_)
55
+ @delegate_hash = delegate_hash.transform_values(&:dup)
56
+ super
57
+ end
58
+
59
+ def select
60
+ keys = types.keys | values.keys | delegate_hash.keys
61
+ keys.each_with_object({}) do |key, hash|
62
+ attribute = self[key]
63
+ if yield(key, attribute)
64
+ hash[key] = attribute
65
+ end
66
+ end
67
+ end
68
+
69
+ def ==(other)
70
+ if other.is_a?(LazyAttributeHash)
71
+ materialize == other.materialize
72
+ else
73
+ materialize == other
74
+ end
75
+ end
76
+
77
+ protected
78
+
79
+ attr_reader :types, :values, :additional_types, :delegate_hash
80
+
81
+ def materialize
82
+ unless @materialized
83
+ values.each_key { |key| self[key] }
84
+ types.each_key { |key| self[key] }
85
+ unless frozen?
86
+ @materialized = true
87
+ end
88
+ end
89
+ delegate_hash
90
+ end
91
+
92
+ private
93
+
94
+ def assign_default_value(name)
95
+ type = additional_types.fetch(name, types[name])
96
+ value_present = true
97
+ value = values.fetch(name) { value_present = false }
98
+
99
+ if value_present
100
+ delegate_hash[name] = Attribute.from_database(name, value, type)
101
+ elsif types.key?(name)
102
+ delegate_hash[name] = Attribute.uninitialized(name, type)
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,147 @@
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
+ class_attribute :user_provided_defaults, instance_accessor: false # :internal:
10
+ self.user_provided_columns = {}
11
+ self.user_provided_defaults = {}
12
+
13
+ delegate :persistable_attribute_names, to: :class
14
+ end
15
+
16
+ module ClassMethods # :nodoc:
17
+ # Defines or overrides a attribute on this model. This allows customization of
18
+ # Active Record's type casting behavior, as well as adding support for user defined
19
+ # types.
20
+ #
21
+ # +name+ The name of the methods to define attribute methods for, and the column which
22
+ # this will persist to.
23
+ #
24
+ # +cast_type+ A type object that contains information about how to type cast the value.
25
+ # See the examples section for more information.
26
+ #
27
+ # ==== Options
28
+ # The options hash accepts the following options:
29
+ #
30
+ # +default+ is the default value that the column should use on a new record.
31
+ #
32
+ # ==== Examples
33
+ #
34
+ # The type detected by Active Record can be overridden.
35
+ #
36
+ # # db/schema.rb
37
+ # create_table :store_listings, force: true do |t|
38
+ # t.decimal :price_in_cents
39
+ # end
40
+ #
41
+ # # app/models/store_listing.rb
42
+ # class StoreListing < ActiveRecord::Base
43
+ # end
44
+ #
45
+ # store_listing = StoreListing.new(price_in_cents: '10.1')
46
+ #
47
+ # # before
48
+ # store_listing.price_in_cents # => BigDecimal.new(10.1)
49
+ #
50
+ # class StoreListing < ActiveRecord::Base
51
+ # attribute :price_in_cents, Type::Integer.new
52
+ # end
53
+ #
54
+ # # after
55
+ # store_listing.price_in_cents # => 10
56
+ #
57
+ # Users may also define their own custom types, as long as they respond to the methods
58
+ # defined on the value type. The `type_cast` method on your type object will be called
59
+ # with values both from the database, and from your controllers. See
60
+ # `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your
61
+ # type objects inherit from an existing type, or the base value type.
62
+ #
63
+ # class MoneyType < ActiveRecord::Type::Integer
64
+ # def type_cast(value)
65
+ # if value.include?('$')
66
+ # price_in_dollars = value.gsub(/\$/, '').to_f
67
+ # price_in_dollars * 100
68
+ # else
69
+ # value.to_i
70
+ # end
71
+ # end
72
+ # end
73
+ #
74
+ # class StoreListing < ActiveRecord::Base
75
+ # attribute :price_in_cents, MoneyType.new
76
+ # end
77
+ #
78
+ # store_listing = StoreListing.new(price_in_cents: '$10.00')
79
+ # store_listing.price_in_cents # => 1000
80
+ def attribute(name, cast_type, options = {})
81
+ name = name.to_s
82
+ clear_caches_calculated_from_columns
83
+ # Assign a new hash to ensure that subclasses do not share a hash
84
+ self.user_provided_columns = user_provided_columns.merge(name => cast_type)
85
+
86
+ if options.key?(:default)
87
+ self.user_provided_defaults = user_provided_defaults.merge(name => options[:default])
88
+ end
89
+ end
90
+
91
+ # Returns an array of column objects for the table associated with this class.
92
+ def columns
93
+ @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name))
94
+ end
95
+
96
+ # Returns a hash of column objects for the table associated with this class.
97
+ def columns_hash
98
+ @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
99
+ end
100
+
101
+ def persistable_attribute_names # :nodoc:
102
+ @persistable_attribute_names ||= connection.schema_cache.columns_hash(table_name).keys
103
+ end
104
+
105
+ def reset_column_information # :nodoc:
106
+ super
107
+ clear_caches_calculated_from_columns
108
+ end
109
+
110
+ private
111
+
112
+ def add_user_provided_columns(schema_columns)
113
+ existing_columns = schema_columns.map do |column|
114
+ new_type = user_provided_columns[column.name]
115
+ if new_type
116
+ column.with_type(new_type)
117
+ else
118
+ column
119
+ end
120
+ end
121
+
122
+ existing_column_names = existing_columns.map(&:name)
123
+ new_columns = user_provided_columns.except(*existing_column_names).map do |(name, type)|
124
+ connection.new_column(name, nil, type)
125
+ end
126
+
127
+ existing_columns + new_columns
128
+ end
129
+
130
+ def clear_caches_calculated_from_columns
131
+ @attributes_builder = nil
132
+ @column_names = nil
133
+ @column_types = nil
134
+ @columns = nil
135
+ @columns_hash = nil
136
+ @content_columns = nil
137
+ @default_attributes = nil
138
+ @persistable_attribute_names = nil
139
+ @attribute_names = nil
140
+ end
141
+
142
+ def raw_default_values
143
+ super.merge(user_provided_defaults)
144
+ end
145
+ end
146
+ end
147
+ end
@@ -185,7 +185,7 @@ module ActiveRecord
185
185
  # Doesn't use after_save as that would save associations added in after_create/after_update twice
186
186
  after_create save_method
187
187
  after_update save_method
188
- elsif reflection.macro == :has_one
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
@@ -214,10 +214,7 @@ module ActiveRecord
214
214
  method = :validate_single_association
215
215
  end
216
216
 
217
- define_non_cyclic_method(validation_method) do
218
- send(method, reflection)
219
- true
220
- end
217
+ define_non_cyclic_method(validation_method) { send(method, reflection) }
221
218
  validate validation_method
222
219
  end
223
220
  end
@@ -283,11 +280,18 @@ module ActiveRecord
283
280
  # go through nested autosave associations that are loaded in memory (without loading
284
281
  # any new ones), and return true if is changed for autosave
285
282
  def nested_records_changed_for_autosave?
286
- self.class._reflections.values.any? do |reflection|
287
- if reflection.options[:autosave]
288
- association = association_instance_get(reflection.name)
289
- association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
283
+ @_nested_records_changed_for_autosave_already_called ||= false
284
+ return false if @_nested_records_changed_for_autosave_already_called
285
+ begin
286
+ @_nested_records_changed_for_autosave_already_called = true
287
+ self.class._reflections.values.any? do |reflection|
288
+ if reflection.options[:autosave]
289
+ association = association_instance_get(reflection.name)
290
+ association && Array.wrap(association.target).any?(&:changed_for_autosave?)
291
+ end
290
292
  end
293
+ ensure
294
+ @_nested_records_changed_for_autosave_already_called = false
291
295
  end
292
296
  end
293
297
 
@@ -314,9 +318,10 @@ module ActiveRecord
314
318
  # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
315
319
  # enabled records if they're marked_for_destruction? or destroyed.
316
320
  def association_valid?(reflection, record)
317
- return true if record.destroyed? || record.marked_for_destruction?
321
+ return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
318
322
 
319
- unless valid = record.valid?
323
+ validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
324
+ unless valid = record.valid?(validation_context)
320
325
  if reflection.options[:autosave]
321
326
  record.errors.each do |attribute, message|
322
327
  attribute = "#{reflection.name}.#{attribute}"
@@ -415,7 +420,9 @@ module ActiveRecord
415
420
 
416
421
  # If the record is new or it has changed, returns true.
417
422
  def record_changed?(reflection, record, key)
418
- record.new_record? || record[reflection.foreign_key] != key || record.attribute_changed?(reflection.foreign_key)
423
+ record.new_record? ||
424
+ (record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) ||
425
+ record.attribute_changed?(reflection.foreign_key)
419
426
  end
420
427
 
421
428
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.