activerecord 3.2.22.5 → 4.0.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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -543
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +20 -29
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +55 -44
  7. data/lib/active_record/aggregations.rb +40 -34
  8. data/lib/active_record/associations.rb +204 -276
  9. data/lib/active_record/associations/alias_tracker.rb +1 -1
  10. data/lib/active_record/associations/association.rb +30 -35
  11. data/lib/active_record/associations/association_scope.rb +40 -40
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -2
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +35 -57
  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 +92 -88
  21. data/lib/active_record/associations/collection_proxy.rb +913 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
  23. data/lib/active_record/associations/has_many_association.rb +35 -9
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -14
  25. data/lib/active_record/associations/has_one_association.rb +33 -13
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +2 -2
  28. data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/join_helper.rb +1 -11
  31. data/lib/active_record/associations/preloader.rb +14 -17
  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 +1 -1
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  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/singular_association.rb +11 -11
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +133 -153
  41. data/lib/active_record/attribute_methods.rb +196 -93
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  43. data/lib/active_record/attribute_methods/dirty.rb +31 -28
  44. data/lib/active_record/attribute_methods/primary_key.rb +38 -30
  45. data/lib/active_record/attribute_methods/query.rb +5 -4
  46. data/lib/active_record/attribute_methods/read.rb +62 -91
  47. data/lib/active_record/attribute_methods/serialization.rb +97 -66
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
  49. data/lib/active_record/attribute_methods/write.rb +32 -39
  50. data/lib/active_record/autosave_association.rb +56 -70
  51. data/lib/active_record/base.rb +53 -450
  52. data/lib/active_record/callbacks.rb +53 -18
  53. data/lib/active_record/coders/yaml_column.rb +11 -9
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
  65. data/lib/active_record/connection_adapters/column.rb +46 -24
  66. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  67. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  68. data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
  69. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  70. data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
  73. data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
  74. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  75. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
  76. data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
  77. data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
  79. data/lib/active_record/connection_handling.rb +98 -0
  80. data/lib/active_record/core.rb +428 -0
  81. data/lib/active_record/counter_cache.rb +106 -108
  82. data/lib/active_record/dynamic_matchers.rb +110 -63
  83. data/lib/active_record/errors.rb +25 -8
  84. data/lib/active_record/explain.rb +8 -58
  85. data/lib/active_record/explain_subscriber.rb +6 -3
  86. data/lib/active_record/fixture_set/file.rb +56 -0
  87. data/lib/active_record/fixtures.rb +146 -148
  88. data/lib/active_record/inheritance.rb +77 -59
  89. data/lib/active_record/integration.rb +5 -5
  90. data/lib/active_record/locale/en.yml +8 -1
  91. data/lib/active_record/locking/optimistic.rb +38 -42
  92. data/lib/active_record/locking/pessimistic.rb +4 -4
  93. data/lib/active_record/log_subscriber.rb +19 -9
  94. data/lib/active_record/migration.rb +318 -153
  95. data/lib/active_record/migration/command_recorder.rb +90 -31
  96. data/lib/active_record/migration/join_table.rb +15 -0
  97. data/lib/active_record/model_schema.rb +69 -92
  98. data/lib/active_record/nested_attributes.rb +113 -148
  99. data/lib/active_record/null_relation.rb +65 -0
  100. data/lib/active_record/persistence.rb +188 -97
  101. data/lib/active_record/query_cache.rb +18 -36
  102. data/lib/active_record/querying.rb +19 -15
  103. data/lib/active_record/railtie.rb +91 -36
  104. data/lib/active_record/railties/console_sandbox.rb +0 -2
  105. data/lib/active_record/railties/controller_runtime.rb +2 -2
  106. data/lib/active_record/railties/databases.rake +90 -309
  107. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  108. data/lib/active_record/readonly_attributes.rb +7 -3
  109. data/lib/active_record/reflection.rb +72 -56
  110. data/lib/active_record/relation.rb +241 -157
  111. data/lib/active_record/relation/batches.rb +25 -22
  112. data/lib/active_record/relation/calculations.rb +143 -121
  113. data/lib/active_record/relation/delegation.rb +96 -18
  114. data/lib/active_record/relation/finder_methods.rb +117 -183
  115. data/lib/active_record/relation/merger.rb +133 -0
  116. data/lib/active_record/relation/predicate_builder.rb +90 -42
  117. data/lib/active_record/relation/query_methods.rb +666 -136
  118. data/lib/active_record/relation/spawn_methods.rb +43 -150
  119. data/lib/active_record/result.rb +33 -6
  120. data/lib/active_record/sanitization.rb +24 -50
  121. data/lib/active_record/schema.rb +19 -12
  122. data/lib/active_record/schema_dumper.rb +31 -39
  123. data/lib/active_record/schema_migration.rb +36 -0
  124. data/lib/active_record/scoping.rb +0 -124
  125. data/lib/active_record/scoping/default.rb +48 -45
  126. data/lib/active_record/scoping/named.rb +74 -103
  127. data/lib/active_record/serialization.rb +6 -2
  128. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  129. data/lib/active_record/store.rb +119 -15
  130. data/lib/active_record/tasks/database_tasks.rb +158 -0
  131. data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
  132. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  133. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  134. data/lib/active_record/test_case.rb +61 -38
  135. data/lib/active_record/timestamp.rb +8 -9
  136. data/lib/active_record/transactions.rb +65 -51
  137. data/lib/active_record/validations.rb +17 -15
  138. data/lib/active_record/validations/associated.rb +20 -14
  139. data/lib/active_record/validations/presence.rb +65 -0
  140. data/lib/active_record/validations/uniqueness.rb +93 -52
  141. data/lib/active_record/version.rb +4 -4
  142. data/lib/rails/generators/active_record.rb +3 -5
  143. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
  144. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  145. data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
  146. data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
  147. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  148. metadata +53 -46
  149. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  150. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  151. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  152. data/lib/active_record/dynamic_finder_match.rb +0 -68
  153. data/lib/active_record/dynamic_scope_match.rb +0 -23
  154. data/lib/active_record/fixtures/file.rb +0 -65
  155. data/lib/active_record/identity_map.rb +0 -162
  156. data/lib/active_record/observer.rb +0 -121
  157. data/lib/active_record/session_store.rb +0 -360
  158. data/lib/rails/generators/active_record/migration.rb +0 -15
  159. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  160. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  161. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  162. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,46 +1,51 @@
1
- require 'active_support/core_ext/class/attribute'
2
- require 'active_support/core_ext/object/blank'
1
+ require 'active_support/core_ext/module/attribute_accessors'
3
2
 
4
3
  module ActiveRecord
5
4
  module AttributeMethods
6
- module Dirty
5
+ module Dirty # :nodoc:
7
6
  extend ActiveSupport::Concern
7
+
8
8
  include ActiveModel::Dirty
9
- include AttributeMethods::Write
10
9
 
11
10
  included do
12
11
  if self < ::ActiveRecord::Timestamp
13
12
  raise "You cannot include Dirty after Timestamp"
14
13
  end
15
14
 
16
- class_attribute :partial_updates
17
- self.partial_updates = true
15
+ class_attribute :partial_writes, instance_writer: false
16
+ self.partial_writes = true
17
+
18
+ def self.partial_updates=(v); self.partial_writes = v; end
19
+ def self.partial_updates?; partial_writes?; end
20
+ def self.partial_updates; partial_writes; end
21
+
22
+ ActiveSupport::Deprecation.deprecate_methods(
23
+ singleton_class,
24
+ :partial_updates= => :partial_writes=,
25
+ :partial_updates? => :partial_writes?,
26
+ :partial_updates => :partial_writes
27
+ )
18
28
  end
19
29
 
20
30
  # Attempts to +save+ the record and clears changed attributes if successful.
21
- def save(*) #:nodoc:
31
+ def save(*)
22
32
  if status = super
23
33
  @previously_changed = changes
24
34
  @changed_attributes.clear
25
- elsif IdentityMap.enabled?
26
- IdentityMap.remove(self)
27
35
  end
28
36
  status
29
37
  end
30
38
 
31
39
  # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
32
- def save!(*) #:nodoc:
40
+ def save!(*)
33
41
  super.tap do
34
42
  @previously_changed = changes
35
43
  @changed_attributes.clear
36
44
  end
37
- rescue
38
- IdentityMap.remove(self) if IdentityMap.enabled?
39
- raise
40
45
  end
41
46
 
42
47
  # <tt>reload</tt> the record and clears changed attributes.
43
- def reload(*) #:nodoc:
48
+ def reload(*)
44
49
  super.tap do
45
50
  @previously_changed.clear
46
51
  @changed_attributes.clear
@@ -58,8 +63,6 @@ module ActiveRecord
58
63
  @changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
59
64
  else
60
65
  old = clone_attribute_value(:read_attribute, attr)
61
- # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
62
- old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
63
66
  @changed_attributes[attr] = old if _field_changed?(attr, old, value)
64
67
  end
65
68
 
@@ -67,14 +70,18 @@ module ActiveRecord
67
70
  super(attr, value)
68
71
  end
69
72
 
70
- def update(*)
71
- if partial_updates?
72
- # Serialized attributes should always be written in case they've been
73
- # changed in place.
74
- super(changed | (attributes.keys & self.class.serialized_attributes.keys))
75
- else
76
- super
77
- end
73
+ def update_record(*)
74
+ partial_writes? ? super(keys_for_partial_write) : super
75
+ end
76
+
77
+ def create_record(*)
78
+ partial_writes? ? super(keys_for_partial_write) : super
79
+ end
80
+
81
+ # Serialized attributes should always be written in case they've been
82
+ # changed in place.
83
+ def keys_for_partial_write
84
+ changed | (attributes.keys & self.class.serialized_attributes.keys)
78
85
  end
79
86
 
80
87
  def _field_changed?(attr, old, value)
@@ -90,10 +97,6 @@ module ActiveRecord
90
97
  old != value
91
98
  end
92
99
 
93
- def clone_with_time_zone_conversion_attribute?(attr, old)
94
- old.class.name == "Time" && time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
95
- end
96
-
97
100
  def changes_from_nil_to_empty_string?(column, old, value)
98
101
  # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
99
102
  # Hence we don't record it as a change if the value changes from nil to ''.
@@ -1,56 +1,73 @@
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
+ protected
41
+
42
+ def attribute_method?(attr_name)
43
+ attr_name == 'id' || super
44
+ end
45
+
27
46
  module ClassMethods
28
47
  def define_method_attribute(attr_name)
29
48
  super
30
49
 
31
50
  if attr_name == primary_key && attr_name != 'id'
32
51
  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
52
  end
40
53
  end
41
54
 
55
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast).to_set
56
+
42
57
  def dangerous_attribute_method?(method_name)
43
- super && !['id', 'id=', 'id?'].include?(method_name)
58
+ super && !ID_ATTRIBUTE_METHODS.include?(method_name)
44
59
  end
45
60
 
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.
61
+ # Defines the primary key field -- can be overridden in subclasses.
62
+ # Overwriting will negate any effect of the +primary_key_prefix_type+
63
+ # setting, though.
48
64
  def primary_key
49
65
  @primary_key = reset_primary_key unless defined? @primary_key
50
66
  @primary_key
51
67
  end
52
68
 
53
- # Returns a quoted version of the primary key name, used to construct SQL statements.
69
+ # Returns a quoted version of the primary key name, used to construct
70
+ # SQL statements.
54
71
  def quoted_primary_key
55
72
  @quoted_primary_key ||= connection.quote_column_name(primary_key)
56
73
  end
@@ -64,7 +81,7 @@ module ActiveRecord
64
81
  end
65
82
 
66
83
  def get_primary_key(base_name) #:nodoc:
67
- return 'id' unless base_name && !base_name.blank?
84
+ return 'id' if base_name.blank?
68
85
 
69
86
  case primary_key_prefix_type
70
87
  when :table_name
@@ -80,32 +97,23 @@ module ActiveRecord
80
97
  end
81
98
  end
82
99
 
83
- def original_primary_key #:nodoc:
84
- deprecated_original_property_getter :primary_key
85
- end
86
-
87
100
  # Sets the name of the primary key column.
88
101
  #
89
102
  # class Project < ActiveRecord::Base
90
- # self.primary_key = "sysid"
103
+ # self.primary_key = 'sysid'
91
104
  # end
92
105
  #
93
- # You can also define the primary_key method yourself:
106
+ # You can also define the +primary_key+ method yourself:
94
107
  #
95
108
  # class Project < ActiveRecord::Base
96
109
  # def self.primary_key
97
- # "foo_" + super
110
+ # 'foo_' + super
98
111
  # end
99
112
  # end
113
+ #
100
114
  # Project.primary_key # => "foo_id"
101
115
  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
116
+ @primary_key = value && value.to_s
109
117
  @quoted_primary_key = nil
110
118
  end
111
119
  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,78 @@ 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 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) }
47
55
  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
56
+ alias_method #{name.inspect}, :__temp__#{safe_name}
57
+ undef_method :__temp__#{safe_name}
58
+ STR
58
59
  end
59
60
 
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
61
  private
80
62
 
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)
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
92
67
  attribute_types_cached_by_default.include?(column.type)
93
68
  end
69
+ end
70
+ end
94
71
 
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})"
114
- end
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
+ def read_attribute(attr_name)
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
+ }
115
87
 
116
- access_code
117
- end
88
+ value = @attributes.fetch(name) {
89
+ return block_given? ? yield(name) : nil
90
+ }
118
91
 
119
- def attribute_cast_code(attr_name)
120
- columns_hash[attr_name].type_cast_code('v')
92
+ if self.class.cache_attribute?(name)
93
+ @attributes_cache[name] = column.type_cast(value)
94
+ else
95
+ column.type_cast value
121
96
  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)
97
+ }
128
98
  end
129
99
 
130
100
  private
131
- def attribute(attribute_name)
132
- read_attribute(attribute_name)
133
- end
101
+
102
+ def attribute(attribute_name)
103
+ read_attribute(attribute_name)
104
+ end
134
105
  end
135
106
  end
136
107
  end