tenacity 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|