activerecord 3.0.20 → 3.1.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 (122) hide show
  1. data/CHANGELOG +220 -91
  2. data/README.rdoc +3 -3
  3. data/examples/performance.rb +88 -109
  4. data/lib/active_record.rb +6 -2
  5. data/lib/active_record/aggregations.rb +22 -45
  6. data/lib/active_record/associations.rb +264 -991
  7. data/lib/active_record/associations/alias_tracker.rb +85 -0
  8. data/lib/active_record/associations/association.rb +231 -0
  9. data/lib/active_record/associations/association_scope.rb +120 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +40 -60
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +15 -63
  12. data/lib/active_record/associations/builder/association.rb +53 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +63 -0
  16. data/lib/active_record/associations/builder/has_many.rb +65 -0
  17. data/lib/active_record/associations/builder/has_one.rb +63 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +524 -0
  20. data/lib/active_record/associations/collection_proxy.rb +125 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +27 -118
  22. data/lib/active_record/associations/has_many_association.rb +50 -79
  23. data/lib/active_record/associations/has_many_through_association.rb +98 -67
  24. data/lib/active_record/associations/has_one_association.rb +45 -115
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency.rb +215 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +150 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_helper.rb +56 -0
  31. data/lib/active_record/associations/preloader.rb +177 -0
  32. data/lib/active_record/associations/preloader/association.rb +126 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  42. data/lib/active_record/associations/singular_association.rb +55 -0
  43. data/lib/active_record/associations/through_association.rb +80 -0
  44. data/lib/active_record/attribute_methods.rb +19 -5
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +9 -8
  46. data/lib/active_record/attribute_methods/dirty.rb +8 -2
  47. data/lib/active_record/attribute_methods/primary_key.rb +33 -13
  48. data/lib/active_record/attribute_methods/read.rb +17 -17
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -4
  50. data/lib/active_record/attribute_methods/write.rb +2 -1
  51. data/lib/active_record/autosave_association.rb +66 -45
  52. data/lib/active_record/base.rb +445 -273
  53. data/lib/active_record/callbacks.rb +24 -33
  54. data/lib/active_record/coders/yaml_column.rb +41 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +106 -13
  56. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +16 -2
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -12
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +16 -16
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +61 -22
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -273
  62. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -42
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +44 -25
  64. data/lib/active_record/connection_adapters/column.rb +268 -0
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +686 -0
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +331 -88
  67. data/lib/active_record/connection_adapters/postgresql_adapter.rb +295 -267
  68. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -7
  69. data/lib/active_record/connection_adapters/sqlite_adapter.rb +108 -26
  70. data/lib/active_record/counter_cache.rb +7 -4
  71. data/lib/active_record/fixtures.rb +174 -192
  72. data/lib/active_record/identity_map.rb +131 -0
  73. data/lib/active_record/locking/optimistic.rb +20 -14
  74. data/lib/active_record/locking/pessimistic.rb +4 -4
  75. data/lib/active_record/log_subscriber.rb +24 -4
  76. data/lib/active_record/migration.rb +265 -144
  77. data/lib/active_record/migration/command_recorder.rb +103 -0
  78. data/lib/active_record/named_scope.rb +68 -25
  79. data/lib/active_record/nested_attributes.rb +58 -15
  80. data/lib/active_record/observer.rb +3 -7
  81. data/lib/active_record/persistence.rb +58 -38
  82. data/lib/active_record/query_cache.rb +25 -3
  83. data/lib/active_record/railtie.rb +21 -12
  84. data/lib/active_record/railties/console_sandbox.rb +6 -0
  85. data/lib/active_record/railties/databases.rake +147 -116
  86. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  87. data/lib/active_record/reflection.rb +176 -44
  88. data/lib/active_record/relation.rb +125 -49
  89. data/lib/active_record/relation/batches.rb +7 -5
  90. data/lib/active_record/relation/calculations.rb +50 -18
  91. data/lib/active_record/relation/finder_methods.rb +47 -26
  92. data/lib/active_record/relation/predicate_builder.rb +24 -21
  93. data/lib/active_record/relation/query_methods.rb +117 -101
  94. data/lib/active_record/relation/spawn_methods.rb +27 -20
  95. data/lib/active_record/result.rb +34 -0
  96. data/lib/active_record/schema.rb +5 -6
  97. data/lib/active_record/schema_dumper.rb +11 -13
  98. data/lib/active_record/serialization.rb +2 -2
  99. data/lib/active_record/serializers/xml_serializer.rb +10 -10
  100. data/lib/active_record/session_store.rb +8 -2
  101. data/lib/active_record/test_case.rb +9 -20
  102. data/lib/active_record/timestamp.rb +21 -9
  103. data/lib/active_record/transactions.rb +16 -15
  104. data/lib/active_record/validations.rb +21 -22
  105. data/lib/active_record/validations/associated.rb +3 -1
  106. data/lib/active_record/validations/uniqueness.rb +48 -58
  107. data/lib/active_record/version.rb +3 -3
  108. data/lib/rails/generators/active_record.rb +6 -0
  109. data/lib/rails/generators/active_record/migration/templates/migration.rb +10 -2
  110. data/lib/rails/generators/active_record/model/model_generator.rb +2 -1
  111. data/lib/rails/generators/active_record/model/templates/migration.rb +6 -5
  112. data/lib/rails/generators/active_record/model/templates/model.rb +2 -0
  113. data/lib/rails/generators/active_record/model/templates/module.rb +2 -0
  114. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  115. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +2 -1
  116. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +2 -2
  117. metadata +106 -77
  118. checksums.yaml +0 -7
  119. data/lib/active_record/association_preload.rb +0 -431
  120. data/lib/active_record/associations/association_collection.rb +0 -572
  121. data/lib/active_record/associations/association_proxy.rb +0 -304
  122. data/lib/active_record/associations/through_association_scope.rb +0 -160
@@ -0,0 +1,56 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ # Helper class module which gets mixed into JoinDependency::JoinAssociation and AssociationScope
4
+ module JoinHelper #:nodoc:
5
+
6
+ def join_type
7
+ Arel::InnerJoin
8
+ end
9
+
10
+ private
11
+
12
+ def construct_tables
13
+ tables = []
14
+ chain.each do |reflection|
15
+ tables << alias_tracker.aliased_table_for(
16
+ table_name_for(reflection),
17
+ table_alias_for(reflection, reflection != self.reflection)
18
+ )
19
+
20
+ if reflection.source_macro == :has_and_belongs_to_many
21
+ tables << alias_tracker.aliased_table_for(
22
+ (reflection.source_reflection || reflection).options[:join_table],
23
+ table_alias_for(reflection, true)
24
+ )
25
+ end
26
+ end
27
+ tables
28
+ end
29
+
30
+ def table_name_for(reflection)
31
+ reflection.table_name
32
+ end
33
+
34
+ def table_alias_for(reflection, join = false)
35
+ name = alias_tracker.pluralize(reflection.name)
36
+ name << "_#{alias_suffix}"
37
+ name << "_join" if join
38
+ name
39
+ end
40
+
41
+ def join(table, constraint)
42
+ table.create_join(table, table.create_on(constraint), join_type)
43
+ end
44
+
45
+ def sanitize(conditions, table)
46
+ conditions = conditions.map do |condition|
47
+ condition = active_record.send(:sanitize_sql, interpolate(condition), table.table_alias || table.name)
48
+ condition = Arel.sql(condition) unless condition.is_a?(Arel::Node)
49
+ condition
50
+ end
51
+
52
+ conditions.length == 1 ? conditions.first : Arel::Nodes::And.new(conditions)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,177 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ # Implements the details of eager loading of Active Record associations.
4
+ #
5
+ # Note that 'eager loading' and 'preloading' are actually the same thing.
6
+ # However, there are two different eager loading strategies.
7
+ #
8
+ # The first one is by using table joins. This was only strategy available
9
+ # prior to Rails 2.1. Suppose that you have an Author model with columns
10
+ # 'name' and 'age', and a Book model with columns 'name' and 'sales'. Using
11
+ # this strategy, Active Record would try to retrieve all data for an author
12
+ # and all of its books via a single query:
13
+ #
14
+ # SELECT * FROM authors
15
+ # LEFT OUTER JOIN books ON authors.id = books.id
16
+ # WHERE authors.name = 'Ken Akamatsu'
17
+ #
18
+ # However, this could result in many rows that contain redundant data. After
19
+ # having received the first row, we already have enough data to instantiate
20
+ # the Author object. In all subsequent rows, only the data for the joined
21
+ # 'books' table is useful; the joined 'authors' data is just redundant, and
22
+ # processing this redundant data takes memory and CPU time. The problem
23
+ # quickly becomes worse and worse as the level of eager loading increases
24
+ # (i.e. if Active Record is to eager load the associations' associations as
25
+ # well).
26
+ #
27
+ # The second strategy is to use multiple database queries, one for each
28
+ # level of association. Since Rails 2.1, this is the default strategy. In
29
+ # situations where a table join is necessary (e.g. when the +:conditions+
30
+ # option references an association's column), it will fallback to the table
31
+ # join strategy.
32
+ class Preloader #:nodoc:
33
+ autoload :Association, 'active_record/associations/preloader/association'
34
+ autoload :SingularAssociation, 'active_record/associations/preloader/singular_association'
35
+ autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
36
+ autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
37
+
38
+ autoload :HasMany, 'active_record/associations/preloader/has_many'
39
+ autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
40
+ autoload :HasOne, 'active_record/associations/preloader/has_one'
41
+ autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
42
+ autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many'
43
+ autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
44
+
45
+ attr_reader :records, :associations, :options, :model
46
+
47
+ # Eager loads the named associations for the given Active Record record(s).
48
+ #
49
+ # In this description, 'association name' shall refer to the name passed
50
+ # to an association creation method. For example, a model that specifies
51
+ # <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
52
+ # names +:author+ and +:buyers+.
53
+ #
54
+ # == Parameters
55
+ # +records+ is an array of ActiveRecord::Base. This array needs not be flat,
56
+ # i.e. +records+ itself may also contain arrays of records. In any case,
57
+ # +preload_associations+ will preload the all associations records by
58
+ # flattening +records+.
59
+ #
60
+ # +associations+ specifies one or more associations that you want to
61
+ # preload. It may be:
62
+ # - a Symbol or a String which specifies a single association name. For
63
+ # example, specifying +:books+ allows this method to preload all books
64
+ # for an Author.
65
+ # - an Array which specifies multiple association names. This array
66
+ # is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
67
+ # allows this method to preload an author's avatar as well as all of his
68
+ # books.
69
+ # - a Hash which specifies multiple association names, as well as
70
+ # association names for the to-be-preloaded association objects. For
71
+ # example, specifying <tt>{ :author => :avatar }</tt> will preload a
72
+ # book's author, as well as that author's avatar.
73
+ #
74
+ # +:associations+ has the same format as the +:include+ option for
75
+ # <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
76
+ #
77
+ # :books
78
+ # [ :books, :author ]
79
+ # { :author => :avatar }
80
+ # [ :books, { :author => :avatar } ]
81
+ #
82
+ # +options+ contains options that will be passed to ActiveRecord::Base#find
83
+ # (which is called under the hood for preloading records). But it is passed
84
+ # only one level deep in the +associations+ argument, i.e. it's not passed
85
+ # to the child associations when +associations+ is a Hash.
86
+ def initialize(records, associations, options = {})
87
+ @records = Array.wrap(records).compact.uniq
88
+ @associations = Array.wrap(associations)
89
+ @options = options
90
+ end
91
+
92
+ def run
93
+ unless records.empty?
94
+ associations.each { |association| preload(association) }
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def preload(association)
101
+ case association
102
+ when Hash
103
+ preload_hash(association)
104
+ when String, Symbol
105
+ preload_one(association.to_sym)
106
+ else
107
+ raise ArgumentError, "#{association.inspect} was not recognised for preload"
108
+ end
109
+ end
110
+
111
+ def preload_hash(association)
112
+ association.each do |parent, child|
113
+ Preloader.new(records, parent, options).run
114
+ Preloader.new(records.map { |record| record.send(parent) }.flatten, child).run
115
+ end
116
+ end
117
+
118
+ # Not all records have the same class, so group then preload group on the reflection
119
+ # itself so that if various subclass share the same association then we do not split
120
+ # them unnecessarily
121
+ #
122
+ # Additionally, polymorphic belongs_to associations can have multiple associated
123
+ # classes, depending on the polymorphic_type field. So we group by the classes as
124
+ # well.
125
+ def preload_one(association)
126
+ grouped_records(association).each do |reflection, klasses|
127
+ klasses.each do |klass, records|
128
+ preloader_for(reflection).new(klass, records, reflection, options).run
129
+ end
130
+ end
131
+ end
132
+
133
+ def grouped_records(association)
134
+ Hash[
135
+ records_by_reflection(association).map do |reflection, records|
136
+ [reflection, records.group_by { |record| association_klass(reflection, record) }]
137
+ end
138
+ ]
139
+ end
140
+
141
+ def records_by_reflection(association)
142
+ records.group_by do |record|
143
+ reflection = record.class.reflections[association]
144
+
145
+ unless reflection
146
+ raise ActiveRecord::ConfigurationError, "Association named '#{association}' was not found; " \
147
+ "perhaps you misspelled it?"
148
+ end
149
+
150
+ reflection
151
+ end
152
+ end
153
+
154
+ def association_klass(reflection, record)
155
+ if reflection.macro == :belongs_to && reflection.options[:polymorphic]
156
+ klass = record.send(reflection.foreign_type)
157
+ klass && klass.constantize
158
+ else
159
+ reflection.klass
160
+ end
161
+ end
162
+
163
+ def preloader_for(reflection)
164
+ case reflection.macro
165
+ when :has_many
166
+ reflection.options[:through] ? HasManyThrough : HasMany
167
+ when :has_one
168
+ reflection.options[:through] ? HasOneThrough : HasOne
169
+ when :has_and_belongs_to_many
170
+ HasAndBelongsToMany
171
+ when :belongs_to
172
+ BelongsTo
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,126 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class Association #:nodoc:
5
+ attr_reader :owners, :reflection, :preload_options, :model, :klass
6
+
7
+ def initialize(klass, owners, reflection, preload_options)
8
+ @klass = klass
9
+ @owners = owners
10
+ @reflection = reflection
11
+ @preload_options = preload_options || {}
12
+ @model = owners.first && owners.first.class
13
+ @scoped = nil
14
+ @owners_by_key = nil
15
+ end
16
+
17
+ def run
18
+ unless owners.first.association(reflection.name).loaded?
19
+ preload
20
+ end
21
+ end
22
+
23
+ def preload
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def scoped
28
+ @scoped ||= build_scope
29
+ end
30
+
31
+ def records_for(ids)
32
+ scoped.where(association_key.in(ids))
33
+ end
34
+
35
+ def table
36
+ klass.arel_table
37
+ end
38
+
39
+ # The name of the key on the associated records
40
+ def association_key_name
41
+ raise NotImplementedError
42
+ end
43
+
44
+ # This is overridden by HABTM as the condition should be on the foreign_key column in
45
+ # the join table
46
+ def association_key
47
+ table[association_key_name]
48
+ end
49
+
50
+ # The name of the key on the model which declares the association
51
+ def owner_key_name
52
+ raise NotImplementedError
53
+ end
54
+
55
+ # We're converting to a string here because postgres will return the aliased association
56
+ # key in a habtm as a string (for whatever reason)
57
+ def owners_by_key
58
+ @owners_by_key ||= owners.group_by do |owner|
59
+ key = owner[owner_key_name]
60
+ key && key.to_s
61
+ end
62
+ end
63
+
64
+ def options
65
+ reflection.options
66
+ end
67
+
68
+ private
69
+
70
+ def associated_records_by_owner
71
+ owner_keys = owners.map { |owner| owner[owner_key_name] }.compact.uniq
72
+
73
+ if klass.nil? || owner_keys.empty?
74
+ records = []
75
+ else
76
+ # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
77
+ # Make several smaller queries if necessary or make one query if the adapter supports it
78
+ sliced = owner_keys.each_slice(model.connection.in_clause_length || owner_keys.size)
79
+ records = sliced.map { |slice| records_for(slice) }.flatten
80
+ end
81
+
82
+ # Each record may have multiple owners, and vice-versa
83
+ records_by_owner = Hash[owners.map { |owner| [owner, []] }]
84
+ records.each do |record|
85
+ owner_key = record[association_key_name].to_s
86
+
87
+ owners_by_key[owner_key].each do |owner|
88
+ records_by_owner[owner] << record
89
+ end
90
+ end
91
+ records_by_owner
92
+ end
93
+
94
+ def build_scope
95
+ scope = klass.scoped
96
+
97
+ scope = scope.where(process_conditions(options[:conditions]))
98
+ scope = scope.where(process_conditions(preload_options[:conditions]))
99
+
100
+ scope = scope.select(preload_options[:select] || options[:select] || table[Arel.star])
101
+ scope = scope.includes(preload_options[:include] || options[:include])
102
+
103
+ if options[:as]
104
+ scope = scope.where(
105
+ klass.table_name => {
106
+ reflection.type => model.base_class.sti_name
107
+ }
108
+ )
109
+ end
110
+
111
+ scope
112
+ end
113
+
114
+ def process_conditions(conditions)
115
+ if conditions.respond_to?(:to_proc)
116
+ conditions = klass.send(:instance_eval, &conditions)
117
+ end
118
+
119
+ if conditions
120
+ klass.send(:sanitize_sql, conditions)
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class BelongsTo < SingularAssociation #:nodoc:
5
+
6
+ def association_key_name
7
+ reflection.options[:primary_key] || klass && klass.primary_key
8
+ end
9
+
10
+ def owner_key_name
11
+ reflection.foreign_key
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class CollectionAssociation < Association #:nodoc:
5
+
6
+ private
7
+
8
+ def build_scope
9
+ super.order(preload_options[:order] || options[:order])
10
+ end
11
+
12
+ def preload
13
+ associated_records_by_owner.each do |owner, records|
14
+ association = owner.association(reflection.name)
15
+ association.loaded!
16
+ association.target.concat(records)
17
+ records.each { |record| association.set_inverse_instance(record) }
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,60 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class HasAndBelongsToMany < CollectionAssociation #:nodoc:
5
+ attr_reader :join_table
6
+
7
+ def initialize(klass, records, reflection, preload_options)
8
+ super
9
+ @join_table = Arel::Table.new(options[:join_table]).alias('t0')
10
+ end
11
+
12
+ # Unlike the other associations, we want to get a raw array of rows so that we can
13
+ # access the aliased column on the join table
14
+ def records_for(ids)
15
+ scope = super
16
+ klass.connection.select_all(scope.arel.to_sql, 'SQL', scope.bind_values)
17
+ end
18
+
19
+ def owner_key_name
20
+ reflection.active_record_primary_key
21
+ end
22
+
23
+ def association_key_name
24
+ 'ar_association_key_name'
25
+ end
26
+
27
+ def association_key
28
+ join_table[reflection.foreign_key]
29
+ end
30
+
31
+ private
32
+
33
+ # Once we have used the join table column (in super), we manually instantiate the
34
+ # actual records, ensuring that we don't create more than one instances of the same
35
+ # record
36
+ def associated_records_by_owner
37
+ records = {}
38
+ super.each do |owner_key, rows|
39
+ rows.map! { |row| records[row[klass.primary_key]] ||= klass.instantiate(row) }
40
+ end
41
+ end
42
+
43
+ def build_scope
44
+ super.joins(join).select(join_select)
45
+ end
46
+
47
+ def join_select
48
+ association_key.as(Arel.sql(association_key_name))
49
+ end
50
+
51
+ def join
52
+ condition = table[reflection.association_primary_key].eq(
53
+ join_table[reflection.association_foreign_key])
54
+
55
+ table.create_join(join_table, table.create_on(condition))
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end