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.
- data/.gitignore +3 -0
- data/EXTEND.rdoc +11 -1
- data/README.rdoc +4 -1
- data/Rakefile +20 -9
- data/history.txt +21 -0
- data/lib/tenacity.rb +12 -4
- data/lib/tenacity/associates_proxy.rb +67 -0
- data/lib/tenacity/association.rb +19 -6
- data/lib/tenacity/associations/has_many.rb +6 -0
- data/lib/tenacity/class_methods.rb +52 -1
- data/lib/tenacity/instance_methods.rb +7 -3
- data/lib/tenacity/orm_ext/activerecord.rb +30 -13
- data/lib/tenacity/orm_ext/couchrest.rb +140 -0
- data/lib/tenacity/orm_ext/datamapper.rb +139 -0
- data/lib/tenacity/orm_ext/mongo_mapper.rb +88 -80
- data/lib/tenacity/orm_ext/mongoid.rb +108 -0
- data/lib/tenacity/orm_ext/sequel.rb +134 -0
- data/lib/tenacity/version.rb +1 -1
- data/tenacity.gemspec +14 -3
- data/test/association_features/belongs_to_test.rb +42 -0
- data/test/association_features/has_many_test.rb +110 -0
- data/test/association_features/has_one_test.rb +41 -0
- data/test/associations/belongs_to_test.rb +36 -127
- data/test/associations/has_many_test.rb +77 -196
- data/test/associations/has_one_test.rb +22 -84
- data/test/core/classmethods_test.rb +24 -22
- data/test/fixtures/active_record_has_many_target.rb +10 -0
- data/test/fixtures/active_record_has_one_target.rb +10 -0
- data/test/fixtures/{active_record_nuts.rb → active_record_nut.rb} +0 -0
- data/test/fixtures/active_record_object.rb +17 -0
- data/test/fixtures/couch_rest_door.rb +0 -2
- data/test/fixtures/couch_rest_has_many_target.rb +12 -0
- data/test/fixtures/couch_rest_has_one_target.rb +12 -0
- data/test/fixtures/couch_rest_object.rb +19 -0
- data/test/fixtures/couch_rest_windshield.rb +0 -2
- data/test/fixtures/data_mapper_has_many_target.rb +19 -0
- data/test/fixtures/data_mapper_has_one_target.rb +19 -0
- data/test/fixtures/data_mapper_object.rb +20 -0
- data/test/fixtures/mongo_mapper_ash_tray.rb +0 -2
- data/test/fixtures/mongo_mapper_dashboard.rb +0 -2
- data/test/fixtures/mongo_mapper_has_many_target.rb +11 -0
- data/test/fixtures/mongo_mapper_has_one_target.rb +11 -0
- data/test/fixtures/mongo_mapper_object.rb +18 -0
- data/test/fixtures/mongo_mapper_vent.rb +0 -2
- data/test/fixtures/mongo_mapper_wheel.rb +0 -2
- data/test/fixtures/mongoid_has_many_target.rb +13 -0
- data/test/fixtures/mongoid_has_one_target.rb +13 -0
- data/test/fixtures/mongoid_object.rb +20 -0
- data/test/fixtures/sequel_has_many_target.rb +10 -0
- data/test/fixtures/sequel_has_one_target.rb +10 -0
- data/test/fixtures/sequel_object.rb +17 -0
- data/test/helpers/active_record_test_helper.rb +51 -0
- data/test/helpers/data_mapper_test_helper.rb +44 -0
- data/test/helpers/mongoid_test_helper.rb +21 -0
- data/test/helpers/sequel_test_helper.rb +60 -0
- data/test/orm_ext/activerecord_test.rb +55 -35
- data/test/orm_ext/couchrest_test.rb +66 -46
- data/test/orm_ext/datamapper_test.rb +112 -0
- data/test/orm_ext/mongo_mapper_test.rb +64 -44
- data/test/orm_ext/mongoid_test.rb +121 -0
- data/test/orm_ext/sequel_test.rb +113 -0
- data/test/test_helper.rb +87 -11
- metadata +159 -59
- data/lib/tenacity/orm_ext/couchrest/couchrest_extended_document.rb +0 -42
- data/lib/tenacity/orm_ext/couchrest/couchrest_model.rb +0 -44
- data/lib/tenacity/orm_ext/couchrest/tenacity_class_methods.rb +0 -43
- data/lib/tenacity/orm_ext/couchrest/tenacity_instance_methods.rb +0 -21
- data/test/fixtures/couch_rest_radio.rb +0 -10
- 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
|
-
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# include
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# The
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
# The
|
30
|
-
#
|
31
|
-
|
32
|
-
module
|
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
|
38
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
+
module ClassMethods #:nodoc:
|
45
|
+
def _t_find(id)
|
46
|
+
find(id)
|
47
|
+
end
|
44
48
|
|
45
|
-
|
46
|
-
|
47
|
-
|
49
|
+
def _t_find_bulk(ids=[])
|
50
|
+
find(ids)
|
51
|
+
end
|
48
52
|
|
49
|
-
|
50
|
-
|
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
|
-
|
57
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
76
|
-
|
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
|
-
|
80
|
-
|
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
|
-
|