activerecord 3.1.12 → 3.2.0.rc1

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 (99) hide show
  1. data/CHANGELOG.md +6263 -103
  2. data/README.rdoc +2 -2
  3. data/examples/performance.rb +55 -31
  4. data/lib/active_record.rb +28 -2
  5. data/lib/active_record/aggregations.rb +2 -2
  6. data/lib/active_record/associations.rb +82 -69
  7. data/lib/active_record/associations/association.rb +2 -37
  8. data/lib/active_record/associations/association_scope.rb +3 -30
  9. data/lib/active_record/associations/builder/association.rb +6 -4
  10. data/lib/active_record/associations/builder/belongs_to.rb +3 -3
  11. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +4 -4
  13. data/lib/active_record/associations/builder/has_one.rb +5 -6
  14. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  15. data/lib/active_record/associations/collection_association.rb +55 -28
  16. data/lib/active_record/associations/collection_proxy.rb +1 -35
  17. data/lib/active_record/associations/has_many_association.rb +5 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +11 -8
  19. data/lib/active_record/associations/join_dependency.rb +1 -1
  20. data/lib/active_record/associations/preloader/association.rb +3 -1
  21. data/lib/active_record/attribute_assignment.rb +221 -0
  22. data/lib/active_record/attribute_methods.rb +212 -32
  23. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  24. data/lib/active_record/attribute_methods/dirty.rb +3 -3
  25. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  26. data/lib/active_record/attribute_methods/read.rb +69 -80
  27. data/lib/active_record/attribute_methods/serialization.rb +89 -0
  28. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
  29. data/lib/active_record/attribute_methods/write.rb +27 -5
  30. data/lib/active_record/autosave_association.rb +23 -8
  31. data/lib/active_record/base.rb +223 -1712
  32. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
  33. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -13
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
  40. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  41. data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
  42. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
  43. data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
  44. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  45. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  46. data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
  47. data/lib/active_record/counter_cache.rb +1 -1
  48. data/lib/active_record/dynamic_matchers.rb +79 -0
  49. data/lib/active_record/errors.rb +11 -1
  50. data/lib/active_record/explain.rb +83 -0
  51. data/lib/active_record/explain_subscriber.rb +21 -0
  52. data/lib/active_record/fixtures.rb +31 -76
  53. data/lib/active_record/fixtures/file.rb +65 -0
  54. data/lib/active_record/identity_map.rb +1 -7
  55. data/lib/active_record/inheritance.rb +167 -0
  56. data/lib/active_record/integration.rb +49 -0
  57. data/lib/active_record/locking/optimistic.rb +19 -11
  58. data/lib/active_record/locking/pessimistic.rb +1 -1
  59. data/lib/active_record/log_subscriber.rb +3 -3
  60. data/lib/active_record/migration.rb +38 -29
  61. data/lib/active_record/migration/command_recorder.rb +7 -7
  62. data/lib/active_record/model_schema.rb +362 -0
  63. data/lib/active_record/nested_attributes.rb +3 -2
  64. data/lib/active_record/persistence.rb +51 -1
  65. data/lib/active_record/querying.rb +58 -0
  66. data/lib/active_record/railtie.rb +24 -28
  67. data/lib/active_record/railties/controller_runtime.rb +3 -1
  68. data/lib/active_record/railties/databases.rake +133 -77
  69. data/lib/active_record/readonly_attributes.rb +26 -0
  70. data/lib/active_record/reflection.rb +7 -15
  71. data/lib/active_record/relation.rb +78 -35
  72. data/lib/active_record/relation/batches.rb +5 -2
  73. data/lib/active_record/relation/calculations.rb +27 -6
  74. data/lib/active_record/relation/delegation.rb +49 -0
  75. data/lib/active_record/relation/finder_methods.rb +5 -4
  76. data/lib/active_record/relation/predicate_builder.rb +13 -16
  77. data/lib/active_record/relation/query_methods.rb +59 -4
  78. data/lib/active_record/result.rb +1 -1
  79. data/lib/active_record/sanitization.rb +194 -0
  80. data/lib/active_record/schema_dumper.rb +5 -2
  81. data/lib/active_record/scoping.rb +152 -0
  82. data/lib/active_record/scoping/default.rb +140 -0
  83. data/lib/active_record/scoping/named.rb +202 -0
  84. data/lib/active_record/serialization.rb +1 -43
  85. data/lib/active_record/serializers/xml_serializer.rb +2 -44
  86. data/lib/active_record/session_store.rb +11 -11
  87. data/lib/active_record/store.rb +50 -0
  88. data/lib/active_record/test_case.rb +11 -7
  89. data/lib/active_record/timestamp.rb +16 -3
  90. data/lib/active_record/transactions.rb +5 -5
  91. data/lib/active_record/translation.rb +22 -0
  92. data/lib/active_record/validations.rb +1 -1
  93. data/lib/active_record/validations/associated.rb +5 -4
  94. data/lib/active_record/validations/uniqueness.rb +4 -4
  95. data/lib/active_record/version.rb +3 -3
  96. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  97. metadata +48 -38
  98. checksums.yaml +0 -7
  99. data/lib/active_record/named_scope.rb +0 -200
@@ -0,0 +1,32 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/deprecation'
3
+
4
+ module ActiveRecord
5
+ module AttributeMethods
6
+ module DeprecatedUnderscoreRead
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ attribute_method_prefix "_"
11
+ end
12
+
13
+ module ClassMethods
14
+ protected
15
+
16
+ def define_method__attribute(attr_name)
17
+ # Do nothing, let it hit method missing instead.
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ def _attribute(attr_name)
24
+ ActiveSupport::Deprecation.warn(
25
+ "You have called '_#{attr_name}'. This is deprecated. Please use " \
26
+ "either '#{attr_name}' or read_attribute('#{attr_name}')."
27
+ )
28
+ read_attribute(attr_name)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -34,9 +34,9 @@ module ActiveRecord
34
34
  @previously_changed = changes
35
35
  @changed_attributes.clear
36
36
  end
37
- rescue
38
- IdentityMap.remove(self) if IdentityMap.enabled?
39
- raise
37
+ rescue
38
+ IdentityMap.remove(self) if IdentityMap.enabled?
39
+ raise
40
40
  end
41
41
 
42
42
  # <tt>reload</tt> the record and clears changed attributes.
@@ -5,11 +5,44 @@ module ActiveRecord
5
5
 
6
6
  # Returns this record's primary key value wrapped in an Array if one is available
7
7
  def to_key
8
- key = send(self.class.primary_key)
8
+ key = self.id
9
9
  [key] if key
10
10
  end
11
11
 
12
+ # Returns the primary key value
13
+ def id
14
+ read_attribute(self.class.primary_key)
15
+ end
16
+
17
+ # Sets the primary key value
18
+ def id=(value)
19
+ write_attribute(self.class.primary_key, value)
20
+ end
21
+
22
+ # Queries the primary key value
23
+ def id?
24
+ query_attribute(self.class.primary_key)
25
+ end
26
+
12
27
  module ClassMethods
28
+ def define_method_attribute(attr_name)
29
+ super
30
+
31
+ if attr_name == primary_key && attr_name != 'id'
32
+ 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
+ end
40
+ end
41
+
42
+ def dangerous_attribute_method?(method_name)
43
+ super && !['id', 'id=', 'id?'].include?(method_name)
44
+ end
45
+
13
46
  # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
14
47
  # primary_key_prefix_type setting, though.
15
48
  def primary_key
@@ -23,11 +56,11 @@ module ActiveRecord
23
56
  end
24
57
 
25
58
  def reset_primary_key #:nodoc:
26
- key = self == base_class ? get_primary_key(base_class.name) :
27
- base_class.primary_key
28
-
29
- set_primary_key(key)
30
- key
59
+ if self == base_class
60
+ self.primary_key = get_primary_key(base_class.name)
61
+ else
62
+ self.primary_key = base_class.primary_key
63
+ end
31
64
  end
32
65
 
33
66
  def get_primary_key(base_name) #:nodoc:
@@ -39,37 +72,41 @@ module ActiveRecord
39
72
  when :table_name_with_underscore
40
73
  base_name.foreign_key
41
74
  else
42
- if ActiveRecord::Base != self && connection.table_exists?(table_name)
43
- connection.primary_key(table_name)
75
+ if ActiveRecord::Base != self && table_exists?
76
+ connection.schema_cache.primary_keys[table_name]
44
77
  else
45
78
  'id'
46
79
  end
47
80
  end
48
81
  end
49
82
 
50
- attr_accessor :original_primary_key
51
-
52
- # Attribute writer for the primary key column
53
- def primary_key=(value)
54
- @quoted_primary_key = nil
55
- @primary_key = value
56
-
57
- connection_pool.primary_keys[table_name] = @primary_key if connected?
83
+ def original_primary_key #:nodoc:
84
+ deprecated_original_property_getter :primary_key
58
85
  end
59
86
 
60
- # Sets the name of the primary key column to use to the given value,
61
- # or (if the value is nil or false) to the value returned by the given
62
- # block.
87
+ # Sets the name of the primary key column.
63
88
  #
64
89
  # class Project < ActiveRecord::Base
65
- # set_primary_key "sysid"
90
+ # self.primary_key = "sysid"
66
91
  # end
67
- def set_primary_key(value = nil, &block)
92
+ #
93
+ # You can also define the primary_key method yourself:
94
+ #
95
+ # class Project < ActiveRecord::Base
96
+ # def self.primary_key
97
+ # "foo_" + super
98
+ # end
99
+ # end
100
+ # Project.primary_key # => "foo_id"
101
+ 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
68
109
  @quoted_primary_key = nil
69
- @primary_key ||= ''
70
- self.original_primary_key = @primary_key
71
- value &&= value.to_s
72
- self.primary_key = block_given? ? instance_eval(&block) : value
73
110
  end
74
111
  end
75
112
  end
@@ -6,13 +6,8 @@ module ActiveRecord
6
6
  ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
7
7
 
8
8
  included do
9
- attribute_method_suffix ""
10
-
11
9
  cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
12
10
  self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
13
-
14
- # Undefine id so it can be used as an attribute name
15
- undef_method(:id) if method_defined?(:id)
16
11
  end
17
12
 
18
13
  module ClassMethods
@@ -34,109 +29,103 @@ module ActiveRecord
34
29
  cached_attributes.include?(attr_name)
35
30
  end
36
31
 
32
+ def undefine_attribute_methods
33
+ if base_class == self
34
+ generated_external_attribute_methods.module_eval do
35
+ instance_methods.each { |m| undef_method(m) }
36
+ end
37
+ end
38
+
39
+ super
40
+ end
41
+
37
42
  protected
43
+ # We want to generate the methods via module_eval rather than define_method,
44
+ # because define_method is slower on dispatch and uses more memory (because it
45
+ # creates a closure).
46
+ #
47
+ # But sometimes the database might return columns with characters that are not
48
+ # allowed in normal method names (like 'my_column(omg)'. So to work around this
49
+ # we first define with the __temp__ identifier, and then use alias method to
50
+ # rename it to what we want.
38
51
  def define_method_attribute(attr_name)
39
- if serialized_attributes.include?(attr_name)
40
- define_read_method_for_serialized_attribute(attr_name)
41
- else
42
- define_read_method(attr_name, attr_name, columns_hash[attr_name])
43
- end
52
+ cast_code = attribute_cast_code(attr_name)
44
53
 
45
- if attr_name == primary_key && attr_name != "id"
46
- define_read_method('id', attr_name, columns_hash[attr_name])
47
- end
54
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
55
+ def __temp__
56
+ #{internal_attribute_access_code(attr_name, cast_code)}
57
+ end
58
+ alias_method '#{attr_name}', :__temp__
59
+ undef_method :__temp__
60
+ STR
61
+
62
+ generated_external_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
63
+ def __temp__(v, attributes, attributes_cache, attr_name)
64
+ #{external_attribute_access_code(attr_name, cast_code)}
65
+ end
66
+ alias_method '#{attr_name}', :__temp__
67
+ undef_method :__temp__
68
+ STR
48
69
  end
49
70
 
50
71
  private
51
72
  def cacheable_column?(column)
52
- serialized_attributes.include?(column.name) || attribute_types_cached_by_default.include?(column.type)
53
- end
54
-
55
- # Define read method for serialized attribute.
56
- def define_read_method_for_serialized_attribute(attr_name)
57
- access_code = "@attributes_cache['#{attr_name}'] ||= @attributes['#{attr_name}']"
58
- generated_attribute_methods.module_eval("def _#{attr_name}; #{access_code}; end; alias #{attr_name} _#{attr_name}", __FILE__, __LINE__)
73
+ attribute_types_cached_by_default.include?(column.type)
59
74
  end
60
75
 
61
- # Define an attribute reader method. Cope with nil column.
62
- # method_name is the same as attr_name except when a non-standard primary key is used,
63
- # we still define #id as an accessor for the key
64
- def define_read_method(method_name, attr_name, column)
65
- cast_code = column.type_cast_code('v')
66
- access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}"
76
+ def internal_attribute_access_code(attr_name, cast_code)
77
+ access_code = "(v=@attributes[attr_name]) && #{cast_code}"
67
78
 
68
- unless attr_name.to_s == self.primary_key.to_s
69
- access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
79
+ unless attr_name == primary_key
80
+ access_code.insert(0, "missing_attribute(attr_name, caller) unless @attributes.has_key?(attr_name); ")
70
81
  end
71
82
 
72
83
  if cache_attribute?(attr_name)
73
- access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
84
+ access_code = "@attributes_cache[attr_name] ||= (#{access_code})"
74
85
  end
75
86
 
76
- # Where possible, generate the method by evalling a string, as this will result in
77
- # faster accesses because it avoids the block eval and then string eval incurred
78
- # by the second branch.
79
- #
80
- # The second, slower, branch is necessary to support instances where the database
81
- # returns columns with extra stuff in (like 'my_column(omg)').
82
- if method_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
83
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__
84
- def _#{method_name}
85
- #{access_code}
86
- end
87
-
88
- alias #{method_name} _#{method_name}
89
- STR
90
- else
91
- generated_attribute_methods.module_eval do
92
- define_method("_#{method_name}") { eval(access_code) }
93
- alias_method(method_name, "_#{method_name}")
94
- end
87
+ "attr_name = '#{attr_name}'; #{access_code}"
88
+ end
89
+
90
+ def external_attribute_access_code(attr_name, cast_code)
91
+ access_code = "v && #{cast_code}"
92
+
93
+ if cache_attribute?(attr_name)
94
+ access_code = "attributes_cache[attr_name] ||= (#{access_code})"
95
95
  end
96
+
97
+ access_code
98
+ end
99
+
100
+ def attribute_cast_code(attr_name)
101
+ columns_hash[attr_name].type_cast_code('v')
96
102
  end
97
103
  end
98
104
 
99
105
  # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
100
106
  # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
101
107
  def read_attribute(attr_name)
102
- method = "_#{attr_name}"
103
- if respond_to? method
104
- send method if @attributes.has_key?(attr_name.to_s)
105
- else
106
- _read_attribute attr_name
107
- end
108
- end
108
+ return unless attr_name
109
109
 
110
- def _read_attribute(attr_name)
111
110
  attr_name = attr_name.to_s
112
- attr_name = self.class.primary_key if attr_name == 'id'
113
- value = @attributes[attr_name]
114
- unless value.nil?
115
- if column = column_for_attribute(attr_name)
116
- if unserializable_attribute?(attr_name, column)
117
- unserialize_attribute(attr_name)
118
- else
119
- column.type_cast(value)
120
- end
121
- else
122
- value
111
+ methods = self.class.generated_external_attribute_methods
112
+
113
+ if methods.method_defined?(attr_name)
114
+ if @attributes.has_key?(attr_name) || attr_name == 'id'
115
+ methods.send(attr_name, @attributes[attr_name], @attributes, @attributes_cache, attr_name)
123
116
  end
117
+ elsif !self.class.attribute_methods_generated?
118
+ # If we haven't generated the caster methods yet, do that and
119
+ # then try again
120
+ self.class.define_attribute_methods
121
+ read_attribute(attr_name)
122
+ else
123
+ # If we get here, the attribute has no associated DB column, so
124
+ # just return it verbatim.
125
+ @attributes[attr_name]
124
126
  end
125
127
  end
126
128
 
127
- # Returns true if the attribute is of a text column and marked for serialization.
128
- def unserializable_attribute?(attr_name, column)
129
- column.text? && self.class.serialized_attributes.include?(attr_name)
130
- end
131
-
132
- # Returns the unserialized object of the attribute.
133
- def unserialize_attribute(attr_name)
134
- coder = self.class.serialized_attributes[attr_name]
135
- unserialized_object = coder.load(@attributes[attr_name])
136
-
137
- @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
138
- end
139
-
140
129
  private
141
130
  def attribute(attribute_name)
142
131
  read_attribute(attribute_name)
@@ -0,0 +1,89 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Serialization
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # Returns a hash of all the attributes that have been specified for serialization as
8
+ # keys and their class restriction as values.
9
+ class_attribute :serialized_attributes
10
+ self.serialized_attributes = {}
11
+ end
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
+ 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.
38
+ #
39
+ # ==== Parameters
40
+ #
41
+ # * +attr_name+ - The field name that should be serialized.
42
+ # * +class_name+ - Optional, class name that the object type should be equal to.
43
+ #
44
+ # ==== Example
45
+ # # Serialize a preferences attribute
46
+ # class User < ActiveRecord::Base
47
+ # serialize :preferences
48
+ # end
49
+ def serialize(attr_name, class_name = Object)
50
+ coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
51
+ class_name
52
+ else
53
+ Coders::YAMLColumn.new(class_name)
54
+ end
55
+
56
+ # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
57
+ # has its own hash of own serialized attributes
58
+ self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
59
+ end
60
+
61
+ private
62
+
63
+ def attribute_cast_code(attr_name)
64
+ if serialized_attributes.include?(attr_name)
65
+ "v.unserialized_value"
66
+ else
67
+ super
68
+ end
69
+ end
70
+ end
71
+
72
+ def set_serialized_attributes
73
+ self.class.serialized_attributes.each do |key, coder|
74
+ if @attributes.key?(key)
75
+ @attributes[key] = Attribute.new(coder, @attributes[key], :serialized)
76
+ end
77
+ end
78
+ end
79
+
80
+ def type_cast_attribute_for_write(column, value)
81
+ if column && coder = self.class.serialized_attributes[column.name]
82
+ Attribute.new(coder, value, :unserialized)
83
+ else
84
+ super
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end