couchobject 0.5.0 → 0.6.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/History.txt +10 -0
- data/Manifest.txt +30 -6
- data/README.txt +580 -42
- data/TODO +2 -2
- data/config/hoe.rb +1 -1
- data/lib/couch_object.rb +7 -2
- data/lib/couch_object/database.rb +19 -34
- data/lib/couch_object/document.rb +13 -6
- data/lib/couch_object/error_classes.rb +110 -0
- data/lib/couch_object/persistable.rb +954 -36
- data/lib/couch_object/persistable/has_many_relations_array.rb +91 -0
- data/lib/couch_object/persistable/meta_classes.rb +568 -0
- data/lib/couch_object/persistable/overloaded_methods.rb +209 -0
- data/lib/couch_object/server.rb +1 -1
- data/lib/couch_object/utils.rb +44 -0
- data/lib/couch_object/version.rb +1 -1
- data/lib/couch_object/view.rb +129 -6
- data/script/console +0 -0
- data/script/destroy +0 -0
- data/script/generate +0 -0
- data/script/txt2html +0 -0
- data/spec/database_spec.rb +23 -31
- data/spec/database_spec.rb.orig +173 -0
- data/spec/document_spec.rb +21 -3
- data/spec/integration/database_integration_spec.rb +46 -15
- data/spec/integration/integration_helper.rb +3 -3
- data/spec/persistable/callback.rb +44 -0
- data/spec/persistable/callback_spec.rb +44 -0
- data/spec/persistable/cloning.rb +77 -0
- data/spec/persistable/cloning_spec.rb +77 -0
- data/spec/persistable/comparing_objects.rb +350 -0
- data/spec/persistable/comparing_objects_spec.rb +350 -0
- data/spec/persistable/deleting.rb +113 -0
- data/spec/persistable/deleting_spec.rb +113 -0
- data/spec/persistable/error_messages.rb +32 -0
- data/spec/persistable/error_messages_spec.rb +32 -0
- data/spec/persistable/loading.rb +339 -0
- data/spec/persistable/loading_spec.rb +339 -0
- data/spec/persistable/new_methods.rb +70 -0
- data/spec/persistable/new_methods_spec.rb +70 -0
- data/spec/persistable/persistable_helper.rb +194 -0
- data/spec/persistable/relations.rb +470 -0
- data/spec/persistable/relations_spec.rb +470 -0
- data/spec/persistable/saving.rb +137 -0
- data/spec/persistable/saving_spec.rb +137 -0
- data/spec/persistable/setting_storage_location.rb +65 -0
- data/spec/persistable/setting_storage_location_spec.rb +65 -0
- data/spec/persistable/timestamps.rb +76 -0
- data/spec/persistable/timestamps_spec.rb +76 -0
- data/spec/persistable/unsaved_changes.rb +211 -0
- data/spec/persistable/unsaved_changes_spec.rb +211 -0
- data/spec/server_spec.rb +5 -5
- data/spec/utils_spec.rb +60 -0
- data/spec/view_spec.rb +40 -7
- data/website/index.html +22 -7
- data/website/index.txt +13 -5
- metadata +93 -61
- data/bin/couch_ruby_view_requestor +0 -81
- data/lib/couch_object/model.rb +0 -5
- data/lib/couch_object/proc_condition.rb +0 -14
- data/spec/model_spec.rb +0 -5
- data/spec/persistable_spec.rb +0 -91
- data/spec/proc_condition_spec.rb +0 -26
@@ -0,0 +1,91 @@
|
|
1
|
+
module CouchObject
|
2
|
+
module Persistable
|
3
|
+
#
|
4
|
+
# This class is used to easily be able to overwrite
|
5
|
+
# functionality in Arrays that enable messaging between classes
|
6
|
+
# that are related
|
7
|
+
#
|
8
|
+
class HasManyRelation < Array
|
9
|
+
def initialize(owner)
|
10
|
+
@owner = owner
|
11
|
+
@call_back_on_add = true
|
12
|
+
super()
|
13
|
+
end
|
14
|
+
|
15
|
+
# This method is needed so that HasManyRelations can be
|
16
|
+
# compared more resonably :)
|
17
|
+
def to_s
|
18
|
+
containing_ids = self.map {|v| [v.id, v.revision]}
|
19
|
+
"<HasManyRelation:#{containing_ids.sort.join(",")}>"
|
20
|
+
end
|
21
|
+
|
22
|
+
# When adding objects from the load relations method the call back
|
23
|
+
# shouldn't be made
|
24
|
+
def disable_call_back_on_add; @call_back_on_add = false; end
|
25
|
+
def enable_call_back_on_add; @call_back_on_add = true; end
|
26
|
+
|
27
|
+
def <<(other_object)
|
28
|
+
# Makes sure there is not more than one reference for each relation
|
29
|
+
return self if self.include?(other_object)
|
30
|
+
|
31
|
+
# Add the new object
|
32
|
+
super(other_object)
|
33
|
+
|
34
|
+
# We have to find the matching local belongs_to relationsship
|
35
|
+
belongs_to_relationship_name = \
|
36
|
+
@owner.define_relationship_name(other_object)
|
37
|
+
|
38
|
+
|
39
|
+
if @call_back_on_add
|
40
|
+
# Perform a callback to the object so it sets it's
|
41
|
+
# belongs_to relationsship
|
42
|
+
|
43
|
+
method_to_call = (belongs_to_relationship_name + "=").to_sym
|
44
|
+
|
45
|
+
unless other_object.send(belongs_to_relationship_name) == @owner
|
46
|
+
other_object.send(method_to_call, @owner)
|
47
|
+
end
|
48
|
+
else
|
49
|
+
# Performs a semi callback to the object so it sets it's
|
50
|
+
# belongs_to relationsship. This semi callback doesn't perform
|
51
|
+
# any callbacks itself
|
52
|
+
|
53
|
+
method_to_call = (belongs_to_relationship_name + \
|
54
|
+
"_without_call_back=").to_sym
|
55
|
+
|
56
|
+
other_object.send(method_to_call, @owner)
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
self
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
alias original_delete delete
|
65
|
+
def delete(object_to_delete)
|
66
|
+
remove(object_to_delete)
|
67
|
+
|
68
|
+
# Issue a delete statement for the object
|
69
|
+
object_to_delete.send(:delete)
|
70
|
+
end
|
71
|
+
|
72
|
+
def remove(object_to_remove)
|
73
|
+
remove_callback(object_to_remove)
|
74
|
+
perform_remove(object_to_remove)
|
75
|
+
end
|
76
|
+
|
77
|
+
def perform_remove(object_to_remove)
|
78
|
+
original_delete(object_to_remove)
|
79
|
+
end
|
80
|
+
|
81
|
+
def remove_callback(other_object)
|
82
|
+
method_to_call = (@owner.define_relationship_name(other_object) + \
|
83
|
+
"_without_call_back=").to_sym
|
84
|
+
other_object.send(method_to_call, nil)
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,568 @@
|
|
1
|
+
module CouchObject
|
2
|
+
module Persistable
|
3
|
+
def self.included(klazz)
|
4
|
+
klazz.extend(ClassMethods)
|
5
|
+
|
6
|
+
#
|
7
|
+
# Using meta programming methods for handling, amongst others,
|
8
|
+
# * setting of the database uri at design time
|
9
|
+
# * including timestamps
|
10
|
+
# * managing relations
|
11
|
+
# are created
|
12
|
+
#
|
13
|
+
klazz.class_eval do
|
14
|
+
|
15
|
+
##
|
16
|
+
# Timestamps
|
17
|
+
##
|
18
|
+
|
19
|
+
# Timestamps are false by default
|
20
|
+
def self.couch_object_timestamp_on_update?; false; end
|
21
|
+
def self.couch_object_timestamp_on_create?; false; end
|
22
|
+
|
23
|
+
#
|
24
|
+
# Adds timestamps to the class.
|
25
|
+
#
|
26
|
+
# Example:
|
27
|
+
#
|
28
|
+
# class Vacation
|
29
|
+
# include CouchObject::Persistable
|
30
|
+
# add_timestamp_for :on_create, :on_update
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# my_vacation = Vacation.new
|
34
|
+
# my_vacation.save(db_address)
|
35
|
+
# my_vacation.created_at => Somedate
|
36
|
+
# my_vacation.updated_at => Somedate
|
37
|
+
#
|
38
|
+
def self.add_timestamp_for(*timestamp_actions)
|
39
|
+
timestamp_actions.each do |action|
|
40
|
+
case action
|
41
|
+
when :on_create
|
42
|
+
self.class_eval do
|
43
|
+
def self.couch_object_timestamp_on_create?; true; end
|
44
|
+
end
|
45
|
+
when :on_update
|
46
|
+
self.class_eval do
|
47
|
+
def self.couch_object_timestamp_on_update?; true; end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
##
|
56
|
+
# Change monitor
|
57
|
+
##
|
58
|
+
|
59
|
+
# to be implemented later.
|
60
|
+
# could be implemented using MonitorFunctions
|
61
|
+
# (http://www.erikveen.dds.nl/monitorfunctions/)
|
62
|
+
|
63
|
+
# Each class monitors it's setters to see if it's content is changed,
|
64
|
+
# in which case a flag is set.
|
65
|
+
# For this purpose all setters are overridden
|
66
|
+
|
67
|
+
# self.instance_variable_set("@couch_has_unsaved_changes_flag", false)
|
68
|
+
# def unsaved_changes?
|
69
|
+
# @couch_has_unsaved_changes_flag
|
70
|
+
# end
|
71
|
+
# puts "Public setters:"
|
72
|
+
# self.public_methods.each do |method|
|
73
|
+
# if method.to_s[-1,1] == "="
|
74
|
+
# # We have to create an alias for the original method
|
75
|
+
# DO MAGIC HERE
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
|
79
|
+
|
80
|
+
##
|
81
|
+
# Database storage location
|
82
|
+
##
|
83
|
+
|
84
|
+
# Location methods are added both as instance methods and
|
85
|
+
# as class level methods. The class level methods are needed
|
86
|
+
# when loading new objects from the database and the instance
|
87
|
+
# methods are used throughout the class
|
88
|
+
def self.location; @couch_object_class_storage_location ||= nil; end
|
89
|
+
def location; @location; end
|
90
|
+
alias storage_location location
|
91
|
+
|
92
|
+
#
|
93
|
+
# Sets the location of the database to use by default
|
94
|
+
#
|
95
|
+
# Example:
|
96
|
+
#
|
97
|
+
# class AppleTree
|
98
|
+
# include CouchObject::Persistable
|
99
|
+
# database 'http://localhost:5984'
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# apple_tree = AppleTree.new
|
103
|
+
# apple_tree.save # saves automatically to the predefined
|
104
|
+
# # database location
|
105
|
+
#
|
106
|
+
def self.database(db_uri)
|
107
|
+
@couch_object_class_storage_location = db_uri
|
108
|
+
self.instance_eval do
|
109
|
+
define_method("location") do
|
110
|
+
@location ||= db_uri
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
|
117
|
+
##
|
118
|
+
# Smart savign
|
119
|
+
##
|
120
|
+
|
121
|
+
def use_smart_save; false; end
|
122
|
+
#
|
123
|
+
# Smart save (defaults to false), if activated, keeps a snapshot of
|
124
|
+
# the objects initial state and evaluates if the class needs to be
|
125
|
+
# saved to the database by comparing it to the snapshot when a save
|
126
|
+
# is requested.
|
127
|
+
#
|
128
|
+
# Please notice:
|
129
|
+
# Only activate this feature in cases where it is needed.
|
130
|
+
# It might slow down the performance of your app if you activate it
|
131
|
+
# for classes that you need many instances of and that you won't
|
132
|
+
# call the save method on after having loaded them from the database.
|
133
|
+
# Please also bare in mind that the class instance will store an
|
134
|
+
# extra copy of its contents which will lead to quite a big memory
|
135
|
+
# overhead for classes that store a lot of data!
|
136
|
+
#
|
137
|
+
def self.smart_save
|
138
|
+
self.instance_eval do
|
139
|
+
define_method("use_smart_save") do
|
140
|
+
true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
#
|
145
|
+
# Smart save can also be used on a per-case basis if it is sometimes
|
146
|
+
# needed and sometimes not.
|
147
|
+
#
|
148
|
+
# Example:
|
149
|
+
#
|
150
|
+
# user_without_smart_save_1 = User.get("foo")
|
151
|
+
# User.smart_save
|
152
|
+
# user_with_smart_save = User.get("bar")
|
153
|
+
# User.deactivate_smart_save
|
154
|
+
# user_without_smart_save_2 = User.get("bong")
|
155
|
+
#
|
156
|
+
def self.deactivate_smart_save
|
157
|
+
self.instance_eval do
|
158
|
+
define_method("use_smart_save") do
|
159
|
+
false
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
|
166
|
+
##
|
167
|
+
# Relations
|
168
|
+
##
|
169
|
+
|
170
|
+
# Default values for has_many, belongs_to and belongs_to_as
|
171
|
+
def has_many; []; end
|
172
|
+
def has_one; []; end
|
173
|
+
def has; []; end
|
174
|
+
def belongs_to; []; end
|
175
|
+
|
176
|
+
#
|
177
|
+
# Defines a has_many relation which then again
|
178
|
+
# needs a corresponding belongs_to relation in the
|
179
|
+
# classes the relation is made with (see the documentation
|
180
|
+
# of belongs_to below)
|
181
|
+
#
|
182
|
+
# Takes:
|
183
|
+
# * a symbol indicating the name of the association.
|
184
|
+
# The association name can be freely chosen.
|
185
|
+
#
|
186
|
+
# Example:
|
187
|
+
#
|
188
|
+
# has_many :fruits
|
189
|
+
#
|
190
|
+
# Requires a belongs_to relation from the other part. F.ex:
|
191
|
+
#
|
192
|
+
# belongs_to: :fruit_basket, :as => :fruits
|
193
|
+
#
|
194
|
+
# Raises:
|
195
|
+
# * HasManyAssociationError if the association name
|
196
|
+
# is left blank.
|
197
|
+
#
|
198
|
+
def self.has_many(what_it_has = nil)
|
199
|
+
raise CouchObject::Errors::HasManyAssociationError if what_it_has == nil
|
200
|
+
|
201
|
+
@couch_object_has_many ||= []
|
202
|
+
@couch_object_has_many << what_it_has unless \
|
203
|
+
@couch_object_has_many.include?(what_it_has)
|
204
|
+
|
205
|
+
self.instance_eval do
|
206
|
+
# The objects are stored in this variable
|
207
|
+
has_many_object_variable = \
|
208
|
+
"@couch_object_#{what_it_has.to_s}"
|
209
|
+
|
210
|
+
#
|
211
|
+
# Getter which also works as a setter because:
|
212
|
+
# * it returns the array that contains the references
|
213
|
+
# * when a new relationship is added using << the action
|
214
|
+
# it performed by the array, and not self
|
215
|
+
#
|
216
|
+
define_method(what_it_has.to_s) do
|
217
|
+
eval("#{has_many_object_variable}.nil? ? " + \
|
218
|
+
"#{has_many_object_variable} = " + \
|
219
|
+
"couch_load_has_many_relations(\"#{what_it_has}\") : " + \
|
220
|
+
"#{has_many_object_variable}")
|
221
|
+
end
|
222
|
+
|
223
|
+
#
|
224
|
+
# Returns:
|
225
|
+
# * the name of the relation
|
226
|
+
#
|
227
|
+
# Example:
|
228
|
+
#
|
229
|
+
# apple_tree.has_many => :fruits
|
230
|
+
# apple_tree.fruits => [apple1, apple2]
|
231
|
+
#
|
232
|
+
all_the_things_it_has = @couch_object_has_many
|
233
|
+
define_method("has_many") do
|
234
|
+
# Filtering out the has_one relations so they don't show up
|
235
|
+
what_is_has_output = []
|
236
|
+
self.send(:has).each do |has|
|
237
|
+
what_is_has_output << has \
|
238
|
+
unless has.to_s[0..7] == "has_one_"
|
239
|
+
end
|
240
|
+
what_is_has_output
|
241
|
+
end
|
242
|
+
define_method("has") do
|
243
|
+
all_the_things_it_has
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
#
|
250
|
+
# Defines a belongs_to relation which then again
|
251
|
+
# needs a corresponding has_many relation in the
|
252
|
+
# class the relation is made with (see the documentation
|
253
|
+
# of has_many above)
|
254
|
+
#
|
255
|
+
# Takes:
|
256
|
+
# * a symbol indicating the name of the association.
|
257
|
+
# The association name can be freely chosen.
|
258
|
+
# * a symbol that indicates the name the corresponding has_many
|
259
|
+
# relationship in the owner class
|
260
|
+
#
|
261
|
+
# Example:
|
262
|
+
#
|
263
|
+
# belongs_to :fruit_basket, :as => :fruits
|
264
|
+
#
|
265
|
+
# Requires a has_many relation from the other class that looks
|
266
|
+
# something like this:
|
267
|
+
#
|
268
|
+
# has_many :fruits
|
269
|
+
#
|
270
|
+
# Raises:
|
271
|
+
# * BelongsToAssociationError if the association name,
|
272
|
+
# or the :as parameter is left blank.
|
273
|
+
#
|
274
|
+
def self.belongs_to(what_it_belongs_to = nil, as = nil)
|
275
|
+
raise CouchObject::Errors::BelongsToAssociationError \
|
276
|
+
if what_it_belongs_to.nil? || as.nil?
|
277
|
+
|
278
|
+
@couch_object_what_it_belongs_to ||= []
|
279
|
+
@couch_object_what_it_belongs_to << what_it_belongs_to unless \
|
280
|
+
@couch_object_what_it_belongs_to.include?(what_it_belongs_to)
|
281
|
+
|
282
|
+
self.instance_eval do
|
283
|
+
# The object are stored in this variable
|
284
|
+
belongs_to_object_variable = \
|
285
|
+
"@couch_object_#{what_it_belongs_to.to_s}"
|
286
|
+
|
287
|
+
# Getter
|
288
|
+
define_method(what_it_belongs_to.to_s) do
|
289
|
+
eval("#{belongs_to_object_variable} ||= " + \
|
290
|
+
" couch_load_belongs_to_relation(\"#{as[:as]}\")")
|
291
|
+
end
|
292
|
+
|
293
|
+
# Setter
|
294
|
+
define_method("#{what_it_belongs_to.to_s}=") do |object_to_add|
|
295
|
+
# The first thing we have to do is to check if it is in
|
296
|
+
# a has_one or has_many relationship!
|
297
|
+
if object_to_add.respond_to?("has_one_#{as[:as]}") || \
|
298
|
+
eval("#{belongs_to_object_variable}" + \
|
299
|
+
".respond_to?(:has_one_#{as[:as]})")
|
300
|
+
is_a_has_many_relationship = false
|
301
|
+
else
|
302
|
+
is_a_has_many_relationship = true
|
303
|
+
end
|
304
|
+
|
305
|
+
# Now... there is no good reason loading a belongs_to relation
|
306
|
+
# from the database only to remove the relation with the child,
|
307
|
+
# because the relation is stored in the child anyway...
|
308
|
+
# We therefore temporarily deactivate the loading of belongs_to
|
309
|
+
# relations
|
310
|
+
original_state_of_load_belongs_to_relations = \
|
311
|
+
@do_not_load_belongs_to_relations
|
312
|
+
@do_not_load_belongs_to_relations = true
|
313
|
+
|
314
|
+
if is_a_has_many_relationship
|
315
|
+
|
316
|
+
# Remove the original relationship in the master object
|
317
|
+
eval("#{belongs_to_object_variable}." \
|
318
|
+
+ "send(:end_relationsship_with, self, \"#{as[:as]}\" ) " \
|
319
|
+
+ "unless #{belongs_to_object_variable} == nil")
|
320
|
+
|
321
|
+
# Sets the new relation
|
322
|
+
instance_variable_set("#{belongs_to_object_variable}", \
|
323
|
+
object_to_add)
|
324
|
+
|
325
|
+
# Set up the new relationship with the master object
|
326
|
+
self.add_relation_to_master(as[:as]) if object_to_add
|
327
|
+
|
328
|
+
else
|
329
|
+
|
330
|
+
# Remove old relationship and set the new one
|
331
|
+
self.set_has_one_relation_to_master(as[:as], nil)
|
332
|
+
|
333
|
+
# Sets the new relation
|
334
|
+
instance_variable_set("#{belongs_to_object_variable}", \
|
335
|
+
object_to_add)
|
336
|
+
|
337
|
+
# Setup the new relationship
|
338
|
+
self.set_has_one_relation_to_master(as[:as], self) \
|
339
|
+
if object_to_add
|
340
|
+
|
341
|
+
end
|
342
|
+
|
343
|
+
# And now we reset the @do_not_load_belongs_to_relations
|
344
|
+
# variable to its original value:
|
345
|
+
@do_not_load_belongs_to_relations = \
|
346
|
+
original_state_of_load_belongs_to_relations
|
347
|
+
|
348
|
+
end
|
349
|
+
|
350
|
+
# Setter without callback for new objects
|
351
|
+
# from the load relations method
|
352
|
+
define_method("#{what_it_belongs_to.to_s}" + \
|
353
|
+
"_without_call_back=") do |object_to_add|
|
354
|
+
|
355
|
+
# Sets the new relation
|
356
|
+
instance_variable_set("#{belongs_to_object_variable}", \
|
357
|
+
object_to_add)
|
358
|
+
end
|
359
|
+
|
360
|
+
#
|
361
|
+
# Returns:
|
362
|
+
# * the getter for a belongs_to relationship as a symbol
|
363
|
+
#
|
364
|
+
# Example:
|
365
|
+
#
|
366
|
+
# fruit.belongs_to => :tree
|
367
|
+
# fruit.tree => <AppleTree>
|
368
|
+
#
|
369
|
+
return_value_for_function = @couch_object_what_it_belongs_to
|
370
|
+
define_method("belongs_to") do
|
371
|
+
return_value_for_function
|
372
|
+
end
|
373
|
+
|
374
|
+
#
|
375
|
+
# Returns:
|
376
|
+
# * what the corresponding has_many relation is called
|
377
|
+
#
|
378
|
+
# Example:
|
379
|
+
#
|
380
|
+
# fruit.belongs_to => :tree
|
381
|
+
# fruit.tree = apple_tree
|
382
|
+
# fruit.belongs_to_as => :fruits
|
383
|
+
# apple_tree.fruits => [fruit]
|
384
|
+
#
|
385
|
+
define_method("belongs_to_#{what_it_belongs_to}_as") do
|
386
|
+
as[:as]
|
387
|
+
end
|
388
|
+
|
389
|
+
end
|
390
|
+
|
391
|
+
end
|
392
|
+
|
393
|
+
#
|
394
|
+
# has_one relations are added as a layer to the has_many
|
395
|
+
# There is created a has_many relation ship but getters and setters
|
396
|
+
# for the has_one relationship on top of that that interact with the
|
397
|
+
# has_many relationship.
|
398
|
+
def self.has_one(what_it_has = nil)
|
399
|
+
raise CouchObject::Errors::HasOneAssociationError if what_it_has == nil
|
400
|
+
|
401
|
+
related_has_many_relationship = "has_one_#{what_it_has.to_s}".to_sym
|
402
|
+
|
403
|
+
# Create the has_many relationship
|
404
|
+
self.send(:has_many, related_has_many_relationship)
|
405
|
+
|
406
|
+
# Create methods to get and set the relationship
|
407
|
+
|
408
|
+
# getter
|
409
|
+
define_method(what_it_has) do
|
410
|
+
self.send(related_has_many_relationship).first
|
411
|
+
end
|
412
|
+
|
413
|
+
define_method("#{what_it_has}=") do |new_relation|
|
414
|
+
# Remove the original relation
|
415
|
+
self.send(related_has_many_relationship).
|
416
|
+
remove(self.send(related_has_many_relationship).first) \
|
417
|
+
unless self.send(related_has_many_relationship) == []
|
418
|
+
|
419
|
+
# Disable callbacks
|
420
|
+
self.send(related_has_many_relationship).disable_call_back_on_add
|
421
|
+
|
422
|
+
if new_relation
|
423
|
+
|
424
|
+
# Create the new
|
425
|
+
self.send(related_has_many_relationship) << new_relation \
|
426
|
+
unless new_relation == nil
|
427
|
+
|
428
|
+
# Set the relationship in the child
|
429
|
+
what_it_belongs_to = define_relationship_name(new_relation)
|
430
|
+
|
431
|
+
new_relation.
|
432
|
+
send("#{what_it_belongs_to}_without_call_back=", self)
|
433
|
+
|
434
|
+
end
|
435
|
+
|
436
|
+
# Reenable callbacks
|
437
|
+
self.send(related_has_many_relationship).enable_call_back_on_add
|
438
|
+
|
439
|
+
end
|
440
|
+
|
441
|
+
define_method("has_one") do
|
442
|
+
what_is_has_output = []
|
443
|
+
self.send(:has).each do |has|
|
444
|
+
what_is_has_output << has.to_s[8..-1].to_sym \
|
445
|
+
if has.to_s[0..7] == "has_one_"
|
446
|
+
end
|
447
|
+
what_is_has_output
|
448
|
+
end
|
449
|
+
|
450
|
+
end
|
451
|
+
|
452
|
+
#
|
453
|
+
# Returns the name of the relation in itself matching one of the
|
454
|
+
# relations in the other object
|
455
|
+
#
|
456
|
+
# Example:
|
457
|
+
# other_object has defined the relationships:
|
458
|
+
# belongs_to :house, :as => :houses
|
459
|
+
# belongs_to :humanity
|
460
|
+
#
|
461
|
+
# self has the relation
|
462
|
+
# has_many :houses
|
463
|
+
#
|
464
|
+
# :houses is returned
|
465
|
+
#
|
466
|
+
def define_relationship_name(other_object)
|
467
|
+
|
468
|
+
belongs_to_relationship_name = nil
|
469
|
+
|
470
|
+
other_object.send(:belongs_to).each do |what_it_belongs_to|
|
471
|
+
|
472
|
+
name_of_relation_in_master = other_object.
|
473
|
+
send("belongs_to_#{what_it_belongs_to}_as".to_sym)
|
474
|
+
|
475
|
+
return what_it_belongs_to.to_s \
|
476
|
+
if self.respond_to?(name_of_relation_in_master)
|
477
|
+
|
478
|
+
end
|
479
|
+
|
480
|
+
# There couldn't be found a match... raising an error
|
481
|
+
raise "The master class #{self} doesn't have a relation" + \
|
482
|
+
" matching the relation defined in the child class " \
|
483
|
+
if belongs_to_relationship_name == nil
|
484
|
+
|
485
|
+
end
|
486
|
+
|
487
|
+
#
|
488
|
+
# This method is called from the method that assigns a
|
489
|
+
# belongs_to relation to inform the master object of the relation
|
490
|
+
# (has_many relations)
|
491
|
+
#
|
492
|
+
def add_relation_to_master(relation_name)
|
493
|
+
|
494
|
+
if master_class = get_master_for_relation(relation_name)
|
495
|
+
|
496
|
+
masters_objects_relations = \
|
497
|
+
master_class.send(relation_name)
|
498
|
+
|
499
|
+
if masters_objects_relations == []
|
500
|
+
masters_objects_relations << self
|
501
|
+
else
|
502
|
+
unless masters_objects_relations.include?(self)
|
503
|
+
masters_objects_relations << self
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
end
|
508
|
+
|
509
|
+
end
|
510
|
+
|
511
|
+
#
|
512
|
+
# This method is called from the method that assigns a
|
513
|
+
# belongs_to relation to inform the master object of the relation
|
514
|
+
# (has_one relations)
|
515
|
+
#
|
516
|
+
def set_has_one_relation_to_master(relation_name, to_what)
|
517
|
+
|
518
|
+
if master_class = get_master_for_relation(relation_name)
|
519
|
+
|
520
|
+
# set up the new relationship in the master
|
521
|
+
master_class.send("#{relation_name}=", to_what)
|
522
|
+
|
523
|
+
end
|
524
|
+
|
525
|
+
end
|
526
|
+
|
527
|
+
#
|
528
|
+
# This method is called from the method that assigns a
|
529
|
+
# belongs_to relation to inform the previous master object
|
530
|
+
# that the relation ship has ended
|
531
|
+
# (has_one relations)
|
532
|
+
#
|
533
|
+
def end_has_one_relation_to_master(relation_name)
|
534
|
+
|
535
|
+
set_has_one_relation_to_master(relation_name, nil)
|
536
|
+
|
537
|
+
end
|
538
|
+
|
539
|
+
|
540
|
+
def get_master_for_relation(relation_name)
|
541
|
+
|
542
|
+
accessor_for_what_it_belongs_to = nil
|
543
|
+
self.send(:belongs_to).each do |what_it_belongs_to|
|
544
|
+
# Only load the belongs to relation that is needed
|
545
|
+
# We therefore have to find out which of the relations to use
|
546
|
+
find_string = "belongs_to_#{what_it_belongs_to}_as"
|
547
|
+
accessor_for_what_it_belongs_to = what_it_belongs_to \
|
548
|
+
if self.send(find_string) == relation_name
|
549
|
+
end
|
550
|
+
|
551
|
+
return nil if accessor_for_what_it_belongs_to == nil
|
552
|
+
|
553
|
+
master_class = self.send(accessor_for_what_it_belongs_to)
|
554
|
+
return nil if master_class == nil
|
555
|
+
|
556
|
+
raise "The master class doesn't have a matching relation " + \
|
557
|
+
"defined" unless master_class.respond_to?(relation_name)
|
558
|
+
|
559
|
+
return master_class
|
560
|
+
|
561
|
+
end
|
562
|
+
|
563
|
+
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
567
|
+
end
|
568
|
+
end
|