tenacity 0.2.0 → 0.3.0

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