activerecord 3.0.0 → 4.0.0

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 (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -6,28 +6,23 @@ module ActiveRecord
6
6
  ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
7
7
 
8
8
  included do
9
- attribute_method_suffix ""
10
-
11
- cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
9
+ class_attribute :attribute_types_cached_by_default, instance_writer: false
12
10
  self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
13
-
14
- # Undefine id so it can be used as an attribute name
15
- undef_method(:id) if method_defined?(:id)
16
11
  end
17
12
 
18
13
  module ClassMethods
19
- # +cache_attributes+ allows you to declare which converted attribute values should
20
- # be cached. Usually caching only pays off for attributes with expensive conversion
21
- # methods, like time related columns (e.g. +created_at+, +updated_at+).
14
+ # +cache_attributes+ allows you to declare which converted attribute
15
+ # values should be cached. Usually caching only pays off for attributes
16
+ # with expensive conversion methods, like time related columns (e.g.
17
+ # +created_at+, +updated_at+).
22
18
  def cache_attributes(*attribute_names)
23
- attribute_names.each {|attr| cached_attributes << attr.to_s}
19
+ cached_attributes.merge attribute_names.map { |attr| attr.to_s }
24
20
  end
25
21
 
26
22
  # Returns the attributes which are cached. By default time related columns
27
23
  # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
28
24
  def cached_attributes
29
- @cached_attributes ||=
30
- columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map{|col| col.name}.to_set
25
+ @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
31
26
  end
32
27
 
33
28
  # Returns +true+ if the provided attribute is being cached.
@@ -36,81 +31,77 @@ module ActiveRecord
36
31
  end
37
32
 
38
33
  protected
39
- def define_method_attribute(attr_name)
40
- if self.serialized_attributes[attr_name]
41
- define_read_method_for_serialized_attribute(attr_name)
42
- else
43
- define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name])
44
- end
45
34
 
46
- if attr_name == primary_key && attr_name != "id"
47
- define_read_method(:id, attr_name, columns_hash[attr_name])
35
+ # We want to generate the methods via module_eval rather than
36
+ # define_method, because define_method is slower on dispatch and
37
+ # uses more memory (because it creates a closure).
38
+ #
39
+ # But sometimes the database might return columns with
40
+ # characters that are not allowed in normal method names (like
41
+ # 'my_column(omg)'. So to work around this we first define with
42
+ # the __temp__ identifier, and then use alias method to rename
43
+ # it to what we want.
44
+ #
45
+ # We are also defining a constant to hold the frozen string of
46
+ # the attribute name. Using a constant means that we do not have
47
+ # to allocate an object on each call to the attribute method.
48
+ # Making it frozen means that it doesn't get duped when used to
49
+ # key the @attributes_cache in read_attribute.
50
+ def define_method_attribute(name)
51
+ safe_name = name.unpack('h*').first
52
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
53
+ def __temp__#{safe_name}
54
+ read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) }
48
55
  end
49
- end
56
+ alias_method #{name.inspect}, :__temp__#{safe_name}
57
+ undef_method :__temp__#{safe_name}
58
+ STR
59
+ end
50
60
 
51
61
  private
52
- # Define read method for serialized attribute.
53
- def define_read_method_for_serialized_attribute(attr_name)
54
- generated_attribute_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__)
55
- end
56
62
 
57
- # Define an attribute reader method. Cope with nil column.
58
- def define_read_method(symbol, attr_name, column)
59
- cast_code = column.type_cast_code('v') if column
60
- access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
61
-
62
- unless attr_name.to_s == self.primary_key.to_s
63
- access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
64
- end
65
-
66
- if cache_attribute?(attr_name)
67
- access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
68
- end
69
- generated_attribute_methods.module_eval("def #{symbol}; #{access_code}; end", __FILE__, __LINE__)
63
+ def cacheable_column?(column)
64
+ if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
65
+ ! serialized_attributes.include? column.name
66
+ else
67
+ attribute_types_cached_by_default.include?(column.type)
70
68
  end
69
+ end
71
70
  end
72
71
 
73
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
74
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
72
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after
73
+ # it has been typecast (for example, "2004-12-12" in a data column is cast
74
+ # to a date object, like Date.new(2004, 12, 12)).
75
75
  def read_attribute(attr_name)
76
- attr_name = attr_name.to_s
77
- attr_name = self.class.primary_key if attr_name == 'id'
78
- if !(value = @attributes[attr_name]).nil?
79
- if column = column_for_attribute(attr_name)
80
- if unserializable_attribute?(attr_name, column)
81
- unserialize_attribute(attr_name)
82
- else
83
- column.type_cast(value)
84
- end
76
+ # If it's cached, just return it
77
+ # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829.
78
+ name = attr_name.to_s
79
+ @attributes_cache[name] || @attributes_cache.fetch(name) {
80
+ column = @columns_hash.fetch(name) {
81
+ return @attributes.fetch(name) {
82
+ if name == 'id' && self.class.primary_key != name
83
+ read_attribute(self.class.primary_key)
84
+ end
85
+ }
86
+ }
87
+
88
+ value = @attributes.fetch(name) {
89
+ return block_given? ? yield(name) : nil
90
+ }
91
+
92
+ if self.class.cache_attribute?(name)
93
+ @attributes_cache[name] = column.type_cast(value)
85
94
  else
86
- value
95
+ column.type_cast value
87
96
  end
88
- else
89
- nil
90
- end
91
- end
92
-
93
- # Returns true if the attribute is of a text column and marked for serialization.
94
- def unserializable_attribute?(attr_name, column)
95
- column.text? && self.class.serialized_attributes[attr_name]
97
+ }
96
98
  end
97
99
 
98
- # Returns the unserialized object of the attribute.
99
- def unserialize_attribute(attr_name)
100
- unserialized_object = object_from_yaml(@attributes[attr_name])
100
+ private
101
101
 
102
- if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
103
- @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
104
- else
105
- raise SerializationTypeMismatch,
106
- "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
107
- end
102
+ def attribute(attribute_name)
103
+ read_attribute(attribute_name)
108
104
  end
109
-
110
- private
111
- def attribute(attribute_name)
112
- read_attribute(attribute_name)
113
- end
114
105
  end
115
106
  end
116
107
  end
@@ -0,0 +1,162 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Serialization
4
+ extend ActiveSupport::Concern
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
+ 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
+ # If you have an attribute that needs to be saved to the database as an
21
+ # object, and retrieved as the same object, then specify the name of that
22
+ # attribute using this method and it will be handled automatically. The
23
+ # serialization is done through YAML. If +class_name+ is specified, the
24
+ # serialized object must be of that class on retrieval or
25
+ # <tt>SerializationTypeMismatch</tt> will be raised.
26
+ #
27
+ # ==== Parameters
28
+ #
29
+ # * +attr_name+ - The field name that should be serialized.
30
+ # * +class_name+ - Optional, class name that the object type should be equal to.
31
+ #
32
+ # ==== Example
33
+ #
34
+ # # Serialize a preferences attribute.
35
+ # class User < ActiveRecord::Base
36
+ # serialize :preferences
37
+ # end
38
+ def serialize(attr_name, class_name = Object)
39
+ include Behavior
40
+
41
+ coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
42
+ class_name
43
+ else
44
+ Coders::YAMLColumn.new(class_name)
45
+ end
46
+
47
+ # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
48
+ # has its own hash of own serialized attributes
49
+ self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
50
+ end
51
+ end
52
+
53
+ # *DEPRECATED*: Use ActiveRecord::AttributeMethods::Serialization::ClassMethods#serialized_attributes class level method instead.
54
+ def serialized_attributes
55
+ message = "Instance level serialized_attributes method is deprecated, please use class level method."
56
+ ActiveSupport::Deprecation.warn message
57
+ defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes
58
+ end
59
+
60
+ class Type # :nodoc:
61
+ def initialize(column)
62
+ @column = column
63
+ end
64
+
65
+ def type_cast(value)
66
+ if value.state == :serialized
67
+ value.unserialized_value @column.type_cast value.value
68
+ else
69
+ value.unserialized_value
70
+ end
71
+ end
72
+
73
+ def type
74
+ @column.type
75
+ end
76
+ end
77
+
78
+ class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
79
+ def unserialized_value(v = value)
80
+ state == :serialized ? unserialize(v) : value
81
+ end
82
+
83
+ def serialized_value
84
+ state == :unserialized ? serialize : value
85
+ end
86
+
87
+ def unserialize(v)
88
+ self.state = :unserialized
89
+ self.value = coder.load(v)
90
+ end
91
+
92
+ def serialize
93
+ self.state = :serialized
94
+ self.value = coder.dump(value)
95
+ end
96
+ end
97
+
98
+ # This is only added to the model when serialize is called, which
99
+ # ensures we do not make things slower when serialization is not used.
100
+ module Behavior # :nodoc:
101
+ extend ActiveSupport::Concern
102
+
103
+ module ClassMethods # :nodoc:
104
+ def initialize_attributes(attributes, options = {})
105
+ serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
106
+ super(attributes, options)
107
+
108
+ serialized_attributes.each do |key, coder|
109
+ if attributes.key?(key)
110
+ attributes[key] = Attribute.new(coder, attributes[key], serialized)
111
+ end
112
+ end
113
+
114
+ attributes
115
+ end
116
+ end
117
+
118
+ def type_cast_attribute_for_write(column, value)
119
+ if column && coder = self.class.serialized_attributes[column.name]
120
+ Attribute.new(coder, value, :unserialized)
121
+ else
122
+ super
123
+ end
124
+ end
125
+
126
+ def _field_changed?(attr, old, value)
127
+ if self.class.serialized_attributes.include?(attr)
128
+ old != value
129
+ else
130
+ super
131
+ end
132
+ end
133
+
134
+ def read_attribute_before_type_cast(attr_name)
135
+ if self.class.serialized_attributes.include?(attr_name)
136
+ super.unserialized_value
137
+ else
138
+ super
139
+ end
140
+ end
141
+
142
+ def attributes_before_type_cast
143
+ super.dup.tap do |attributes|
144
+ self.class.serialized_attributes.each_key do |key|
145
+ if attributes.key?(key)
146
+ attributes[key] = attributes[key].unserialized_value
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ def typecasted_attribute_value(name)
153
+ if self.class.serialized_attributes.include?(name)
154
+ @attributes[name].serialized_value
155
+ else
156
+ super
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -1,60 +1,58 @@
1
1
  module ActiveRecord
2
2
  module AttributeMethods
3
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
+
4
19
  extend ActiveSupport::Concern
5
20
 
6
21
  included do
7
- cattr_accessor :time_zone_aware_attributes, :instance_writer => false
22
+ mattr_accessor :time_zone_aware_attributes, instance_writer: false
8
23
  self.time_zone_aware_attributes = false
9
24
 
10
- class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
25
+ class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
11
26
  self.skip_time_zone_conversion_for_attributes = []
12
27
  end
13
28
 
14
29
  module ClassMethods
15
30
  protected
16
- # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
17
- # This enhanced read method automatically converts the UTC time stored in the database to the time
18
- # zone stored in Time.zone.
19
- def define_method_attribute(attr_name)
20
- if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
21
- method_body, line = <<-EOV, __LINE__ + 1
22
- def #{attr_name}(reload = false)
23
- cached = @attributes_cache['#{attr_name}']
24
- return cached if cached && !reload
25
- time = read_attribute('#{attr_name}')
26
- @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
27
- end
28
- EOV
29
- generated_attribute_methods.module_eval(method_body, __FILE__, line)
30
- else
31
- super
32
- end
33
- end
34
-
35
- # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
36
- # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
37
- def define_method_attribute=(attr_name)
38
- if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
39
- method_body, line = <<-EOV, __LINE__ + 1
40
- def #{attr_name}=(time)
41
- unless time.acts_like?(:time)
42
- time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
43
- end
44
- time = time.in_time_zone rescue nil if time
45
- write_attribute(:#{attr_name}, time)
46
- end
47
- EOV
48
- generated_attribute_methods.module_eval(method_body, __FILE__, line)
49
- else
50
- super
51
- 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
52
47
  end
48
+ end
53
49
 
54
50
  private
55
- def create_time_zone_conversion_attribute?(name, column)
56
- time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
57
- 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
58
56
  end
59
57
  end
60
58
  end
@@ -9,29 +9,55 @@ module ActiveRecord
9
9
 
10
10
  module ClassMethods
11
11
  protected
12
- def define_method_attribute=(attr_name)
13
- generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
14
- 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)
20
+ end
21
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
22
+ undef_method :__temp__#{safe_name}=
23
+ STR
24
+ end
15
25
  end
16
26
 
17
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings
18
- # 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+.
19
30
  def write_attribute(attr_name, value)
20
31
  attr_name = attr_name.to_s
21
- attr_name = self.class.primary_key if attr_name == 'id'
32
+ attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
22
33
  @attributes_cache.delete(attr_name)
23
- if (column = column_for_attribute(attr_name)) && column.number?
24
- @attributes[attr_name] = convert_number_column_value(value)
34
+ column = column_for_attribute(attr_name)
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
40
+ end
41
+
42
+ if column || @attributes.has_key?(attr_name)
43
+ @attributes[attr_name] = type_cast_attribute_for_write(column, value)
25
44
  else
26
- @attributes[attr_name] = value
45
+ raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
27
46
  end
28
47
  end
48
+ alias_method :raw_write_attribute, :write_attribute
29
49
 
30
50
  private
31
- # Handle *= for method_missing.
32
- def attribute=(attribute_name, value)
33
- write_attribute(attribute_name, value)
34
- end
51
+ # Handle *= for method_missing.
52
+ def attribute=(attribute_name, value)
53
+ write_attribute(attribute_name, value)
54
+ end
55
+
56
+ def type_cast_attribute_for_write(column, value)
57
+ return value unless column
58
+
59
+ column.type_cast_for_write value
60
+ end
35
61
  end
36
62
  end
37
63
  end