activerecord 4.1.15 → 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 -2176
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +12 -8
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/association_scope.rb +53 -21
  7. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  8. data/lib/active_record/associations/builder/association.rb +16 -5
  9. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  10. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -11
  11. data/lib/active_record/associations/builder/has_one.rb +2 -2
  12. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  13. data/lib/active_record/associations/collection_association.rb +32 -44
  14. data/lib/active_record/associations/collection_proxy.rb +1 -10
  15. data/lib/active_record/associations/has_many_association.rb +60 -14
  16. data/lib/active_record/associations/has_many_through_association.rb +34 -23
  17. data/lib/active_record/associations/has_one_association.rb +0 -1
  18. data/lib/active_record/associations/join_dependency/join_association.rb +18 -14
  19. data/lib/active_record/associations/join_dependency.rb +7 -9
  20. data/lib/active_record/associations/preloader/association.rb +9 -5
  21. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  22. data/lib/active_record/associations/preloader.rb +2 -2
  23. data/lib/active_record/associations/singular_association.rb +16 -1
  24. data/lib/active_record/associations/through_association.rb +6 -22
  25. data/lib/active_record/associations.rb +58 -33
  26. data/lib/active_record/attribute.rb +131 -0
  27. data/lib/active_record/attribute_assignment.rb +19 -11
  28. data/lib/active_record/attribute_decorators.rb +66 -0
  29. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  30. data/lib/active_record/attribute_methods/dirty.rb +85 -42
  31. data/lib/active_record/attribute_methods/primary_key.rb +6 -8
  32. data/lib/active_record/attribute_methods/read.rb +14 -57
  33. data/lib/active_record/attribute_methods/serialization.rb +12 -146
  34. data/lib/active_record/attribute_methods/time_zone_conversion.rb +32 -40
  35. data/lib/active_record/attribute_methods/write.rb +8 -23
  36. data/lib/active_record/attribute_methods.rb +53 -90
  37. data/lib/active_record/attribute_set/builder.rb +32 -0
  38. data/lib/active_record/attribute_set.rb +77 -0
  39. data/lib/active_record/attributes.rb +122 -0
  40. data/lib/active_record/autosave_association.rb +11 -21
  41. data/lib/active_record/base.rb +9 -19
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +69 -45
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -42
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -60
  45. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +37 -2
  46. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +102 -21
  47. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +9 -33
  48. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +178 -55
  49. data/lib/active_record/connection_adapters/abstract/transaction.rb +120 -115
  50. data/lib/active_record/connection_adapters/abstract_adapter.rb +143 -57
  51. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +156 -107
  52. data/lib/active_record/connection_adapters/column.rb +13 -244
  53. data/lib/active_record/connection_adapters/connection_specification.rb +6 -20
  54. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -15
  55. data/lib/active_record/connection_adapters/mysql_adapter.rb +55 -143
  56. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  57. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  58. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -20
  59. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +96 -0
  60. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  61. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  62. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  64. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  67. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +76 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +85 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  85. data/lib/active_record/connection_adapters/postgresql/quoting.rb +42 -122
  86. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  87. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +154 -0
  88. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +86 -34
  89. data/lib/active_record/connection_adapters/postgresql/utils.rb +66 -0
  90. data/lib/active_record/connection_adapters/postgresql_adapter.rb +188 -452
  91. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -47
  93. data/lib/active_record/connection_handling.rb +1 -1
  94. data/lib/active_record/core.rb +119 -22
  95. data/lib/active_record/counter_cache.rb +60 -6
  96. data/lib/active_record/enum.rb +9 -10
  97. data/lib/active_record/errors.rb +27 -26
  98. data/lib/active_record/explain.rb +1 -1
  99. data/lib/active_record/fixtures.rb +52 -45
  100. data/lib/active_record/gem_version.rb +3 -3
  101. data/lib/active_record/inheritance.rb +33 -8
  102. data/lib/active_record/integration.rb +4 -4
  103. data/lib/active_record/locking/optimistic.rb +34 -16
  104. data/lib/active_record/migration/command_recorder.rb +19 -2
  105. data/lib/active_record/migration/join_table.rb +1 -1
  106. data/lib/active_record/migration.rb +22 -32
  107. data/lib/active_record/model_schema.rb +39 -48
  108. data/lib/active_record/nested_attributes.rb +8 -18
  109. data/lib/active_record/persistence.rb +39 -22
  110. data/lib/active_record/query_cache.rb +3 -3
  111. data/lib/active_record/querying.rb +1 -8
  112. data/lib/active_record/railtie.rb +17 -10
  113. data/lib/active_record/railties/databases.rake +47 -42
  114. data/lib/active_record/readonly_attributes.rb +0 -1
  115. data/lib/active_record/reflection.rb +225 -92
  116. data/lib/active_record/relation/batches.rb +0 -2
  117. data/lib/active_record/relation/calculations.rb +28 -32
  118. data/lib/active_record/relation/delegation.rb +1 -1
  119. data/lib/active_record/relation/finder_methods.rb +42 -20
  120. data/lib/active_record/relation/merger.rb +0 -1
  121. data/lib/active_record/relation/predicate_builder/array_handler.rb +16 -11
  122. data/lib/active_record/relation/predicate_builder/relation_handler.rb +0 -4
  123. data/lib/active_record/relation/predicate_builder.rb +1 -22
  124. data/lib/active_record/relation/query_methods.rb +98 -62
  125. data/lib/active_record/relation/spawn_methods.rb +6 -7
  126. data/lib/active_record/relation.rb +35 -11
  127. data/lib/active_record/result.rb +16 -9
  128. data/lib/active_record/sanitization.rb +8 -1
  129. data/lib/active_record/schema.rb +0 -1
  130. data/lib/active_record/schema_dumper.rb +51 -9
  131. data/lib/active_record/schema_migration.rb +4 -0
  132. data/lib/active_record/scoping/default.rb +5 -4
  133. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  134. data/lib/active_record/statement_cache.rb +79 -5
  135. data/lib/active_record/store.rb +5 -5
  136. data/lib/active_record/tasks/database_tasks.rb +37 -5
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  138. data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -2
  139. data/lib/active_record/timestamp.rb +9 -7
  140. data/lib/active_record/transactions.rb +35 -21
  141. data/lib/active_record/type/binary.rb +40 -0
  142. data/lib/active_record/type/boolean.rb +19 -0
  143. data/lib/active_record/type/date.rb +46 -0
  144. data/lib/active_record/type/date_time.rb +43 -0
  145. data/lib/active_record/type/decimal.rb +40 -0
  146. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  147. data/lib/active_record/type/float.rb +19 -0
  148. data/lib/active_record/type/hash_lookup_type_map.rb +19 -0
  149. data/lib/active_record/type/integer.rb +23 -0
  150. data/lib/active_record/type/mutable.rb +16 -0
  151. data/lib/active_record/type/numeric.rb +36 -0
  152. data/lib/active_record/type/serialized.rb +51 -0
  153. data/lib/active_record/type/string.rb +36 -0
  154. data/lib/active_record/type/text.rb +11 -0
  155. data/lib/active_record/type/time.rb +26 -0
  156. data/lib/active_record/type/time_value.rb +38 -0
  157. data/lib/active_record/type/type_map.rb +48 -0
  158. data/lib/active_record/type/value.rb +101 -0
  159. data/lib/active_record/type.rb +20 -0
  160. data/lib/active_record/validations/uniqueness.rb +9 -23
  161. data/lib/active_record/validations.rb +21 -16
  162. data/lib/active_record.rb +2 -1
  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
@@ -34,28 +34,43 @@ module ActiveRecord
34
34
  # <tt>reload</tt> the record and clears changed attributes.
35
35
  def reload(*)
36
36
  super.tap do
37
- reset_changes
37
+ clear_changes_information
38
38
  end
39
39
  end
40
40
 
41
- def initialize_dup(other) # :nodoc:
42
- super
43
- init_changed_attributes
44
- end
41
+ def initialize_dup(other) # :nodoc:
42
+ super
43
+ calculate_changes_from_defaults
44
+ end
45
+
46
+ def changed?
47
+ super || changed_in_place.any?
48
+ end
49
+
50
+ def changed
51
+ super | changed_in_place
52
+ end
53
+
54
+ def changes_applied
55
+ super
56
+ store_original_raw_attributes
57
+ end
45
58
 
46
- private
47
- def initialize_internals_callback
59
+ def clear_changes_information
48
60
  super
49
- init_changed_attributes
61
+ original_raw_attributes.clear
62
+ end
63
+
64
+ def changed_attributes
65
+ super.reverse_merge(attributes_changed_in_place).freeze
50
66
  end
51
67
 
52
- def init_changed_attributes
68
+ private
69
+
70
+ def calculate_changes_from_defaults
53
71
  @changed_attributes = nil
54
- # Intentionally avoid using #column_defaults since overridden defaults (as is done in
55
- # optimistic locking) won't get written unless they get marked as changed
56
- self.class.columns.each do |c|
57
- attr, orig_value = c.name, c.default
58
- changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
72
+ self.class.column_defaults.each do |attr, orig_value|
73
+ set_attribute_was(attr, orig_value) if _field_changed?(attr, orig_value)
59
74
  end
60
75
  end
61
76
 
@@ -63,19 +78,35 @@ module ActiveRecord
63
78
  def write_attribute(attr, value)
64
79
  attr = attr.to_s
65
80
 
66
- save_changed_attribute(attr, value)
81
+ old_value = old_attribute_value(attr)
82
+
83
+ result = super
84
+ store_original_raw_attribute(attr)
85
+ save_changed_attribute(attr, old_value)
86
+ result
87
+ end
88
+
89
+ def raw_write_attribute(attr, value)
90
+ attr = attr.to_s
91
+
92
+ result = super
93
+ original_raw_attributes[attr] = value
94
+ result
95
+ end
67
96
 
68
- super(attr, value)
97
+ def save_changed_attribute(attr, old_value)
98
+ if attribute_changed?(attr)
99
+ clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
100
+ else
101
+ set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
102
+ end
69
103
  end
70
104
 
71
- def save_changed_attribute(attr, value)
72
- # The attribute already has an unsaved change.
105
+ def old_attribute_value(attr)
73
106
  if attribute_changed?(attr)
74
- old = changed_attributes[attr]
75
- changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
107
+ changed_attributes[attr]
76
108
  else
77
- old = clone_attribute_value(:read_attribute, attr)
78
- changed_attributes[attr] = old if _field_changed?(attr, old, value)
109
+ clone_attribute_value(:read_attribute, attr)
79
110
  end
80
111
  end
81
112
 
@@ -93,34 +124,46 @@ module ActiveRecord
93
124
  changed
94
125
  end
95
126
 
96
- def _field_changed?(attr, old, value)
97
- if column = column_for_attribute(attr)
98
- if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
99
- changes_from_zero_to_string?(old, value))
100
- value = nil
101
- else
102
- value = column.type_cast(value)
103
- end
127
+ def _field_changed?(attr, old_value)
128
+ @attributes[attr].changed_from?(old_value)
129
+ end
130
+
131
+ def attributes_changed_in_place
132
+ changed_in_place.each_with_object({}) do |attr_name, h|
133
+ orig = @attributes[attr_name].original_value
134
+ h[attr_name] = orig
104
135
  end
136
+ end
137
+
138
+ def changed_in_place
139
+ self.class.attribute_names.select do |attr_name|
140
+ changed_in_place?(attr_name)
141
+ end
142
+ end
105
143
 
106
- old != value
144
+ def changed_in_place?(attr_name)
145
+ old_value = original_raw_attribute(attr_name)
146
+ @attributes[attr_name].changed_in_place_from?(old_value)
107
147
  end
108
148
 
109
- def changes_from_nil_to_empty_string?(column, old, value)
110
- # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
111
- # Hence we don't record it as a change if the value changes from nil to ''.
112
- # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
113
- # be typecast back to 0 (''.to_i => 0)
114
- column.null && (old.nil? || old == 0) && value.blank?
149
+ def original_raw_attribute(attr_name)
150
+ original_raw_attributes.fetch(attr_name) do
151
+ read_attribute_before_type_cast(attr_name)
152
+ end
153
+ end
154
+
155
+ def original_raw_attributes
156
+ @original_raw_attributes ||= {}
115
157
  end
116
158
 
117
- def changes_from_zero_to_string?(old, value)
118
- # For columns with old 0 and value non-empty string
119
- old == 0 && value.is_a?(String) && value.present? && non_zero?(value)
159
+ def store_original_raw_attribute(attr_name)
160
+ original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database
120
161
  end
121
162
 
122
- def non_zero?(value)
123
- value !~ /\A0+(\.0+)?\z/
163
+ def store_original_raw_attributes
164
+ attribute_names.each do |attr|
165
+ store_original_raw_attribute(attr)
166
+ end
124
167
  end
125
168
  end
126
169
  end
@@ -15,9 +15,10 @@ module ActiveRecord
15
15
 
16
16
  # Returns the primary key value.
17
17
  def id
18
- return unless self.class.primary_key
19
- sync_with_transaction_state
20
- read_attribute(self.class.primary_key)
18
+ if pk = self.class.primary_key
19
+ sync_with_transaction_state
20
+ read_attribute(pk)
21
+ end
21
22
  end
22
23
 
23
24
  # Sets the primary key value.
@@ -88,12 +89,9 @@ module ActiveRecord
88
89
  end
89
90
 
90
91
  def get_primary_key(base_name) #:nodoc:
91
- return 'id' if base_name.blank?
92
-
93
- case primary_key_prefix_type
94
- when :table_name
92
+ if base_name && primary_key_prefix_type == :table_name
95
93
  base_name.foreign_key(false)
96
- when :table_name_with_underscore
94
+ elsif base_name && primary_key_prefix_type == :table_name_with_underscore
97
95
  base_name.foreign_key
98
96
  else
99
97
  if ActiveRecord::Base != self && table_exists?
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  # the attribute name. Using a constant means that we do not have
23
23
  # to allocate an object on each call to the attribute method.
24
24
  # Making it frozen means that it doesn't get duped when used to
25
- # key the @attributes_cache in read_attribute.
25
+ # key the @attributes in read_attribute.
26
26
  def method_body(method_name, const_name)
27
27
  <<-EOMETHOD
28
28
  def #{method_name}
@@ -35,35 +35,22 @@ module ActiveRecord
35
35
 
36
36
  extend ActiveSupport::Concern
37
37
 
38
- ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
39
-
40
- included do
41
- class_attribute :attribute_types_cached_by_default, instance_writer: false
42
- self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
43
- end
44
-
45
38
  module ClassMethods
46
- # +cache_attributes+ allows you to declare which converted attribute
47
- # values should be cached. Usually caching only pays off for attributes
48
- # with expensive conversion methods, like time related columns (e.g.
49
- # +created_at+, +updated_at+).
50
- def cache_attributes(*attribute_names)
51
- cached_attributes.merge attribute_names.map { |attr| attr.to_s }
39
+ [:cache_attributes, :cached_attributes, :cache_attribute?].each do |method_name|
40
+ define_method method_name do |*|
41
+ cached_attributes_deprecation_warning(method_name)
42
+ true
43
+ end
52
44
  end
53
45
 
54
- # Returns the attributes which are cached. By default time related columns
55
- # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
56
- def cached_attributes
57
- @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
58
- end
46
+ protected
59
47
 
60
- # Returns +true+ if the provided attribute is being cached.
61
- def cache_attribute?(attr_name)
62
- cached_attributes.include?(attr_name)
48
+ def cached_attributes_deprecation_warning(method_name)
49
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
50
+ Calling `#{method_name}` is no longer necessary. All attributes are cached.
51
+ MESSAGE
63
52
  end
64
53
 
65
- protected
66
-
67
54
  if Module.methods_transplantable?
68
55
  def define_method_attribute(name)
69
56
  method = ReaderMethodCache[name]
@@ -89,45 +76,15 @@ module ActiveRecord
89
76
  end
90
77
  end
91
78
  end
92
-
93
- private
94
-
95
- def cacheable_column?(column)
96
- if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
97
- ! serialized_attributes.include? column.name
98
- else
99
- attribute_types_cached_by_default.include?(column.type)
100
- end
101
- end
102
79
  end
103
80
 
104
81
  # Returns the value of the attribute identified by <tt>attr_name</tt> after
105
82
  # it has been typecast (for example, "2004-12-12" in a date column is cast
106
83
  # to a date object, like Date.new(2004, 12, 12)).
107
- def read_attribute(attr_name)
108
- # If it's cached, just return it
109
- # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829.
84
+ def read_attribute(attr_name, &block)
110
85
  name = attr_name.to_s
111
- @attributes_cache[name] || @attributes_cache.fetch(name) {
112
- column = @column_types_override[name] if @column_types_override
113
- column ||= @column_types[name]
114
-
115
- return @attributes.fetch(name) {
116
- if name == 'id' && self.class.primary_key != name
117
- read_attribute(self.class.primary_key)
118
- end
119
- } unless column
120
-
121
- value = @attributes.fetch(name) {
122
- return block_given? ? yield(name) : nil
123
- }
124
-
125
- if self.class.cache_attribute?(name)
126
- @attributes_cache[name] = column.type_cast(value)
127
- else
128
- column.type_cast value
129
- end
130
- }
86
+ name = self.class.primary_key if name == 'id'
87
+ @attributes.fetch_value(name, &block)
131
88
  end
132
89
 
133
90
  private
@@ -3,20 +3,7 @@ module ActiveRecord
3
3
  module Serialization
4
4
  extend ActiveSupport::Concern
5
5
 
6
- included do
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
- self.serialized_attributes = {}
11
- end
12
-
13
6
  module ClassMethods
14
- ##
15
- # :method: serialized_attributes
16
- #
17
- # Returns a hash of all the attributes that have been specified for
18
- # serialization as keys and their class restriction as values.
19
-
20
7
  # If you have an attribute that needs to be saved to the database as an
21
8
  # object, and retrieved as the same object, then specify the name of that
22
9
  # attribute using this method and it will be handled automatically. The
@@ -50,8 +37,6 @@ module ActiveRecord
50
37
  # serialize :preferences, Hash
51
38
  # end
52
39
  def serialize(attr_name, class_name_or_coder = Object)
53
- include Behavior
54
-
55
40
  # When ::JSON is used, force it to go through the Active Support JSON encoder
56
41
  # to ensure special objects (e.g. Active Record models) are dumped correctly
57
42
  # using the #as_json hook.
@@ -63,140 +48,21 @@ module ActiveRecord
63
48
  Coders::YAMLColumn.new(class_name_or_coder)
64
49
  end
65
50
 
66
- # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
67
- # has its own hash of own serialized attributes
68
- self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
69
- end
70
- end
71
-
72
- class Type # :nodoc:
73
- def initialize(column)
74
- @column = column
75
- end
76
-
77
- def type_cast(value)
78
- if value.state == :serialized
79
- value.unserialized_value @column.type_cast value.value
80
- else
81
- value.unserialized_value
51
+ decorate_attribute_type(attr_name, :serialize) do |type|
52
+ Type::Serialized.new(type, coder)
82
53
  end
83
54
  end
84
55
 
85
- def type
86
- @column.type
87
- end
88
-
89
- def accessor
90
- ActiveRecord::Store::IndifferentHashAccessor
91
- end
92
- end
93
-
94
- class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
95
- def unserialized_value(v = value)
96
- state == :serialized ? unserialize(v) : value
97
- end
98
-
99
- def serialized_value
100
- state == :unserialized ? serialize : value
101
- end
102
-
103
- def unserialize(v)
104
- self.state = :unserialized
105
- self.value = coder.load(v)
106
- end
107
-
108
- def serialize
109
- self.state = :serialized
110
- self.value = coder.dump(value)
111
- end
112
- end
113
-
114
- # This is only added to the model when serialize is called, which
115
- # ensures we do not make things slower when serialization is not used.
116
- module Behavior # :nodoc:
117
- extend ActiveSupport::Concern
118
-
119
- module ClassMethods # :nodoc:
120
- def initialize_attributes(attributes, options = {})
121
- serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
122
- super(attributes, options)
123
-
124
- serialized_attributes.each do |key, coder|
125
- if attributes.key?(key)
126
- attributes[key] = Attribute.new(coder, attributes[key], serialized)
127
- end
128
- end
129
-
130
- attributes
131
- end
132
- end
133
-
134
- def should_record_timestamps?
135
- super || (self.record_timestamps && (attributes.keys & self.class.serialized_attributes.keys).present?)
136
- end
137
-
138
- def keys_for_partial_write
139
- super | (attributes.keys & self.class.serialized_attributes.keys)
140
- end
141
-
142
- def type_cast_attribute_for_write(column, value)
143
- if column && coder = self.class.serialized_attributes[column.name]
144
- Attribute.new(coder, value, :unserialized)
145
- else
146
- super
147
- end
148
- end
149
-
150
- def raw_type_cast_attribute_for_write(column, value)
151
- if column && coder = self.class.serialized_attributes[column.name]
152
- Attribute.new(coder, value, :serialized)
153
- else
154
- super
155
- end
156
- end
157
-
158
- def _field_changed?(attr, old, value)
159
- if self.class.serialized_attributes.include?(attr)
160
- old != value
161
- else
162
- super
163
- end
164
- end
165
-
166
- def read_attribute_before_type_cast(attr_name)
167
- if self.class.serialized_attributes.include?(attr_name)
168
- super.unserialized_value
169
- else
170
- super
171
- end
172
- end
173
-
174
- def attributes_before_type_cast
175
- super.dup.tap do |attributes|
176
- self.class.serialized_attributes.each_key do |key|
177
- if attributes.key?(key)
178
- attributes[key] = attributes[key].unserialized_value
179
- end
180
- end
181
- end
182
- end
183
-
184
- def typecasted_attribute_value(name)
185
- if self.class.serialized_attributes.include?(name)
186
- @attributes[name].serialized_value
187
- else
188
- super
189
- end
190
- end
191
-
192
- def attributes_for_coder
193
- attribute_names.each_with_object({}) do |name, attrs|
194
- attrs[name] = if self.class.serialized_attributes.include?(name)
195
- @attributes[name].serialized_value
196
- else
197
- read_attribute(name)
198
- end
199
- end
56
+ def serialized_attributes
57
+ ActiveSupport::Deprecation.warn(<<-WARNING.strip_heredoc)
58
+ `serialized_attributes` is deprecated without replacement, and will
59
+ be removed in Rails 5.0.
60
+ WARNING
61
+ @serialized_attributes ||= Hash[
62
+ columns.select { |t| t.cast_type.is_a?(Type::Serialized) }.map { |c|
63
+ [c.name, c.cast_type.coder]
64
+ }
65
+ ]
200
66
  end
201
67
  end
202
68
  end
@@ -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