tenacity 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/EXTEND.rdoc +9 -13
  2. data/Rakefile +6 -0
  3. data/history.txt +17 -0
  4. data/lib/tenacity.rb +13 -9
  5. data/lib/tenacity/associate_proxy.rb +56 -0
  6. data/lib/tenacity/associates_proxy.rb +5 -3
  7. data/lib/tenacity/association.rb +81 -9
  8. data/lib/tenacity/associations/belongs_to.rb +28 -16
  9. data/lib/tenacity/associations/has_many.rb +113 -53
  10. data/lib/tenacity/associations/has_one.rb +33 -14
  11. data/lib/tenacity/class_methods.rb +117 -9
  12. data/lib/tenacity/errors.rb +9 -0
  13. data/lib/tenacity/instance_methods.rb +40 -3
  14. data/lib/tenacity/orm_ext/activerecord.rb +114 -95
  15. data/lib/tenacity/orm_ext/couchrest.rb +132 -113
  16. data/lib/tenacity/orm_ext/datamapper.rb +138 -112
  17. data/lib/tenacity/orm_ext/helpers.rb +36 -0
  18. data/lib/tenacity/orm_ext/mongo_mapper.rb +102 -84
  19. data/lib/tenacity/orm_ext/mongoid.rb +106 -87
  20. data/lib/tenacity/orm_ext/sequel.rb +137 -110
  21. data/lib/tenacity/version.rb +1 -1
  22. data/tenacity.gemspec +2 -1
  23. data/test/association_features/belongs_to_test.rb +46 -1
  24. data/test/association_features/has_many_test.rb +187 -2
  25. data/test/association_features/has_one_test.rb +57 -2
  26. data/test/associations/belongs_to_test.rb +41 -7
  27. data/test/associations/has_many_test.rb +59 -10
  28. data/test/associations/has_one_test.rb +57 -2
  29. data/test/fixtures/active_record_car.rb +5 -4
  30. data/test/fixtures/active_record_climate_control_unit.rb +1 -0
  31. data/test/fixtures/active_record_engine.rb +1 -0
  32. data/test/fixtures/active_record_has_many_target.rb +7 -0
  33. data/test/fixtures/active_record_has_one_target.rb +7 -0
  34. data/test/fixtures/active_record_object.rb +19 -0
  35. data/test/fixtures/couch_rest_has_many_target.rb +7 -0
  36. data/test/fixtures/couch_rest_has_one_target.rb +7 -0
  37. data/test/fixtures/couch_rest_object.rb +14 -0
  38. data/test/fixtures/data_mapper_has_many_target.rb +23 -3
  39. data/test/fixtures/data_mapper_has_one_target.rb +23 -3
  40. data/test/fixtures/data_mapper_object.rb +14 -0
  41. data/test/fixtures/mongo_mapper_air_filter.rb +6 -0
  42. data/test/fixtures/mongo_mapper_alternator.rb +7 -0
  43. data/test/fixtures/mongo_mapper_autosave_false_has_many_target.rb +8 -0
  44. data/test/fixtures/mongo_mapper_autosave_false_has_one_target.rb +8 -0
  45. data/test/fixtures/mongo_mapper_autosave_true_has_many_target.rb +8 -0
  46. data/test/fixtures/mongo_mapper_autosave_true_has_one_target.rb +8 -0
  47. data/test/fixtures/mongo_mapper_circuit_board.rb +6 -0
  48. data/test/fixtures/mongo_mapper_dashboard.rb +5 -2
  49. data/test/fixtures/mongo_mapper_has_many_target.rb +7 -0
  50. data/test/fixtures/mongo_mapper_has_one_target.rb +7 -0
  51. data/test/fixtures/mongo_mapper_object.rb +14 -0
  52. data/test/fixtures/mongo_mapper_wheel.rb +2 -0
  53. data/test/fixtures/mongo_mapper_windows.rb +6 -0
  54. data/test/fixtures/mongoid_has_many_target.rb +7 -0
  55. data/test/fixtures/mongoid_has_one_target.rb +7 -0
  56. data/test/fixtures/mongoid_object.rb +14 -0
  57. data/test/fixtures/sequel_has_many_target.rb +7 -0
  58. data/test/fixtures/sequel_has_one_target.rb +7 -0
  59. data/test/fixtures/sequel_object.rb +14 -0
  60. data/test/helpers/active_record_test_helper.rb +87 -8
  61. data/test/helpers/couch_rest_test_helper.rb +7 -0
  62. data/test/helpers/data_mapper_test_helper.rb +41 -3
  63. data/test/helpers/sequel_test_helper.rb +65 -9
  64. data/test/orm_ext/activerecord_test.rb +1 -1
  65. data/test/orm_ext/datamapper_test.rb +33 -24
  66. data/test/orm_ext/mongo_mapper_test.rb +6 -6
  67. data/test/orm_ext/mongoid_test.rb +6 -6
  68. data/test/orm_ext/sequel_test.rb +1 -1
  69. data/test/test_helper.rb +18 -9
  70. metadata +119 -55
@@ -1,140 +1,159 @@
1
1
  module Tenacity
2
- # Tenacity relationships on CouchRest objects require no special keys
3
- # defined on the object. Tenacity will define the keys that it needs
4
- # to support the relationships. Take the following class for example:
5
- #
6
- # class Car < CouchRest::ExtendedDocument
7
- # include Tenacity
8
- #
9
- # t_has_many :wheels
10
- # t_has_one :dashboard
11
- # t_belongs_to :driver
12
- # end
13
- #
14
- # == t_belongs_to
15
- #
16
- # The +t_belongs_to+ association will define a property named after the association.
17
- # The example above will create a property named <tt>:driver_id</tt>
18
- #
19
- #
20
- # == t_has_one
21
- #
22
- # The +t_has_one+ association will not define any new properties on the object, since
23
- # the associated object holds the foreign key.
24
- #
25
- #
26
- # == t_has_many
27
- #
28
- # The +t_has_many+ association will define a property named after the association.
29
- # The example above will create a property named <tt>:wheels_ids</tt>
30
- #
31
- module CouchRest
32
-
33
- def self.setup(model) #:nodoc:
34
- begin
35
- require 'couchrest_model'
36
- if model.ancestors.include?(::CouchRest::Model::Base)
37
- model.send :include, CouchRest::InstanceMethods
38
- model.extend CouchRest::ClassMethods
39
- end
40
- rescue LoadError
41
- # CouchRest::Model not available
42
- end
2
+ module OrmExt
3
+ # Tenacity relationships on CouchRest objects require no special keys
4
+ # defined on the object. Tenacity will define the keys that it needs
5
+ # to support the relationships. Take the following class for example:
6
+ #
7
+ # class Car < CouchRest::ExtendedDocument
8
+ # include Tenacity
9
+ #
10
+ # t_has_many :wheels
11
+ # t_has_one :dashboard
12
+ # t_belongs_to :driver
13
+ # end
14
+ #
15
+ # == t_belongs_to
16
+ #
17
+ # The +t_belongs_to+ association will define a property named after the association.
18
+ # The example above will create a property named <tt>:driver_id</tt>
19
+ #
20
+ #
21
+ # == t_has_one
22
+ #
23
+ # The +t_has_one+ association will not define any new properties on the object, since
24
+ # the associated object holds the foreign key.
25
+ #
26
+ #
27
+ # == t_has_many
28
+ #
29
+ # The +t_has_many+ association will define a property named after the association.
30
+ # The example above will create a property named <tt>:wheels_ids</tt>
31
+ #
32
+ module CouchRest
33
+
34
+ def self.setup(model) #:nodoc:
35
+ begin
36
+ require 'couchrest_model'
37
+ if model.ancestors.include?(::CouchRest::Model::Base)
38
+ model.send :include, CouchRest::InstanceMethods
39
+ model.extend CouchRest::ClassMethods
40
+ end
41
+ rescue LoadError
42
+ # CouchRest::Model not available
43
+ end
43
44
 
44
- begin
45
- require 'couchrest_extended_document'
46
- if model.ancestors.include?(::CouchRest::ExtendedDocument)
47
- model.send :include, CouchRest::InstanceMethods
48
- model.extend CouchRest::ClassMethods
45
+ begin
46
+ require 'couchrest_extended_document'
47
+ if model.ancestors.include?(::CouchRest::ExtendedDocument)
48
+ model.send :include, CouchRest::InstanceMethods
49
+ model.extend CouchRest::ClassMethods
50
+ end
51
+ rescue LoadError
52
+ # CouchRest::ExtendedDocument not available
49
53
  end
50
- rescue LoadError
51
- # CouchRest::ExtendedDocument not available
52
- end
53
54
 
54
- # For pre 1.0 versions of couchrest
55
- begin
56
- require 'couchrest'
57
- if model.ancestors.include?(::CouchRest::ExtendedDocument)
58
- model.send :include, CouchRest::InstanceMethods
59
- model.extend CouchRest::ClassMethods
55
+ # For pre 1.0 versions of couchrest
56
+ begin
57
+ require 'couchrest'
58
+ if model.ancestors.include?(::CouchRest::ExtendedDocument)
59
+ model.send :include, CouchRest::InstanceMethods
60
+ model.extend CouchRest::ClassMethods
61
+ end
62
+ rescue LoadError
63
+ rescue NameError
64
+ # CouchRest::ExtendedDocument not available
60
65
  end
61
- rescue LoadError
62
- rescue NameError
63
- # CouchRest::ExtendedDocument not available
64
66
  end
65
- end
66
67
 
67
- module ClassMethods #:nodoc:
68
- def _t_find(id)
69
- (id.nil? || id.strip == "") ? nil : get(id)
70
- end
68
+ module ClassMethods #:nodoc:
69
+ include Tenacity::OrmExt::Helpers
71
70
 
72
- def _t_find_bulk(ids)
73
- return [] if ids.nil? || ids.empty?
71
+ def _t_id_type
72
+ String
73
+ end
74
74
 
75
- docs = []
76
- result = database.get_bulk ids
77
- result['rows'].each do |row|
78
- docs << (row['doc'].nil? ? nil : create_from_database(row['doc']))
75
+ def _t_find(id)
76
+ (id.nil? || id.strip == "") ? nil : get(_t_serialize(id))
79
77
  end
80
- docs.reject { |doc| doc.nil? }
81
- end
82
78
 
83
- def _t_find_first_by_associate(property, id)
84
- self.send("by_#{property}", :key => id.to_s).first
85
- end
79
+ def _t_find_bulk(ids)
80
+ return [] if ids.nil? || ids.empty?
81
+ ids = [ids] unless ids.class == Array
86
82
 
87
- def _t_find_all_by_associate(property, id)
88
- self.send("by_#{property}", :key => id.to_s)
89
- end
83
+ docs = []
84
+ result = database.get_bulk(_t_serialize_ids(ids))
85
+ result['rows'].each do |row|
86
+ docs << (row['doc'].nil? ? nil : create_from_database(row['doc']))
87
+ end
88
+ docs.reject { |doc| doc.nil? }
89
+ end
90
90
 
91
- def _t_initialize_has_many_association(association)
92
- unless self.respond_to?(association.foreign_keys_property)
93
- property association.foreign_keys_property, :type => [String]
94
- view_by association.foreign_keys_property
95
- after_save { |record| record.class._t_save_associates(record, association) if record.class.respond_to?(:_t_save_associates) }
91
+ def _t_find_first_by_associate(property, id)
92
+ self.send("by_#{property}", :key => _t_serialize(id)).first
96
93
  end
97
- end
98
94
 
99
- def _t_initialize_belongs_to_association(association)
100
- property_name = association.foreign_key
101
- unless self.respond_to?(property_name)
102
- property property_name, :type => String
103
- view_by property_name
104
- before_save { |record| _t_stringify_belongs_to_value(record, association) if self.respond_to?(:_t_stringify_belongs_to_value) }
95
+ def _t_find_all_by_associate(property, id)
96
+ self.send("by_#{property}", :key => _t_serialize(id))
105
97
  end
106
- end
107
98
 
108
- def _t_delete(ids, run_callbacks=true)
109
- docs = _t_find_bulk(ids)
110
- if run_callbacks
111
- docs.each { |doc| doc.destroy }
112
- else
113
- docs.each { |doc| database.delete_doc(doc) }
99
+ def _t_initialize_tenacity
100
+ after_save { |record| record._t_save_autosave_associations }
114
101
  end
115
- end
116
- end
117
102
 
118
- module InstanceMethods #:nodoc:
119
- def _t_reload
120
- return if self.id.nil?
121
- new_doc = database.get(self.id)
122
- self.clear
123
- new_doc.each { |k,v| self[k] = new_doc[k] }
124
- end
103
+ def _t_initialize_has_one_association(association)
104
+ before_destroy { |record| record._t_cleanup_has_one_association(association) }
105
+ end
125
106
 
126
- def _t_associate_many(association, associate_ids)
127
- self.send(association.foreign_keys_property + '=', associate_ids.map { |associate_id| associate_id.to_s })
128
- end
107
+ 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
+ end
115
+
116
+ def _t_initialize_belongs_to_association(association)
117
+ property_name = association.foreign_key
118
+ unless self.respond_to?(property_name)
119
+ property property_name, :type => id_class_for(association)
120
+ property association.polymorphic_type, :type => String if association.polymorphic?
121
+ view_by property_name
122
+ after_destroy { |record| record._t_cleanup_belongs_to_association(association) }
123
+ end
124
+ end
129
125
 
130
- def _t_get_associate_ids(association)
131
- self.send(association.foreign_keys_property) || []
126
+ def _t_delete(ids, run_callbacks=true)
127
+ docs = _t_find_bulk(ids)
128
+ if run_callbacks
129
+ docs.each { |doc| doc.destroy }
130
+ else
131
+ docs.each { |doc| database.delete_doc(doc) }
132
+ end
133
+ end
132
134
  end
133
135
 
134
- def _t_clear_associates(association)
135
- self.send(association.foreign_keys_property + '=', [])
136
+ module InstanceMethods #:nodoc:
137
+ 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 + '=', [])
154
+ end
136
155
  end
137
- end
138
156
 
157
+ end
139
158
  end
140
159
  end
@@ -1,139 +1,165 @@
1
1
  module Tenacity
2
- # Tenacity relationships on DataMapper objects require that certain columns
3
- # exist on the associated table, and that join tables exist for one-to-many
4
- # relationships. Take the following class for example:
5
- #
6
- # class Car
7
- # include DataMapper::Resource
8
- # include Tenacity
9
- #
10
- # property :id, Serial
11
- # property :driver_id, String
12
- #
13
- # t_has_many :wheels
14
- # t_has_one :dashboard
15
- # t_belongs_to :driver
16
- # end
17
- #
18
- #
19
- # == t_belongs_to
20
- #
21
- # The +t_belongs_to+ association requires that a property exist in the table
22
- # to hold the id of the assoicated object.
23
- #
24
- #
25
- # == t_has_one
26
- #
27
- # The +t_has_one+ association requires no special column in the table, since
28
- # the associated object holds the foreign key.
29
- #
30
- #
31
- # == t_has_many
32
- #
33
- # The +t_has_many+ association requires that a join table exist to store the
34
- # associations. The name of the join table follows ActiveRecord conventions.
35
- # The name of the join table in this example would be cars_wheels, since cars
36
- # comes before wheels when shorted alphabetically.
37
- #
38
- # create_table :car_wheels do
39
- # column :car_id, Integer
40
- # column :wheel_id, String
41
- # end
42
- #
43
- module DataMapper
44
-
45
- def self.setup(model)
46
- require 'datamapper'
47
- if model.included_modules.include?(::DataMapper::Resource)
48
- model.send :include, DataMapper::InstanceMethods
49
- model.extend DataMapper::ClassMethods
2
+ module OrmExt
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:
6
+ #
7
+ # class Car
8
+ # include DataMapper::Resource
9
+ # include Tenacity
10
+ #
11
+ # property :id, Serial
12
+ # property :driver_id, String
13
+ #
14
+ # t_has_many :wheels
15
+ # t_has_one :dashboard
16
+ # t_belongs_to :driver
17
+ # end
18
+ #
19
+ #
20
+ # == t_belongs_to
21
+ #
22
+ # The +t_belongs_to+ association requires that a property exist in the table
23
+ # to hold the id of the assoicated object.
24
+ #
25
+ #
26
+ # == t_has_one
27
+ #
28
+ # The +t_has_one+ association requires no special column in the table, since
29
+ # the associated object holds the foreign key.
30
+ #
31
+ #
32
+ # == t_has_many
33
+ #
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
43
+ #
44
+ module DataMapper
45
+
46
+ def self.setup(model)
47
+ require 'datamapper'
48
+ if model.included_modules.include?(::DataMapper::Resource)
49
+ model.send :include, DataMapper::InstanceMethods
50
+ model.extend DataMapper::ClassMethods
51
+ end
52
+ rescue LoadError
53
+ # DataMapper not available
50
54
  end
51
- rescue LoadError
52
- # DataMapper not available
53
- end
54
55
 
55
- module ClassMethods #:nodoc:
56
- def _t_find(id)
57
- get(id)
58
- end
56
+ module ClassMethods #:nodoc:
57
+ include Tenacity::OrmExt::Helpers
59
58
 
60
- def _t_find_bulk(ids)
61
- return [] if ids.nil? || ids.empty?
62
- all(:id => ids)
63
- end
59
+ def _t_id_type
60
+ Integer
61
+ end
64
62
 
65
- def _t_find_first_by_associate(property, id)
66
- first(property => id.to_s)
67
- end
63
+ def _t_find(id)
64
+ get(_t_serialize(id))
65
+ end
68
66
 
69
- def _t_find_all_by_associate(property, id)
70
- all(property => id.to_s)
71
- end
67
+ def _t_find_bulk(ids)
68
+ return [] if ids.nil? || ids == []
69
+ all(:id => _t_serialize_ids(ids))
70
+ end
72
71
 
73
- def _t_initialize_has_many_association(association)
74
- after :save do |record|
75
- record.class._t_save_associates(record, association)
72
+ def _t_find_first_by_associate(property, id)
73
+ first(property => _t_serialize(id))
76
74
  end
77
- end
78
75
 
79
- def _t_initialize_belongs_to_association(association)
80
- before :save do |record|
81
- record.class._t_stringify_belongs_to_value(record, association)
76
+ def _t_find_all_by_associate(property, id)
77
+ all(property => _t_serialize(id))
82
78
  end
83
- end
84
79
 
85
- def _t_delete(ids, run_callbacks=true)
86
- objects = _t_find_bulk(ids)
87
- if run_callbacks
88
- objects.each { |object| object.destroy }
89
- else
90
- objects.each { |object| object.destroy! }
80
+ def _t_initialize_tenacity
81
+ after :save do |record|
82
+ record._t_save_autosave_associations
83
+ end
91
84
  end
92
- end
93
- end
94
85
 
95
- module InstanceMethods #:nodoc:
96
- # DataMapper will not issue callbacks unless at least one of the properties is dirty.
97
- # So, taint the object by marking one of the attributes as dirty, save the object,
98
- # and resture the persisted_state by reloading the object from the database.
99
- def save
100
- if clean?
101
- taint!; super; reload
102
- else
103
- super
86
+ def _t_initialize_has_one_association(association)
87
+ after :destroy do |record|
88
+ record._t_cleanup_has_one_association(association)
89
+ end
104
90
  end
105
- end
106
91
 
107
- def _t_reload
108
- reload
109
- end
92
+ def _t_initialize_has_many_association(association)
93
+ after :save do |record|
94
+ record.class._t_save_associates(record, association)
95
+ end
110
96
 
111
- def _t_clear_associates(association)
112
- self.repository.adapter.execute("delete from #{association.join_table} where #{association.association_key} = #{self.id}")
113
- end
97
+ after :destroy do |record|
98
+ record._t_cleanup_has_many_association(association)
99
+ end
100
+ end
114
101
 
115
- def _t_associate_many(association, associate_ids)
116
- self.transaction do
117
- _t_clear_associates(association)
118
- associate_ids.each do |associate_id|
119
- self.repository.adapter.execute("insert into #{association.join_table} (#{association.association_key}, #{association.association_foreign_key}) values (#{self.id}, '#{associate_id}')")
102
+ def _t_initialize_belongs_to_association(association)
103
+ after :destroy do |record|
104
+ record._t_cleanup_belongs_to_association(association)
120
105
  end
121
106
  end
122
- end
123
107
 
124
- def _t_get_associate_ids(association)
125
- return [] if self.id.nil?
126
- self.repository.adapter.select("select #{association.association_foreign_key} from #{association.join_table} where #{association.association_key} = #{self.id}")
108
+ def _t_delete(ids, run_callbacks=true)
109
+ objects = _t_find_bulk(ids)
110
+ if run_callbacks
111
+ objects.each { |object| object.destroy }
112
+ else
113
+ objects.each { |object| object.destroy! }
114
+ end
115
+ end
127
116
  end
128
117
 
129
- private
118
+ module InstanceMethods #:nodoc:
119
+ include Tenacity::OrmExt::Helpers
120
+
121
+ # DataMapper will not issue callbacks unless at least one of the properties is dirty.
122
+ # So, taint the object by marking one of the attributes as dirty, save the object,
123
+ # and resture the persisted_state by reloading the object from the database.
124
+ def save
125
+ if clean?
126
+ taint!; super; reload
127
+ else
128
+ super
129
+ end
130
+ end
131
+
132
+ def _t_reload
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
130
139
 
131
- def taint!
132
- property = properties.first.name
133
- self.persisted_state = ::DataMapper::Resource::State::Dirty.new(self) unless self.persisted_state.kind_of?(::DataMapper::Resource::State::Dirty)
134
- self.persisted_state.original_attributes[properties[property]] = self.send(property)
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)}")
152
+ end
153
+
154
+ private
155
+
156
+ def taint!
157
+ property = properties.first.name
158
+ self.persisted_state = ::DataMapper::Resource::State::Dirty.new(self) unless self.persisted_state.kind_of?(::DataMapper::Resource::State::Dirty)
159
+ self.persisted_state.original_attributes[properties[property]] = self.send(property)
160
+ end
135
161
  end
136
- end
137
162
 
163
+ end
138
164
  end
139
165
  end