tenacity 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/EXTEND.rdoc +9 -13
- data/Rakefile +6 -0
- data/history.txt +17 -0
- data/lib/tenacity.rb +13 -9
- data/lib/tenacity/associate_proxy.rb +56 -0
- data/lib/tenacity/associates_proxy.rb +5 -3
- data/lib/tenacity/association.rb +81 -9
- data/lib/tenacity/associations/belongs_to.rb +28 -16
- data/lib/tenacity/associations/has_many.rb +113 -53
- data/lib/tenacity/associations/has_one.rb +33 -14
- data/lib/tenacity/class_methods.rb +117 -9
- data/lib/tenacity/errors.rb +9 -0
- data/lib/tenacity/instance_methods.rb +40 -3
- data/lib/tenacity/orm_ext/activerecord.rb +114 -95
- data/lib/tenacity/orm_ext/couchrest.rb +132 -113
- data/lib/tenacity/orm_ext/datamapper.rb +138 -112
- data/lib/tenacity/orm_ext/helpers.rb +36 -0
- data/lib/tenacity/orm_ext/mongo_mapper.rb +102 -84
- data/lib/tenacity/orm_ext/mongoid.rb +106 -87
- data/lib/tenacity/orm_ext/sequel.rb +137 -110
- data/lib/tenacity/version.rb +1 -1
- data/tenacity.gemspec +2 -1
- data/test/association_features/belongs_to_test.rb +46 -1
- data/test/association_features/has_many_test.rb +187 -2
- data/test/association_features/has_one_test.rb +57 -2
- data/test/associations/belongs_to_test.rb +41 -7
- data/test/associations/has_many_test.rb +59 -10
- data/test/associations/has_one_test.rb +57 -2
- data/test/fixtures/active_record_car.rb +5 -4
- data/test/fixtures/active_record_climate_control_unit.rb +1 -0
- data/test/fixtures/active_record_engine.rb +1 -0
- data/test/fixtures/active_record_has_many_target.rb +7 -0
- data/test/fixtures/active_record_has_one_target.rb +7 -0
- data/test/fixtures/active_record_object.rb +19 -0
- data/test/fixtures/couch_rest_has_many_target.rb +7 -0
- data/test/fixtures/couch_rest_has_one_target.rb +7 -0
- data/test/fixtures/couch_rest_object.rb +14 -0
- data/test/fixtures/data_mapper_has_many_target.rb +23 -3
- data/test/fixtures/data_mapper_has_one_target.rb +23 -3
- data/test/fixtures/data_mapper_object.rb +14 -0
- data/test/fixtures/mongo_mapper_air_filter.rb +6 -0
- data/test/fixtures/mongo_mapper_alternator.rb +7 -0
- data/test/fixtures/mongo_mapper_autosave_false_has_many_target.rb +8 -0
- data/test/fixtures/mongo_mapper_autosave_false_has_one_target.rb +8 -0
- data/test/fixtures/mongo_mapper_autosave_true_has_many_target.rb +8 -0
- data/test/fixtures/mongo_mapper_autosave_true_has_one_target.rb +8 -0
- data/test/fixtures/mongo_mapper_circuit_board.rb +6 -0
- data/test/fixtures/mongo_mapper_dashboard.rb +5 -2
- data/test/fixtures/mongo_mapper_has_many_target.rb +7 -0
- data/test/fixtures/mongo_mapper_has_one_target.rb +7 -0
- data/test/fixtures/mongo_mapper_object.rb +14 -0
- data/test/fixtures/mongo_mapper_wheel.rb +2 -0
- data/test/fixtures/mongo_mapper_windows.rb +6 -0
- data/test/fixtures/mongoid_has_many_target.rb +7 -0
- data/test/fixtures/mongoid_has_one_target.rb +7 -0
- data/test/fixtures/mongoid_object.rb +14 -0
- data/test/fixtures/sequel_has_many_target.rb +7 -0
- data/test/fixtures/sequel_has_one_target.rb +7 -0
- data/test/fixtures/sequel_object.rb +14 -0
- data/test/helpers/active_record_test_helper.rb +87 -8
- data/test/helpers/couch_rest_test_helper.rb +7 -0
- data/test/helpers/data_mapper_test_helper.rb +41 -3
- data/test/helpers/sequel_test_helper.rb +65 -9
- data/test/orm_ext/activerecord_test.rb +1 -1
- data/test/orm_ext/datamapper_test.rb +33 -24
- data/test/orm_ext/mongo_mapper_test.rb +6 -6
- data/test/orm_ext/mongoid_test.rb +6 -6
- data/test/orm_ext/sequel_test.rb +1 -1
- data/test/test_helper.rb +18 -9
- metadata +119 -55
data/EXTEND.rdoc
CHANGED
@@ -5,14 +5,6 @@ only, so as long as they have been implemented and are available on the model
|
|
5
5
|
object, Tenacity will be able to manage the object's relationships.
|
6
6
|
|
7
7
|
|
8
|
-
== A note about IDs
|
9
|
-
|
10
|
-
An ID can be an integer, a string, or an object, depending on the database
|
11
|
-
and database client you are using. To be as compatible as possible, Tenacity
|
12
|
-
treats all IDs as strings. So, all database client extensions should accept
|
13
|
-
strings for IDs as input parameters, and return strings for IDs.
|
14
|
-
|
15
|
-
|
16
8
|
== The Association Class
|
17
9
|
|
18
10
|
A few of the methods take an association as a parameter. This association
|
@@ -56,21 +48,25 @@ delete callback meethods are not run. Return nothing.
|
|
56
48
|
|
57
49
|
Perform any database client specific initialization necessary to support a has
|
58
50
|
many association. This could include defining properties, or callback methods,
|
59
|
-
on the object. This method
|
51
|
+
on the object. This method must also call
|
52
|
+
_t_cleanup_has_many_association(association) to cleanup the association
|
53
|
+
when the after the object has been deleted.
|
60
54
|
|
61
55
|
_t_initialize_belongs_to_association(association)
|
62
56
|
|
63
57
|
Perform any database client specific initialization necessary to support a
|
64
58
|
belongs to association. This could include defining properties, or callback
|
65
|
-
methods, on the object. This method
|
66
|
-
|
59
|
+
methods, on the object. This method must also call
|
60
|
+
_t_cleanup_belongs_to_association(association) to cleanup the association
|
61
|
+
when the after the object has been deleted.
|
67
62
|
|
68
63
|
_t_initialize_has_one_association(association)
|
69
64
|
|
70
65
|
Perform any database client specific initialization necessary to support a has one
|
71
66
|
association. This could include defining properties, or callback methods,
|
72
|
-
on the object. This method
|
73
|
-
|
67
|
+
on the object. This method must also call
|
68
|
+
_t_cleanup_has_one_association(association) to cleanup the association
|
69
|
+
when the after the object has been deleted.
|
74
70
|
|
75
71
|
== Instance Methods
|
76
72
|
|
data/Rakefile
CHANGED
@@ -25,6 +25,12 @@ Rake::TestTask.new(:test) do |test|
|
|
25
25
|
test.verbose = true
|
26
26
|
end
|
27
27
|
|
28
|
+
desc 'Run an abbreviated version of the test suite'
|
29
|
+
task :quick_test do
|
30
|
+
ENV['QUICK'] = 'true'
|
31
|
+
Rake::Task["test"].invoke
|
32
|
+
end
|
33
|
+
|
28
34
|
Rcov::RcovTask.new do |test|
|
29
35
|
test.libs << 'test' << 'test/fixtures'
|
30
36
|
test.pattern = 'test/**/*_test.rb'
|
data/history.txt
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
== 0.4.0
|
2
|
+
|
3
|
+
* Major enhancements
|
4
|
+
|
5
|
+
* Tenacity will no longer convert foreign keys to strings before storing them
|
6
|
+
in the database. Instead, the ID will be stored without any modification
|
7
|
+
(when possible).
|
8
|
+
|
9
|
+
* Minor enhancements
|
10
|
+
|
11
|
+
* Added support for the :dependent option to all associations
|
12
|
+
* Added support for the :readonly option to all associations
|
13
|
+
* Added support for the :limit option to the t_has_many association
|
14
|
+
* Added support for the :offset option to the t_has_many association
|
15
|
+
* Added support for the :autosave option to all associations
|
16
|
+
* Added support for polymorphic associations to all associations
|
17
|
+
|
1
18
|
== 0.3.0
|
2
19
|
|
3
20
|
* Major enhancements
|
data/lib/tenacity.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
require File.join('active_support', 'inflector')
|
2
2
|
|
3
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'associate_proxy')
|
3
4
|
require File.join(File.dirname(__FILE__), 'tenacity', 'associates_proxy')
|
4
5
|
require File.join(File.dirname(__FILE__), 'tenacity', 'association')
|
5
6
|
require File.join(File.dirname(__FILE__), 'tenacity', 'class_methods')
|
7
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'errors')
|
6
8
|
require File.join(File.dirname(__FILE__), 'tenacity', 'instance_methods')
|
7
9
|
require File.join(File.dirname(__FILE__), 'tenacity', 'associations', 'belongs_to')
|
8
10
|
require File.join(File.dirname(__FILE__), 'tenacity', 'associations', 'has_many')
|
9
11
|
require File.join(File.dirname(__FILE__), 'tenacity', 'associations', 'has_one')
|
12
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'helpers')
|
10
13
|
require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'activerecord')
|
11
14
|
require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'couchrest')
|
12
15
|
require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'datamapper')
|
@@ -17,20 +20,21 @@ require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'sequel')
|
|
17
20
|
module Tenacity #:nodoc:
|
18
21
|
include InstanceMethods
|
19
22
|
|
20
|
-
include BelongsTo
|
21
|
-
include HasMany
|
22
|
-
include HasOne
|
23
|
+
include Associations::BelongsTo
|
24
|
+
include Associations::HasMany
|
25
|
+
include Associations::HasOne
|
23
26
|
|
24
27
|
def self.included(model)
|
25
|
-
ActiveRecord.setup(model)
|
26
|
-
CouchRest.setup(model)
|
27
|
-
DataMapper.setup(model)
|
28
|
-
MongoMapper.setup(model)
|
29
|
-
Mongoid.setup(model)
|
30
|
-
Sequel.setup(model)
|
28
|
+
OrmExt::ActiveRecord.setup(model)
|
29
|
+
OrmExt::CouchRest.setup(model)
|
30
|
+
OrmExt::DataMapper.setup(model)
|
31
|
+
OrmExt::MongoMapper.setup(model)
|
32
|
+
OrmExt::Mongoid.setup(model)
|
33
|
+
OrmExt::Sequel.setup(model)
|
31
34
|
|
32
35
|
raise "Tenacity does not support the database client used by #{model}" unless model.respond_to?(:_t_find)
|
33
36
|
model.extend(ClassMethods)
|
37
|
+
model._t_initialize_tenacity
|
34
38
|
end
|
35
39
|
end
|
36
40
|
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Tenacity
|
2
|
+
class AssociateProxy #:nodoc:
|
3
|
+
alias_method :proxy_respond_to?, :respond_to?
|
4
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
|
5
|
+
|
6
|
+
def initialize(target, association)
|
7
|
+
raise "Cannot create a Tenacity::AssociateProxy with a nil target" if target.nil?
|
8
|
+
@target = target
|
9
|
+
@association = association
|
10
|
+
@marked_for_destruction = false
|
11
|
+
end
|
12
|
+
|
13
|
+
def respond_to?(*args)
|
14
|
+
proxy_respond_to?(*args) || @target.respond_to?(*args)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Explicitly proxy === because the instance method removal above doesn't catch it.
|
18
|
+
def ===(other)
|
19
|
+
other === @target
|
20
|
+
end
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
@target.inspect
|
24
|
+
end
|
25
|
+
|
26
|
+
def save
|
27
|
+
if @association.readonly?
|
28
|
+
raise ReadOnlyError
|
29
|
+
else
|
30
|
+
@target.save
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def association_target
|
35
|
+
@target
|
36
|
+
end
|
37
|
+
|
38
|
+
def mark_for_destruction
|
39
|
+
@marked_for_destruction = true
|
40
|
+
end
|
41
|
+
|
42
|
+
def marked_for_destruction?
|
43
|
+
@marked_for_destruction
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def method_missing(method, *args)
|
49
|
+
if block_given?
|
50
|
+
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
51
|
+
else
|
52
|
+
@target.send(method, *args)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -20,17 +20,19 @@ module Tenacity
|
|
20
20
|
|
21
21
|
def <<(object)
|
22
22
|
object.save unless @parent.id.nil?
|
23
|
-
@target << object
|
23
|
+
@target << AssociateProxy.new(object, @association)
|
24
24
|
end
|
25
25
|
|
26
26
|
def push(*objects)
|
27
27
|
objects.each { |object| object.save } unless @parent.id.nil?
|
28
|
-
|
28
|
+
proxies = objects.map { |object| AssociateProxy.new(object, @association) }
|
29
|
+
@target.push(*proxies)
|
29
30
|
end
|
30
31
|
|
31
32
|
def concat(objects)
|
32
33
|
objects.each { |object| object.save } unless @parent.id.nil?
|
33
|
-
|
34
|
+
proxies = objects.map { |object| AssociateProxy.new(object, @association) }
|
35
|
+
@target.concat(proxies)
|
34
36
|
end
|
35
37
|
|
36
38
|
def destroy_all
|
data/lib/tenacity/association.rb
CHANGED
@@ -8,15 +8,33 @@ module Tenacity
|
|
8
8
|
# Type type of the association (<tt>:t_has_one</tt>, <tt>:t_has_many</tt>, or <tt>:t_belongs_to</tt>)
|
9
9
|
attr_reader :type
|
10
10
|
|
11
|
-
# The name of the association
|
12
|
-
attr_reader :name
|
13
|
-
|
14
11
|
# The class defining the association
|
15
12
|
attr_reader :source
|
16
13
|
|
17
14
|
# The name of the associated class
|
18
15
|
attr_reader :class_name
|
19
16
|
|
17
|
+
# What happens to the associated object when the object is deleted
|
18
|
+
attr_reader :dependent
|
19
|
+
|
20
|
+
# Are the associated objects read only?
|
21
|
+
attr_reader :readonly
|
22
|
+
|
23
|
+
# The limit on the number of results to be returned.
|
24
|
+
attr_reader :limit
|
25
|
+
|
26
|
+
# The offset from where the results should be fetched.
|
27
|
+
attr_reader :offset
|
28
|
+
|
29
|
+
# Should the associated object be saved when the parent object is saved?
|
30
|
+
attr_reader :autosave
|
31
|
+
|
32
|
+
# The interface this association is reffered to as
|
33
|
+
attr_reader :as
|
34
|
+
|
35
|
+
# Is this association a polymorphic association?
|
36
|
+
attr_reader :polymorphic
|
37
|
+
|
20
38
|
def initialize(type, name, source, options={})
|
21
39
|
@type = type
|
22
40
|
@name = name
|
@@ -33,6 +51,13 @@ module Tenacity
|
|
33
51
|
@join_table = options[:join_table]
|
34
52
|
@association_key = options[:association_key]
|
35
53
|
@association_foreign_key = options[:association_foreign_key]
|
54
|
+
@dependent = options[:dependent]
|
55
|
+
@readonly = options[:readonly]
|
56
|
+
@limit = options[:limit]
|
57
|
+
@offset = options[:offset]
|
58
|
+
@autosave = options[:autosave]
|
59
|
+
@polymorphic = options[:polymorphic]
|
60
|
+
@as = options[:as]
|
36
61
|
|
37
62
|
if @foreign_keys_property
|
38
63
|
if @foreign_keys_property.to_s == ActiveSupport::Inflector.singularize(name) + "_ids"
|
@@ -41,9 +66,18 @@ module Tenacity
|
|
41
66
|
end
|
42
67
|
end
|
43
68
|
|
69
|
+
# The name of the association
|
70
|
+
def name
|
71
|
+
@as.nil? ? @name : @as
|
72
|
+
end
|
73
|
+
|
44
74
|
# Get the associated class
|
45
|
-
def associate_class
|
46
|
-
@
|
75
|
+
def associate_class(object=nil)
|
76
|
+
if @type == :t_belongs_to && polymorphic?
|
77
|
+
Kernel.const_get(object.send(polymorphic_type))
|
78
|
+
else
|
79
|
+
@clazz ||= Kernel.const_get(@class_name)
|
80
|
+
end
|
47
81
|
end
|
48
82
|
|
49
83
|
# Get the foreign key used by this association. <tt>t_has_one</tt> and
|
@@ -52,10 +86,9 @@ module Tenacity
|
|
52
86
|
def foreign_key(clazz=nil)
|
53
87
|
@foreign_key || begin
|
54
88
|
if @type == :t_belongs_to
|
55
|
-
|
89
|
+
belongs_to_foreign_key
|
56
90
|
elsif @type == :t_has_one || @type == :t_has_many
|
57
|
-
|
58
|
-
"#{ActiveSupport::Inflector.underscore(clazz)}_id"
|
91
|
+
has_x_foreign_key(clazz)
|
59
92
|
end
|
60
93
|
end
|
61
94
|
end
|
@@ -68,7 +101,14 @@ module Tenacity
|
|
68
101
|
# Get the name of the join table used by this association
|
69
102
|
def join_table
|
70
103
|
table_name = fetch_table_name
|
71
|
-
|
104
|
+
|
105
|
+
if @type == :t_has_many && polymorphic?
|
106
|
+
association_name_for_join_table = name.to_s.pluralize
|
107
|
+
else
|
108
|
+
association_name_for_join_table = name
|
109
|
+
end
|
110
|
+
|
111
|
+
@join_table || (name.to_s < table_name ? "#{association_name_for_join_table}_#{table_name}" : "#{table_name}_#{association_name_for_join_table}")
|
72
112
|
end
|
73
113
|
|
74
114
|
# Get the name of the column in the join table that represents this object
|
@@ -82,6 +122,21 @@ module Tenacity
|
|
82
122
|
@association_foreign_key || name.to_s.singularize + '_id'
|
83
123
|
end
|
84
124
|
|
125
|
+
# Are the associated objects read only?
|
126
|
+
def readonly?
|
127
|
+
@readonly == true
|
128
|
+
end
|
129
|
+
|
130
|
+
# Is this association a polymorphic association?
|
131
|
+
def polymorphic?
|
132
|
+
@polymorphic == true || !@as.nil?
|
133
|
+
end
|
134
|
+
|
135
|
+
# The name of the property that stores the polymorphic type (for polymorphic associations)
|
136
|
+
def polymorphic_type
|
137
|
+
(name.to_s + "_type").to_sym
|
138
|
+
end
|
139
|
+
|
85
140
|
private
|
86
141
|
|
87
142
|
def fetch_table_name
|
@@ -91,5 +146,22 @@ module Tenacity
|
|
91
146
|
"#{ActiveSupport::Inflector.underscore(@source)}s"
|
92
147
|
end
|
93
148
|
end
|
149
|
+
|
150
|
+
def belongs_to_foreign_key
|
151
|
+
if polymorphic?
|
152
|
+
(name.to_s + "_id").to_sym
|
153
|
+
else
|
154
|
+
@class_name.underscore + "_id"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def has_x_foreign_key(clazz)
|
159
|
+
raise "The class of the associate must be provided in order to determine the name of the foreign key" if clazz.nil?
|
160
|
+
if polymorphic?
|
161
|
+
(@as.to_s + "_id").to_sym
|
162
|
+
else
|
163
|
+
"#{ActiveSupport::Inflector.underscore(clazz)}_id"
|
164
|
+
end
|
165
|
+
end
|
94
166
|
end
|
95
167
|
end
|
@@ -1,28 +1,40 @@
|
|
1
1
|
module Tenacity
|
2
|
-
module
|
2
|
+
module Associations
|
3
|
+
module BelongsTo #:nodoc:
|
3
4
|
|
4
|
-
|
5
|
+
def _t_cleanup_belongs_to_association(association)
|
6
|
+
associate_id = self.send(association.foreign_key)
|
7
|
+
if associate_id != nil && associate_id.to_s.strip != ''
|
8
|
+
if association.dependent == :destroy
|
9
|
+
association.associate_class._t_delete(associate_id)
|
10
|
+
elsif association.dependent == :delete
|
11
|
+
association.associate_class._t_delete(associate_id, false)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
5
15
|
|
6
|
-
|
7
|
-
associate_id = self.send(association.foreign_key)
|
8
|
-
clazz = association.associate_class
|
9
|
-
clazz._t_find(associate_id)
|
10
|
-
end
|
16
|
+
private
|
11
17
|
|
12
|
-
|
13
|
-
|
14
|
-
|
18
|
+
def belongs_to_associate(association)
|
19
|
+
associate_id = self.send(association.foreign_key)
|
20
|
+
clazz = association.associate_class(self)
|
21
|
+
associate = clazz._t_find(associate_id)
|
22
|
+
associate
|
23
|
+
end
|
15
24
|
|
16
|
-
|
17
|
-
|
18
|
-
|
25
|
+
def set_belongs_to_associate(association, associate)
|
26
|
+
self.send "#{association.foreign_key}=", _t_serialize(associate.id)
|
27
|
+
self.send "#{association.polymorphic_type}=", associate.class.to_s if association.polymorphic?
|
28
|
+
associate
|
19
29
|
end
|
20
30
|
|
21
|
-
|
22
|
-
|
31
|
+
module ClassMethods #:nodoc:
|
32
|
+
def initialize_belongs_to_association(association)
|
33
|
+
_t_initialize_belongs_to_association(association) if self.respond_to?(:_t_initialize_belongs_to_association)
|
34
|
+
end
|
23
35
|
end
|
24
|
-
end
|
25
36
|
|
37
|
+
end
|
26
38
|
end
|
27
39
|
end
|
28
40
|
|
@@ -1,80 +1,140 @@
|
|
1
1
|
module Tenacity
|
2
|
-
module
|
2
|
+
module Associations
|
3
|
+
module HasMany #:nodoc:
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
def _t_remove_associates(association)
|
6
|
+
instance_variable_set _t_ivar_name(association), []
|
7
|
+
_t_clear_associates(association)
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
+
def _t_cleanup_has_many_association(association)
|
11
|
+
associates = has_many_associates(association)
|
12
|
+
unless associates.nil? || associates.empty?
|
13
|
+
if association.dependent == :destroy
|
14
|
+
associates.each { |associate| association.associate_class._t_delete([_t_serialize(associate.id)]) }
|
15
|
+
elsif association.dependent == :delete_all
|
16
|
+
associates.each { |associate| association.associate_class._t_delete([_t_serialize(associate.id)], false) }
|
17
|
+
elsif association.dependent == :nullify
|
18
|
+
associates.each do |associate|
|
19
|
+
associate.send "#{association.foreign_key(self.class)}=", nil
|
20
|
+
associate.save
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
10
25
|
|
11
|
-
|
12
|
-
ids = _t_get_associate_ids(association)
|
13
|
-
clazz = association.associate_class
|
14
|
-
clazz._t_find_bulk(ids)
|
15
|
-
end
|
26
|
+
private
|
16
27
|
|
17
|
-
|
18
|
-
|
19
|
-
|
28
|
+
def has_many_associates(association)
|
29
|
+
ids = _t_get_associate_ids(association)
|
30
|
+
pruned_ids = prune_associate_ids(association, ids)
|
31
|
+
clazz = association.associate_class
|
32
|
+
clazz._t_find_bulk(pruned_ids)
|
33
|
+
end
|
20
34
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
35
|
+
def set_has_many_associates(association, associates)
|
36
|
+
associates.map { |associate| AssociateProxy.new(associate, association) }
|
37
|
+
end
|
25
38
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
39
|
+
def has_many_associate_ids(association)
|
40
|
+
ids = _t_get_associate_ids(association)
|
41
|
+
prune_associate_ids(association, ids)
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_has_many_associate_ids(association, associate_ids)
|
45
|
+
clazz = association.associate_class
|
46
|
+
instance_variable_set _t_ivar_name(association), clazz._t_find_bulk(associate_ids)
|
47
|
+
end
|
48
|
+
|
49
|
+
def save_without_callback
|
50
|
+
@perform_save_associates_callback = false
|
51
|
+
save
|
52
|
+
ensure
|
53
|
+
@perform_save_associates_callback = true
|
54
|
+
end
|
32
55
|
|
33
|
-
|
34
|
-
|
35
|
-
|
56
|
+
def prune_associate_ids(association, associate_ids)
|
57
|
+
if association.limit || association.offset
|
58
|
+
sorted_ids = associate_ids.sort { |a,b| a <=> b }
|
36
59
|
|
37
|
-
|
60
|
+
limit = association.limit || associate_ids.size
|
61
|
+
offset = association.offset || 0
|
62
|
+
sorted_ids[offset...(offset + limit)]
|
63
|
+
else
|
64
|
+
associate_ids
|
65
|
+
end
|
38
66
|
end
|
39
67
|
|
40
|
-
|
41
|
-
|
68
|
+
module ClassMethods #:nodoc:
|
69
|
+
def initialize_has_many_association(association)
|
70
|
+
_t_initialize_has_many_association(association) if self.respond_to?(:_t_initialize_has_many_association)
|
71
|
+
|
72
|
+
attr_accessor :perform_save_associates_callback
|
73
|
+
end
|
74
|
+
|
75
|
+
def _t_save_associates(record, association)
|
76
|
+
return if record.perform_save_associates_callback == false
|
77
|
+
|
78
|
+
old_associates = get_current_associates(record, association)
|
79
|
+
|
80
|
+
# Some ORM libraries (CouchRest, ActiveRecord, etc) return a proxy in
|
81
|
+
# place of the associated objects. The actual associated objects
|
82
|
+
# will be fetched the first time they are needed. So, force them to
|
83
|
+
# be fetched here, before we clear them out in the database.
|
84
|
+
old_associates.inspect
|
85
|
+
|
86
|
+
_t_clear_old_associations(record, association)
|
42
87
|
|
43
|
-
|
88
|
+
associates = (record.instance_variable_get record._t_ivar_name(association)) || []
|
89
|
+
associates.each do |associate|
|
90
|
+
associate._t_reload
|
91
|
+
associate.send("#{association.foreign_key(record.class)}=", _t_serialize(record.id, association))
|
92
|
+
associate.send "#{association.polymorphic_type}=", self.to_s if association.polymorphic?
|
93
|
+
save_associate(associate)
|
94
|
+
end
|
44
95
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
96
|
+
unless associates.blank?
|
97
|
+
associate_ids = associates.map { |associate| _t_serialize(associate.id) }
|
98
|
+
record._t_associate_many(association, associate_ids)
|
99
|
+
save_associate(record)
|
100
|
+
end
|
101
|
+
|
102
|
+
destroy_orphaned_associates(association, old_associates, associates)
|
50
103
|
end
|
51
104
|
|
52
|
-
|
53
|
-
|
54
|
-
record
|
105
|
+
def _t_clear_old_associations(record, association)
|
106
|
+
property_name = association.foreign_key(record.class)
|
107
|
+
old_associates = get_current_associates(record, association)
|
108
|
+
old_associates.each do |old_associate|
|
109
|
+
old_associate.send("#{property_name}=", nil)
|
110
|
+
save_associate(old_associate)
|
111
|
+
end
|
112
|
+
|
113
|
+
record._t_clear_associates(association)
|
55
114
|
save_associate(record)
|
56
115
|
end
|
57
|
-
end
|
58
116
|
|
59
|
-
|
60
|
-
|
61
|
-
|
117
|
+
def save_associate(associate)
|
118
|
+
associate.respond_to?(:_t_save_without_callback) ? associate._t_save_without_callback : associate.save
|
119
|
+
end
|
62
120
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
121
|
+
def get_current_associates(record, association)
|
122
|
+
clazz = association.associate_class
|
123
|
+
property_name = association.foreign_key(record.class)
|
124
|
+
clazz._t_find_all_by_associate(property_name, _t_serialize(record.id, association))
|
67
125
|
end
|
68
126
|
|
69
|
-
|
70
|
-
|
127
|
+
def destroy_orphaned_associates(association, old_associates, associates)
|
128
|
+
if association.dependent == :destroy || association.dependent == :delete_all
|
129
|
+
issue_callbacks = (association.dependent == :destroy)
|
130
|
+
(old_associates.map{|a| a.id} - associates.map{|a| a.id}).each do |associate_id|
|
131
|
+
association.associate_class._t_delete([_t_serialize(associate_id)], issue_callbacks)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
71
135
|
end
|
72
136
|
|
73
|
-
def save_associate(associate)
|
74
|
-
associate.respond_to?(:_t_save_without_callback) ? associate._t_save_without_callback : associate.save
|
75
|
-
end
|
76
137
|
end
|
77
|
-
|
78
138
|
end
|
79
139
|
end
|
80
140
|
|