activerecord 4.0.4 → 4.1.16

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1632 -1797
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/examples/performance.rb +30 -18
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +2 -1
  8. data/lib/active_record/association_relation.rb +4 -0
  9. data/lib/active_record/associations/alias_tracker.rb +49 -29
  10. data/lib/active_record/associations/association.rb +9 -17
  11. data/lib/active_record/associations/association_scope.rb +59 -49
  12. data/lib/active_record/associations/belongs_to_association.rb +34 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +6 -1
  14. data/lib/active_record/associations/builder/association.rb +84 -54
  15. data/lib/active_record/associations/builder/belongs_to.rb +90 -58
  16. data/lib/active_record/associations/builder/collection_association.rb +47 -45
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +119 -25
  18. data/lib/active_record/associations/builder/has_many.rb +3 -3
  19. data/lib/active_record/associations/builder/has_one.rb +5 -7
  20. data/lib/active_record/associations/builder/singular_association.rb +6 -7
  21. data/lib/active_record/associations/collection_association.rb +121 -111
  22. data/lib/active_record/associations/collection_proxy.rb +73 -18
  23. data/lib/active_record/associations/has_many_association.rb +14 -11
  24. data/lib/active_record/associations/has_many_through_association.rb +33 -6
  25. data/lib/active_record/associations/has_one_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency/join_association.rb +46 -104
  27. data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
  28. data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
  29. data/lib/active_record/associations/join_dependency.rb +208 -168
  30. data/lib/active_record/associations/preloader/association.rb +69 -27
  31. data/lib/active_record/associations/preloader/collection_association.rb +2 -2
  32. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  33. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/through_association.rb +58 -26
  35. data/lib/active_record/associations/preloader.rb +63 -49
  36. data/lib/active_record/associations/singular_association.rb +6 -5
  37. data/lib/active_record/associations/through_association.rb +30 -9
  38. data/lib/active_record/associations.rb +116 -42
  39. data/lib/active_record/attribute_assignment.rb +6 -3
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
  41. data/lib/active_record/attribute_methods/dirty.rb +35 -26
  42. data/lib/active_record/attribute_methods/primary_key.rb +8 -1
  43. data/lib/active_record/attribute_methods/read.rb +56 -29
  44. data/lib/active_record/attribute_methods/serialization.rb +44 -12
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +13 -1
  46. data/lib/active_record/attribute_methods/write.rb +59 -26
  47. data/lib/active_record/attribute_methods.rb +82 -43
  48. data/lib/active_record/autosave_association.rb +209 -194
  49. data/lib/active_record/base.rb +6 -2
  50. data/lib/active_record/callbacks.rb +2 -2
  51. data/lib/active_record/coders/json.rb +13 -0
  52. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +5 -10
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -24
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -13
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +6 -3
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +90 -0
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +45 -70
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -0
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -96
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +74 -66
  63. data/lib/active_record/connection_adapters/column.rb +1 -35
  64. data/lib/active_record/connection_adapters/connection_specification.rb +231 -43
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -5
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +24 -17
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +22 -15
  68. data/lib/active_record/connection_adapters/postgresql/cast.rb +12 -4
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -44
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -14
  71. data/lib/active_record/connection_adapters/postgresql/quoting.rb +37 -12
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +20 -11
  73. data/lib/active_record/connection_adapters/postgresql_adapter.rb +98 -52
  74. data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -60
  76. data/lib/active_record/connection_handling.rb +39 -5
  77. data/lib/active_record/core.rb +38 -54
  78. data/lib/active_record/counter_cache.rb +9 -10
  79. data/lib/active_record/dynamic_matchers.rb +6 -2
  80. data/lib/active_record/enum.rb +199 -0
  81. data/lib/active_record/errors.rb +22 -5
  82. data/lib/active_record/fixture_set/file.rb +2 -1
  83. data/lib/active_record/fixtures.rb +173 -76
  84. data/lib/active_record/gem_version.rb +15 -0
  85. data/lib/active_record/inheritance.rb +23 -9
  86. data/lib/active_record/integration.rb +54 -1
  87. data/lib/active_record/locking/optimistic.rb +7 -2
  88. data/lib/active_record/locking/pessimistic.rb +1 -1
  89. data/lib/active_record/log_subscriber.rb +6 -13
  90. data/lib/active_record/migration/command_recorder.rb +8 -2
  91. data/lib/active_record/migration.rb +91 -56
  92. data/lib/active_record/model_schema.rb +7 -14
  93. data/lib/active_record/nested_attributes.rb +25 -13
  94. data/lib/active_record/no_touching.rb +52 -0
  95. data/lib/active_record/null_relation.rb +26 -6
  96. data/lib/active_record/persistence.rb +23 -29
  97. data/lib/active_record/querying.rb +15 -12
  98. data/lib/active_record/railtie.rb +12 -61
  99. data/lib/active_record/railties/databases.rake +37 -56
  100. data/lib/active_record/readonly_attributes.rb +0 -6
  101. data/lib/active_record/reflection.rb +230 -79
  102. data/lib/active_record/relation/batches.rb +74 -24
  103. data/lib/active_record/relation/calculations.rb +52 -48
  104. data/lib/active_record/relation/delegation.rb +54 -39
  105. data/lib/active_record/relation/finder_methods.rb +210 -67
  106. data/lib/active_record/relation/merger.rb +15 -12
  107. data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
  108. data/lib/active_record/relation/predicate_builder/relation_handler.rb +17 -0
  109. data/lib/active_record/relation/predicate_builder.rb +81 -40
  110. data/lib/active_record/relation/query_methods.rb +185 -108
  111. data/lib/active_record/relation/spawn_methods.rb +8 -5
  112. data/lib/active_record/relation.rb +79 -84
  113. data/lib/active_record/result.rb +45 -6
  114. data/lib/active_record/runtime_registry.rb +5 -0
  115. data/lib/active_record/sanitization.rb +4 -4
  116. data/lib/active_record/schema_dumper.rb +18 -6
  117. data/lib/active_record/schema_migration.rb +31 -18
  118. data/lib/active_record/scoping/default.rb +5 -18
  119. data/lib/active_record/scoping/named.rb +14 -29
  120. data/lib/active_record/scoping.rb +5 -0
  121. data/lib/active_record/store.rb +67 -18
  122. data/lib/active_record/tasks/database_tasks.rb +66 -26
  123. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -10
  124. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  125. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  126. data/lib/active_record/timestamp.rb +6 -6
  127. data/lib/active_record/transactions.rb +10 -12
  128. data/lib/active_record/validations/presence.rb +1 -1
  129. data/lib/active_record/validations/uniqueness.rb +19 -9
  130. data/lib/active_record/version.rb +4 -7
  131. data/lib/active_record.rb +5 -7
  132. data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
  133. data/lib/rails/generators/active_record/migration.rb +18 -0
  134. data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
  135. data/lib/rails/generators/active_record.rb +2 -8
  136. metadata +18 -30
  137. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
  138. data/lib/active_record/associations/join_helper.rb +0 -45
  139. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
  141. data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
  142. data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
  143. data/lib/active_record/test_case.rb +0 -96
@@ -1,6 +1,38 @@
1
+ require 'active_support/core_ext/module/method_transplanting'
2
+
1
3
  module ActiveRecord
2
4
  module AttributeMethods
3
5
  module Read
6
+ ReaderMethodCache = Class.new(AttributeMethodCache) {
7
+ private
8
+ # We want to generate the methods via module_eval rather than
9
+ # define_method, because define_method is slower on dispatch.
10
+ # Evaluating many similar methods may use more memory as the instruction
11
+ # sequences are duplicated and cached (in MRI). define_method may
12
+ # be slower on dispatch, but if you're careful about the closure
13
+ # created, then define_method will consume much less memory.
14
+ #
15
+ # But sometimes the database might return columns with
16
+ # characters that are not allowed in normal method names (like
17
+ # 'my_column(omg)'. So to work around this we first define with
18
+ # the __temp__ identifier, and then use alias method to rename
19
+ # it to what we want.
20
+ #
21
+ # We are also defining a constant to hold the frozen string of
22
+ # the attribute name. Using a constant means that we do not have
23
+ # to allocate an object on each call to the attribute method.
24
+ # Making it frozen means that it doesn't get duped when used to
25
+ # key the @attributes_cache in read_attribute.
26
+ def method_body(method_name, const_name)
27
+ <<-EOMETHOD
28
+ def #{method_name}
29
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
30
+ read_attribute(name) { |n| missing_attribute(n, caller) }
31
+ end
32
+ EOMETHOD
33
+ end
34
+ }.new
35
+
4
36
  extend ActiveSupport::Concern
5
37
 
6
38
  ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
@@ -32,35 +64,30 @@ module ActiveRecord
32
64
 
33
65
  protected
34
66
 
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) }
67
+ if Module.methods_transplantable?
68
+ def define_method_attribute(name)
69
+ method = ReaderMethodCache[name]
70
+ generated_attribute_methods.module_eval { define_method name, method }
71
+ end
72
+ else
73
+ def define_method_attribute(name)
74
+ safe_name = name.unpack('h*').first
75
+ temp_method = "__temp__#{safe_name}"
76
+
77
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
78
+
79
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
80
+ def #{temp_method}
81
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
82
+ read_attribute(name) { |n| missing_attribute(n, caller) }
83
+ end
84
+ STR
85
+
86
+ generated_attribute_methods.module_eval do
87
+ alias_method name, temp_method
88
+ undef_method temp_method
60
89
  end
61
- alias_method #{name.inspect}, :__temp__#{safe_name}
62
- undef_method :__temp__#{safe_name}
63
- STR
90
+ end
64
91
  end
65
92
 
66
93
  private
@@ -75,7 +102,7 @@ module ActiveRecord
75
102
  end
76
103
 
77
104
  # 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
105
+ # it has been typecast (for example, "2004-12-12" in a date column is cast
79
106
  # to a date object, like Date.new(2004, 12, 12)).
80
107
  def read_attribute(attr_name)
81
108
  # If it's cached, just return it
@@ -24,10 +24,14 @@ module ActiveRecord
24
24
  # serialized object must be of that class on retrieval or
25
25
  # <tt>SerializationTypeMismatch</tt> will be raised.
26
26
  #
27
+ # A notable side effect of serialized attributes is that the model will
28
+ # be updated on every save, even if it is not dirty.
29
+ #
27
30
  # ==== Parameters
28
31
  #
29
32
  # * +attr_name+ - The field name that should be serialized.
30
- # * +class_name+ - Optional, class name that the object type should be equal to.
33
+ # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump`
34
+ # or a class name that the object type should be equal to.
31
35
  #
32
36
  # ==== Example
33
37
  #
@@ -35,13 +39,28 @@ module ActiveRecord
35
39
  # class User < ActiveRecord::Base
36
40
  # serialize :preferences
37
41
  # end
38
- def serialize(attr_name, class_name = Object)
42
+ #
43
+ # # Serialize preferences using JSON as coder.
44
+ # class User < ActiveRecord::Base
45
+ # serialize :preferences, JSON
46
+ # end
47
+ #
48
+ # # Serialize preferences as Hash using YAML coder.
49
+ # class User < ActiveRecord::Base
50
+ # serialize :preferences, Hash
51
+ # end
52
+ def serialize(attr_name, class_name_or_coder = Object)
39
53
  include Behavior
40
54
 
41
- coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
42
- class_name
55
+ # When ::JSON is used, force it to go through the Active Support JSON encoder
56
+ # to ensure special objects (e.g. Active Record models) are dumped correctly
57
+ # using the #as_json hook.
58
+ coder = if class_name_or_coder == ::JSON
59
+ Coders::JSON
60
+ elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
61
+ class_name_or_coder
43
62
  else
44
- Coders::YAMLColumn.new(class_name)
63
+ Coders::YAMLColumn.new(class_name_or_coder)
45
64
  end
46
65
 
47
66
  # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
@@ -50,13 +69,6 @@ module ActiveRecord
50
69
  end
51
70
  end
52
71
 
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
72
  class Type # :nodoc:
61
73
  def initialize(column)
62
74
  @column = column
@@ -73,6 +85,10 @@ module ActiveRecord
73
85
  def type
74
86
  @column.type
75
87
  end
88
+
89
+ def accessor
90
+ ActiveRecord::Store::IndifferentHashAccessor
91
+ end
76
92
  end
77
93
 
78
94
  class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
@@ -115,6 +131,14 @@ module ActiveRecord
115
131
  end
116
132
  end
117
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
+
118
142
  def type_cast_attribute_for_write(column, value)
119
143
  if column && coder = self.class.serialized_attributes[column.name]
120
144
  Attribute.new(coder, value, :unserialized)
@@ -123,6 +147,14 @@ module ActiveRecord
123
147
  end
124
148
  end
125
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
+
126
158
  def _field_changed?(attr, old, value)
127
159
  if self.class.serialized_attributes.include?(attr)
128
160
  old != value
@@ -34,7 +34,7 @@ module ActiveRecord
34
34
  if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
35
35
  method_body, line = <<-EOV, __LINE__ + 1
36
36
  def #{attr_name}=(time)
37
- time_with_zone = time.respond_to?(:in_time_zone) ? time.in_time_zone : nil
37
+ time_with_zone = convert_value_to_time_zone("#{attr_name}", time)
38
38
  previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
39
39
  write_attribute(:#{attr_name}, time)
40
40
  #{attr_name}_will_change! if previous_time != time_with_zone
@@ -54,6 +54,18 @@ module ActiveRecord
54
54
  (:datetime == column.type || :timestamp == column.type)
55
55
  end
56
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
67
+ end
68
+ end
57
69
  end
58
70
  end
59
71
  end
@@ -1,6 +1,21 @@
1
+ require 'active_support/core_ext/module/method_transplanting'
2
+
1
3
  module ActiveRecord
2
4
  module AttributeMethods
3
5
  module Write
6
+ WriterMethodCache = Class.new(AttributeMethodCache) {
7
+ private
8
+
9
+ def method_body(method_name, const_name)
10
+ <<-EOMETHOD
11
+ def #{method_name}(value)
12
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
13
+ write_attribute(name, value)
14
+ end
15
+ EOMETHOD
16
+ end
17
+ }.new
18
+
4
19
  extend ActiveSupport::Concern
5
20
 
6
21
  included do
@@ -10,19 +25,29 @@ module ActiveRecord
10
25
  module ClassMethods
11
26
  protected
12
27
 
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::AttrNames.set_name_cache safe_name, name
28
+ if Module.methods_transplantable?
29
+ # See define_method_attribute in read.rb for an explanation of
30
+ # this code.
31
+ def define_method_attribute=(name)
32
+ method = WriterMethodCache[name]
33
+ generated_attribute_methods.module_eval {
34
+ define_method "#{name}=", method
35
+ }
36
+ end
37
+ else
38
+ def define_method_attribute=(name)
39
+ safe_name = name.unpack('h*').first
40
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
18
41
 
19
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
20
- def __temp__#{safe_name}=(value)
21
- write_attribute(AttrNames::ATTR_#{safe_name}, value)
22
- end
23
- alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
24
- undef_method :__temp__#{safe_name}=
25
- STR
42
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
43
+ def __temp__#{safe_name}=(value)
44
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
45
+ write_attribute(name, value)
46
+ end
47
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
48
+ undef_method :__temp__#{safe_name}=
49
+ STR
50
+ end
26
51
  end
27
52
  end
28
53
 
@@ -30,6 +55,27 @@ module ActiveRecord
30
55
  # specified +value+. Empty strings for fixnum and float columns are
31
56
  # turned into +nil+.
32
57
  def write_attribute(attr_name, value)
58
+ write_attribute_with_type_cast(attr_name, value, :type_cast_attribute_for_write)
59
+ end
60
+
61
+ def raw_write_attribute(attr_name, value)
62
+ write_attribute_with_type_cast(attr_name, value, :raw_type_cast_attribute_for_write)
63
+ end
64
+
65
+ private
66
+ # Handle *= for method_missing.
67
+ def attribute=(attribute_name, value)
68
+ write_attribute(attribute_name, value)
69
+ end
70
+
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)
33
79
  attr_name = attr_name.to_s
34
80
  attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
35
81
  @attributes_cache.delete(attr_name)
@@ -42,24 +88,11 @@ module ActiveRecord
42
88
  end
43
89
 
44
90
  if column || @attributes.has_key?(attr_name)
45
- @attributes[attr_name] = type_cast_attribute_for_write(column, value)
91
+ @attributes[attr_name] = send(type_cast_method, column, value)
46
92
  else
47
93
  raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
48
94
  end
49
95
  end
50
- alias_method :raw_write_attribute, :write_attribute
51
-
52
- private
53
- # Handle *= for method_missing.
54
- def attribute=(attribute_name, value)
55
- write_attribute(attribute_name, value)
56
- end
57
-
58
- def type_cast_attribute_for_write(column, value)
59
- return value unless column
60
-
61
- column.type_cast_for_write value
62
- end
63
96
  end
64
97
  end
65
98
  end
@@ -1,5 +1,6 @@
1
1
  require 'active_support/core_ext/enumerable'
2
2
  require 'mutex_m'
3
+ require 'thread_safe'
3
4
 
4
5
  module ActiveRecord
5
6
  # = Active Record Attribute Methods
@@ -19,6 +20,39 @@ module ActiveRecord
19
20
  include Serialization
20
21
  end
21
22
 
23
+ AttrNames = Module.new {
24
+ def self.set_name_cache(name, value)
25
+ const_name = "ATTR_#{name}"
26
+ unless const_defined? const_name
27
+ const_set const_name, value.dup.freeze
28
+ end
29
+ end
30
+ }
31
+
32
+ BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
33
+
34
+ class AttributeMethodCache
35
+ def initialize
36
+ @module = Module.new
37
+ @method_cache = ThreadSafe::Cache.new
38
+ end
39
+
40
+ def [](name)
41
+ @method_cache.compute_if_absent(name) do
42
+ safe_name = name.unpack('h*').first
43
+ temp_method = "__temp__#{safe_name}"
44
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
45
+ @module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
46
+ @module.instance_method temp_method
47
+ end
48
+ end
49
+
50
+ private
51
+ def method_body; raise NotImplementedError; end
52
+ end
53
+
54
+ class GeneratedAttributeMethods < Module; end # :nodoc:
55
+
22
56
  module ClassMethods
23
57
  def inherited(child_class) #:nodoc:
24
58
  child_class.initialize_generated_modules
@@ -26,25 +60,17 @@ module ActiveRecord
26
60
  end
27
61
 
28
62
  def initialize_generated_modules # :nodoc:
29
- @generated_attribute_methods = Module.new {
30
- extend Mutex_m
31
-
32
- const_set :AttrNames, Module.new {
33
- def self.set_name_cache(name, value)
34
- const_name = "ATTR_#{name}"
35
- unless const_defined? const_name
36
- const_set const_name, value.dup.freeze
37
- end
38
- end
39
- }
40
- }
63
+ @generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m }
41
64
  @attribute_methods_generated = false
42
65
  include @generated_attribute_methods
66
+
67
+ super
43
68
  end
44
69
 
45
70
  # Generates all the attribute related methods for columns in the database
46
71
  # accessors, mutators and query methods.
47
72
  def define_attribute_methods # :nodoc:
73
+ return false if @attribute_methods_generated
48
74
  # Use a mutex; we don't want two thread simultaneously trying to define
49
75
  # attribute methods.
50
76
  generated_attribute_methods.synchronize do
@@ -58,7 +84,7 @@ module ActiveRecord
58
84
 
59
85
  def undefine_attribute_methods # :nodoc:
60
86
  generated_attribute_methods.synchronize do
61
- super if @attribute_methods_generated
87
+ super if defined?(@attribute_methods_generated) && @attribute_methods_generated
62
88
  @attribute_methods_generated = false
63
89
  end
64
90
  end
@@ -79,29 +105,48 @@ module ActiveRecord
79
105
  # # => false
80
106
  def instance_method_already_implemented?(method_name)
81
107
  if dangerous_attribute_method?(method_name)
82
- raise DangerousAttributeError, "#{method_name} is defined by Active Record"
108
+ raise DangerousAttributeError, "#{method_name} is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name."
83
109
  end
84
110
 
85
111
  if superclass == Base
86
112
  super
87
113
  else
88
- # If B < A and A defines its own attribute method, then we don't want to overwrite that.
89
- defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
90
- base_defined = Base.method_defined?(method_name) || Base.private_method_defined?(method_name)
91
- defined && !base_defined || super
114
+ # If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
115
+ # defines its own attribute method, then we don't want to overwrite that.
116
+ defined = method_defined_within?(method_name, superclass, Base) &&
117
+ ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
118
+ defined || super
92
119
  end
93
120
  end
94
121
 
95
- # A method name is 'dangerous' if it is already defined by Active Record, but
122
+ # A method name is 'dangerous' if it is already (re)defined by Active Record, but
96
123
  # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
97
124
  def dangerous_attribute_method?(name) # :nodoc:
98
125
  method_defined_within?(name, Base)
99
126
  end
100
127
 
101
- def method_defined_within?(name, klass, sup = klass.superclass) # :nodoc:
128
+ def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
102
129
  if klass.method_defined?(name) || klass.private_method_defined?(name)
103
- if sup.method_defined?(name) || sup.private_method_defined?(name)
104
- klass.instance_method(name).owner != sup.instance_method(name).owner
130
+ if superklass.method_defined?(name) || superklass.private_method_defined?(name)
131
+ klass.instance_method(name).owner != superklass.instance_method(name).owner
132
+ else
133
+ true
134
+ end
135
+ else
136
+ false
137
+ end
138
+ end
139
+
140
+ # A class method is 'dangerous' if it is already (re)defined by Active Record, but
141
+ # not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
142
+ def dangerous_class_method?(method_name)
143
+ BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
144
+ end
145
+
146
+ def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc
147
+ if klass.respond_to?(name, true)
148
+ if superklass.respond_to?(name, true)
149
+ klass.method(name).owner != superklass.method(name).owner
105
150
  else
106
151
  true
107
152
  end
@@ -161,6 +206,7 @@ module ActiveRecord
161
206
  # this is probably horribly slow, but should only happen at most once for a given AR class
162
207
  attribute_method.bind(self).call(*args, &block)
163
208
  else
209
+ return super unless respond_to_missing?(method, true)
164
210
  send(method, *args, &block)
165
211
  end
166
212
  else
@@ -168,20 +214,6 @@ module ActiveRecord
168
214
  end
169
215
  end
170
216
 
171
- def attribute_missing(match, *args, &block) # :nodoc:
172
- if self.class.columns_hash[match.attr_name]
173
- ActiveSupport::Deprecation.warn(
174
- "The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
175
- "dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \
176
- "is a column of the table. If this error has happened through normal usage of Active " \
177
- "Record (rather than through your own code or external libraries), please report it as " \
178
- "a bug."
179
- )
180
- end
181
-
182
- super
183
- end
184
-
185
217
  # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
186
218
  # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
187
219
  # which will all return +true+. It also define the attribute methods if they have
@@ -210,7 +242,7 @@ module ActiveRecord
210
242
  # For queries selecting a subset of columns, return false for unselected columns.
211
243
  # We check defined?(@attributes) not to issue warnings if called on objects that
212
244
  # have been allocated but not yet initialized.
213
- if defined?(@attributes) && @attributes.present? && self.class.column_names.include?(name)
245
+ if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
214
246
  return has_attribute?(name)
215
247
  end
216
248
 
@@ -263,24 +295,31 @@ module ActiveRecord
263
295
 
264
296
  # Returns an <tt>#inspect</tt>-like string for the value of the
265
297
  # attribute +attr_name+. String attributes are truncated upto 50
266
- # characters, and Date and Time attributes are returned in the
267
- # <tt>:db</tt> format. Other attributes return the value of
268
- # <tt>#inspect</tt> without modification.
298
+ # characters, Date and Time attributes are returned in the
299
+ # <tt>:db</tt> format, Array attributes are truncated upto 10 values.
300
+ # Other attributes return the value of <tt>#inspect</tt> without
301
+ # modification.
269
302
  #
270
303
  # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
271
304
  #
272
305
  # person.attribute_for_inspect(:name)
273
- # # => "\"David Heinemeier Hansson David Heinemeier Hansson D...\""
306
+ # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
274
307
  #
275
308
  # person.attribute_for_inspect(:created_at)
276
309
  # # => "\"2012-10-22 00:15:07\""
310
+ #
311
+ # person.attribute_for_inspect(:tag_ids)
312
+ # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]"
277
313
  def attribute_for_inspect(attr_name)
278
314
  value = read_attribute(attr_name)
279
315
 
280
316
  if value.is_a?(String) && value.length > 50
281
- "#{value[0..50]}...".inspect
317
+ "#{value[0, 50]}...".inspect
282
318
  elsif value.is_a?(Date) || value.is_a?(Time)
283
319
  %("#{value.to_s(:db)}")
320
+ elsif value.is_a?(Array) && value.size > 10
321
+ inspected = value.first(10).inspect
322
+ %(#{inspected[0...-1]}, ...])
284
323
  else
285
324
  value.inspect
286
325
  end
@@ -324,7 +363,7 @@ module ActiveRecord
324
363
  end
325
364
 
326
365
  # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
327
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). It raises
366
+ # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
328
367
  # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
329
368
  #
330
369
  # Alias for the <tt>read_attribute</tt> method.