tenacity 0.4.1 → 0.5.0

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