tenacity 0.2.0 → 0.3.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 (69) hide show
  1. data/.gitignore +3 -0
  2. data/EXTEND.rdoc +11 -1
  3. data/README.rdoc +4 -1
  4. data/Rakefile +20 -9
  5. data/history.txt +21 -0
  6. data/lib/tenacity.rb +12 -4
  7. data/lib/tenacity/associates_proxy.rb +67 -0
  8. data/lib/tenacity/association.rb +19 -6
  9. data/lib/tenacity/associations/has_many.rb +6 -0
  10. data/lib/tenacity/class_methods.rb +52 -1
  11. data/lib/tenacity/instance_methods.rb +7 -3
  12. data/lib/tenacity/orm_ext/activerecord.rb +30 -13
  13. data/lib/tenacity/orm_ext/couchrest.rb +140 -0
  14. data/lib/tenacity/orm_ext/datamapper.rb +139 -0
  15. data/lib/tenacity/orm_ext/mongo_mapper.rb +88 -80
  16. data/lib/tenacity/orm_ext/mongoid.rb +108 -0
  17. data/lib/tenacity/orm_ext/sequel.rb +134 -0
  18. data/lib/tenacity/version.rb +1 -1
  19. data/tenacity.gemspec +14 -3
  20. data/test/association_features/belongs_to_test.rb +42 -0
  21. data/test/association_features/has_many_test.rb +110 -0
  22. data/test/association_features/has_one_test.rb +41 -0
  23. data/test/associations/belongs_to_test.rb +36 -127
  24. data/test/associations/has_many_test.rb +77 -196
  25. data/test/associations/has_one_test.rb +22 -84
  26. data/test/core/classmethods_test.rb +24 -22
  27. data/test/fixtures/active_record_has_many_target.rb +10 -0
  28. data/test/fixtures/active_record_has_one_target.rb +10 -0
  29. data/test/fixtures/{active_record_nuts.rb → active_record_nut.rb} +0 -0
  30. data/test/fixtures/active_record_object.rb +17 -0
  31. data/test/fixtures/couch_rest_door.rb +0 -2
  32. data/test/fixtures/couch_rest_has_many_target.rb +12 -0
  33. data/test/fixtures/couch_rest_has_one_target.rb +12 -0
  34. data/test/fixtures/couch_rest_object.rb +19 -0
  35. data/test/fixtures/couch_rest_windshield.rb +0 -2
  36. data/test/fixtures/data_mapper_has_many_target.rb +19 -0
  37. data/test/fixtures/data_mapper_has_one_target.rb +19 -0
  38. data/test/fixtures/data_mapper_object.rb +20 -0
  39. data/test/fixtures/mongo_mapper_ash_tray.rb +0 -2
  40. data/test/fixtures/mongo_mapper_dashboard.rb +0 -2
  41. data/test/fixtures/mongo_mapper_has_many_target.rb +11 -0
  42. data/test/fixtures/mongo_mapper_has_one_target.rb +11 -0
  43. data/test/fixtures/mongo_mapper_object.rb +18 -0
  44. data/test/fixtures/mongo_mapper_vent.rb +0 -2
  45. data/test/fixtures/mongo_mapper_wheel.rb +0 -2
  46. data/test/fixtures/mongoid_has_many_target.rb +13 -0
  47. data/test/fixtures/mongoid_has_one_target.rb +13 -0
  48. data/test/fixtures/mongoid_object.rb +20 -0
  49. data/test/fixtures/sequel_has_many_target.rb +10 -0
  50. data/test/fixtures/sequel_has_one_target.rb +10 -0
  51. data/test/fixtures/sequel_object.rb +17 -0
  52. data/test/helpers/active_record_test_helper.rb +51 -0
  53. data/test/helpers/data_mapper_test_helper.rb +44 -0
  54. data/test/helpers/mongoid_test_helper.rb +21 -0
  55. data/test/helpers/sequel_test_helper.rb +60 -0
  56. data/test/orm_ext/activerecord_test.rb +55 -35
  57. data/test/orm_ext/couchrest_test.rb +66 -46
  58. data/test/orm_ext/datamapper_test.rb +112 -0
  59. data/test/orm_ext/mongo_mapper_test.rb +64 -44
  60. data/test/orm_ext/mongoid_test.rb +121 -0
  61. data/test/orm_ext/sequel_test.rb +113 -0
  62. data/test/test_helper.rb +87 -11
  63. metadata +159 -59
  64. data/lib/tenacity/orm_ext/couchrest/couchrest_extended_document.rb +0 -42
  65. data/lib/tenacity/orm_ext/couchrest/couchrest_model.rb +0 -44
  66. data/lib/tenacity/orm_ext/couchrest/tenacity_class_methods.rb +0 -43
  67. data/lib/tenacity/orm_ext/couchrest/tenacity_instance_methods.rb +0 -21
  68. data/test/fixtures/couch_rest_radio.rb +0 -10
  69. data/test/fixtures/mongo_mapper_button.rb +0 -6
@@ -0,0 +1,140 @@
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
43
+
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
49
+ end
50
+ rescue LoadError
51
+ # CouchRest::ExtendedDocument not available
52
+ end
53
+
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
60
+ end
61
+ rescue LoadError
62
+ rescue NameError
63
+ # CouchRest::ExtendedDocument not available
64
+ end
65
+ end
66
+
67
+ module ClassMethods #:nodoc:
68
+ def _t_find(id)
69
+ (id.nil? || id.strip == "") ? nil : get(id)
70
+ end
71
+
72
+ def _t_find_bulk(ids)
73
+ return [] if ids.nil? || ids.empty?
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']))
79
+ end
80
+ docs.reject { |doc| doc.nil? }
81
+ end
82
+
83
+ def _t_find_first_by_associate(property, id)
84
+ self.send("by_#{property}", :key => id.to_s).first
85
+ end
86
+
87
+ def _t_find_all_by_associate(property, id)
88
+ self.send("by_#{property}", :key => id.to_s)
89
+ end
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) }
96
+ end
97
+ end
98
+
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) }
105
+ end
106
+ end
107
+
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) }
114
+ end
115
+ end
116
+ end
117
+
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
125
+
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
129
+
130
+ def _t_get_associate_ids(association)
131
+ self.send(association.foreign_keys_property) || []
132
+ end
133
+
134
+ def _t_clear_associates(association)
135
+ self.send(association.foreign_keys_property + '=', [])
136
+ end
137
+ end
138
+
139
+ end
140
+ end
@@ -0,0 +1,139 @@
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
50
+ end
51
+ rescue LoadError
52
+ # DataMapper not available
53
+ end
54
+
55
+ module ClassMethods #:nodoc:
56
+ def _t_find(id)
57
+ get(id)
58
+ end
59
+
60
+ def _t_find_bulk(ids)
61
+ return [] if ids.nil? || ids.empty?
62
+ all(:id => ids)
63
+ end
64
+
65
+ def _t_find_first_by_associate(property, id)
66
+ first(property => id.to_s)
67
+ end
68
+
69
+ def _t_find_all_by_associate(property, id)
70
+ all(property => id.to_s)
71
+ end
72
+
73
+ def _t_initialize_has_many_association(association)
74
+ after :save do |record|
75
+ record.class._t_save_associates(record, association)
76
+ end
77
+ end
78
+
79
+ def _t_initialize_belongs_to_association(association)
80
+ before :save do |record|
81
+ record.class._t_stringify_belongs_to_value(record, association)
82
+ end
83
+ end
84
+
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! }
91
+ end
92
+ end
93
+ end
94
+
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
104
+ end
105
+ end
106
+
107
+ def _t_reload
108
+ reload
109
+ end
110
+
111
+ def _t_clear_associates(association)
112
+ self.repository.adapter.execute("delete from #{association.join_table} where #{association.association_key} = #{self.id}")
113
+ end
114
+
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}')")
120
+ end
121
+ end
122
+ end
123
+
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}")
127
+ end
128
+
129
+ private
130
+
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)
135
+ end
136
+ end
137
+
138
+ end
139
+ end
@@ -1,97 +1,105 @@
1
- # Tenacity relationships on MongoMapper objects require no special keys
2
- # defined on the object. Tenacity will define the keys that it needs
3
- # to support the relationships. Take the following class for example:
4
- #
5
- # class Car < ActiveRecord::Base
6
- # include MongoMapper::Document
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 key named after the association.
17
- # The example above will create a key named <tt>:driver_id</tt>
18
- #
19
- #
20
- # == t_has_one
21
- #
22
- # The +t_has_one+ association will not define any new keys 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 key named after the association.
29
- # The example above will create a key named <tt>:wheels_ids</tt>
30
- #
31
- module TenacityMongoMapperPlugin
32
- module ClassMethods #:nodoc:
33
- def _t_find(id)
34
- find(id)
35
- end
1
+ module Tenacity
2
+ # Tenacity relationships on MongoMapper 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 < ActiveRecord::Base
7
+ # include MongoMapper::Document
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 key named after the association.
18
+ # The example above will create a key named <tt>:driver_id</tt>
19
+ #
20
+ #
21
+ # == t_has_one
22
+ #
23
+ # The +t_has_one+ association will not define any new keys 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 key named after the association.
30
+ # The example above will create a key named <tt>:wheels_ids</tt>
31
+ #
32
+ module MongoMapper
36
33
 
37
- def _t_find_bulk(ids=[])
38
- find(ids)
34
+ def self.setup(model) #:nodoc:
35
+ require 'mongo_mapper'
36
+ if model.included_modules.include?(::MongoMapper::Document)
37
+ model.send :include, MongoMapper::InstanceMethods
38
+ model.extend MongoMapper::ClassMethods
39
+ end
40
+ rescue LoadError
41
+ # MongoMapper not available
39
42
  end
40
43
 
41
- def _t_find_first_by_associate(property, id)
42
- first(property => id.to_s)
43
- end
44
+ module ClassMethods #:nodoc:
45
+ def _t_find(id)
46
+ find(id)
47
+ end
44
48
 
45
- def _t_find_all_by_associate(property, id)
46
- all(property => id.to_s)
47
- end
49
+ def _t_find_bulk(ids=[])
50
+ find(ids)
51
+ end
48
52
 
49
- def _t_initialize_has_many_association(association)
50
- unless self.respond_to?(association.foreign_keys_property)
51
- key association.foreign_keys_property, Array
52
- after_save { |record| _t_save_associates(record, association) }
53
+ def _t_find_first_by_associate(property, id)
54
+ first(property => id.to_s)
53
55
  end
54
- end
55
56
 
56
- def _t_initialize_belongs_to_association(association)
57
- unless self.respond_to?(association.foreign_key)
58
- key association.foreign_key, String
59
- before_save { |record| _t_stringify_belongs_to_value(record, association) }
57
+ def _t_find_all_by_associate(property, id)
58
+ all(property => id.to_s)
60
59
  end
61
- end
62
- end
63
60
 
64
- module InstanceMethods #:nodoc:
65
- def _t_reload
66
- reload
67
- rescue MongoMapper::DocumentNotFound
68
- nil
69
- end
61
+ def _t_initialize_has_many_association(association)
62
+ unless self.respond_to?(association.foreign_keys_property)
63
+ key association.foreign_keys_property, Array
64
+ after_save { |record| _t_save_associates(record, association) }
65
+ end
66
+ end
70
67
 
71
- def _t_associate_many(association, associate_ids)
72
- self.send(association.foreign_keys_property + '=', associate_ids.map { |associate_id| associate_id.to_s })
73
- end
68
+ def _t_initialize_belongs_to_association(association)
69
+ unless self.respond_to?(association.foreign_key)
70
+ key association.foreign_key, String
71
+ before_save { |record| _t_stringify_belongs_to_value(record, association) }
72
+ end
73
+ end
74
74
 
75
- def _t_get_associate_ids(association)
76
- self.send(association.foreign_keys_property)
75
+ def _t_delete(ids, run_callbacks=true)
76
+ if run_callbacks
77
+ destroy(ids)
78
+ else
79
+ delete(ids)
80
+ end
81
+ end
77
82
  end
78
83
 
79
- def _t_clear_associates(association)
80
- self.send(association.foreign_keys_property + '=', [])
84
+ module InstanceMethods #:nodoc:
85
+ def _t_reload
86
+ reload
87
+ rescue ::MongoMapper::DocumentNotFound
88
+ nil
89
+ end
90
+
91
+ def _t_associate_many(association, associate_ids)
92
+ self.send(association.foreign_keys_property + '=', associate_ids.map { |associate_id| associate_id.to_s })
93
+ end
94
+
95
+ def _t_get_associate_ids(association)
96
+ self.send(association.foreign_keys_property)
97
+ end
98
+
99
+ def _t_clear_associates(association)
100
+ self.send(association.foreign_keys_property + '=', [])
101
+ end
81
102
  end
82
- end
83
- end
84
103
 
85
- module TenacityPluginAddition #:nodoc:
86
- def self.included(model)
87
- model.plugin TenacityMongoMapperPlugin
88
104
  end
89
105
  end
90
-
91
- begin
92
- require 'mongo_mapper'
93
- MongoMapper::Document.append_inclusions(TenacityPluginAddition)
94
- rescue LoadError
95
- # MongoMapper not available
96
- end
97
-