tenacity 0.4.1 → 0.5.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/.gitignore +1 -0
- data/EXTEND.rdoc +18 -21
- data/Gemfile +0 -2
- data/README.rdoc +3 -1
- data/Rakefile +7 -0
- data/history.txt +17 -0
- data/lib/tenacity.rb +4 -0
- data/lib/tenacity/associates_proxy.rb +5 -7
- data/lib/tenacity/association.rb +9 -47
- data/lib/tenacity/associations/belongs_to.rb +1 -2
- data/lib/tenacity/associations/has_many.rb +27 -21
- data/lib/tenacity/associations/has_one.rb +3 -2
- data/lib/tenacity/class_methods.rb +14 -60
- data/lib/tenacity/errors.rb +8 -0
- data/lib/tenacity/instance_methods.rb +42 -12
- data/lib/tenacity/orm_ext/activerecord.rb +11 -32
- data/lib/tenacity/orm_ext/couchrest.rb +14 -22
- data/lib/tenacity/orm_ext/datamapper.rb +14 -31
- data/lib/tenacity/orm_ext/helpers.rb +3 -3
- data/lib/tenacity/orm_ext/mongo_mapper.rb +16 -22
- data/lib/tenacity/orm_ext/mongoid.rb +10 -18
- data/lib/tenacity/orm_ext/ripple.rb +270 -0
- data/lib/tenacity/orm_ext/sequel.rb +21 -33
- data/lib/tenacity/orm_ext/toystore.rb +114 -0
- data/lib/tenacity/version.rb +1 -1
- data/tenacity.gemspec +10 -3
- data/test/association_features/belongs_to_test.rb +12 -0
- data/test/association_features/has_many_test.rb +32 -2
- data/test/association_features/has_one_test.rb +18 -4
- data/test/associations/has_one_test.rb +0 -1
- data/test/core/classmethods_test.rb +7 -0
- data/test/fixtures/active_record_has_many_target.rb +4 -0
- data/test/fixtures/active_record_has_one_target.rb +4 -0
- data/test/fixtures/active_record_object.rb +8 -0
- data/test/fixtures/couch_rest_has_many_target.rb +4 -0
- data/test/fixtures/couch_rest_has_one_target.rb +4 -0
- data/test/fixtures/couch_rest_object.rb +8 -0
- data/test/fixtures/data_mapper_has_many_target.rb +10 -0
- data/test/fixtures/data_mapper_has_one_target.rb +10 -0
- data/test/fixtures/data_mapper_object.rb +8 -0
- data/test/fixtures/mongo_mapper_has_many_target.rb +4 -0
- data/test/fixtures/mongo_mapper_has_one_target.rb +4 -0
- data/test/fixtures/mongo_mapper_object.rb +8 -0
- data/test/fixtures/mongoid_has_many_target.rb +4 -0
- data/test/fixtures/mongoid_has_one_target.rb +4 -0
- data/test/fixtures/mongoid_object.rb +8 -0
- data/test/fixtures/no_associations.rb +4 -0
- data/test/fixtures/ripple_has_many_target.rb +24 -0
- data/test/fixtures/ripple_has_one_target.rb +24 -0
- data/test/fixtures/ripple_object.rb +42 -0
- data/test/fixtures/sequel_has_many_target.rb +4 -0
- data/test/fixtures/sequel_has_one_target.rb +4 -0
- data/test/fixtures/sequel_object.rb +8 -0
- data/test/fixtures/toystore_has_many_target.rb +28 -0
- data/test/fixtures/toystore_has_one_target.rb +28 -0
- data/test/fixtures/toystore_object.rb +46 -0
- data/test/helpers/active_record_test_helper.rb +12 -95
- data/test/helpers/data_mapper_test_helper.rb +0 -64
- data/test/helpers/ripple_test_helper.rb +19 -0
- data/test/helpers/sequel_test_helper.rb +13 -60
- data/test/helpers/toystore_test_helper.rb +17 -0
- data/test/orm_ext/activerecord_test.rb +16 -26
- data/test/orm_ext/couchrest_test.rb +10 -29
- data/test/orm_ext/datamapper_test.rb +16 -29
- data/test/orm_ext/mongo_mapper_test.rb +11 -29
- data/test/orm_ext/mongoid_test.rb +11 -29
- data/test/orm_ext/ripple_test.rb +140 -0
- data/test/orm_ext/sequel_test.rb +15 -26
- data/test/orm_ext/toystore_test.rb +103 -0
- data/test/test_helper.rb +35 -23
- metadata +99 -133
data/lib/tenacity/errors.rb
CHANGED
@@ -6,4 +6,12 @@ module Tenacity
|
|
6
6
|
# Raised on attempt to update an associate that is instantiated as read only.
|
7
7
|
class ReadOnlyError < TenacityError
|
8
8
|
end
|
9
|
+
|
10
|
+
# Raised when one of the objects specified in the relationship does not exist in the database
|
11
|
+
class ObjectDoesNotExistError < TenacityError
|
12
|
+
end
|
13
|
+
|
14
|
+
# Rasied when an attempt is made to delete an object whose id is in use by an association
|
15
|
+
class ObjectIdInUseError < TenacityError
|
16
|
+
end
|
9
17
|
end
|
@@ -6,26 +6,51 @@ module Tenacity
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def _t_save_autosave_associations
|
9
|
-
self.class._tenacity_associations
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
9
|
+
associations = self.class._tenacity_associations
|
10
|
+
self.class._tenacity_associations.select { |a| a.autosave == true }.each do |association|
|
11
|
+
if association.type == :t_has_one || association.type == :t_belongs_to
|
12
|
+
associate = instance_variable_get(_t_ivar_name(association))
|
13
|
+
autosave_save_or_destroy(associate) unless associate.nil?
|
14
|
+
elsif association.type == :t_has_many
|
15
|
+
associates = instance_variable_get(_t_ivar_name(association))
|
16
|
+
unless associates.nil?
|
17
|
+
associates.each { |associate| autosave_save_or_destroy(associate) }
|
18
|
+
instance_variable_set(_t_ivar_name(association), associates.reject { |associate| associate.marked_for_destruction? })
|
20
19
|
end
|
21
20
|
end
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
24
|
+
def _t_verify_associates_exist
|
25
|
+
associations_requiring_associate_validation.each do |association|
|
26
|
+
associate_id = self.send(association.foreign_key)
|
27
|
+
unless associate_id.nil?
|
28
|
+
associate_class = association.associate_class(self)
|
29
|
+
associate = associate_class._t_find(_t_serialize(associate_id, association))
|
30
|
+
raise ObjectDoesNotExistError.new("#{associate_class} object with an id of #{associate_id} does not exist!") if associate.nil?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
25
35
|
private
|
26
36
|
|
27
37
|
def autosave_save_or_destroy(associate)
|
28
|
-
associate.marked_for_destruction? ? associate
|
38
|
+
associate.marked_for_destruction? ? autosave_destroy(associate) : associate.save
|
39
|
+
end
|
40
|
+
|
41
|
+
def autosave_destroy(associate)
|
42
|
+
nullify_has_one_associations(associate)
|
43
|
+
associate.destroy
|
44
|
+
end
|
45
|
+
|
46
|
+
def nullify_has_one_associations(associate)
|
47
|
+
associate.class._tenacity_associations.select { |a| a.type == :t_has_one }.each do |association|
|
48
|
+
has_one_associate = associate.has_one_associate(association)
|
49
|
+
if has_one_associate
|
50
|
+
has_one_associate.send "#{association.foreign_key(associate.class)}=", nil
|
51
|
+
has_one_associate.save
|
52
|
+
end
|
53
|
+
end
|
29
54
|
end
|
30
55
|
|
31
56
|
def get_associate(association, params)
|
@@ -67,5 +92,10 @@ module Tenacity
|
|
67
92
|
self.class._t_serialize(object, association)
|
68
93
|
end
|
69
94
|
|
95
|
+
def associations_requiring_associate_validation
|
96
|
+
associations = self.class._tenacity_associations
|
97
|
+
associations.select { |a| a.foreign_key_constraints_enabled? && a.type == :t_belongs_to }
|
98
|
+
end
|
99
|
+
|
70
100
|
end
|
71
101
|
end
|
@@ -1,8 +1,7 @@
|
|
1
1
|
module Tenacity
|
2
2
|
module OrmExt
|
3
3
|
# Tenacity relationships on ActiveRecord objects require that certain columns
|
4
|
-
# exist on the associated table
|
5
|
-
# relationships. Take the following class for example:
|
4
|
+
# exist on the associated table. Take the following class for example:
|
6
5
|
#
|
7
6
|
# class Car < ActiveRecord::Base
|
8
7
|
# include Tenacity
|
@@ -31,15 +30,8 @@ module Tenacity
|
|
31
30
|
#
|
32
31
|
# == t_has_many
|
33
32
|
#
|
34
|
-
# The +t_has_many+ association requires
|
35
|
-
#
|
36
|
-
# The name of the join table in this example would be cars_wheels, since cars
|
37
|
-
# comes before wheels when shorted alphabetically.
|
38
|
-
#
|
39
|
-
# create_table :cars_wheels do |t|
|
40
|
-
# t.integer :car_id
|
41
|
-
# t.string :wheel_id
|
42
|
-
# end
|
33
|
+
# The +t_has_many+ association requires nothing special, as the associates
|
34
|
+
# are looked up using the associate class.
|
43
35
|
#
|
44
36
|
module ActiveRecord
|
45
37
|
|
@@ -77,17 +69,22 @@ module Tenacity
|
|
77
69
|
find(:all, :conditions => ["#{property} = ?", _t_serialize(id)])
|
78
70
|
end
|
79
71
|
|
72
|
+
def _t_find_all_ids_by_associate(property, id)
|
73
|
+
connection.select_values("SELECT id FROM #{table_name} WHERE #{property} = #{_t_serialize_id_for_sql(id)}")
|
74
|
+
end
|
75
|
+
|
80
76
|
def _t_initialize_has_one_association(association)
|
81
|
-
|
77
|
+
before_destroy { |record| record._t_cleanup_has_one_association(association) }
|
82
78
|
end
|
83
79
|
|
84
80
|
def _t_initialize_tenacity
|
81
|
+
before_save { |record| record._t_verify_associates_exist }
|
85
82
|
after_save { |record| record._t_save_autosave_associations }
|
86
83
|
end
|
87
84
|
|
88
85
|
def _t_initialize_has_many_association(association)
|
89
86
|
after_save { |record| record.class._t_save_associates(record, association) }
|
90
|
-
|
87
|
+
before_destroy { |record| record._t_cleanup_has_many_association(association) }
|
91
88
|
end
|
92
89
|
|
93
90
|
def _t_initialize_belongs_to_association(association)
|
@@ -108,25 +105,7 @@ module Tenacity
|
|
108
105
|
|
109
106
|
def _t_reload
|
110
107
|
reload
|
111
|
-
|
112
|
-
|
113
|
-
def _t_clear_associates(association)
|
114
|
-
self.connection.execute("delete from #{association.join_table} where #{association.association_key} = #{_t_serialize_id_for_sql(self.id)}")
|
115
|
-
end
|
116
|
-
|
117
|
-
def _t_associate_many(association, associate_ids)
|
118
|
-
self.transaction do
|
119
|
-
_t_clear_associates(association)
|
120
|
-
associate_ids.each do |associate_id|
|
121
|
-
self.connection.execute("insert into #{association.join_table} (#{association.association_key}, #{association.association_foreign_key}) values (#{_t_serialize_id_for_sql(self.id)}, #{_t_serialize_id_for_sql(associate_id)})")
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def _t_get_associate_ids(association)
|
127
|
-
return [] if self.id.nil?
|
128
|
-
rows = self.connection.execute("select #{association.association_foreign_key} from #{association.join_table} where #{association.association_key} = #{_t_serialize_id_for_sql(self.id)}")
|
129
|
-
ids = []; rows.each { |r| ids << r[0] }; ids
|
108
|
+
self
|
130
109
|
end
|
131
110
|
end
|
132
111
|
|
@@ -96,7 +96,13 @@ module Tenacity
|
|
96
96
|
self.send("by_#{property}", :key => _t_serialize(id))
|
97
97
|
end
|
98
98
|
|
99
|
+
def _t_find_all_ids_by_associate(property, id)
|
100
|
+
results = self.send("by_#{property}", :key => _t_serialize(id), :include_docs => false)
|
101
|
+
results['rows'].map { |r| r['id'] }
|
102
|
+
end
|
103
|
+
|
99
104
|
def _t_initialize_tenacity
|
105
|
+
before_save { |record| record._t_verify_associates_exist }
|
100
106
|
after_save { |record| record._t_save_autosave_associations }
|
101
107
|
end
|
102
108
|
|
@@ -105,12 +111,8 @@ module Tenacity
|
|
105
111
|
end
|
106
112
|
|
107
113
|
def _t_initialize_has_many_association(association)
|
108
|
-
|
109
|
-
|
110
|
-
view_by association.foreign_keys_property
|
111
|
-
after_save { |record| record.class._t_save_associates(record, association) if record.class.respond_to?(:_t_save_associates) }
|
112
|
-
after_destroy { |record| record._t_cleanup_has_many_association(association) }
|
113
|
-
end
|
114
|
+
after_save { |record| record.class._t_save_associates(record, association) }
|
115
|
+
before_destroy { |record| record._t_cleanup_has_many_association(association) }
|
114
116
|
end
|
115
117
|
|
116
118
|
def _t_initialize_belongs_to_association(association)
|
@@ -135,22 +137,12 @@ module Tenacity
|
|
135
137
|
|
136
138
|
module InstanceMethods #:nodoc:
|
137
139
|
def _t_reload
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
def _t_associate_many(association, associate_ids)
|
145
|
-
self.send(association.foreign_keys_property + '=', associate_ids)
|
146
|
-
end
|
147
|
-
|
148
|
-
def _t_get_associate_ids(association)
|
149
|
-
self.send(association.foreign_keys_property) || []
|
150
|
-
end
|
151
|
-
|
152
|
-
def _t_clear_associates(association)
|
153
|
-
self.send(association.foreign_keys_property + '=', [])
|
140
|
+
unless self.id.nil?
|
141
|
+
new_doc = database.get(self.id)
|
142
|
+
self.clear
|
143
|
+
new_doc.each { |k,v| self[k] = new_doc[k] }
|
144
|
+
end
|
145
|
+
self
|
154
146
|
end
|
155
147
|
end
|
156
148
|
|
@@ -1,8 +1,7 @@
|
|
1
1
|
module Tenacity
|
2
2
|
module OrmExt
|
3
3
|
# Tenacity relationships on DataMapper objects require that certain columns
|
4
|
-
# exist on the associated table
|
5
|
-
# relationships. Take the following class for example:
|
4
|
+
# exist on the associated table. Take the following class for example:
|
6
5
|
#
|
7
6
|
# class Car
|
8
7
|
# include DataMapper::Resource
|
@@ -31,15 +30,8 @@ module Tenacity
|
|
31
30
|
#
|
32
31
|
# == t_has_many
|
33
32
|
#
|
34
|
-
# The +t_has_many+ association requires
|
35
|
-
#
|
36
|
-
# The name of the join table in this example would be cars_wheels, since cars
|
37
|
-
# comes before wheels when shorted alphabetically.
|
38
|
-
#
|
39
|
-
# create_table :car_wheels do
|
40
|
-
# column :car_id, Integer
|
41
|
-
# column :wheel_id, String
|
42
|
-
# end
|
33
|
+
# The +t_has_many+ association requires nothing special, as the associates
|
34
|
+
# are looked up using the associate class.
|
43
35
|
#
|
44
36
|
module DataMapper
|
45
37
|
|
@@ -77,14 +69,22 @@ module Tenacity
|
|
77
69
|
all(property => _t_serialize(id))
|
78
70
|
end
|
79
71
|
|
72
|
+
def _t_find_all_ids_by_associate(property, id)
|
73
|
+
repository.adapter.select("SELECT id from #{storage_names[:default]} WHERE #{property} = #{_t_serialize_id_for_sql(id)}")
|
74
|
+
end
|
75
|
+
|
80
76
|
def _t_initialize_tenacity
|
77
|
+
before :save do |record|
|
78
|
+
record._t_verify_associates_exist
|
79
|
+
end
|
80
|
+
|
81
81
|
after :save do |record|
|
82
82
|
record._t_save_autosave_associations
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
86
|
def _t_initialize_has_one_association(association)
|
87
|
-
|
87
|
+
before :destroy do |record|
|
88
88
|
record._t_cleanup_has_one_association(association)
|
89
89
|
end
|
90
90
|
end
|
@@ -94,7 +94,7 @@ module Tenacity
|
|
94
94
|
record.class._t_save_associates(record, association)
|
95
95
|
end
|
96
96
|
|
97
|
-
|
97
|
+
before :destroy do |record|
|
98
98
|
record._t_cleanup_has_many_association(association)
|
99
99
|
end
|
100
100
|
end
|
@@ -131,24 +131,7 @@ module Tenacity
|
|
131
131
|
|
132
132
|
def _t_reload
|
133
133
|
reload
|
134
|
-
|
135
|
-
|
136
|
-
def _t_clear_associates(association)
|
137
|
-
self.repository.adapter.execute("delete from #{association.join_table} where #{association.association_key} = #{_t_serialize_id_for_sql(self.id)}")
|
138
|
-
end
|
139
|
-
|
140
|
-
def _t_associate_many(association, associate_ids)
|
141
|
-
self.transaction do
|
142
|
-
_t_clear_associates(association)
|
143
|
-
associate_ids.each do |associate_id|
|
144
|
-
self.repository.adapter.execute("insert into #{association.join_table} (#{association.association_key}, #{association.association_foreign_key}) values (#{_t_serialize_id_for_sql(self.id)}, #{_t_serialize_id_for_sql(associate_id)})")
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def _t_get_associate_ids(association)
|
150
|
-
return [] if self.id.nil?
|
151
|
-
self.repository.adapter.select("select #{association.association_foreign_key} from #{association.join_table} where #{association.association_key} = #{_t_serialize_id_for_sql(self.id)}")
|
134
|
+
self.class._t_find(self.id)
|
152
135
|
end
|
153
136
|
|
154
137
|
private
|
@@ -12,11 +12,11 @@ module Tenacity
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
def _t_serialize_ids(ids)
|
15
|
+
def _t_serialize_ids(ids, association=nil)
|
16
16
|
if ids.respond_to?(:map)
|
17
|
-
ids.map { |id| _t_serialize(id) }
|
17
|
+
ids.map { |id| _t_serialize(id, association) }
|
18
18
|
else
|
19
|
-
_t_serialize(ids)
|
19
|
+
_t_serialize(ids, association)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
@@ -4,7 +4,7 @@ module Tenacity
|
|
4
4
|
# defined on the object. Tenacity will define the keys that it needs
|
5
5
|
# to support the relationships. Take the following class for example:
|
6
6
|
#
|
7
|
-
# class Car
|
7
|
+
# class Car
|
8
8
|
# include MongoMapper::Document
|
9
9
|
# include Tenacity
|
10
10
|
#
|
@@ -65,20 +65,23 @@ module Tenacity
|
|
65
65
|
all(property => _t_serialize(id))
|
66
66
|
end
|
67
67
|
|
68
|
+
def _t_find_all_ids_by_associate(property, id)
|
69
|
+
results = collection.find({property => _t_serialize(id)}, {:fields => 'id'}).to_a
|
70
|
+
results.map { |r| r['_id'] }
|
71
|
+
end
|
72
|
+
|
68
73
|
def _t_initialize_tenacity
|
74
|
+
before_save { |record| record._t_verify_associates_exist }
|
69
75
|
after_save { |record| record._t_save_autosave_associations }
|
70
76
|
end
|
71
77
|
|
72
78
|
def _t_initialize_has_one_association(association)
|
73
|
-
|
79
|
+
before_destroy { |record| record._t_cleanup_has_one_association(association) }
|
74
80
|
end
|
75
81
|
|
76
82
|
def _t_initialize_has_many_association(association)
|
77
|
-
|
78
|
-
|
79
|
-
after_save { |record| record.class._t_save_associates(record, association) }
|
80
|
-
after_destroy { |record| record._t_cleanup_has_many_association(association) }
|
81
|
-
end
|
83
|
+
after_save { |record| record.class._t_save_associates(record, association) }
|
84
|
+
before_destroy { |record| record._t_cleanup_has_many_association(association) }
|
82
85
|
end
|
83
86
|
|
84
87
|
def _t_initialize_belongs_to_association(association)
|
@@ -100,21 +103,12 @@ module Tenacity
|
|
100
103
|
|
101
104
|
module InstanceMethods #:nodoc:
|
102
105
|
def _t_reload
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
self.send(association.foreign_keys_property + '=', associate_ids)
|
110
|
-
end
|
111
|
-
|
112
|
-
def _t_get_associate_ids(association)
|
113
|
-
self.send(association.foreign_keys_property)
|
114
|
-
end
|
115
|
-
|
116
|
-
def _t_clear_associates(association)
|
117
|
-
self.send(association.foreign_keys_property + '=', [])
|
106
|
+
begin
|
107
|
+
reload
|
108
|
+
rescue ::MongoMapper::DocumentNotFound
|
109
|
+
# Ignore
|
110
|
+
end
|
111
|
+
self
|
118
112
|
end
|
119
113
|
end
|
120
114
|
|
@@ -70,20 +70,23 @@ module Tenacity
|
|
70
70
|
find(:all, :conditions => { property => _t_serialize(id) })
|
71
71
|
end
|
72
72
|
|
73
|
+
def _t_find_all_ids_by_associate(property, id)
|
74
|
+
results = collection.find({property => _t_serialize(id)}, {:fields => 'id'}).to_a
|
75
|
+
results.map { |r| r['_id'] }
|
76
|
+
end
|
77
|
+
|
73
78
|
def _t_initialize_tenacity
|
79
|
+
before_save { |record| record._t_verify_associates_exist }
|
74
80
|
after_save { |record| record._t_save_autosave_associations }
|
75
81
|
end
|
76
82
|
|
77
83
|
def _t_initialize_has_one_association(association)
|
78
|
-
|
84
|
+
before_destroy { |record| record._t_cleanup_has_one_association(association) }
|
79
85
|
end
|
80
86
|
|
81
87
|
def _t_initialize_has_many_association(association)
|
82
|
-
|
83
|
-
|
84
|
-
after_save { |record| self.class._t_save_associates(record, association) }
|
85
|
-
after_destroy { |record| record._t_cleanup_has_many_association(association) }
|
86
|
-
end
|
88
|
+
after_save { |record| self.class._t_save_associates(record, association) }
|
89
|
+
before_destroy { |record| record._t_cleanup_has_many_association(association) }
|
87
90
|
end
|
88
91
|
|
89
92
|
def _t_initialize_belongs_to_association(association)
|
@@ -107,18 +110,7 @@ module Tenacity
|
|
107
110
|
module InstanceMethods #:nodoc:
|
108
111
|
def _t_reload
|
109
112
|
reload
|
110
|
-
|
111
|
-
|
112
|
-
def _t_associate_many(association, associate_ids)
|
113
|
-
self.send(association.foreign_keys_property + '=', associate_ids)
|
114
|
-
end
|
115
|
-
|
116
|
-
def _t_get_associate_ids(association)
|
117
|
-
self.send(association.foreign_keys_property)
|
118
|
-
end
|
119
|
-
|
120
|
-
def _t_clear_associates(association)
|
121
|
-
self.send(association.foreign_keys_property + '=', [])
|
113
|
+
self
|
122
114
|
end
|
123
115
|
end
|
124
116
|
|
@@ -0,0 +1,270 @@
|
|
1
|
+
module Tenacity
|
2
|
+
module OrmExt
|
3
|
+
# Tenacity relationships on Ripple objects require no special properties
|
4
|
+
# defined on the object. Tenacity will define the properties that it needs
|
5
|
+
# to support the relationships. Take the following class for example:
|
6
|
+
#
|
7
|
+
# class Car
|
8
|
+
# include Ripple::Document
|
9
|
+
# include Tenacity
|
10
|
+
#
|
11
|
+
# t_has_many :wheels
|
12
|
+
# t_has_one :dashboard
|
13
|
+
# t_belongs_to :driver
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# == t_belongs_to
|
17
|
+
#
|
18
|
+
# The +t_belongs_to+ association will define a property named after the association.
|
19
|
+
# The example above will create a property named <tt>:driver_id</tt> The +t_belongs_to+
|
20
|
+
# relationship will also create a bucket in Riak that acts as an index to find
|
21
|
+
# objects by their foreign key. The bucket will be named after the Ripple class
|
22
|
+
# and the name of the property used to store the foreign key. In the above example,
|
23
|
+
# the bucket will be named tenacity_car_driver_id.
|
24
|
+
#
|
25
|
+
#
|
26
|
+
# == t_has_one
|
27
|
+
#
|
28
|
+
# The +t_has_one+ association will not define any new properties on the object, since
|
29
|
+
# the associated object holds the foreign key.
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# == t_has_many
|
33
|
+
#
|
34
|
+
# The +t_has_many+ association will define a property named after the association.
|
35
|
+
# The example above will create a property named <tt>:wheels_ids</tt>
|
36
|
+
#
|
37
|
+
module Ripple
|
38
|
+
|
39
|
+
def self.setup(model) #:nodoc:
|
40
|
+
require 'ripple'
|
41
|
+
if model.included_modules.include?(::Ripple::Document)
|
42
|
+
model.send :include, Ripple::InstanceMethods
|
43
|
+
model.extend Ripple::ClassMethods
|
44
|
+
end
|
45
|
+
rescue LoadError
|
46
|
+
# Ripple not available
|
47
|
+
end
|
48
|
+
|
49
|
+
module ClassMethods #:nodoc:
|
50
|
+
include Tenacity::OrmExt::Helpers
|
51
|
+
|
52
|
+
attr_accessor :_t_has_one_associations
|
53
|
+
attr_accessor :_t_has_many_associations
|
54
|
+
attr_accessor :_t_belongs_to_associations
|
55
|
+
|
56
|
+
def _t_id_type
|
57
|
+
String
|
58
|
+
end
|
59
|
+
|
60
|
+
def _t_find(id)
|
61
|
+
find(_t_serialize(id))
|
62
|
+
end
|
63
|
+
|
64
|
+
def _t_find_bulk(ids)
|
65
|
+
objects = find(_t_serialize_ids(ids)) || []
|
66
|
+
objects = [objects] unless objects.respond_to?(:each)
|
67
|
+
objects.reject(&:nil?)
|
68
|
+
end
|
69
|
+
|
70
|
+
def _t_find_first_by_associate(property, id)
|
71
|
+
bucket = ::Ripple.client.bucket(_t_bucket_name(property))
|
72
|
+
if bucket.exist?(id)
|
73
|
+
object = bucket.get(id)
|
74
|
+
find(object.data.first)
|
75
|
+
else
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def _t_find_all_by_associate(property, id)
|
81
|
+
find(_t_find_all_ids_by_associate(property, id)) || []
|
82
|
+
end
|
83
|
+
|
84
|
+
def _t_find_all_ids_by_associate(property, id)
|
85
|
+
bucket = ::Ripple.client.bucket(_t_bucket_name(property))
|
86
|
+
if bucket.exist?(id)
|
87
|
+
object = bucket.get(id)
|
88
|
+
object.data || []
|
89
|
+
else
|
90
|
+
[]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def _t_initialize_tenacity
|
95
|
+
end
|
96
|
+
|
97
|
+
def _t_initialize_has_one_association(association)
|
98
|
+
@_t_has_one_associations ||= []
|
99
|
+
@_t_has_one_associations << association
|
100
|
+
end
|
101
|
+
|
102
|
+
def _t_initialize_has_many_association(association)
|
103
|
+
@_t_has_many_associations ||= []
|
104
|
+
@_t_has_many_associations << association
|
105
|
+
end
|
106
|
+
|
107
|
+
def _t_initialize_belongs_to_association(association)
|
108
|
+
@_t_belongs_to_associations ||= []
|
109
|
+
@_t_belongs_to_associations << association
|
110
|
+
|
111
|
+
property association.foreign_key, id_class_for(association)
|
112
|
+
property association.polymorphic_type, String if association.polymorphic?
|
113
|
+
end
|
114
|
+
|
115
|
+
def _t_delete(ids, run_callbacks=true)
|
116
|
+
docs = _t_find_bulk(ids)
|
117
|
+
if run_callbacks
|
118
|
+
docs.each { |doc| doc.destroy }
|
119
|
+
else
|
120
|
+
docs.each { |doc| doc.delete }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def _t_bucket_name(property_name)
|
127
|
+
prefix = ENV['TENACITY_TEST'] == 'true' ? 'tenacity_test' : 'tenacity'
|
128
|
+
"#{prefix}_#{ActiveSupport::Inflector.underscore(self.name)}_#{property_name.to_s}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
module InstanceMethods #:nodoc:
|
133
|
+
def id
|
134
|
+
key
|
135
|
+
end
|
136
|
+
|
137
|
+
def _t_reload
|
138
|
+
reload
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
142
|
+
def save
|
143
|
+
before_save
|
144
|
+
super
|
145
|
+
after_save
|
146
|
+
end
|
147
|
+
|
148
|
+
def destroy(run_callbacks=true)
|
149
|
+
before_destroy if run_callbacks
|
150
|
+
super()
|
151
|
+
after_destroy if run_callbacks
|
152
|
+
end
|
153
|
+
|
154
|
+
def delete
|
155
|
+
destroy(false)
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def before_save
|
161
|
+
_t_verify_associates_exist
|
162
|
+
remember_old_associate_ids
|
163
|
+
end
|
164
|
+
|
165
|
+
def after_save
|
166
|
+
update_associate_indexes
|
167
|
+
_t_save_autosave_associations
|
168
|
+
|
169
|
+
associations = self.class._t_has_many_associations || []
|
170
|
+
associations.each { |association| self.class._t_save_associates(self, association) }
|
171
|
+
end
|
172
|
+
|
173
|
+
def before_destroy
|
174
|
+
associations = self.class._t_has_one_associations || []
|
175
|
+
associations.each { |association| self._t_cleanup_has_one_association(association) }
|
176
|
+
|
177
|
+
associations = self.class._t_has_many_associations || []
|
178
|
+
associations.each { |association| self._t_cleanup_has_many_association(association) }
|
179
|
+
end
|
180
|
+
|
181
|
+
def after_destroy
|
182
|
+
delete_associate_indexes
|
183
|
+
|
184
|
+
associations = self.class._t_belongs_to_associations || []
|
185
|
+
associations.each { |association| self._t_cleanup_belongs_to_association(association) }
|
186
|
+
end
|
187
|
+
|
188
|
+
def remember_old_associate_ids
|
189
|
+
@old_associate_ids = {}
|
190
|
+
|
191
|
+
unless self.id.nil?
|
192
|
+
old_instance = self.class._t_find(self.id)
|
193
|
+
associations = self.class._t_belongs_to_associations || []
|
194
|
+
associations.each do |association|
|
195
|
+
@old_associate_ids[association.name] = old_instance.send(association.foreign_key)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def update_associate_indexes
|
201
|
+
manage_associate_indexes(:update)
|
202
|
+
end
|
203
|
+
|
204
|
+
def delete_associate_indexes
|
205
|
+
manage_associate_indexes(:delete)
|
206
|
+
end
|
207
|
+
|
208
|
+
def manage_associate_indexes(operation)
|
209
|
+
associations = self.class._t_belongs_to_associations || []
|
210
|
+
associations.each do |association|
|
211
|
+
associate_id = self.send(association.foreign_key)
|
212
|
+
if operation == :update
|
213
|
+
update_associate_index(association, associate_id)
|
214
|
+
else
|
215
|
+
delete_associate_index(association, associate_id)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def update_associate_index(association, associate_id)
|
221
|
+
bucket = get_bucket_for_association(association)
|
222
|
+
if !@old_associate_ids[association.name].nil? && associate_id.nil?
|
223
|
+
clear_associate_index_of_nilified_id(association, bucket)
|
224
|
+
else
|
225
|
+
store_id_in_associate_index(associate_id, bucket)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def store_id_in_associate_index(associate_id, bucket)
|
230
|
+
unless associate_id.nil?
|
231
|
+
if bucket.exist?(associate_id)
|
232
|
+
object = bucket.get(associate_id)
|
233
|
+
object.data << self.id unless object.data.include?(self.id)
|
234
|
+
else
|
235
|
+
object = bucket.new(associate_id)
|
236
|
+
object.data = [self.id]
|
237
|
+
end
|
238
|
+
object.store
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def clear_associate_index_of_nilified_id(association, bucket)
|
243
|
+
if bucket.exist?(@old_associate_ids[association.name])
|
244
|
+
object = bucket.get(@old_associate_ids[association.name])
|
245
|
+
object.data.delete(self.id)
|
246
|
+
object.store
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def delete_associate_index(association, associate_id)
|
251
|
+
unless associate_id.nil?
|
252
|
+
bucket = get_bucket_for_association(association)
|
253
|
+
if bucket.exist?(associate_id)
|
254
|
+
object = bucket.get(associate_id)
|
255
|
+
object.data.delete(self.id)
|
256
|
+
object.store
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def get_bucket_for_association(association)
|
262
|
+
::Ripple.client.bucket(self.class.send(:_t_bucket_name, association.foreign_key))
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
|