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.
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
+