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.
- data/CHANGELOG +220 -91
- data/README.rdoc +3 -3
- data/examples/performance.rb +88 -109
- data/lib/active_record.rb +6 -2
- data/lib/active_record/aggregations.rb +22 -45
- data/lib/active_record/associations.rb +264 -991
- data/lib/active_record/associations/alias_tracker.rb +85 -0
- data/lib/active_record/associations/association.rb +231 -0
- data/lib/active_record/associations/association_scope.rb +120 -0
- data/lib/active_record/associations/belongs_to_association.rb +40 -60
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +15 -63
- data/lib/active_record/associations/builder/association.rb +53 -0
- data/lib/active_record/associations/builder/belongs_to.rb +85 -0
- data/lib/active_record/associations/builder/collection_association.rb +75 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +63 -0
- data/lib/active_record/associations/builder/has_many.rb +65 -0
- data/lib/active_record/associations/builder/has_one.rb +63 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +524 -0
- data/lib/active_record/associations/collection_proxy.rb +125 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +27 -118
- data/lib/active_record/associations/has_many_association.rb +50 -79
- data/lib/active_record/associations/has_many_through_association.rb +98 -67
- data/lib/active_record/associations/has_one_association.rb +45 -115
- data/lib/active_record/associations/has_one_through_association.rb +21 -25
- data/lib/active_record/associations/join_dependency.rb +215 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +150 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_helper.rb +56 -0
- data/lib/active_record/associations/preloader.rb +177 -0
- data/lib/active_record/associations/preloader/association.rb +126 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +67 -0
- data/lib/active_record/associations/singular_association.rb +55 -0
- data/lib/active_record/associations/through_association.rb +80 -0
- data/lib/active_record/attribute_methods.rb +19 -5
- data/lib/active_record/attribute_methods/before_type_cast.rb +9 -8
- data/lib/active_record/attribute_methods/dirty.rb +8 -2
- data/lib/active_record/attribute_methods/primary_key.rb +33 -13
- data/lib/active_record/attribute_methods/read.rb +17 -17
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -4
- data/lib/active_record/attribute_methods/write.rb +2 -1
- data/lib/active_record/autosave_association.rb +66 -45
- data/lib/active_record/base.rb +445 -273
- data/lib/active_record/callbacks.rb +24 -33
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +106 -13
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +16 -2
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -12
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +16 -16
- data/lib/active_record/connection_adapters/abstract/quoting.rb +61 -22
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -273
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -42
- data/lib/active_record/connection_adapters/abstract_adapter.rb +44 -25
- data/lib/active_record/connection_adapters/column.rb +268 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +686 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +331 -88
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +295 -267
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -7
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +108 -26
- data/lib/active_record/counter_cache.rb +7 -4
- data/lib/active_record/fixtures.rb +174 -192
- data/lib/active_record/identity_map.rb +131 -0
- data/lib/active_record/locking/optimistic.rb +20 -14
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +24 -4
- data/lib/active_record/migration.rb +265 -144
- data/lib/active_record/migration/command_recorder.rb +103 -0
- data/lib/active_record/named_scope.rb +68 -25
- data/lib/active_record/nested_attributes.rb +58 -15
- data/lib/active_record/observer.rb +3 -7
- data/lib/active_record/persistence.rb +58 -38
- data/lib/active_record/query_cache.rb +25 -3
- data/lib/active_record/railtie.rb +21 -12
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/databases.rake +147 -116
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/reflection.rb +176 -44
- data/lib/active_record/relation.rb +125 -49
- data/lib/active_record/relation/batches.rb +7 -5
- data/lib/active_record/relation/calculations.rb +50 -18
- data/lib/active_record/relation/finder_methods.rb +47 -26
- data/lib/active_record/relation/predicate_builder.rb +24 -21
- data/lib/active_record/relation/query_methods.rb +117 -101
- data/lib/active_record/relation/spawn_methods.rb +27 -20
- data/lib/active_record/result.rb +34 -0
- data/lib/active_record/schema.rb +5 -6
- data/lib/active_record/schema_dumper.rb +11 -13
- data/lib/active_record/serialization.rb +2 -2
- data/lib/active_record/serializers/xml_serializer.rb +10 -10
- data/lib/active_record/session_store.rb +8 -2
- data/lib/active_record/test_case.rb +9 -20
- data/lib/active_record/timestamp.rb +21 -9
- data/lib/active_record/transactions.rb +16 -15
- data/lib/active_record/validations.rb +21 -22
- data/lib/active_record/validations/associated.rb +3 -1
- data/lib/active_record/validations/uniqueness.rb +48 -58
- data/lib/active_record/version.rb +3 -3
- data/lib/rails/generators/active_record.rb +6 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +10 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +2 -1
- data/lib/rails/generators/active_record/model/templates/migration.rb +6 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +2 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +2 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +2 -1
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +2 -2
- metadata +106 -77
- checksums.yaml +0 -7
- data/lib/active_record/association_preload.rb +0 -431
- data/lib/active_record/associations/association_collection.rb +0 -572
- data/lib/active_record/associations/association_proxy.rb +0 -304
- data/lib/active_record/associations/through_association_scope.rb +0 -160
@@ -0,0 +1,125 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
# Association proxies in Active Record are middlemen between the object that
|
4
|
+
# holds the association, known as the <tt>@owner</tt>, and the actual associated
|
5
|
+
# object, known as the <tt>@target</tt>. The kind of association any proxy is
|
6
|
+
# about is available in <tt>@reflection</tt>. That's an instance of the class
|
7
|
+
# ActiveRecord::Reflection::AssociationReflection.
|
8
|
+
#
|
9
|
+
# For example, given
|
10
|
+
#
|
11
|
+
# class Blog < ActiveRecord::Base
|
12
|
+
# has_many :posts
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# blog = Blog.find(:first)
|
16
|
+
#
|
17
|
+
# the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
|
18
|
+
# <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
|
19
|
+
# the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
|
20
|
+
#
|
21
|
+
# This class has most of the basic instance methods removed, and delegates
|
22
|
+
# unknown methods to <tt>@target</tt> via <tt>method_missing</tt>. As a
|
23
|
+
# corner case, it even removes the +class+ method and that's why you get
|
24
|
+
#
|
25
|
+
# blog.posts.class # => Array
|
26
|
+
#
|
27
|
+
# though the object behind <tt>blog.posts</tt> is not an Array, but an
|
28
|
+
# ActiveRecord::Associations::HasManyAssociation.
|
29
|
+
#
|
30
|
+
# The <tt>@target</tt> object is not \loaded until needed. For example,
|
31
|
+
#
|
32
|
+
# blog.posts.count
|
33
|
+
#
|
34
|
+
# is computed directly through SQL and does not trigger by itself the
|
35
|
+
# instantiation of the actual post records.
|
36
|
+
class CollectionProxy # :nodoc:
|
37
|
+
alias :proxy_extend :extend
|
38
|
+
|
39
|
+
instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }
|
40
|
+
|
41
|
+
delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from,
|
42
|
+
:lock, :readonly, :having, :to => :scoped
|
43
|
+
|
44
|
+
delegate :target, :load_target, :loaded?, :scoped,
|
45
|
+
:to => :@association
|
46
|
+
|
47
|
+
delegate :select, :find, :first, :last,
|
48
|
+
:build, :create, :create!,
|
49
|
+
:concat, :delete_all, :destroy_all, :delete, :destroy, :uniq,
|
50
|
+
:sum, :count, :size, :length, :empty?,
|
51
|
+
:any?, :many?, :include?,
|
52
|
+
:to => :@association
|
53
|
+
|
54
|
+
def initialize(association)
|
55
|
+
@association = association
|
56
|
+
Array.wrap(association.options[:extend]).each { |ext| proxy_extend(ext) }
|
57
|
+
end
|
58
|
+
|
59
|
+
def respond_to?(*args)
|
60
|
+
super ||
|
61
|
+
(load_target && target.respond_to?(*args)) ||
|
62
|
+
@association.klass.respond_to?(*args)
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_missing(method, *args, &block)
|
66
|
+
match = DynamicFinderMatch.match(method)
|
67
|
+
if match && match.creator?
|
68
|
+
attributes = match.attribute_names
|
69
|
+
return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)])
|
70
|
+
end
|
71
|
+
|
72
|
+
if target.respond_to?(method) || (!@association.klass.respond_to?(method) && Class.respond_to?(method))
|
73
|
+
if load_target
|
74
|
+
if target.respond_to?(method)
|
75
|
+
target.send(method, *args, &block)
|
76
|
+
else
|
77
|
+
begin
|
78
|
+
super
|
79
|
+
rescue NoMethodError => e
|
80
|
+
raise e, e.message.sub(/ for #<.*$/, " via proxy for #{target}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
else
|
86
|
+
scoped.readonly(nil).send(method, *args, &block)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Forwards <tt>===</tt> explicitly to the \target because the instance method
|
91
|
+
# removal above doesn't catch it. Loads the \target if needed.
|
92
|
+
def ===(other)
|
93
|
+
other === load_target
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_ary
|
97
|
+
load_target.dup
|
98
|
+
end
|
99
|
+
alias_method :to_a, :to_ary
|
100
|
+
|
101
|
+
def <<(*records)
|
102
|
+
@association.concat(records) && self
|
103
|
+
end
|
104
|
+
alias_method :push, :<<
|
105
|
+
|
106
|
+
def clear
|
107
|
+
delete_all
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
def reload
|
112
|
+
@association.reload
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
def new(*args, &block)
|
117
|
+
if @association.is_a?(HasManyThroughAssociation)
|
118
|
+
@association.build(*args, &block)
|
119
|
+
else
|
120
|
+
method_missing(:new, *args, &block)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -1,142 +1,51 @@
|
|
1
|
-
require 'active_support/deprecation'
|
2
|
-
|
3
1
|
module ActiveRecord
|
4
2
|
# = Active Record Has And Belongs To Many Association
|
5
3
|
module Associations
|
6
|
-
class HasAndBelongsToManyAssociation <
|
4
|
+
class HasAndBelongsToManyAssociation < CollectionAssociation #:nodoc:
|
5
|
+
attr_reader :join_table
|
6
|
+
|
7
7
|
def initialize(owner, reflection)
|
8
|
+
@join_table = Arel::Table.new(reflection.options[:join_table])
|
8
9
|
super
|
9
|
-
if columns.size > 2
|
10
|
-
ActiveSupport::Deprecation.warn "Having additional attributes on the join table of a has_and_belongs_to_many association is deprecated and will be removed in Rails 3.1. Please use a has_many :through association instead."
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def create(attributes = {})
|
15
|
-
create_record(attributes) { |record| insert_record(record) }
|
16
10
|
end
|
17
11
|
|
18
|
-
def
|
19
|
-
|
20
|
-
end
|
12
|
+
def insert_record(record, validate = true)
|
13
|
+
return if record.new_record? && !record.save(:validate => validate)
|
21
14
|
|
22
|
-
|
23
|
-
|
24
|
-
|
15
|
+
if options[:insert_sql]
|
16
|
+
owner.connection.insert(interpolate(options[:insert_sql], record))
|
17
|
+
else
|
18
|
+
stmt = join_table.compile_insert(
|
19
|
+
join_table[reflection.foreign_key] => owner.id,
|
20
|
+
join_table[reflection.association_foreign_key] => record.id
|
21
|
+
)
|
25
22
|
|
26
|
-
|
27
|
-
|
28
|
-
end
|
23
|
+
owner.connection.insert stmt.to_sql
|
24
|
+
end
|
29
25
|
|
30
|
-
|
31
|
-
@has_primary_key ||= @owner.connection.supports_primary_key? && @owner.connection.primary_key(@reflection.options[:join_table])
|
26
|
+
record
|
32
27
|
end
|
33
28
|
|
34
|
-
|
35
|
-
def construct_find_options!(options)
|
36
|
-
options[:joins] = Arel::SqlLiteral.new @join_sql
|
37
|
-
options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
|
38
|
-
options[:select] ||= (@reflection.options[:select] || Arel::SqlLiteral.new('*'))
|
39
|
-
end
|
29
|
+
private
|
40
30
|
|
41
31
|
def count_records
|
42
32
|
load_target.size
|
43
33
|
end
|
44
34
|
|
45
|
-
def
|
46
|
-
if
|
47
|
-
|
48
|
-
record.save!
|
49
|
-
else
|
50
|
-
return false unless record.save(:validate => validate)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
if @reflection.options[:insert_sql]
|
55
|
-
@owner.connection.insert(interpolate_and_sanitize_sql(@reflection.options[:insert_sql], record))
|
35
|
+
def delete_records(records, method)
|
36
|
+
if sql = options[:delete_sql]
|
37
|
+
records.each { |record| owner.connection.delete(interpolate(sql, record)) }
|
56
38
|
else
|
57
|
-
relation
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
name = column.name
|
63
|
-
value = case name.to_s
|
64
|
-
when @reflection.primary_key_name.to_s
|
65
|
-
@owner.id
|
66
|
-
when @reflection.association_foreign_key.to_s
|
67
|
-
record.id
|
68
|
-
when *timestamps
|
69
|
-
timezone
|
70
|
-
else
|
71
|
-
@owner.send(:quote_value, record[name], column) if record.has_attribute?(name)
|
72
|
-
end
|
73
|
-
[relation[name], value] unless value.nil?
|
74
|
-
end]
|
75
|
-
|
76
|
-
relation.insert(attributes)
|
39
|
+
relation = join_table
|
40
|
+
stmt = relation.where(relation[reflection.foreign_key].eq(owner.id).
|
41
|
+
and(relation[reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
|
42
|
+
).compile_delete
|
43
|
+
owner.connection.delete stmt.to_sql
|
77
44
|
end
|
78
|
-
|
79
|
-
return true
|
80
45
|
end
|
81
46
|
|
82
|
-
def
|
83
|
-
|
84
|
-
records.each { |record| @owner.connection.delete(interpolate_and_sanitize_sql(sql, record)) }
|
85
|
-
else
|
86
|
-
relation = Arel::Table.new(@reflection.options[:join_table])
|
87
|
-
relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
|
88
|
-
and(relation[@reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
|
89
|
-
).delete
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def construct_sql
|
94
|
-
if @reflection.options[:finder_sql]
|
95
|
-
@finder_sql = interpolate_and_sanitize_sql(@reflection.options[:finder_sql])
|
96
|
-
else
|
97
|
-
@finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
|
98
|
-
@finder_sql << " AND (#{conditions})" if conditions
|
99
|
-
end
|
100
|
-
|
101
|
-
@join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
|
102
|
-
|
103
|
-
construct_counter_sql
|
104
|
-
end
|
105
|
-
|
106
|
-
def construct_scope
|
107
|
-
{ :find => { :conditions => @finder_sql,
|
108
|
-
:joins => @join_sql,
|
109
|
-
:readonly => false,
|
110
|
-
:order => @reflection.options[:order],
|
111
|
-
:include => @reflection.options[:include],
|
112
|
-
:limit => @reflection.options[:limit] } }
|
113
|
-
end
|
114
|
-
|
115
|
-
# Join tables with additional columns on top of the two foreign keys must be considered
|
116
|
-
# ambiguous unless a select clause has been explicitly defined. Otherwise you can get
|
117
|
-
# broken records back, if, for example, the join column also has an id column. This will
|
118
|
-
# then overwrite the id column of the records coming back.
|
119
|
-
def finding_with_ambiguous_select?(select_clause)
|
120
|
-
!select_clause && columns.size != 2
|
121
|
-
end
|
122
|
-
|
123
|
-
private
|
124
|
-
def create_record(attributes, &block)
|
125
|
-
# Can't use Base.create because the foreign key may be a protected attribute.
|
126
|
-
ensure_owner_is_not_new
|
127
|
-
if attributes.is_a?(Array)
|
128
|
-
attributes.collect { |attr| create(attr) }
|
129
|
-
else
|
130
|
-
build_record(attributes, &block)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def record_timestamp_columns(record)
|
135
|
-
if record.record_timestamps
|
136
|
-
record.send(:all_timestamp_attributes).map { |x| x.to_s }
|
137
|
-
else
|
138
|
-
[]
|
139
|
-
end
|
47
|
+
def invertible_for?(record)
|
48
|
+
false
|
140
49
|
end
|
141
50
|
end
|
142
51
|
end
|
@@ -5,19 +5,14 @@ module ActiveRecord
|
|
5
5
|
#
|
6
6
|
# If the association has a <tt>:through</tt> option further specialization
|
7
7
|
# is provided by its child HasManyThroughAssociation.
|
8
|
-
class HasManyAssociation <
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
class HasManyAssociation < CollectionAssociation #:nodoc:
|
9
|
+
|
10
|
+
def insert_record(record, validate = true)
|
11
|
+
set_owner_attributes(record)
|
12
|
+
record.save(:validate => validate)
|
12
13
|
end
|
13
|
-
|
14
|
-
|
15
|
-
if @reflection.options[:primary_key]
|
16
|
-
quote_value(@owner.send(@reflection.options[:primary_key]))
|
17
|
-
else
|
18
|
-
@owner.quoted_id
|
19
|
-
end
|
20
|
-
end
|
14
|
+
|
15
|
+
private
|
21
16
|
|
22
17
|
# Returns the number of records in this collection.
|
23
18
|
#
|
@@ -34,94 +29,70 @@ module ActiveRecord
|
|
34
29
|
# the loaded flag is set to true as well.
|
35
30
|
def count_records
|
36
31
|
count = if has_cached_counter?
|
37
|
-
|
38
|
-
elsif
|
39
|
-
|
32
|
+
owner.send(:read_attribute, cached_counter_attribute_name)
|
33
|
+
elsif options[:counter_sql] || options[:finder_sql]
|
34
|
+
reflection.klass.count_by_sql(custom_counter_sql)
|
40
35
|
else
|
41
|
-
|
36
|
+
scoped.count
|
42
37
|
end
|
43
38
|
|
44
39
|
# If there's nothing in the database and @target has no new records
|
45
40
|
# we are certain the current target is an empty array. This is a
|
46
41
|
# documented side-effect of the method that may avoid an extra SELECT.
|
47
|
-
@target ||= [] and loaded if count == 0
|
48
|
-
|
49
|
-
if @reflection.options[:limit]
|
50
|
-
count = [ @reflection.options[:limit], count ].min
|
51
|
-
end
|
52
|
-
|
53
|
-
return count
|
54
|
-
end
|
42
|
+
@target ||= [] and loaded! if count == 0
|
55
43
|
|
56
|
-
|
57
|
-
@owner.attribute_present?(cached_counter_attribute_name)
|
44
|
+
[options[:limit], count].compact.min
|
58
45
|
end
|
59
46
|
|
60
|
-
def
|
61
|
-
|
47
|
+
def has_cached_counter?(reflection = reflection)
|
48
|
+
owner.attribute_present?(cached_counter_attribute_name(reflection))
|
62
49
|
end
|
63
50
|
|
64
|
-
def
|
65
|
-
|
66
|
-
force ? record.save! : record.save(:validate => validate)
|
51
|
+
def cached_counter_attribute_name(reflection = reflection)
|
52
|
+
"#{reflection.name}_count"
|
67
53
|
end
|
68
54
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
@reflection.klass.delete(records.map { |record| record.id })
|
76
|
-
else
|
77
|
-
relation = Arel::Table.new(@reflection.table_name)
|
78
|
-
relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
|
79
|
-
and(relation[@reflection.klass.primary_key].in(records.map { |r| r.id }))
|
80
|
-
).update(relation[@reflection.primary_key_name] => nil)
|
81
|
-
|
82
|
-
@owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
|
55
|
+
def update_counter(difference, reflection = reflection)
|
56
|
+
if has_cached_counter?(reflection)
|
57
|
+
counter = cached_counter_attribute_name(reflection)
|
58
|
+
owner.class.update_counters(owner.id, counter => difference)
|
59
|
+
owner[counter] += difference
|
60
|
+
owner.changed_attributes.delete(counter) # eww
|
83
61
|
end
|
84
62
|
end
|
85
63
|
|
86
|
-
|
87
|
-
|
64
|
+
# This shit is nasty. We need to avoid the following situation:
|
65
|
+
#
|
66
|
+
# * An associated record is deleted via record.destroy
|
67
|
+
# * Hence the callbacks run, and they find a belongs_to on the record with a
|
68
|
+
# :counter_cache options which points back at our owner. So they update the
|
69
|
+
# counter cache.
|
70
|
+
# * In which case, we must make sure to *not* update the counter cache, or else
|
71
|
+
# it will be decremented twice.
|
72
|
+
#
|
73
|
+
# Hence this method.
|
74
|
+
def inverse_updates_counter_cache?(reflection = reflection)
|
75
|
+
counter_name = cached_counter_attribute_name(reflection)
|
76
|
+
reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
|
77
|
+
inverse_reflection.counter_cache_column == counter_name
|
78
|
+
}
|
88
79
|
end
|
89
80
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
99
|
-
@finder_sql << " AND (#{conditions})" if conditions
|
81
|
+
# Deletes the records according to the <tt>:dependent</tt> option.
|
82
|
+
def delete_records(records, method)
|
83
|
+
if method == :destroy
|
84
|
+
records.each { |r| r.destroy }
|
85
|
+
update_counter(-records.length) unless inverse_updates_counter_cache?
|
86
|
+
else
|
87
|
+
keys = records.map { |r| r[reflection.association_primary_key] }
|
88
|
+
scope = scoped.where(reflection.association_primary_key => keys)
|
100
89
|
|
90
|
+
if method == :delete_all
|
91
|
+
update_counter(-scope.delete_all)
|
101
92
|
else
|
102
|
-
|
103
|
-
|
93
|
+
update_counter(-scope.update_all(reflection.foreign_key => nil))
|
94
|
+
end
|
104
95
|
end
|
105
|
-
|
106
|
-
construct_counter_sql
|
107
|
-
end
|
108
|
-
|
109
|
-
def construct_scope
|
110
|
-
create_scoping = {}
|
111
|
-
set_belongs_to_association_for(create_scoping)
|
112
|
-
{
|
113
|
-
:find => { :conditions => @finder_sql,
|
114
|
-
:readonly => false,
|
115
|
-
:order => @reflection.options[:order],
|
116
|
-
:limit => @reflection.options[:limit],
|
117
|
-
:include => @reflection.options[:include]},
|
118
|
-
:create => create_scoping
|
119
|
-
}
|
120
|
-
end
|
121
|
-
|
122
|
-
def we_can_set_the_inverse_on_this?(record)
|
123
|
-
inverse = @reflection.inverse_of
|
124
|
-
return !inverse.nil?
|
125
96
|
end
|
126
97
|
end
|
127
98
|
end
|