couchobject 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/History.txt +10 -0
  2. data/Manifest.txt +30 -6
  3. data/README.txt +580 -42
  4. data/TODO +2 -2
  5. data/config/hoe.rb +1 -1
  6. data/lib/couch_object.rb +7 -2
  7. data/lib/couch_object/database.rb +19 -34
  8. data/lib/couch_object/document.rb +13 -6
  9. data/lib/couch_object/error_classes.rb +110 -0
  10. data/lib/couch_object/persistable.rb +954 -36
  11. data/lib/couch_object/persistable/has_many_relations_array.rb +91 -0
  12. data/lib/couch_object/persistable/meta_classes.rb +568 -0
  13. data/lib/couch_object/persistable/overloaded_methods.rb +209 -0
  14. data/lib/couch_object/server.rb +1 -1
  15. data/lib/couch_object/utils.rb +44 -0
  16. data/lib/couch_object/version.rb +1 -1
  17. data/lib/couch_object/view.rb +129 -6
  18. data/script/console +0 -0
  19. data/script/destroy +0 -0
  20. data/script/generate +0 -0
  21. data/script/txt2html +0 -0
  22. data/spec/database_spec.rb +23 -31
  23. data/spec/database_spec.rb.orig +173 -0
  24. data/spec/document_spec.rb +21 -3
  25. data/spec/integration/database_integration_spec.rb +46 -15
  26. data/spec/integration/integration_helper.rb +3 -3
  27. data/spec/persistable/callback.rb +44 -0
  28. data/spec/persistable/callback_spec.rb +44 -0
  29. data/spec/persistable/cloning.rb +77 -0
  30. data/spec/persistable/cloning_spec.rb +77 -0
  31. data/spec/persistable/comparing_objects.rb +350 -0
  32. data/spec/persistable/comparing_objects_spec.rb +350 -0
  33. data/spec/persistable/deleting.rb +113 -0
  34. data/spec/persistable/deleting_spec.rb +113 -0
  35. data/spec/persistable/error_messages.rb +32 -0
  36. data/spec/persistable/error_messages_spec.rb +32 -0
  37. data/spec/persistable/loading.rb +339 -0
  38. data/spec/persistable/loading_spec.rb +339 -0
  39. data/spec/persistable/new_methods.rb +70 -0
  40. data/spec/persistable/new_methods_spec.rb +70 -0
  41. data/spec/persistable/persistable_helper.rb +194 -0
  42. data/spec/persistable/relations.rb +470 -0
  43. data/spec/persistable/relations_spec.rb +470 -0
  44. data/spec/persistable/saving.rb +137 -0
  45. data/spec/persistable/saving_spec.rb +137 -0
  46. data/spec/persistable/setting_storage_location.rb +65 -0
  47. data/spec/persistable/setting_storage_location_spec.rb +65 -0
  48. data/spec/persistable/timestamps.rb +76 -0
  49. data/spec/persistable/timestamps_spec.rb +76 -0
  50. data/spec/persistable/unsaved_changes.rb +211 -0
  51. data/spec/persistable/unsaved_changes_spec.rb +211 -0
  52. data/spec/server_spec.rb +5 -5
  53. data/spec/utils_spec.rb +60 -0
  54. data/spec/view_spec.rb +40 -7
  55. data/website/index.html +22 -7
  56. data/website/index.txt +13 -5
  57. metadata +93 -61
  58. data/bin/couch_ruby_view_requestor +0 -81
  59. data/lib/couch_object/model.rb +0 -5
  60. data/lib/couch_object/proc_condition.rb +0 -14
  61. data/spec/model_spec.rb +0 -5
  62. data/spec/persistable_spec.rb +0 -91
  63. 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