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