tenacity 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +6 -0
  2. data/EXTEND.rdoc +79 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +70 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +103 -0
  7. data/Rakefile +96 -0
  8. data/lib/tenacity/associations/belongs_to.rb +28 -0
  9. data/lib/tenacity/associations/has_many.rb +86 -0
  10. data/lib/tenacity/associations/has_one.rb +40 -0
  11. data/lib/tenacity/class_methods.rb +226 -0
  12. data/lib/tenacity/instance_methods.rb +31 -0
  13. data/lib/tenacity/orm_ext/activerecord.rb +116 -0
  14. data/lib/tenacity/orm_ext/couchrest/couchrest_extended_document.rb +45 -0
  15. data/lib/tenacity/orm_ext/couchrest/couchrest_model.rb +46 -0
  16. data/lib/tenacity/orm_ext/couchrest/tenacity_class_methods.rb +52 -0
  17. data/lib/tenacity/orm_ext/couchrest/tenacity_instance_methods.rb +21 -0
  18. data/lib/tenacity/orm_ext/mongo_mapper.rb +107 -0
  19. data/lib/tenacity/version.rb +3 -0
  20. data/lib/tenacity.rb +27 -0
  21. data/tenacity.gemspec +34 -0
  22. data/test/associations/belongs_to_test.rb +119 -0
  23. data/test/associations/has_many_test.rb +184 -0
  24. data/test/associations/has_one_test.rb +77 -0
  25. data/test/core/classmethods_test.rb +53 -0
  26. data/test/fixtures/active_record_car.rb +6 -0
  27. data/test/fixtures/active_record_climate_control_unit.rb +5 -0
  28. data/test/fixtures/active_record_nuts.rb +5 -0
  29. data/test/fixtures/couch_rest_radio.rb +10 -0
  30. data/test/fixtures/mongo_mapper_button.rb +6 -0
  31. data/test/fixtures/mongo_mapper_dashboard.rb +10 -0
  32. data/test/fixtures/mongo_mapper_wheel.rb +9 -0
  33. data/test/helpers/active_record_test_helper.rb +7 -0
  34. data/test/helpers/couch_rest_test_helper.rb +8 -0
  35. data/test/helpers/mongo_mapper_test_helper.rb +3 -0
  36. data/test/orm_ext/activerecord_test.rb +85 -0
  37. data/test/orm_ext/couchrest_test.rb +95 -0
  38. data/test/orm_ext/mongo_mapper_test.rb +93 -0
  39. data/test/test_helper.rb +47 -0
  40. 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
+
@@ -0,0 +1,3 @@
1
+ module Tenacity
2
+ VERSION = "0.1.0"
3
+ end
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
+