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
@@ -1,113 +1,144 @@
1
- require "active_record/associations/through_association_scope"
2
1
  require 'active_support/core_ext/object/blank'
3
2
 
4
3
  module ActiveRecord
5
4
  # = Active Record Has Many Through Association
6
5
  module Associations
7
6
  class HasManyThroughAssociation < HasManyAssociation #:nodoc:
8
- include ThroughAssociationScope
7
+ include ThroughAssociation
9
8
 
10
9
  alias_method :new, :build
11
10
 
12
- def create!(attrs = nil)
13
- create_record(attrs, true)
14
- end
15
-
16
- def create(attrs = nil)
17
- create_record(attrs, false)
18
- end
19
-
20
- def destroy(*records)
21
- transaction do
22
- delete_records(flatten_deeper(records))
23
- super
24
- end
25
- end
26
-
27
11
  # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
28
12
  # loaded and calling collection.size if it has. If it's more likely than not that the collection does
29
13
  # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
30
14
  # SELECT query if you use #length.
31
15
  def size
32
- return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter?
33
- return @target.size if loaded?
34
- return count
16
+ if has_cached_counter?
17
+ owner.send(:read_attribute, cached_counter_attribute_name)
18
+ elsif loaded?
19
+ target.size
20
+ else
21
+ count
22
+ end
35
23
  end
36
24
 
37
- protected
38
- def create_record(attrs, force = true)
39
- ensure_owner_is_not_new
40
-
41
- transaction do
42
- object = @reflection.klass.new(attrs)
43
- add_record_to_target_with_callbacks(object) {|r| insert_record(object, force) }
44
- object
25
+ def concat(*records)
26
+ unless owner.new_record?
27
+ records.flatten.each do |record|
28
+ raise_on_type_mismatch(record)
29
+ record.save! if record.new_record?
45
30
  end
46
31
  end
47
32
 
48
- def target_reflection_has_associated_record?
49
- if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank?
50
- false
51
- else
52
- true
33
+ super
34
+ end
35
+
36
+ def insert_record(record, validate = true)
37
+ ensure_not_nested
38
+ return if record.new_record? && !record.save(:validate => validate)
39
+
40
+ through_record(record).save!
41
+ update_counter(1)
42
+ record
43
+ end
44
+
45
+ private
46
+
47
+ def through_record(record)
48
+ through_association = owner.association(through_reflection.name)
49
+ attributes = construct_join_attributes(record)
50
+
51
+ through_record = Array.wrap(through_association.target).find { |candidate|
52
+ candidate.attributes.slice(*attributes.keys) == attributes
53
+ }
54
+
55
+ unless through_record
56
+ through_record = through_association.build(attributes)
57
+ through_record.send("#{source_reflection.name}=", record)
53
58
  end
54
- end
55
59
 
56
- def construct_find_options!(options)
57
- options[:joins] = construct_joins(options[:joins])
58
- options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include]
60
+ through_record
59
61
  end
60
62
 
61
- def insert_record(record, force = true, validate = true)
62
- if record.new_record?
63
- if force
64
- record.save!
65
- else
66
- return false unless record.save(:validate => validate)
63
+ def build_record(attributes, options = {})
64
+ ensure_not_nested
65
+
66
+ record = super(attributes, options)
67
+
68
+ inverse = source_reflection.inverse_of
69
+ if inverse
70
+ if inverse.macro == :has_many
71
+ record.send(inverse.name) << through_record(record)
72
+ elsif inverse.macro == :has_one
73
+ record.send("#{inverse.name}=", through_record(record))
67
74
  end
68
75
  end
69
76
 
70
- through_association = @owner.send(@reflection.through_reflection.name)
71
- through_association.create!(construct_join_attributes(record))
77
+ record
72
78
  end
73
79
 
74
- # TODO - add dependent option support
75
- def delete_records(records)
76
- klass = @reflection.through_reflection.klass
77
- records.each do |associate|
78
- klass.delete_all(construct_join_attributes(associate))
80
+ def target_reflection_has_associated_record?
81
+ if through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?
82
+ false
83
+ else
84
+ true
79
85
  end
80
86
  end
81
87
 
82
- def find_target
83
- return [] unless target_reflection_has_associated_record?
84
- with_scope(construct_scope) { @reflection.klass.find(:all) }
88
+ def update_through_counter?(method)
89
+ case method
90
+ when :destroy
91
+ !inverse_updates_counter_cache?(through_reflection)
92
+ when :nullify
93
+ false
94
+ else
95
+ true
96
+ end
85
97
  end
86
98
 
87
- def construct_sql
88
- case
89
- when @reflection.options[:finder_sql]
90
- @finder_sql = interpolate_and_sanitize_sql(@reflection.options[:finder_sql])
99
+ def delete_records(records, method)
100
+ ensure_not_nested
91
101
 
92
- @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
93
- @finder_sql << " AND (#{conditions})" if conditions
94
- else
95
- @finder_sql = construct_conditions
102
+ through = owner.association(through_reflection.name)
103
+ scope = through.scoped.where(construct_join_attributes(*records))
104
+
105
+ case method
106
+ when :destroy
107
+ count = scope.destroy_all.length
108
+ when :nullify
109
+ count = scope.update_all(source_reflection.foreign_key => nil)
110
+ else
111
+ count = scope.delete_all
112
+ end
113
+
114
+ delete_through_records(through, records)
115
+
116
+ if through_reflection.macro == :has_many && update_through_counter?(method)
117
+ update_counter(-count, through_reflection)
96
118
  end
97
119
 
98
- construct_counter_sql
120
+ update_counter(-count)
99
121
  end
100
122
 
101
- def has_cached_counter?
102
- @owner.attribute_present?(cached_counter_attribute_name)
123
+ def delete_through_records(through, records)
124
+ if through_reflection.macro == :has_many
125
+ records.each do |record|
126
+ through.target.delete(through_record(record))
127
+ end
128
+ else
129
+ records.each do |record|
130
+ through.target = nil if through.target == through_record(record)
131
+ end
132
+ end
103
133
  end
104
134
 
105
- def cached_counter_attribute_name
106
- "#{@reflection.name}_count"
135
+ def find_target
136
+ return [] unless target_reflection_has_associated_record?
137
+ scoped.all
107
138
  end
108
139
 
109
140
  # NOTE - not sure that we can actually cope with inverses here
110
- def we_can_set_the_inverse_on_this?(record)
141
+ def invertible_for?(record)
111
142
  false
112
143
  end
113
144
  end
@@ -1,142 +1,72 @@
1
+ require 'active_support/core_ext/object/inclusion'
2
+
1
3
  module ActiveRecord
2
4
  # = Active Record Belongs To Has One Association
3
5
  module Associations
4
- class HasOneAssociation < AssociationProxy #:nodoc:
5
- def initialize(owner, reflection)
6
- super
7
- construct_sql
8
- end
6
+ class HasOneAssociation < SingularAssociation #:nodoc:
7
+ def replace(record, save = true)
8
+ raise_on_type_mismatch(record) if record
9
+ load_target
9
10
 
10
- def create(attrs = {}, replace_existing = true)
11
- new_record(replace_existing) do |reflection|
12
- attrs = merge_with_conditions(attrs)
13
- reflection.create_association(attrs)
14
- end
15
- end
11
+ reflection.klass.transaction do
12
+ if target && target != record
13
+ remove_target!(options[:dependent])
14
+ end
16
15
 
17
- def create!(attrs = {}, replace_existing = true)
18
- new_record(replace_existing) do |reflection|
19
- attrs = merge_with_conditions(attrs)
20
- reflection.create_association!(attrs)
21
- end
22
- end
16
+ if record
17
+ set_inverse_instance(record)
18
+ set_owner_attributes(record)
23
19
 
24
- def build(attrs = {}, replace_existing = true)
25
- new_record(replace_existing) do |reflection|
26
- attrs = merge_with_conditions(attrs)
27
- reflection.build_association(attrs)
20
+ if owner.persisted? && save && !record.save
21
+ nullify_owner_attributes(record)
22
+ set_owner_attributes(target)
23
+ raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
24
+ end
25
+ end
28
26
  end
29
- end
30
27
 
31
- def replace(obj, dont_save = false)
32
- load_target
28
+ self.target = record
29
+ end
33
30
 
34
- unless @target.nil? || @target == obj
35
- if dependent? && !dont_save
36
- case @reflection.options[:dependent]
31
+ def delete(method = options[:dependent])
32
+ if load_target
33
+ case method
37
34
  when :delete
38
- @target.delete unless @target.new_record?
39
- @owner.clear_association_cache
35
+ target.delete
40
36
  when :destroy
41
- @target.destroy unless @target.new_record?
42
- @owner.clear_association_cache
37
+ target.destroy
43
38
  when :nullify
44
- @target[@reflection.primary_key_name] = nil
45
- @target.save unless @owner.new_record? || @target.new_record?
46
- end
47
- else
48
- @target[@reflection.primary_key_name] = nil
49
- @target.save unless @owner.new_record? || @target.new_record?
39
+ target.update_attribute(reflection.foreign_key, nil)
50
40
  end
51
41
  end
52
-
53
- if obj.nil?
54
- @target = nil
55
- else
56
- raise_on_type_mismatch(obj)
57
- set_belongs_to_association_for(obj)
58
- @target = (AssociationProxy === obj ? obj.target : obj)
59
- end
60
-
61
- set_inverse_instance(obj, @owner)
62
- @loaded = true
63
-
64
- unless @owner.new_record? or obj.nil? or dont_save
65
- return (obj.save ? self : false)
66
- else
67
- return (obj.nil? ? nil : self)
68
- end
69
42
  end
70
43
 
71
- protected
72
- def owner_quoted_id
73
- if @reflection.options[:primary_key]
74
- @owner.class.quote_value(@owner.send(@reflection.options[:primary_key]))
75
- else
76
- @owner.quoted_id
77
- end
78
- end
79
-
80
44
  private
81
- def find_target
82
- options = @reflection.options.dup
83
- (options.keys - [:select, :order, :include, :readonly]).each do |key|
84
- options.delete key
85
- end
86
- options[:conditions] = @finder_sql
87
-
88
- the_target = @reflection.klass.find(:first, options)
89
- set_inverse_instance(the_target, @owner)
90
- the_target
91
- end
92
-
93
- def construct_sql
94
- case
95
- when @reflection.options[:as]
96
- @finder_sql =
97
- "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
98
- "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
99
- else
100
- @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
101
- end
102
- @finder_sql << " AND (#{conditions})" if conditions
103
- end
104
45
 
105
- def construct_scope
106
- create_scoping = {}
107
- set_belongs_to_association_for(create_scoping)
108
- { :create => create_scoping }
46
+ # The reason that the save param for replace is false, if for create (not just build),
47
+ # is because the setting of the foreign keys is actually handled by the scoping when
48
+ # the record is instantiated, and so they are set straight away and do not need to be
49
+ # updated within replace.
50
+ def set_new_record(record)
51
+ replace(record, false)
109
52
  end
110
53
 
111
- def new_record(replace_existing)
112
- # Make sure we load the target first, if we plan on replacing the existing
113
- # instance. Otherwise, if the target has not previously been loaded
114
- # elsewhere, the instance we create will get orphaned.
115
- load_target if replace_existing
116
- record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) do
117
- yield @reflection
118
- end
119
-
120
- if replace_existing
121
- replace(record, true)
54
+ def remove_target!(method)
55
+ if method.in?([:delete, :destroy])
56
+ target.send(method)
122
57
  else
123
- record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
124
- self.target = record
125
- set_inverse_instance(record, @owner)
126
- end
58
+ nullify_owner_attributes(target)
127
59
 
128
- record
129
- end
130
-
131
- def we_can_set_the_inverse_on_this?(record)
132
- inverse = @reflection.inverse_of
133
- return !inverse.nil?
60
+ if target.persisted? && owner.persisted? && !target.save
61
+ set_owner_attributes(target)
62
+ raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
63
+ "The record failed to save when after its foreign key was set to nil."
64
+ end
65
+ end
134
66
  end
135
67
 
136
- def merge_with_conditions(attrs={})
137
- attrs ||= {}
138
- attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
139
- attrs
68
+ def nullify_owner_attributes(record)
69
+ record[reflection.foreign_key] = nil
140
70
  end
141
71
  end
142
72
  end
@@ -1,40 +1,36 @@
1
- require "active_record/associations/through_association_scope"
2
-
3
1
  module ActiveRecord
4
2
  # = Active Record Has One Through Association
5
3
  module Associations
6
- class HasOneThroughAssociation < HasOneAssociation
7
- include ThroughAssociationScope
4
+ class HasOneThroughAssociation < HasOneAssociation #:nodoc:
5
+ include ThroughAssociation
8
6
 
9
- def replace(new_value)
10
- create_through_record(new_value)
11
- @target = new_value
7
+ def replace(record)
8
+ create_through_record(record)
9
+ self.target = record
12
10
  end
13
11
 
14
12
  private
15
13
 
16
- def create_through_record(new_value) #nodoc:
17
- klass = @reflection.through_reflection.klass
14
+ def create_through_record(record)
15
+ ensure_not_nested
16
+
17
+ through_proxy = owner.association(through_reflection.name)
18
+ through_record = through_proxy.send(:load_target)
18
19
 
19
- current_object = @owner.send(@reflection.through_reflection.name)
20
+ if through_record && !record
21
+ through_record.destroy
22
+ elsif record
23
+ attributes = construct_join_attributes(record)
20
24
 
21
- if current_object
22
- new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
23
- elsif new_value
24
- if @owner.new_record?
25
- self.target = new_value
26
- through_association = @owner.send(:association_instance_get, @reflection.through_reflection.name)
27
- through_association.build(construct_join_attributes(new_value))
28
- else
29
- @owner.send(@reflection.through_reflection.name, klass.create(construct_join_attributes(new_value)))
25
+ if through_record
26
+ through_record.update_attributes(attributes)
27
+ elsif owner.new_record?
28
+ through_proxy.build(attributes)
29
+ else
30
+ through_proxy.create(attributes)
31
+ end
30
32
  end
31
33
  end
32
- end
33
-
34
- private
35
- def find_target
36
- with_scope(construct_scope) { @reflection.klass.find(:first) }
37
- end
38
34
  end
39
35
  end
40
36
  end