tenacity 0.1.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 +6 -0
- data/EXTEND.rdoc +79 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +70 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +103 -0
- data/Rakefile +96 -0
- data/lib/tenacity/associations/belongs_to.rb +28 -0
- data/lib/tenacity/associations/has_many.rb +86 -0
- data/lib/tenacity/associations/has_one.rb +40 -0
- data/lib/tenacity/class_methods.rb +226 -0
- data/lib/tenacity/instance_methods.rb +31 -0
- data/lib/tenacity/orm_ext/activerecord.rb +116 -0
- data/lib/tenacity/orm_ext/couchrest/couchrest_extended_document.rb +45 -0
- data/lib/tenacity/orm_ext/couchrest/couchrest_model.rb +46 -0
- data/lib/tenacity/orm_ext/couchrest/tenacity_class_methods.rb +52 -0
- data/lib/tenacity/orm_ext/couchrest/tenacity_instance_methods.rb +21 -0
- data/lib/tenacity/orm_ext/mongo_mapper.rb +107 -0
- data/lib/tenacity/version.rb +3 -0
- data/lib/tenacity.rb +27 -0
- data/tenacity.gemspec +34 -0
- data/test/associations/belongs_to_test.rb +119 -0
- data/test/associations/has_many_test.rb +184 -0
- data/test/associations/has_one_test.rb +77 -0
- data/test/core/classmethods_test.rb +53 -0
- data/test/fixtures/active_record_car.rb +6 -0
- data/test/fixtures/active_record_climate_control_unit.rb +5 -0
- data/test/fixtures/active_record_nuts.rb +5 -0
- data/test/fixtures/couch_rest_radio.rb +10 -0
- data/test/fixtures/mongo_mapper_button.rb +6 -0
- data/test/fixtures/mongo_mapper_dashboard.rb +10 -0
- data/test/fixtures/mongo_mapper_wheel.rb +9 -0
- data/test/helpers/active_record_test_helper.rb +7 -0
- data/test/helpers/couch_rest_test_helper.rb +8 -0
- data/test/helpers/mongo_mapper_test_helper.rb +3 -0
- data/test/orm_ext/activerecord_test.rb +85 -0
- data/test/orm_ext/couchrest_test.rb +95 -0
- data/test/orm_ext/mongo_mapper_test.rb +93 -0
- data/test/test_helper.rb +47 -0
- metadata +253 -0
@@ -0,0 +1,226 @@
|
|
1
|
+
module Tenacity
|
2
|
+
# Associations are a set of macro-like class methods for tying objects together through
|
3
|
+
# their ids. They express relationships like "Project has one Project Manager"
|
4
|
+
# or "Project belongs to a Portfolio". Each macro adds a number of methods to the
|
5
|
+
# class which are specialized according to the collection or association symbol and the
|
6
|
+
# options hash. It works much the same way as Ruby's own <tt>attr*</tt>
|
7
|
+
# methods.
|
8
|
+
#
|
9
|
+
# class Project
|
10
|
+
# include Tenacity
|
11
|
+
#
|
12
|
+
# t_belongs_to :portfolio
|
13
|
+
# t_has_one :project_manager
|
14
|
+
# t_has_many :milestones
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# The project class now has the following methods (and more) to ease the traversal and
|
18
|
+
# manipulation of its relationships:
|
19
|
+
# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
|
20
|
+
# * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
|
21
|
+
# * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone), Project#milestones.delete(milestone)</tt>
|
22
|
+
#
|
23
|
+
# == Cardinality and associations
|
24
|
+
#
|
25
|
+
# Tenacity associations can be used to describe one-to-one and one-to-many
|
26
|
+
# relationships between models. Each model uses an association to describe its role in
|
27
|
+
# the relation. The +t_belongs_to+ association is always used in the model that has
|
28
|
+
# the foreign key.
|
29
|
+
#
|
30
|
+
# === One-to-one
|
31
|
+
#
|
32
|
+
# Use +t_has_one+ in the base, and +t_belongs_to+ in the associated model.
|
33
|
+
#
|
34
|
+
# class Employee < ActiveRecord::Base
|
35
|
+
# include Tenacity
|
36
|
+
# t_has_one :office
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# class Office
|
40
|
+
# include MongoMapper::Document
|
41
|
+
# include Tenacity
|
42
|
+
# t_belongs_to :employee # foreign key - employee_id
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# === One-to-many
|
46
|
+
#
|
47
|
+
# Use +t_has_many+ in the base, and +t_belongs_to+ in the associated model.
|
48
|
+
#
|
49
|
+
# class Manager < ActiveRecord::Base
|
50
|
+
# include Tenacity
|
51
|
+
# t_has_many :employees
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# class Employee
|
55
|
+
# include MongoMapper::Document
|
56
|
+
# include Tenacity
|
57
|
+
# t_belongs_to :manager # foreign key - manager_id
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# == Caching
|
61
|
+
#
|
62
|
+
# All of the methods are built on a simple caching principle that will keep the result
|
63
|
+
# of the last query around unless specifically instructed not to. The cache is even
|
64
|
+
# shared across methods to make it even cheaper to use the macro-added methods without
|
65
|
+
# worrying too much about performance at the first go.
|
66
|
+
#
|
67
|
+
# project.milestones # fetches milestones from the database
|
68
|
+
# project.milestones.size # uses the milestone cache
|
69
|
+
# project.milestones.empty? # uses the milestone cache
|
70
|
+
# project.milestones(true).size # fetches milestones from the database
|
71
|
+
# project.milestones # uses the milestone cache
|
72
|
+
#
|
73
|
+
module ClassMethods
|
74
|
+
|
75
|
+
# Specifies a one-to-one association with another class. This method should only be used
|
76
|
+
# if the other class contains the foreign key. If the current class contains the foreign key,
|
77
|
+
# then you should use +t_belongs_to+ instead.
|
78
|
+
#
|
79
|
+
# The following methods for retrieval and query of a single associated object will be added:
|
80
|
+
#
|
81
|
+
# [association(force_reload = false)]
|
82
|
+
# Returns the associated object. +nil+ is returned if none is found.
|
83
|
+
# [association=(associate)]
|
84
|
+
# Assigns the associate object, extracts the primary key, sets it as the foreign key,
|
85
|
+
# and saves the associate object.
|
86
|
+
#
|
87
|
+
# (+association+ is replaced with the symbol passed as the first argument, so
|
88
|
+
# <tt>t_has_one :manager</tt> would add among others <tt>manager.nil?</tt>.)
|
89
|
+
#
|
90
|
+
# === Example
|
91
|
+
#
|
92
|
+
# An Account class declares <tt>t_has_one :beneficiary</tt>, which will add:
|
93
|
+
# * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>)
|
94
|
+
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
|
95
|
+
#
|
96
|
+
def t_has_one(association_id, args={})
|
97
|
+
extend(HasOne::ClassMethods)
|
98
|
+
initialize_has_one_association(association_id)
|
99
|
+
|
100
|
+
define_method(association_id) do |*params|
|
101
|
+
get_associate(association_id, params) do
|
102
|
+
has_one_associate(association_id)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
define_method("#{association_id}=") do |associate|
|
107
|
+
set_associate(association_id, associate) do
|
108
|
+
set_has_one_associate(association_id, associate)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Specifies a one-to-one association with another class. This method should only be used
|
114
|
+
# if this class contains the foreign key. If the other class contains the foreign key,
|
115
|
+
# then you should use +t_has_one+ instead.
|
116
|
+
#
|
117
|
+
# Methods will be added for retrieval and query for a single associated object, for which
|
118
|
+
# this object holds an id:
|
119
|
+
#
|
120
|
+
# [association(force_reload = false)]
|
121
|
+
# Returns the associated object. +nil+ is returned if none is found.
|
122
|
+
# [association=(associate)]
|
123
|
+
# Assigns the associate object, extracts the primary key, and sets it as the foreign key.
|
124
|
+
#
|
125
|
+
# (+association+ is replaced with the symbol passed as the first argument, so
|
126
|
+
# <tt>t_belongs_to :author</tt> would add among others <tt>author.nil?</tt>.)
|
127
|
+
#
|
128
|
+
# === Example
|
129
|
+
#
|
130
|
+
# A Post class declares <tt>t_belongs_to :author</tt>, which will add:
|
131
|
+
# * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
|
132
|
+
# * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
|
133
|
+
#
|
134
|
+
def t_belongs_to(association_id, args={})
|
135
|
+
extend(BelongsTo::ClassMethods)
|
136
|
+
initialize_belongs_to_association(association_id)
|
137
|
+
|
138
|
+
define_method(association_id) do |*params|
|
139
|
+
get_associate(association_id, params) do
|
140
|
+
belongs_to_associate(association_id)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
define_method("#{association_id}=") do |associate|
|
145
|
+
set_associate(association_id, associate) do
|
146
|
+
set_belongs_to_associate(association_id, associate)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Specifies a one-to-many association. The following methods for retrieval and query of
|
152
|
+
# collections of associated objects will be added:
|
153
|
+
#
|
154
|
+
# [collection(force_reload = false)]
|
155
|
+
# Returns an array of all the associated objects.
|
156
|
+
# An empty array is returned if none are found.
|
157
|
+
# [collection<<(object, ...)]
|
158
|
+
# Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
|
159
|
+
# Note that this operation does not update the association until the parent object is saved.
|
160
|
+
# [collection.delete(object, ...)]
|
161
|
+
# Removes one or more objects from the collection.
|
162
|
+
# [collection=objects]
|
163
|
+
# Replaces the collections content by setting it to the list of specified objects.
|
164
|
+
# [collection_singular_ids]
|
165
|
+
# Returns an array of the associated objects' ids
|
166
|
+
# [collection_singular_ids=ids]
|
167
|
+
# Replace the collection with the objects identified by the primary keys in +ids+.
|
168
|
+
# [collection.clear]
|
169
|
+
# Removes every object from the collection.
|
170
|
+
# [collection.empty?]
|
171
|
+
# Returns +true+ if there are no associated objects.
|
172
|
+
# [collection.size]
|
173
|
+
# Returns the number of associated objects.
|
174
|
+
#
|
175
|
+
# (*Note*: +collection+ is replaced with the symbol passed as the first argument, so
|
176
|
+
# <tt>t_has_many :clients</tt> would add among others <tt>clients.empty?</tt>.)
|
177
|
+
#
|
178
|
+
# === Example
|
179
|
+
#
|
180
|
+
# Example: A Firm class declares <tt>t_has_many :clients</tt>, which will add:
|
181
|
+
# * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => ["firm_id = ?", id]</tt>)
|
182
|
+
# * <tt>Firm#clients<<</tt>
|
183
|
+
# * <tt>Firm#clients.delete</tt>
|
184
|
+
# * <tt>Firm#clients=</tt>
|
185
|
+
# * <tt>Firm#client_ids</tt>
|
186
|
+
# * <tt>Firm#client_ids=</tt>
|
187
|
+
# * <tt>Firm#clients.clear</tt>
|
188
|
+
# * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
|
189
|
+
# * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
|
190
|
+
#
|
191
|
+
def t_has_many(association_id, args={})
|
192
|
+
extend(HasMany::ClassMethods)
|
193
|
+
initialize_has_many_association(association_id)
|
194
|
+
|
195
|
+
define_method(association_id) do |*params|
|
196
|
+
get_associate(association_id, params) do
|
197
|
+
has_many_associates(association_id)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
define_method("#{association_id}=") do |associates|
|
202
|
+
set_associate(association_id, associates)
|
203
|
+
end
|
204
|
+
|
205
|
+
define_method("#{ActiveSupport::Inflector.singularize(association_id.to_s)}_ids") do
|
206
|
+
has_many_associate_ids(association_id)
|
207
|
+
end
|
208
|
+
|
209
|
+
define_method("#{ActiveSupport::Inflector.singularize(association_id.to_s)}_ids=") do |associate_ids|
|
210
|
+
set_has_many_associate_ids(association_id, associate_ids)
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
define_method(:_t_save_without_callback) do
|
216
|
+
save_without_callback
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def associate_class(association_id)
|
221
|
+
Kernel.const_get(association_id.to_s.singularize.camelcase.to_sym)
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Tenacity
|
2
|
+
module InstanceMethods #:nodoc:
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def get_associate(association_id, params)
|
7
|
+
_t_reload
|
8
|
+
force_reload = params.first unless params.empty?
|
9
|
+
value = instance_variable_get ivar_name(association_id)
|
10
|
+
if value.nil? || force_reload
|
11
|
+
value = yield
|
12
|
+
instance_variable_set ivar_name(association_id), value
|
13
|
+
end
|
14
|
+
value
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_associate(association_id, associate)
|
18
|
+
yield if block_given?
|
19
|
+
instance_variable_set ivar_name(association_id), associate
|
20
|
+
end
|
21
|
+
|
22
|
+
def ivar_name(association_id)
|
23
|
+
"@_t_" + association_id.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def associate_class(association_id)
|
27
|
+
self.class.associate_class(association_id)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# Tenacity relationships on ActiveRecord objects require that certain columns
|
2
|
+
# exist on the associated table, and that join tables exist for one-to-many
|
3
|
+
# relationships. Take the following class for example:
|
4
|
+
#
|
5
|
+
# class Car < ActiveRecord::Base
|
6
|
+
# include Tenacity
|
7
|
+
#
|
8
|
+
# t_has_many :wheels
|
9
|
+
# t_has_one :dashboard
|
10
|
+
# t_belongs_to :driver
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
#
|
14
|
+
# == t_belongs_to
|
15
|
+
#
|
16
|
+
# The +t_belongs_to+ association requires that a property exist in the table
|
17
|
+
# to hold the id of the assoicated object.
|
18
|
+
#
|
19
|
+
# create_table :cars do |t|
|
20
|
+
# t.string :driver_id
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
#
|
24
|
+
# == t_has_one
|
25
|
+
#
|
26
|
+
# The +t_has_one+ association requires no special column in the table, since
|
27
|
+
# the associated object holds the foreign key.
|
28
|
+
#
|
29
|
+
#
|
30
|
+
# == t_has_many
|
31
|
+
#
|
32
|
+
# The +t_has_many+ association requires that a join table exist to store the
|
33
|
+
# associations. The name of the join table follows ActiveRecord conventions.
|
34
|
+
# The name of the join table in this example would be cars_wheels, since cars
|
35
|
+
# comes before wheels when shorted alphabetically.
|
36
|
+
#
|
37
|
+
# create_table :cars_wheels do |t|
|
38
|
+
# t.integer :car_id
|
39
|
+
# t.string :wheel_id
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
begin
|
43
|
+
require 'active_record'
|
44
|
+
|
45
|
+
module ActiveRecord
|
46
|
+
class Base #:nodoc:
|
47
|
+
|
48
|
+
def self._t_find(id)
|
49
|
+
find_by_id(id)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self._t_find_bulk(ids)
|
53
|
+
return [] if ids.nil? || ids.empty?
|
54
|
+
find(:all, :conditions => ["id in (?)", ids])
|
55
|
+
end
|
56
|
+
|
57
|
+
def self._t_find_first_by_associate(property, id)
|
58
|
+
find(:first, :conditions => ["#{property} = ?", id.to_s])
|
59
|
+
end
|
60
|
+
|
61
|
+
def self._t_find_all_by_associate(property, id)
|
62
|
+
find(:all, :conditions => ["#{property} = ?", id.to_s])
|
63
|
+
end
|
64
|
+
|
65
|
+
def self._t_initialize_has_many_association(association_id)
|
66
|
+
after_save { |record| record.class._t_save_associates(record, association_id) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def self._t_initialize_belongs_to_association(association_id)
|
70
|
+
before_save { |record| record.class._t_stringify_belongs_to_value(record, association_id) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def _t_reload
|
74
|
+
reload
|
75
|
+
end
|
76
|
+
|
77
|
+
def _t_clear_associates(association_id)
|
78
|
+
t_join_table_name = self.class._t_join_table_name(association_id)
|
79
|
+
self.connection.execute("delete from #{t_join_table_name} where #{self.class._t_my_id_column} = #{self.id}")
|
80
|
+
end
|
81
|
+
|
82
|
+
def _t_associate_many(association_id, associate_ids)
|
83
|
+
t_join_table_name = self.class._t_join_table_name(association_id)
|
84
|
+
values = associate_ids.map { |associate_id| "(#{self.id}, '#{associate_id}')" }.join(',')
|
85
|
+
|
86
|
+
self.transaction do
|
87
|
+
_t_clear_associates(association_id)
|
88
|
+
self.connection.execute("insert into #{t_join_table_name} (#{self.class._t_my_id_column}, #{self.class._t_associate_id_column(association_id)}) values #{values}")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def _t_get_associate_ids(association_id)
|
93
|
+
t_join_table_name = self.class._t_join_table_name(association_id)
|
94
|
+
rows = self.connection.execute("select #{self.class._t_associate_id_column(association_id)} from #{t_join_table_name} where #{self.class._t_my_id_column} = #{self.id}")
|
95
|
+
ids = []; rows.each { |r| ids << r[0] }; ids
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def self._t_my_id_column
|
101
|
+
table_name.singularize + '_id'
|
102
|
+
end
|
103
|
+
|
104
|
+
def self._t_associate_id_column(association_id)
|
105
|
+
association_id.to_s.singularize + '_id'
|
106
|
+
end
|
107
|
+
|
108
|
+
def self._t_join_table_name(association_id)
|
109
|
+
association_id.to_s < table_name ? "#{association_id}_#{table_name}" : "#{table_name}_#{association_id}"
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
rescue LoadError
|
115
|
+
# ActiveRecord not available
|
116
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Tenacity relationships on CouchRest 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 < CouchRest::ExtendedDocument
|
6
|
+
# include Tenacity
|
7
|
+
#
|
8
|
+
# t_has_many :wheels
|
9
|
+
# t_has_one :dashboard
|
10
|
+
# t_belongs_to :driver
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# == t_belongs_to
|
14
|
+
#
|
15
|
+
# The +t_belongs_to+ association will define a property named after the association.
|
16
|
+
# The example above will create a property named <tt>:driver_id</tt>
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# == t_has_one
|
20
|
+
#
|
21
|
+
# The +t_has_one+ association will not define any new properties on the object, since
|
22
|
+
# the associated object holds the foreign key. If the CouchRest::ExtendedDocument class
|
23
|
+
# is the target of a t_has_one association from another class, then a property
|
24
|
+
# named after the association will be created on the CouchRest::ExtendedDocument object to
|
25
|
+
# hold the foreign key to the other object.
|
26
|
+
#
|
27
|
+
#
|
28
|
+
# == t_has_many
|
29
|
+
#
|
30
|
+
# The +t_has_many+ association will define a property named after the association.
|
31
|
+
# The example above will create a property named <tt>:wheels_ids</tt>
|
32
|
+
#
|
33
|
+
|
34
|
+
begin
|
35
|
+
require 'couchrest'
|
36
|
+
|
37
|
+
module CouchRest
|
38
|
+
class ExtendedDocument #:nodoc:
|
39
|
+
include CouchRest::TenacityInstanceMethods
|
40
|
+
extend CouchRest::TenacityClassMethods
|
41
|
+
end
|
42
|
+
end
|
43
|
+
rescue LoadError
|
44
|
+
# CouchRest::ExtendedDocument not available
|
45
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Tenacity relationships on CouchRest 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 < CouchRest::Model::Base
|
6
|
+
# include Tenacity
|
7
|
+
#
|
8
|
+
# t_has_many :wheels
|
9
|
+
# t_has_one :dashboard
|
10
|
+
# t_belongs_to :driver
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# == t_belongs_to
|
14
|
+
#
|
15
|
+
# The +t_belongs_to+ association will define a property named after the association.
|
16
|
+
# The example above will create a property named <tt>:driver_id</tt>
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# == t_has_one
|
20
|
+
#
|
21
|
+
# The +t_has_one+ association will not define any new properties on the object, since
|
22
|
+
# the associated object holds the foreign key. If the CouchRest::Model class
|
23
|
+
# is the target of a t_has_one association from another class, then a property
|
24
|
+
# named after the association will be created on the CouchRest::Model object to
|
25
|
+
# hold the foreign key to the other object.
|
26
|
+
#
|
27
|
+
#
|
28
|
+
# == t_has_many
|
29
|
+
#
|
30
|
+
# The +t_has_many+ association will define a property named after the association.
|
31
|
+
# The example above will create a property named <tt>:wheels_ids</tt>
|
32
|
+
#
|
33
|
+
begin
|
34
|
+
require 'couchrest'
|
35
|
+
|
36
|
+
module CouchRest
|
37
|
+
module Model
|
38
|
+
class Base
|
39
|
+
include CouchRest::TenacityInstanceMethods
|
40
|
+
extend CouchRest::TenacityClassMethods
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
rescue LoadError
|
45
|
+
# CouchRest::Model not available
|
46
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module TenacityClassMethods
|
3
|
+
def _t_find(id)
|
4
|
+
get(id)
|
5
|
+
end
|
6
|
+
|
7
|
+
def _t_find_bulk(ids)
|
8
|
+
return [] if ids.nil? || ids.empty?
|
9
|
+
|
10
|
+
docs = []
|
11
|
+
result = database.get_bulk ids
|
12
|
+
result['rows'].each do |row|
|
13
|
+
docs << (row['doc'].nil? ? nil : create_from_database(row['doc']))
|
14
|
+
end
|
15
|
+
docs.reject { |doc| doc.nil? }
|
16
|
+
end
|
17
|
+
|
18
|
+
def _t_find_first_by_associate(property, id)
|
19
|
+
self.send("by_#{property}", :key => id.to_s).first
|
20
|
+
end
|
21
|
+
|
22
|
+
def _t_find_all_by_associate(property, id)
|
23
|
+
self.send("by_#{property}", :key => id.to_s)
|
24
|
+
end
|
25
|
+
|
26
|
+
def _t_initialize_has_many_association(association_id)
|
27
|
+
unless self.respond_to?(has_many_property_name(association_id))
|
28
|
+
property has_many_property_name(association_id), :type => [String]
|
29
|
+
view_by has_many_property_name(association_id)
|
30
|
+
after_save { |record| record.class._t_save_associates(record, association_id) if record.class.respond_to?(:_t_save_associates) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def _t_initialize_belongs_to_association(association_id)
|
35
|
+
property_name = "#{association_id}_id"
|
36
|
+
unless self.respond_to?(property_name)
|
37
|
+
property property_name, :type => String
|
38
|
+
view_by property_name
|
39
|
+
before_save { |record| _t_stringify_belongs_to_value(record, association_id) if self.respond_to?(:_t_stringify_belongs_to_value) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def _t_initialize_has_one_association(association_id)
|
44
|
+
property_name = "#{association_id}_id"
|
45
|
+
unless self.respond_to?(property_name)
|
46
|
+
property property_name, :type => String
|
47
|
+
view_by property_name
|
48
|
+
before_save { |record| _t_stringify_has_one_value(record, association_id) if self.respond_to?(:_t_stringify_has_one_value) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module TenacityInstanceMethods
|
3
|
+
def _t_reload
|
4
|
+
new_doc = database.get(self.id)
|
5
|
+
self.clear
|
6
|
+
new_doc.each { |k,v| self[k] = new_doc[k] }
|
7
|
+
end
|
8
|
+
|
9
|
+
def _t_associate_many(association_id, associate_ids)
|
10
|
+
self.send(has_many_property_name(association_id) + '=', associate_ids.map { |associate_id| associate_id.to_s })
|
11
|
+
end
|
12
|
+
|
13
|
+
def _t_get_associate_ids(association_id)
|
14
|
+
self.send(has_many_property_name(association_id)) || []
|
15
|
+
end
|
16
|
+
|
17
|
+
def _t_clear_associates(association_id)
|
18
|
+
self.send(has_many_property_name(association_id) + '=', [])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,107 @@
|
|
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. If the MongoMapper class
|
24
|
+
# is the target of a t_has_one association from another class, then a property
|
25
|
+
# named after the association will be created on the MongoMapper object to
|
26
|
+
# hold the foreign key to the other object.
|
27
|
+
#
|
28
|
+
#
|
29
|
+
# == t_has_many
|
30
|
+
#
|
31
|
+
# The +t_has_many+ association will define a key named after the association.
|
32
|
+
# The example above will create a key named <tt>:wheels_ids</tt>
|
33
|
+
#
|
34
|
+
module TenacityMongoMapperPlugin
|
35
|
+
module ClassMethods #:nodoc:
|
36
|
+
def _t_find(id)
|
37
|
+
find(id)
|
38
|
+
end
|
39
|
+
|
40
|
+
def _t_find_bulk(ids=[])
|
41
|
+
find(ids)
|
42
|
+
end
|
43
|
+
|
44
|
+
def _t_find_first_by_associate(property, id)
|
45
|
+
first(property => id.to_s)
|
46
|
+
end
|
47
|
+
|
48
|
+
def _t_find_all_by_associate(property, id)
|
49
|
+
all(property => id.to_s)
|
50
|
+
end
|
51
|
+
|
52
|
+
def _t_initialize_has_many_association(association_id)
|
53
|
+
unless self.respond_to?(has_many_property_name(association_id))
|
54
|
+
key has_many_property_name(association_id), Array
|
55
|
+
end
|
56
|
+
after_save { |record| _t_save_associates(record, association_id) }
|
57
|
+
end
|
58
|
+
|
59
|
+
def _t_initialize_belongs_to_association(association_id)
|
60
|
+
unless self.respond_to?("#{association_id}_id")
|
61
|
+
key "#{association_id}_id", String
|
62
|
+
end
|
63
|
+
before_save { |record| _t_stringify_belongs_to_value(record, association_id) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def _t_initialize_has_one_association(association_id)
|
67
|
+
unless self.respond_to?("#{association_id}_id")
|
68
|
+
key "#{association_id}_id", String
|
69
|
+
end
|
70
|
+
before_save { |record| _t_stringify_has_one_value(record, association_id) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module InstanceMethods #:nodoc:
|
75
|
+
def _t_reload
|
76
|
+
reload
|
77
|
+
rescue MongoMapper::DocumentNotFound
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
|
81
|
+
def _t_associate_many(association_id, associate_ids)
|
82
|
+
self.send(has_many_property_name(association_id) + '=', associate_ids.map { |associate_id| associate_id.to_s })
|
83
|
+
end
|
84
|
+
|
85
|
+
def _t_get_associate_ids(association_id)
|
86
|
+
self.send(has_many_property_name(association_id))
|
87
|
+
end
|
88
|
+
|
89
|
+
def _t_clear_associates(association_id)
|
90
|
+
self.send(has_many_property_name(association_id) + '=', [])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
module TenacityPluginAddition #:nodoc:
|
96
|
+
def self.included(model)
|
97
|
+
model.plugin TenacityMongoMapperPlugin
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
begin
|
102
|
+
require 'mongo_mapper'
|
103
|
+
MongoMapper::Document.append_inclusions(TenacityPluginAddition)
|
104
|
+
rescue
|
105
|
+
# MongoMapper not available
|
106
|
+
end
|
107
|
+
|
data/lib/tenacity.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.join('active_support', 'inflector')
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'class_methods')
|
4
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'instance_methods')
|
5
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'associations', 'belongs_to')
|
6
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'associations', 'has_many')
|
7
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'associations', 'has_one')
|
8
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'activerecord')
|
9
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'couchrest', 'tenacity_class_methods')
|
10
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'couchrest', 'tenacity_instance_methods')
|
11
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'couchrest', 'couchrest_extended_document')
|
12
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'couchrest', 'couchrest_model')
|
13
|
+
require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'mongo_mapper')
|
14
|
+
|
15
|
+
module Tenacity #:nodoc:
|
16
|
+
include InstanceMethods
|
17
|
+
|
18
|
+
include BelongsTo
|
19
|
+
include HasMany
|
20
|
+
include HasOne
|
21
|
+
|
22
|
+
def self.included(model)
|
23
|
+
raise "Tenacity does not support the ORM used by #{model}" unless model.respond_to?(:_t_find)
|
24
|
+
model.extend(ClassMethods)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|