tenacity 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|