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,215 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class JoinDependency # :nodoc:
4
+ autoload :JoinPart, 'active_record/associations/join_dependency/join_part'
5
+ autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
6
+ autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
7
+
8
+ attr_reader :join_parts, :reflections, :alias_tracker, :active_record
9
+
10
+ def initialize(base, associations, joins)
11
+ @active_record = base
12
+ @table_joins = joins
13
+ @join_parts = [JoinBase.new(base)]
14
+ @associations = {}
15
+ @reflections = []
16
+ @alias_tracker = AliasTracker.new(joins)
17
+ @alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
18
+ build(associations)
19
+ end
20
+
21
+ def graft(*associations)
22
+ associations.each do |association|
23
+ join_associations.detect {|a| association == a} ||
24
+ build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
25
+ end
26
+ self
27
+ end
28
+
29
+ def join_associations
30
+ join_parts.last(join_parts.length - 1)
31
+ end
32
+
33
+ def join_base
34
+ join_parts.first
35
+ end
36
+
37
+ def columns
38
+ join_parts.collect { |join_part|
39
+ table = join_part.aliased_table
40
+ join_part.column_names_with_alias.collect{ |column_name, aliased_name|
41
+ table[column_name].as Arel.sql(aliased_name)
42
+ }
43
+ }.flatten
44
+ end
45
+
46
+ def instantiate(rows)
47
+ primary_key = join_base.aliased_primary_key
48
+ parents = {}
49
+
50
+ records = rows.map { |model|
51
+ primary_id = model[primary_key]
52
+ parent = parents[primary_id] ||= join_base.instantiate(model)
53
+ construct(parent, @associations, join_associations, model)
54
+ parent
55
+ }.uniq
56
+
57
+ remove_duplicate_results!(active_record, records, @associations)
58
+ records
59
+ end
60
+
61
+ def remove_duplicate_results!(base, records, associations)
62
+ case associations
63
+ when Symbol, String
64
+ reflection = base.reflections[associations]
65
+ remove_uniq_by_reflection(reflection, records)
66
+ when Array
67
+ associations.each do |association|
68
+ remove_duplicate_results!(base, records, association)
69
+ end
70
+ when Hash
71
+ associations.keys.each do |name|
72
+ reflection = base.reflections[name]
73
+ remove_uniq_by_reflection(reflection, records)
74
+
75
+ parent_records = []
76
+ records.each do |record|
77
+ if descendant = record.send(reflection.name)
78
+ if reflection.collection?
79
+ parent_records.concat descendant.target.uniq
80
+ else
81
+ parent_records << descendant
82
+ end
83
+ end
84
+ end
85
+
86
+ remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
87
+ end
88
+ end
89
+ end
90
+
91
+ protected
92
+
93
+ def cache_joined_association(association)
94
+ associations = []
95
+ parent = association.parent
96
+ while parent != join_base
97
+ associations.unshift(parent.reflection.name)
98
+ parent = parent.parent
99
+ end
100
+ ref = @associations
101
+ associations.each do |key|
102
+ ref = ref[key]
103
+ end
104
+ ref[association.reflection.name] ||= {}
105
+ end
106
+
107
+ def build(associations, parent = nil, join_type = Arel::InnerJoin)
108
+ parent ||= join_parts.last
109
+ case associations
110
+ when Symbol, String
111
+ reflection = parent.reflections[associations.to_s.intern] or
112
+ raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
113
+ unless join_association = find_join_association(reflection, parent)
114
+ @reflections << reflection
115
+ join_association = build_join_association(reflection, parent)
116
+ join_association.join_type = join_type
117
+ @join_parts << join_association
118
+ cache_joined_association(join_association)
119
+ end
120
+ join_association
121
+ when Array
122
+ associations.each do |association|
123
+ build(association, parent, join_type)
124
+ end
125
+ when Hash
126
+ associations.keys.sort_by { |a| a.to_s }.each do |name|
127
+ join_association = build(name, parent, join_type)
128
+ build(associations[name], join_association, join_type)
129
+ end
130
+ else
131
+ raise ConfigurationError, associations.inspect
132
+ end
133
+ end
134
+
135
+ def find_join_association(name_or_reflection, parent)
136
+ if String === name_or_reflection
137
+ name_or_reflection = name_or_reflection.to_sym
138
+ end
139
+
140
+ join_associations.detect { |j|
141
+ j.reflection == name_or_reflection && j.parent == parent
142
+ }
143
+ end
144
+
145
+ def remove_uniq_by_reflection(reflection, records)
146
+ if reflection && reflection.collection?
147
+ records.each { |record| record.send(reflection.name).target.uniq! }
148
+ end
149
+ end
150
+
151
+ def build_join_association(reflection, parent)
152
+ JoinAssociation.new(reflection, self, parent)
153
+ end
154
+
155
+ def construct(parent, associations, join_parts, row)
156
+ case associations
157
+ when Symbol, String
158
+ name = associations.to_s
159
+
160
+ join_part = join_parts.detect { |j|
161
+ j.reflection.name.to_s == name &&
162
+ j.parent_table_name == parent.class.table_name }
163
+
164
+ raise(ConfigurationError, "No such association") unless join_part
165
+
166
+ join_parts.delete(join_part)
167
+ construct_association(parent, join_part, row)
168
+ when Array
169
+ associations.each do |association|
170
+ construct(parent, association, join_parts, row)
171
+ end
172
+ when Hash
173
+ associations.sort_by { |k,_| k.to_s }.each do |association_name, assoc|
174
+ association = construct(parent, association_name, join_parts, row)
175
+ construct(association, assoc, join_parts, row) if association
176
+ end
177
+ else
178
+ raise ConfigurationError, associations.inspect
179
+ end
180
+ end
181
+
182
+ def construct_association(record, join_part, row)
183
+ return if record.id.to_s != join_part.parent.record_id(row).to_s
184
+
185
+ macro = join_part.reflection.macro
186
+ if macro == :has_one
187
+ return if record.association_cache.key?(join_part.reflection.name)
188
+ association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
189
+ set_target_and_inverse(join_part, association, record)
190
+ else
191
+ return if row[join_part.aliased_primary_key].nil?
192
+ association = join_part.instantiate(row)
193
+ case macro
194
+ when :has_many, :has_and_belongs_to_many
195
+ other = record.association(join_part.reflection.name)
196
+ other.loaded!
197
+ other.target.push(association)
198
+ other.set_inverse_instance(association)
199
+ when :belongs_to
200
+ set_target_and_inverse(join_part, association, record)
201
+ else
202
+ raise ConfigurationError, "unknown macro: #{join_part.reflection.macro}"
203
+ end
204
+ end
205
+ association
206
+ end
207
+
208
+ def set_target_and_inverse(join_part, association, record)
209
+ other = record.association(join_part.reflection.name)
210
+ other.target = association
211
+ other.set_inverse_instance(association)
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,150 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class JoinDependency # :nodoc:
4
+ class JoinAssociation < JoinPart # :nodoc:
5
+ include JoinHelper
6
+
7
+ # The reflection of the association represented
8
+ attr_reader :reflection
9
+
10
+ # The JoinDependency object which this JoinAssociation exists within. This is mainly
11
+ # relevant for generating aliases which do not conflict with other joins which are
12
+ # part of the query.
13
+ attr_reader :join_dependency
14
+
15
+ # A JoinBase instance representing the active record we are joining onto.
16
+ # (So in Author.has_many :posts, the Author would be that base record.)
17
+ attr_reader :parent
18
+
19
+ # What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin
20
+ attr_accessor :join_type
21
+
22
+ # These implement abstract methods from the superclass
23
+ attr_reader :aliased_prefix
24
+
25
+ attr_reader :tables
26
+
27
+ delegate :options, :through_reflection, :source_reflection, :chain, :to => :reflection
28
+ delegate :table, :table_name, :to => :parent, :prefix => :parent
29
+ delegate :alias_tracker, :to => :join_dependency
30
+
31
+ alias :alias_suffix :parent_table_name
32
+
33
+ def initialize(reflection, join_dependency, parent = nil)
34
+ reflection.check_validity!
35
+
36
+ if reflection.options[:polymorphic]
37
+ raise EagerLoadPolymorphicError.new(reflection)
38
+ end
39
+
40
+ super(reflection.klass)
41
+
42
+ @reflection = reflection
43
+ @join_dependency = join_dependency
44
+ @parent = parent
45
+ @join_type = Arel::InnerJoin
46
+ @aliased_prefix = "t#{ join_dependency.join_parts.size }"
47
+ @tables = construct_tables.reverse
48
+ end
49
+
50
+ def ==(other)
51
+ other.class == self.class &&
52
+ other.reflection == reflection &&
53
+ other.parent == parent
54
+ end
55
+
56
+ def find_parent_in(other_join_dependency)
57
+ other_join_dependency.join_parts.detect do |join_part|
58
+ parent == join_part
59
+ end
60
+ end
61
+
62
+ def join_to(relation)
63
+ tables = @tables.dup
64
+ foreign_table = parent_table
65
+
66
+ # The chain starts with the target table, but we want to end with it here (makes
67
+ # more sense in this context), so we reverse
68
+ chain.reverse.each_with_index do |reflection, i|
69
+ table = tables.shift
70
+
71
+ case reflection.source_macro
72
+ when :belongs_to
73
+ key = reflection.association_primary_key
74
+ foreign_key = reflection.foreign_key
75
+ when :has_and_belongs_to_many
76
+ # Join the join table first...
77
+ relation.from(join(
78
+ table,
79
+ table[reflection.foreign_key].
80
+ eq(foreign_table[reflection.active_record_primary_key])
81
+ ))
82
+
83
+ foreign_table, table = table, tables.shift
84
+
85
+ key = reflection.association_primary_key
86
+ foreign_key = reflection.association_foreign_key
87
+ else
88
+ key = reflection.foreign_key
89
+ foreign_key = reflection.active_record_primary_key
90
+ end
91
+
92
+ constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
93
+
94
+ relation.from(join(table, constraint))
95
+
96
+ unless conditions[i].empty?
97
+ relation.where(sanitize(conditions[i], table))
98
+ end
99
+
100
+ # The current table in this iteration becomes the foreign table in the next
101
+ foreign_table = table
102
+ end
103
+
104
+ relation
105
+ end
106
+
107
+ def build_constraint(reflection, table, key, foreign_table, foreign_key)
108
+ constraint = table[key].eq(foreign_table[foreign_key])
109
+
110
+ if reflection.klass.finder_needs_type_condition?
111
+ constraint = table.create_and([
112
+ constraint,
113
+ reflection.klass.send(:type_condition, table)
114
+ ])
115
+ end
116
+
117
+ constraint
118
+ end
119
+
120
+ def join_relation(joining_relation)
121
+ self.join_type = Arel::OuterJoin
122
+ joining_relation.joins(self)
123
+ end
124
+
125
+ def table
126
+ tables.last
127
+ end
128
+
129
+ def aliased_table_name
130
+ table.table_alias || table.name
131
+ end
132
+
133
+ def conditions
134
+ @conditions ||= reflection.conditions.reverse
135
+ end
136
+
137
+ private
138
+
139
+ def interpolate(conditions)
140
+ if conditions.respond_to?(:to_proc)
141
+ instance_eval(&conditions)
142
+ else
143
+ conditions
144
+ end
145
+ end
146
+
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,24 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class JoinDependency # :nodoc:
4
+ class JoinBase < JoinPart # :nodoc:
5
+ def ==(other)
6
+ other.class == self.class &&
7
+ other.active_record == active_record
8
+ end
9
+
10
+ def aliased_prefix
11
+ "t0"
12
+ end
13
+
14
+ def table
15
+ Arel::Table.new(table_name, arel_engine)
16
+ end
17
+
18
+ def aliased_table_name
19
+ active_record.table_name
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,78 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class JoinDependency # :nodoc:
4
+ # A JoinPart represents a part of a JoinDependency. It is an abstract class, inherited
5
+ # by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
6
+ # everything else is being joined onto. A JoinAssociation represents an association which
7
+ # is joining to the base. A JoinAssociation may result in more than one actual join
8
+ # operations (for example a has_and_belongs_to_many JoinAssociation would result in
9
+ # two; one for the join table and one for the target table).
10
+ class JoinPart # :nodoc:
11
+ # The Active Record class which this join part is associated 'about'; for a JoinBase
12
+ # this is the actual base model, for a JoinAssociation this is the target model of the
13
+ # association.
14
+ attr_reader :active_record
15
+
16
+ delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :active_record
17
+
18
+ def initialize(active_record)
19
+ @active_record = active_record
20
+ @cached_record = {}
21
+ @column_names_with_alias = nil
22
+ end
23
+
24
+ def aliased_table
25
+ Arel::Nodes::TableAlias.new table, aliased_table_name
26
+ end
27
+
28
+ def ==(other)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ # An Arel::Table for the active_record
33
+ def table
34
+ raise NotImplementedError
35
+ end
36
+
37
+ # The prefix to be used when aliasing columns in the active_record's table
38
+ def aliased_prefix
39
+ raise NotImplementedError
40
+ end
41
+
42
+ # The alias for the active_record's table
43
+ def aliased_table_name
44
+ raise NotImplementedError
45
+ end
46
+
47
+ # The alias for the primary key of the active_record's table
48
+ def aliased_primary_key
49
+ "#{aliased_prefix}_r0"
50
+ end
51
+
52
+ # An array of [column_name, alias] pairs for the table
53
+ def column_names_with_alias
54
+ unless @column_names_with_alias
55
+ @column_names_with_alias = []
56
+
57
+ ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
58
+ @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
59
+ end
60
+ end
61
+ @column_names_with_alias
62
+ end
63
+
64
+ def extract_record(row)
65
+ Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}]
66
+ end
67
+
68
+ def record_id(row)
69
+ row[aliased_primary_key]
70
+ end
71
+
72
+ def instantiate(row)
73
+ @cached_record[record_id(row)] ||= active_record.send(:instantiate, extract_record(row))
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end