activerecord 3.2.22.4 → 4.0.13

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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2799 -617
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +23 -32
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +40 -34
  7. data/lib/active_record/association_relation.rb +22 -0
  8. data/lib/active_record/associations/alias_tracker.rb +4 -2
  9. data/lib/active_record/associations/association.rb +60 -46
  10. data/lib/active_record/associations/association_scope.rb +46 -40
  11. data/lib/active_record/associations/belongs_to_association.rb +17 -4
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +73 -56
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +130 -96
  21. data/lib/active_record/associations/collection_proxy.rb +916 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
  23. data/lib/active_record/associations/has_many_association.rb +35 -8
  24. data/lib/active_record/associations/has_many_through_association.rb +37 -17
  25. data/lib/active_record/associations/has_one_association.rb +42 -19
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
  28. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  29. data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
  30. data/lib/active_record/associations/join_dependency.rb +30 -9
  31. data/lib/active_record/associations/join_helper.rb +1 -11
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/preloader.rb +20 -43
  39. data/lib/active_record/associations/singular_association.rb +11 -11
  40. data/lib/active_record/associations/through_association.rb +3 -3
  41. data/lib/active_record/associations.rb +223 -282
  42. data/lib/active_record/attribute_assignment.rb +134 -154
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  44. data/lib/active_record/attribute_methods/dirty.rb +36 -29
  45. data/lib/active_record/attribute_methods/primary_key.rb +45 -31
  46. data/lib/active_record/attribute_methods/query.rb +5 -4
  47. data/lib/active_record/attribute_methods/read.rb +67 -90
  48. data/lib/active_record/attribute_methods/serialization.rb +133 -70
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
  50. data/lib/active_record/attribute_methods/write.rb +34 -39
  51. data/lib/active_record/attribute_methods.rb +268 -108
  52. data/lib/active_record/autosave_association.rb +80 -73
  53. data/lib/active_record/base.rb +54 -451
  54. data/lib/active_record/callbacks.rb +60 -22
  55. data/lib/active_record/coders/yaml_column.rb +18 -21
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
  67. data/lib/active_record/connection_adapters/column.rb +67 -36
  68. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
  70. data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
  71. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
  72. data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
  79. data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
  80. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
  81. data/lib/active_record/connection_handling.rb +98 -0
  82. data/lib/active_record/core.rb +472 -0
  83. data/lib/active_record/counter_cache.rb +107 -108
  84. data/lib/active_record/dynamic_matchers.rb +115 -63
  85. data/lib/active_record/errors.rb +36 -18
  86. data/lib/active_record/explain.rb +15 -63
  87. data/lib/active_record/explain_registry.rb +30 -0
  88. data/lib/active_record/explain_subscriber.rb +8 -4
  89. data/lib/active_record/fixture_set/file.rb +55 -0
  90. data/lib/active_record/fixtures.rb +159 -155
  91. data/lib/active_record/inheritance.rb +93 -59
  92. data/lib/active_record/integration.rb +8 -8
  93. data/lib/active_record/locale/en.yml +8 -1
  94. data/lib/active_record/locking/optimistic.rb +39 -43
  95. data/lib/active_record/locking/pessimistic.rb +4 -4
  96. data/lib/active_record/log_subscriber.rb +19 -9
  97. data/lib/active_record/migration/command_recorder.rb +102 -33
  98. data/lib/active_record/migration/join_table.rb +15 -0
  99. data/lib/active_record/migration.rb +411 -173
  100. data/lib/active_record/model_schema.rb +81 -94
  101. data/lib/active_record/nested_attributes.rb +173 -131
  102. data/lib/active_record/null_relation.rb +67 -0
  103. data/lib/active_record/persistence.rb +254 -106
  104. data/lib/active_record/query_cache.rb +18 -36
  105. data/lib/active_record/querying.rb +19 -15
  106. data/lib/active_record/railtie.rb +113 -38
  107. data/lib/active_record/railties/console_sandbox.rb +3 -4
  108. data/lib/active_record/railties/controller_runtime.rb +4 -3
  109. data/lib/active_record/railties/databases.rake +115 -368
  110. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  111. data/lib/active_record/readonly_attributes.rb +7 -3
  112. data/lib/active_record/reflection.rb +110 -61
  113. data/lib/active_record/relation/batches.rb +29 -29
  114. data/lib/active_record/relation/calculations.rb +155 -125
  115. data/lib/active_record/relation/delegation.rb +94 -18
  116. data/lib/active_record/relation/finder_methods.rb +151 -203
  117. data/lib/active_record/relation/merger.rb +188 -0
  118. data/lib/active_record/relation/predicate_builder.rb +85 -42
  119. data/lib/active_record/relation/query_methods.rb +793 -146
  120. data/lib/active_record/relation/spawn_methods.rb +43 -150
  121. data/lib/active_record/relation.rb +293 -173
  122. data/lib/active_record/result.rb +48 -7
  123. data/lib/active_record/runtime_registry.rb +17 -0
  124. data/lib/active_record/sanitization.rb +41 -54
  125. data/lib/active_record/schema.rb +19 -12
  126. data/lib/active_record/schema_dumper.rb +41 -41
  127. data/lib/active_record/schema_migration.rb +46 -0
  128. data/lib/active_record/scoping/default.rb +56 -52
  129. data/lib/active_record/scoping/named.rb +78 -103
  130. data/lib/active_record/scoping.rb +54 -124
  131. data/lib/active_record/serialization.rb +6 -2
  132. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  133. data/lib/active_record/statement_cache.rb +26 -0
  134. data/lib/active_record/store.rb +131 -15
  135. data/lib/active_record/tasks/database_tasks.rb +204 -0
  136. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
  138. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  140. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  141. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  142. data/lib/active_record/test_case.rb +67 -38
  143. data/lib/active_record/timestamp.rb +16 -11
  144. data/lib/active_record/transactions.rb +73 -51
  145. data/lib/active_record/validations/associated.rb +19 -13
  146. data/lib/active_record/validations/presence.rb +65 -0
  147. data/lib/active_record/validations/uniqueness.rb +110 -57
  148. data/lib/active_record/validations.rb +18 -17
  149. data/lib/active_record/version.rb +7 -6
  150. data/lib/active_record.rb +63 -45
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
  152. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  154. data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
  155. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  156. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  157. data/lib/rails/generators/active_record.rb +3 -5
  158. metadata +43 -29
  159. data/examples/associations.png +0 -0
  160. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  161. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  162. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  163. data/lib/active_record/dynamic_finder_match.rb +0 -68
  164. data/lib/active_record/dynamic_scope_match.rb +0 -23
  165. data/lib/active_record/fixtures/file.rb +0 -65
  166. data/lib/active_record/identity_map.rb +0 -162
  167. data/lib/active_record/observer.rb +0 -121
  168. data/lib/active_record/session_store.rb +0 -360
  169. data/lib/rails/generators/active_record/migration.rb +0 -15
  170. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  171. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  172. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  173. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,56 +1,79 @@
1
+ require 'set'
2
+
1
3
  module ActiveRecord
2
4
  module AttributeMethods
3
5
  module PrimaryKey
4
6
  extend ActiveSupport::Concern
5
7
 
6
- # Returns this record's primary key value wrapped in an Array if one is available
8
+ # Returns this record's primary key value wrapped in an Array if one is
9
+ # available.
7
10
  def to_key
11
+ sync_with_transaction_state
8
12
  key = self.id
9
13
  [key] if key
10
14
  end
11
15
 
12
- # Returns the primary key value
16
+ # Returns the primary key value.
13
17
  def id
18
+ sync_with_transaction_state
14
19
  read_attribute(self.class.primary_key)
15
20
  end
16
21
 
17
- # Sets the primary key value
22
+ # Sets the primary key value.
18
23
  def id=(value)
19
- write_attribute(self.class.primary_key, value)
24
+ sync_with_transaction_state
25
+ write_attribute(self.class.primary_key, value) if self.class.primary_key
20
26
  end
21
27
 
22
- # Queries the primary key value
28
+ # Queries the primary key value.
23
29
  def id?
30
+ sync_with_transaction_state
24
31
  query_attribute(self.class.primary_key)
25
32
  end
26
33
 
34
+ # Returns the primary key value before type cast.
35
+ def id_before_type_cast
36
+ sync_with_transaction_state
37
+ read_attribute_before_type_cast(self.class.primary_key)
38
+ end
39
+
40
+ # Returns the primary key previous value.
41
+ def id_was
42
+ sync_with_transaction_state
43
+ attribute_was(self.class.primary_key)
44
+ end
45
+
46
+ protected
47
+
48
+ def attribute_method?(attr_name)
49
+ attr_name == 'id' || super
50
+ end
51
+
27
52
  module ClassMethods
28
53
  def define_method_attribute(attr_name)
29
54
  super
30
55
 
31
56
  if attr_name == primary_key && attr_name != 'id'
32
57
  generated_attribute_methods.send(:alias_method, :id, primary_key)
33
- generated_external_attribute_methods.module_eval <<-CODE, __FILE__, __LINE__
34
- def id(v, attributes, attributes_cache, attr_name)
35
- attr_name = '#{primary_key}'
36
- send(attr_name, attributes[attr_name], attributes, attributes_cache, attr_name)
37
- end
38
- CODE
39
58
  end
40
59
  end
41
60
 
61
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set
62
+
42
63
  def dangerous_attribute_method?(method_name)
43
- super && !['id', 'id=', 'id?'].include?(method_name)
64
+ super && !ID_ATTRIBUTE_METHODS.include?(method_name)
44
65
  end
45
66
 
46
- # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
47
- # primary_key_prefix_type setting, though.
67
+ # Defines the primary key field -- can be overridden in subclasses.
68
+ # Overwriting will negate any effect of the +primary_key_prefix_type+
69
+ # setting, though.
48
70
  def primary_key
49
71
  @primary_key = reset_primary_key unless defined? @primary_key
50
72
  @primary_key
51
73
  end
52
74
 
53
- # Returns a quoted version of the primary key name, used to construct SQL statements.
75
+ # Returns a quoted version of the primary key name, used to construct
76
+ # SQL statements.
54
77
  def quoted_primary_key
55
78
  @quoted_primary_key ||= connection.quote_column_name(primary_key)
56
79
  end
@@ -64,7 +87,7 @@ module ActiveRecord
64
87
  end
65
88
 
66
89
  def get_primary_key(base_name) #:nodoc:
67
- return 'id' unless base_name && !base_name.blank?
90
+ return 'id' if base_name.blank?
68
91
 
69
92
  case primary_key_prefix_type
70
93
  when :table_name
@@ -73,39 +96,30 @@ module ActiveRecord
73
96
  base_name.foreign_key
74
97
  else
75
98
  if ActiveRecord::Base != self && table_exists?
76
- connection.schema_cache.primary_keys[table_name]
99
+ connection.schema_cache.primary_keys(table_name)
77
100
  else
78
101
  'id'
79
102
  end
80
103
  end
81
104
  end
82
105
 
83
- def original_primary_key #:nodoc:
84
- deprecated_original_property_getter :primary_key
85
- end
86
-
87
106
  # Sets the name of the primary key column.
88
107
  #
89
108
  # class Project < ActiveRecord::Base
90
- # self.primary_key = "sysid"
109
+ # self.primary_key = 'sysid'
91
110
  # end
92
111
  #
93
- # You can also define the primary_key method yourself:
112
+ # You can also define the +primary_key+ method yourself:
94
113
  #
95
114
  # class Project < ActiveRecord::Base
96
115
  # def self.primary_key
97
- # "foo_" + super
116
+ # 'foo_' + super
98
117
  # end
99
118
  # end
119
+ #
100
120
  # Project.primary_key # => "foo_id"
101
121
  def primary_key=(value)
102
- @original_primary_key = @primary_key if defined?(@primary_key)
103
- @primary_key = value && value.to_s
104
- @quoted_primary_key = nil
105
- end
106
-
107
- def set_primary_key(value = nil, &block) #:nodoc:
108
- deprecated_property_setter :primary_key, value, block
122
+ @primary_key = value && value.to_s
109
123
  @quoted_primary_key = nil
110
124
  end
111
125
  end
@@ -1,5 +1,3 @@
1
- require 'active_support/core_ext/object/blank'
2
-
3
1
  module ActiveRecord
4
2
  module AttributeMethods
5
3
  module Query
@@ -10,8 +8,11 @@ module ActiveRecord
10
8
  end
11
9
 
12
10
  def query_attribute(attr_name)
13
- unless value = read_attribute(attr_name)
14
- false
11
+ value = read_attribute(attr_name) { |n| missing_attribute(n, caller) }
12
+
13
+ case value
14
+ when true then true
15
+ when false, nil then false
15
16
  else
16
17
  column = self.class.columns_hash[attr_name]
17
18
  if column.nil?
@@ -6,14 +6,15 @@ module ActiveRecord
6
6
  ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
7
7
 
8
8
  included do
9
- cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
9
+ class_attribute :attribute_types_cached_by_default, instance_writer: false
10
10
  self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
11
11
  end
12
12
 
13
13
  module ClassMethods
14
- # +cache_attributes+ allows you to declare which converted attribute values should
15
- # be cached. Usually caching only pays off for attributes with expensive conversion
16
- # 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+).
17
18
  def cache_attributes(*attribute_names)
18
19
  cached_attributes.merge attribute_names.map { |attr| attr.to_s }
19
20
  end
@@ -29,108 +30,84 @@ module ActiveRecord
29
30
  cached_attributes.include?(attr_name)
30
31
  end
31
32
 
32
- def undefine_attribute_methods
33
- generated_external_attribute_methods.module_eval do
34
- instance_methods.each { |m| undef_method(m) }
35
- end
36
-
37
- super
38
- end
39
-
40
- def type_cast_attribute(attr_name, attributes, cache = {}) #:nodoc:
41
- return unless attr_name
42
- attr_name = attr_name.to_s
33
+ protected
43
34
 
44
- if generated_external_attribute_methods.method_defined?(attr_name)
45
- if attributes.has_key?(attr_name) || attr_name == 'id'
46
- generated_external_attribute_methods.send(attr_name, attributes[attr_name], attributes, cache, attr_name)
35
+ # We want to generate the methods via module_eval rather than
36
+ # define_method, because define_method is slower on dispatch.
37
+ # Evaluating many similar methods may use more memory as the instruction
38
+ # sequences are duplicated and cached (in MRI). define_method may
39
+ # be slower on dispatch, but if you're careful about the closure
40
+ # created, then define_method will consume much less memory.
41
+ #
42
+ # But sometimes the database might return columns with
43
+ # characters that are not allowed in normal method names (like
44
+ # 'my_column(omg)'. So to work around this we first define with
45
+ # the __temp__ identifier, and then use alias method to rename
46
+ # it to what we want.
47
+ #
48
+ # We are also defining a constant to hold the frozen string of
49
+ # the attribute name. Using a constant means that we do not have
50
+ # to allocate an object on each call to the attribute method.
51
+ # Making it frozen means that it doesn't get duped when used to
52
+ # key the @attributes_cache in read_attribute.
53
+ def define_method_attribute(name)
54
+ safe_name = name.unpack('h*').first
55
+ generated_attribute_methods::AttrNames.set_name_cache safe_name, name
56
+
57
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
58
+ def __temp__#{safe_name}
59
+ read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) }
47
60
  end
48
- elsif !attribute_methods_generated?
49
- # If we haven't generated the caster methods yet, do that and
50
- # then try again
51
- define_attribute_methods
52
- type_cast_attribute(attr_name, attributes, cache)
53
- else
54
- # If we get here, the attribute has no associated DB column, so
55
- # just return it verbatim.
56
- attributes[attr_name]
57
- end
61
+ alias_method #{name.inspect}, :__temp__#{safe_name}
62
+ undef_method :__temp__#{safe_name}
63
+ STR
58
64
  end
59
65
 
60
- protected
61
- # We want to generate the methods via module_eval rather than define_method,
62
- # because define_method is slower on dispatch and uses more memory (because it
63
- # creates a closure).
64
- #
65
- # But sometimes the database might return columns with characters that are not
66
- # allowed in normal method names (like 'my_column(omg)'. So to work around this
67
- # we first define with the __temp__ identifier, and then use alias method to
68
- # rename it to what we want.
69
- def define_method_attribute(attr_name)
70
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
71
- def __temp__
72
- #{internal_attribute_access_code(attr_name, attribute_cast_code(attr_name))}
73
- end
74
- alias_method '#{attr_name}', :__temp__
75
- undef_method :__temp__
76
- STR
77
- end
78
-
79
66
  private
80
67
 
81
- def define_external_attribute_method(attr_name)
82
- generated_external_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
83
- def __temp__(v, attributes, attributes_cache, attr_name)
84
- #{external_attribute_access_code(attr_name, attribute_cast_code(attr_name))}
85
- end
86
- alias_method '#{attr_name}', :__temp__
87
- undef_method :__temp__
88
- STR
89
- end
90
-
91
- def cacheable_column?(column)
68
+ def cacheable_column?(column)
69
+ if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
70
+ ! serialized_attributes.include? column.name
71
+ else
92
72
  attribute_types_cached_by_default.include?(column.type)
93
73
  end
74
+ end
75
+ end
94
76
 
95
- def internal_attribute_access_code(attr_name, cast_code)
96
- access_code = "(v=@attributes[attr_name]) && #{cast_code}"
97
-
98
- unless attr_name == primary_key
99
- access_code.insert(0, "missing_attribute(attr_name, caller) unless @attributes.has_key?(attr_name); ")
100
- end
101
-
102
- if cache_attribute?(attr_name)
103
- access_code = "@attributes_cache[attr_name] ||= (#{access_code})"
104
- end
105
-
106
- "attr_name = '#{attr_name}'; #{access_code}"
107
- end
108
-
109
- def external_attribute_access_code(attr_name, cast_code)
110
- access_code = "v && #{cast_code}"
111
-
112
- if cache_attribute?(attr_name)
113
- access_code = "attributes_cache[attr_name] ||= (#{access_code})"
77
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after
78
+ # it has been typecast (for example, "2004-12-12" in a data column is cast
79
+ # to a date object, like Date.new(2004, 12, 12)).
80
+ def read_attribute(attr_name)
81
+ # If it's cached, just return it
82
+ # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829.
83
+ name = attr_name.to_s
84
+ @attributes_cache[name] || @attributes_cache.fetch(name) {
85
+ column = @column_types_override[name] if @column_types_override
86
+ column ||= @column_types[name]
87
+
88
+ return @attributes.fetch(name) {
89
+ if name == 'id' && self.class.primary_key != name
90
+ read_attribute(self.class.primary_key)
114
91
  end
92
+ } unless column
115
93
 
116
- access_code
117
- end
94
+ value = @attributes.fetch(name) {
95
+ return block_given? ? yield(name) : nil
96
+ }
118
97
 
119
- def attribute_cast_code(attr_name)
120
- columns_hash[attr_name].type_cast_code('v')
98
+ if self.class.cache_attribute?(name)
99
+ @attributes_cache[name] = column.type_cast(value)
100
+ else
101
+ column.type_cast value
121
102
  end
122
- end
123
-
124
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
125
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
126
- def read_attribute(attr_name)
127
- self.class.type_cast_attribute(attr_name, @attributes, @attributes_cache)
103
+ }
128
104
  end
129
105
 
130
106
  private
131
- def attribute(attribute_name)
132
- read_attribute(attribute_name)
133
- end
107
+
108
+ def attribute(attribute_name)
109
+ read_attribute(attribute_name)
110
+ end
134
111
  end
135
112
  end
136
113
  end
@@ -4,114 +4,177 @@ module ActiveRecord
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- # Returns a hash of all the attributes that have been specified for serialization as
8
- # keys and their class restriction as values.
9
- class_attribute :serialized_attributes
7
+ # Returns a hash of all the attributes that have been specified for
8
+ # serialization as keys and their class restriction as values.
9
+ class_attribute :serialized_attributes, instance_accessor: false
10
10
  self.serialized_attributes = {}
11
11
  end
12
12
 
13
- class Attribute < Struct.new(:coder, :value, :state)
14
- def unserialized_value
15
- state == :serialized ? unserialize : value
16
- end
17
-
18
- def serialized_value
19
- state == :unserialized ? serialize : value
20
- end
21
-
22
- def unserialize
23
- self.state = :unserialized
24
- self.value = coder.load(value)
25
- end
26
-
27
- def serialize
28
- self.state = :serialized
29
- self.value = coder.dump(value)
30
- end
31
- end
32
-
33
13
  module ClassMethods
34
- # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
35
- # then specify the name of that attribute using this method and it will be handled automatically.
36
- # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
37
- # class on retrieval or SerializationTypeMismatch will be raised.
14
+ ##
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.
38
26
  #
39
27
  # ==== Parameters
40
28
  #
41
29
  # * +attr_name+ - The field name that should be serialized.
42
- # * +class_name+ - Optional, class name that the object type should be equal to.
30
+ # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump`
31
+ # or a class name that the object type should be equal to.
43
32
  #
44
33
  # ==== Example
45
- # # Serialize a preferences attribute
34
+ #
35
+ # # Serialize a preferences attribute.
46
36
  # class User < ActiveRecord::Base
47
37
  # serialize :preferences
48
38
  # end
49
- def serialize(attr_name, class_name = Object)
50
- coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
51
- class_name
39
+ #
40
+ # # Serialize preferences using JSON as coder.
41
+ # class User < ActiveRecord::Base
42
+ # serialize :preferences, JSON
43
+ # end
44
+ #
45
+ # # Serialize preferences as Hash using YAML coder.
46
+ # class User < ActiveRecord::Base
47
+ # serialize :preferences, Hash
48
+ # end
49
+ def serialize(attr_name, class_name_or_coder = Object)
50
+ include Behavior
51
+
52
+ coder = if [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
53
+ class_name_or_coder
52
54
  else
53
- Coders::YAMLColumn.new(class_name)
55
+ Coders::YAMLColumn.new(class_name_or_coder)
54
56
  end
55
57
 
56
58
  # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
57
59
  # has its own hash of own serialized attributes
58
60
  self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
59
61
  end
62
+ end
60
63
 
61
- def initialize_attributes(attributes, options = {}) #:nodoc:
62
- serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
63
- super(attributes, options)
64
+ # *DEPRECATED*: Use ActiveRecord::AttributeMethods::Serialization::ClassMethods#serialized_attributes class level method instead.
65
+ def serialized_attributes
66
+ message = "Instance level serialized_attributes method is deprecated, please use class level method."
67
+ ActiveSupport::Deprecation.warn message
68
+ defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes
69
+ end
64
70
 
65
- serialized_attributes.each do |key, coder|
66
- if attributes.key?(key)
67
- attributes[key] = Attribute.new(coder, attributes[key], serialized)
68
- end
71
+ class Type # :nodoc:
72
+ def initialize(column)
73
+ @column = column
74
+ end
75
+
76
+ def type_cast(value)
77
+ if value.state == :serialized
78
+ value.unserialized_value @column.type_cast value.value
79
+ else
80
+ value.unserialized_value
69
81
  end
82
+ end
70
83
 
71
- attributes
84
+ def type
85
+ @column.type
72
86
  end
87
+ end
73
88
 
74
- private
89
+ class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
90
+ def unserialized_value(v = value)
91
+ state == :serialized ? unserialize(v) : value
92
+ end
75
93
 
76
- def attribute_cast_code(attr_name)
77
- if serialized_attributes.include?(attr_name)
78
- "v.unserialized_value"
94
+ def serialized_value
95
+ state == :unserialized ? serialize : value
96
+ end
97
+
98
+ def unserialize(v)
99
+ self.state = :unserialized
100
+ self.value = coder.load(v)
101
+ end
102
+
103
+ def serialize
104
+ self.state = :serialized
105
+ self.value = coder.dump(value)
106
+ end
107
+ end
108
+
109
+ # This is only added to the model when serialize is called, which
110
+ # ensures we do not make things slower when serialization is not used.
111
+ module Behavior # :nodoc:
112
+ extend ActiveSupport::Concern
113
+
114
+ module ClassMethods # :nodoc:
115
+ def initialize_attributes(attributes, options = {})
116
+ serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
117
+ super(attributes, options)
118
+
119
+ serialized_attributes.each do |key, coder|
120
+ if attributes.key?(key)
121
+ attributes[key] = Attribute.new(coder, attributes[key], serialized)
122
+ end
123
+ end
124
+
125
+ attributes
126
+ end
127
+ end
128
+
129
+ def type_cast_attribute_for_write(column, value)
130
+ if column && coder = self.class.serialized_attributes[column.name]
131
+ Attribute.new(coder, value, :unserialized)
79
132
  else
80
133
  super
81
134
  end
82
135
  end
83
- end
84
136
 
85
- def type_cast_attribute_for_write(column, value)
86
- if column && coder = self.class.serialized_attributes[column.name]
87
- Attribute.new(coder, value, :unserialized)
88
- else
89
- super
137
+ def _field_changed?(attr, old, value)
138
+ if self.class.serialized_attributes.include?(attr)
139
+ old != value
140
+ else
141
+ super
142
+ end
90
143
  end
91
- end
92
144
 
93
- def _field_changed?(attr, old, value)
94
- if self.class.serialized_attributes.include?(attr)
95
- old != value
96
- else
97
- super
145
+ def read_attribute_before_type_cast(attr_name)
146
+ if self.class.serialized_attributes.include?(attr_name)
147
+ super.unserialized_value
148
+ else
149
+ super
150
+ end
98
151
  end
99
- end
100
152
 
101
- def read_attribute_before_type_cast(attr_name)
102
- if serialized_attributes.include?(attr_name)
103
- super.unserialized_value
104
- else
105
- super
153
+ def attributes_before_type_cast
154
+ super.dup.tap do |attributes|
155
+ self.class.serialized_attributes.each_key do |key|
156
+ if attributes.key?(key)
157
+ attributes[key] = attributes[key].unserialized_value
158
+ end
159
+ end
160
+ end
106
161
  end
107
- end
108
162
 
109
- def attributes_before_type_cast
110
- super.dup.tap do |attributes|
111
- self.class.serialized_attributes.each_key do |key|
112
- if attributes.key?(key)
113
- attributes[key] = attributes[key].unserialized_value
114
- end
163
+ def typecasted_attribute_value(name)
164
+ if self.class.serialized_attributes.include?(name)
165
+ @attributes[name].serialized_value
166
+ else
167
+ super
168
+ end
169
+ end
170
+
171
+ def attributes_for_coder
172
+ attribute_names.each_with_object({}) do |name, attrs|
173
+ attrs[name] = if self.class.serialized_attributes.include?(name)
174
+ @attributes[name].serialized_value
175
+ else
176
+ read_attribute(name)
177
+ end
115
178
  end
116
179
  end
117
180
  end