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.
Files changed (71) hide show
  1. data/.gitignore +1 -0
  2. data/EXTEND.rdoc +18 -21
  3. data/Gemfile +0 -2
  4. data/README.rdoc +3 -1
  5. data/Rakefile +7 -0
  6. data/history.txt +17 -0
  7. data/lib/tenacity.rb +4 -0
  8. data/lib/tenacity/associates_proxy.rb +5 -7
  9. data/lib/tenacity/association.rb +9 -47
  10. data/lib/tenacity/associations/belongs_to.rb +1 -2
  11. data/lib/tenacity/associations/has_many.rb +27 -21
  12. data/lib/tenacity/associations/has_one.rb +3 -2
  13. data/lib/tenacity/class_methods.rb +14 -60
  14. data/lib/tenacity/errors.rb +8 -0
  15. data/lib/tenacity/instance_methods.rb +42 -12
  16. data/lib/tenacity/orm_ext/activerecord.rb +11 -32
  17. data/lib/tenacity/orm_ext/couchrest.rb +14 -22
  18. data/lib/tenacity/orm_ext/datamapper.rb +14 -31
  19. data/lib/tenacity/orm_ext/helpers.rb +3 -3
  20. data/lib/tenacity/orm_ext/mongo_mapper.rb +16 -22
  21. data/lib/tenacity/orm_ext/mongoid.rb +10 -18
  22. data/lib/tenacity/orm_ext/ripple.rb +270 -0
  23. data/lib/tenacity/orm_ext/sequel.rb +21 -33
  24. data/lib/tenacity/orm_ext/toystore.rb +114 -0
  25. data/lib/tenacity/version.rb +1 -1
  26. data/tenacity.gemspec +10 -3
  27. data/test/association_features/belongs_to_test.rb +12 -0
  28. data/test/association_features/has_many_test.rb +32 -2
  29. data/test/association_features/has_one_test.rb +18 -4
  30. data/test/associations/has_one_test.rb +0 -1
  31. data/test/core/classmethods_test.rb +7 -0
  32. data/test/fixtures/active_record_has_many_target.rb +4 -0
  33. data/test/fixtures/active_record_has_one_target.rb +4 -0
  34. data/test/fixtures/active_record_object.rb +8 -0
  35. data/test/fixtures/couch_rest_has_many_target.rb +4 -0
  36. data/test/fixtures/couch_rest_has_one_target.rb +4 -0
  37. data/test/fixtures/couch_rest_object.rb +8 -0
  38. data/test/fixtures/data_mapper_has_many_target.rb +10 -0
  39. data/test/fixtures/data_mapper_has_one_target.rb +10 -0
  40. data/test/fixtures/data_mapper_object.rb +8 -0
  41. data/test/fixtures/mongo_mapper_has_many_target.rb +4 -0
  42. data/test/fixtures/mongo_mapper_has_one_target.rb +4 -0
  43. data/test/fixtures/mongo_mapper_object.rb +8 -0
  44. data/test/fixtures/mongoid_has_many_target.rb +4 -0
  45. data/test/fixtures/mongoid_has_one_target.rb +4 -0
  46. data/test/fixtures/mongoid_object.rb +8 -0
  47. data/test/fixtures/no_associations.rb +4 -0
  48. data/test/fixtures/ripple_has_many_target.rb +24 -0
  49. data/test/fixtures/ripple_has_one_target.rb +24 -0
  50. data/test/fixtures/ripple_object.rb +42 -0
  51. data/test/fixtures/sequel_has_many_target.rb +4 -0
  52. data/test/fixtures/sequel_has_one_target.rb +4 -0
  53. data/test/fixtures/sequel_object.rb +8 -0
  54. data/test/fixtures/toystore_has_many_target.rb +28 -0
  55. data/test/fixtures/toystore_has_one_target.rb +28 -0
  56. data/test/fixtures/toystore_object.rb +46 -0
  57. data/test/helpers/active_record_test_helper.rb +12 -95
  58. data/test/helpers/data_mapper_test_helper.rb +0 -64
  59. data/test/helpers/ripple_test_helper.rb +19 -0
  60. data/test/helpers/sequel_test_helper.rb +13 -60
  61. data/test/helpers/toystore_test_helper.rb +17 -0
  62. data/test/orm_ext/activerecord_test.rb +16 -26
  63. data/test/orm_ext/couchrest_test.rb +10 -29
  64. data/test/orm_ext/datamapper_test.rb +16 -29
  65. data/test/orm_ext/mongo_mapper_test.rb +11 -29
  66. data/test/orm_ext/mongoid_test.rb +11 -29
  67. data/test/orm_ext/ripple_test.rb +140 -0
  68. data/test/orm_ext/sequel_test.rb +15 -26
  69. data/test/orm_ext/toystore_test.rb +103 -0
  70. data/test/test_helper.rb +35 -23
  71. metadata +99 -133
@@ -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.each do |association|
10
- if association.autosave == true
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? })
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.destroy : associate.save
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, and that join tables exist for one-to-many
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 that a join table exist to store the
35
- # associations. The name of the join table follows ActiveRecord conventions.
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
- after_destroy { |record| record._t_cleanup_has_one_association(association) }
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
- after_destroy { |record| record._t_cleanup_has_many_association(association) }
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
- end
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
- unless self.respond_to?(association.foreign_keys_property)
109
- property association.foreign_keys_property, :type => [id_class_for(association)]
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
- return if self.id.nil?
139
- new_doc = database.get(self.id)
140
- self.clear
141
- new_doc.each { |k,v| self[k] = new_doc[k] }
142
- end
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, and that join tables exist for one-to-many
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 that a join table exist to store the
35
- # associations. The name of the join table follows ActiveRecord conventions.
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
- after :destroy do |record|
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
- after :destroy do |record|
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
- end
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 < ActiveRecord::Base
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
- after_destroy { |record| record._t_cleanup_has_one_association(association) }
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
- unless self.respond_to?(association.foreign_keys_property)
78
- key association.foreign_keys_property, Array
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
- reload
104
- rescue ::MongoMapper::DocumentNotFound
105
- nil
106
- end
107
-
108
- def _t_associate_many(association, associate_ids)
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
- after_destroy { |record| record._t_cleanup_has_one_association(association) }
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
- unless self.respond_to?(association.foreign_keys_property)
83
- field association.foreign_keys_property, :type => Array
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
- end
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
+