Module: CouchObject::Persistable

Defined in:
lib/couch_object/persistable.rb,
lib/couch_object/persistable/meta_classes.rb,
lib/couch_object/persistable/overloaded_methods.rb,
lib/couch_object/persistable/has_many_relations_array.rb

Defined Under Namespace

Modules: ClassMethods Classes: HasManyRelation

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#created_atObject (readonly)

Accessors for instance variables specific to the persistable CouchObject



296
297
298
# File 'lib/couch_object/persistable.rb', line 296

def created_at
  @created_at
end

#idObject (readonly)

Accessors for instance variables specific to the persistable CouchObject



296
297
298
# File 'lib/couch_object/persistable.rb', line 296

def id
  @id
end

#revisionObject (readonly)

Accessors for instance variables specific to the persistable CouchObject



296
297
298
# File 'lib/couch_object/persistable.rb', line 296

def revision
  @revision
end

#updated_atObject (readonly)

Accessors for instance variables specific to the persistable CouchObject



296
297
298
# File 'lib/couch_object/persistable.rb', line 296

def updated_at
  @updated_at
end

Class Method Details

.included(klazz) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
# File 'lib/couch_object/persistable/meta_classes.rb', line 3

def self.included(klazz)
  klazz.extend(ClassMethods)

  # 
  # Using meta programming methods for handling, amongst others,
  # * setting of the database uri at design time
  # * including timestamps
  # * managing relations
  # are created
  # 
  klazz.class_eval do

    ##
    # Timestamps
    ##

    # Timestamps are false by default
    def self.couch_object_timestamp_on_update?; false; end
    def self.couch_object_timestamp_on_create?; false; end

    # 
    # Adds timestamps to the class.
    # 
    # Example:
    # 
    #   class Vacation
    #     include CouchObject::Persistable
    #     add_timestamp_for :on_create, :on_update
    #   end
    #   
    #   my_vacation = Vacation.new
    #   my_vacation.save(db_address)
    #   my_vacation.created_at => Somedate
    #   my_vacation.updated_at => Somedate
    # 
    def self.add_timestamp_for(*timestamp_actions)
      timestamp_actions.each do |action|
        case action
        when :on_create
          self.class_eval do
            def self.couch_object_timestamp_on_create?; true; end
          end
        when :on_update
          self.class_eval do
            def self.couch_object_timestamp_on_update?; true; end
          end
        end
      end
    end



    ##
    # Change monitor
    ##
    
    # to be implemented later.
    # could be implemented using MonitorFunctions
    # (http://www.erikveen.dds.nl/monitorfunctions/)
    
    # Each class monitors it's setters to see if it's content is changed,
    # in which case a flag is set.
    # For this purpose all setters are overridden
    
    # self.instance_variable_set("@couch_has_unsaved_changes_flag", false)
    # def unsaved_changes?
    #   @couch_has_unsaved_changes_flag
    # end
    # puts "Public setters:"
    # self.public_methods.each do |method|
    #   if method.to_s[-1,1] == "="
    #     # We have to create an alias for the original method
    #     DO MAGIC HERE
    #   end
    # end


    ##
    # Database storage location
    ##

    # Location methods are added both as instance methods and 
    # as class level methods. The class level methods are needed
    # when loading new objects from the database and the instance
    # methods are used throughout the class
    def self.location; @couch_object_class_storage_location ||= nil; end
    def location; @location; end
    alias storage_location location

    # 
    # Sets the location of the database to use by default
    # 
    # Example:
    #   
    #   class AppleTree
    #     include CouchObject::Persistable
    #     database 'http://localhost:5984'
    #   end
    #   
    #   apple_tree = AppleTree.new
    #   apple_tree.save # saves automatically to the predefined 
    #                   # database location
    # 
    def self.database(db_uri)
      @couch_object_class_storage_location = db_uri
      self.instance_eval do
        define_method("location") do
          @location ||= db_uri
        end
      end
    end  



    ##
    # Smart savign
    ##
    
    def use_smart_save; false; end
    # 
    # Smart save (defaults to false), if activated, keeps a snapshot of
    # the objects initial state and evaluates if the class needs to be
    # saved to the database by comparing it to the snapshot when a save
    # is requested.
    # 
    # Please notice:
    # Only activate this feature in cases where it is needed.
    # It might slow down the performance of your app if you activate it
    # for classes that you need many instances of and that you won't 
    # call the save method on after having loaded them from the database.
    # Please also bare in mind that the class instance will store an 
    # extra copy of its contents which will lead to quite a big memory
    # overhead for classes that store a lot of data!
    #         
    def self.smart_save
      self.instance_eval do
        define_method("use_smart_save") do 
          true
        end
      end
    end
    # 
    # Smart save can also be used on a per-case basis if it is sometimes
    # needed and sometimes not.
    # 
    # Example:
    # 
    # user_without_smart_save_1 = User.get("foo")
    # User.smart_save
    # user_with_smart_save = User.get("bar")
    # User.deactivate_smart_save
    # user_without_smart_save_2 = User.get("bong")
    # 
    def self.deactivate_smart_save
      self.instance_eval do
        define_method("use_smart_save") do 
          false
        end
      end
    end



    ##
    # Relations
    ##

    # Default values for has_many, belongs_to and belongs_to_as
    def has_many; []; end
    def has_one; []; end
    def has; []; end
    def belongs_to; []; end

    # 
    # Defines a has_many relation which then again
    # needs a corresponding belongs_to relation in the
    # classes the relation is made with (see the documentation
    # of belongs_to below)
    # 
    # Takes:
    # * a symbol indicating the name of the association.
    #   The association name can be freely chosen.
    #   
    # Example:
    # 
    #   has_many :fruits
    #   
    #   Requires a belongs_to relation from the other part. F.ex:
    # 
    #     belongs_to: :fruit_basket, :as => :fruits
    # 
    # Raises:
    # * HasManyAssociationError if the association name
    #   is left blank.
    #
    def self.has_many(what_it_has = nil)
      raise CouchObject::Errors::HasManyAssociationError if what_it_has == nil

      @couch_object_has_many ||= []
      @couch_object_has_many << what_it_has unless \
        @couch_object_has_many.include?(what_it_has)

      self.instance_eval do
        # The objects are stored in this variable
        has_many_object_variable = \
          "@couch_object_#{what_it_has.to_s}"

        #
        # Getter which also works as a setter because:
        # * it returns the array that contains the references
        # * when a new relationship is added using << the action
        #   it performed by the array, and not self
        #
        define_method(what_it_has.to_s) do 
          eval("#{has_many_object_variable}.nil? ? " + \
            "#{has_many_object_variable} = " + \
            "couch_load_has_many_relations(\"#{what_it_has}\") : " + \
            "#{has_many_object_variable}")
        end

        # 
        # Returns:
        # * the name of the relation
        # 
        # Example:
        # 
        #   apple_tree.has_many => :fruits
        #   apple_tree.fruits => [apple1, apple2]
        #             
        all_the_things_it_has = @couch_object_has_many
        define_method("has_many") do
          # Filtering out the has_one relations so they don't show up
          what_is_has_output = []
          self.send(:has).each do |has|
            what_is_has_output << has \
              unless has.to_s[0..7] == "has_one_"
          end
          what_is_has_output
        end
        define_method("has") do
          all_the_things_it_has
        end
      end

    end

    # 
    # Defines a belongs_to relation which then again
    # needs a corresponding has_many relation in the
    # class the relation is made with (see the documentation
    # of has_many above)
    # 
    # Takes:
    # * a symbol indicating the name of the association.
    #   The association name can be freely chosen.
    # *	a symbol that indicates the name the corresponding has_many
    # 	relationship in the owner class
    #   
    # Example:
    # 
    #   belongs_to :fruit_basket, :as => :fruits
    #   
    #   Requires a has_many relation from the other class that looks
    #	  something like this:
    # 
    #     has_many :fruits
    # 
    # Raises:
    # * BelongsToAssociationError if the association name, 
    # or the :as parameter is left blank.
    #
    def self.belongs_to(what_it_belongs_to = nil, as = nil)
      raise CouchObject::Errors::BelongsToAssociationError \
        if what_it_belongs_to.nil? || as.nil?

      @couch_object_what_it_belongs_to ||= []
      @couch_object_what_it_belongs_to << what_it_belongs_to unless \
        @couch_object_what_it_belongs_to.include?(what_it_belongs_to)

      self.instance_eval do
        # The object are stored in this variable
        belongs_to_object_variable = \
          "@couch_object_#{what_it_belongs_to.to_s}"
        
        # Getter
        define_method(what_it_belongs_to.to_s) do
          eval("#{belongs_to_object_variable} ||= " + \
            " couch_load_belongs_to_relation(\"#{as[:as]}\")")
        end

        # Setter
        define_method("#{what_it_belongs_to.to_s}=") do |object_to_add|
          # The first thing we have to do is to check if it is in
          # a has_one or has_many relationship!
          if object_to_add.respond_to?("has_one_#{as[:as]}") || \
              eval("#{belongs_to_object_variable}" + \
              ".respond_to?(:has_one_#{as[:as]})")
            is_a_has_many_relationship = false
          else
            is_a_has_many_relationship = true
          end
            
          # Now... there is no good reason loading a belongs_to relation
          # from the database only to remove the relation with the child,
          # because the relation is stored in the child anyway...
          # We therefore temporarily deactivate the loading of belongs_to
          # relations
          original_state_of_load_belongs_to_relations = \
            @do_not_load_belongs_to_relations
          @do_not_load_belongs_to_relations = true
                    
          if is_a_has_many_relationship
          
            # Remove the original relationship in the master object
            eval("#{belongs_to_object_variable}." \
              + "send(:end_relationsship_with, self, \"#{as[:as]}\" ) " \
              + "unless #{belongs_to_object_variable} == nil")
          
            # Sets the new relation
            instance_variable_set("#{belongs_to_object_variable}", \
              object_to_add)
          
            # Set up the new relationship with the master object
            self.add_relation_to_master(as[:as]) if object_to_add
            
          else
            
            # Remove old relationship and set the new one
            self.set_has_one_relation_to_master(as[:as], nil)

            # Sets the new relation
            instance_variable_set("#{belongs_to_object_variable}", \
              object_to_add)

            # Setup the new relationship                
            self.set_has_one_relation_to_master(as[:as], self) \
              if object_to_add    
            
          end
          
          # And now we reset the @do_not_load_belongs_to_relations
          # variable to its original value:
          @do_not_load_belongs_to_relations = \
            original_state_of_load_belongs_to_relations
        
        end

        # Setter without callback for new objects 
        # from the load relations method
        define_method("#{what_it_belongs_to.to_s}" + \
            "_without_call_back=") do |object_to_add|

          # Sets the new relation
          instance_variable_set("#{belongs_to_object_variable}", \
            object_to_add)
        end

        # 
        # Returns:
        # * the getter for a belongs_to relationship as a symbol
        #  
        # Example:
        # 
        #   fruit.belongs_to => :tree
        #   fruit.tree => <AppleTree>
        #   
        return_value_for_function = @couch_object_what_it_belongs_to
        define_method("belongs_to") do
          return_value_for_function
        end

        # 
        # Returns:
        # * what the corresponding has_many relation is called
        # 
        # Example:
        # 
        #   fruit.belongs_to => :tree
        #   fruit.tree = apple_tree
        #   fruit.belongs_to_as => :fruits
        #   apple_tree.fruits => [fruit]
        #   
        define_method("belongs_to_#{what_it_belongs_to}_as") do
          as[:as]
        end

      end
      
    end

    # 
    # has_one relations are added as a layer to the has_many
    # There is created a has_many relation ship but getters and setters
    # for the has_one relationship on top of that that interact with the 
    # has_many relationship.
    def self.has_one(what_it_has = nil)
      raise CouchObject::Errors::HasOneAssociationError if what_it_has == nil

      related_has_many_relationship = "has_one_#{what_it_has.to_s}".to_sym
      
      # Create the has_many relationship
      self.send(:has_many, related_has_many_relationship)
      
      # Create methods to get and set the relationship

      # getter
      define_method(what_it_has) do
        self.send(related_has_many_relationship).first
      end

      define_method("#{what_it_has}=") do |new_relation|
        # Remove the original relation
        self.send(related_has_many_relationship).
          remove(self.send(related_has_many_relationship).first) \
          unless self.send(related_has_many_relationship) == []
            
        # Disable callbacks
        self.send(related_has_many_relationship).disable_call_back_on_add
        
        if new_relation

          # Create the new
          self.send(related_has_many_relationship) << new_relation \
            unless new_relation == nil
        
          # Set the relationship in the child
          what_it_belongs_to = define_relationship_name(new_relation)

          new_relation.
            send("#{what_it_belongs_to}_without_call_back=", self)

        end
        
        # Reenable callbacks
        self.send(related_has_many_relationship).enable_call_back_on_add            
        
      end
      
      define_method("has_one") do
        what_is_has_output = []
        self.send(:has).each do |has|
          what_is_has_output << has.to_s[8..-1].to_sym \
            if has.to_s[0..7] == "has_one_"
        end
        what_is_has_output
      end
      
    end

    # 
    # Returns the name of the relation in itself matching one of the
    # relations in the other object
    # 
    # Example:
    #   other_object has defined the relationships:
    #     belongs_to :house, :as => :houses
    #     belongs_to :humanity
    #     
    #   self has the relation
    #     has_many :houses
    #     
    #   :houses is returned
    #   
    def define_relationship_name(other_object)

      belongs_to_relationship_name = nil

      other_object.send(:belongs_to).each do |what_it_belongs_to|

        name_of_relation_in_master = other_object.
          send("belongs_to_#{what_it_belongs_to}_as".to_sym)            

        return what_it_belongs_to.to_s \
          if self.respond_to?(name_of_relation_in_master)

      end

      # There couldn't be found a match... raising an error
      raise "The master class #{self} doesn't have a relation" + \
        " matching the relation defined in the child class " \
        if belongs_to_relationship_name == nil

    end

    # 
    # This method is called from the method that assigns a 
    # belongs_to relation to inform the master object of the relation
    # (has_many relations)
    #
    def add_relation_to_master(relation_name)
      
      if master_class = get_master_for_relation(relation_name)
               
        masters_objects_relations = \
          master_class.send(relation_name) 
      
        if masters_objects_relations == []
          masters_objects_relations << self
        else
          unless masters_objects_relations.include?(self)
            masters_objects_relations << self
          end
        end
        
      end
      
    end

    # 
    # This method is called from the method that assigns a 
    # belongs_to relation to inform the master object of the relation
    # (has_one relations)
    #
    def set_has_one_relation_to_master(relation_name, to_what)
      
      if master_class = get_master_for_relation(relation_name)

        # set up the new relationship in the master
        master_class.send("#{relation_name}=", to_what)

      end

    end

    # 
    # This method is called from the method that assigns a 
    # belongs_to relation to inform the previous master object 
    # that the relation ship has ended
    # (has_one relations)
    #
    def end_has_one_relation_to_master(relation_name)

      set_has_one_relation_to_master(relation_name, nil)

    end


    def get_master_for_relation(relation_name)

      accessor_for_what_it_belongs_to = nil
      self.send(:belongs_to).each do |what_it_belongs_to|
        # Only load the belongs to relation that is needed
        # We therefore have to find out which of the relations to use
        find_string = "belongs_to_#{what_it_belongs_to}_as"
        accessor_for_what_it_belongs_to = what_it_belongs_to \
          if self.send(find_string) == relation_name
      end
      
      return nil if accessor_for_what_it_belongs_to == nil

      master_class = self.send(accessor_for_what_it_belongs_to)
      return nil if master_class == nil
      
      raise "The master class doesn't have a matching relation " + \
        "defined" unless master_class.respond_to?(relation_name)

      return master_class
      
    end


  end
end

.use_smart_saveObject



332
333
334
# File 'lib/couch_object/persistable.rb', line 332

def self.use_smart_save
  true
end

Instance Method Details

#<(other_object) ⇒ Object



127
128
129
# File 'lib/couch_object/persistable/overloaded_methods.rb', line 127

def <(other_object)
  other_object > self
end

#<=(other_object) ⇒ Object

<= and >= Uses the results from the methods ==, < and > to determine the result

Takes:

  • other_object to compare size with

Raises:

  • CantCompareSize when trying to compare two object that have not been saved or saved but do not have timestamps and that are not equal

Returns:

  • true or false



145
146
147
# File 'lib/couch_object/persistable/overloaded_methods.rb', line 145

def <=(other_object)
  self == other_object || self < other_object ? true : false
end

#==(other_object) ⇒ Object

Equality is checked for using the following rules:

  • if the object types do not match the result is false

  • if the object_id of the class sent as an argument is the same as the object_id of self, equal returns true.

  • if the two objects have a different class type it fails as a result of the object_id test above which can’t be true

  • fails if the object_id or revision numbers are different

  • it fails if one or both of the objects are new and they don’t share the object_id, because in that case they will be stored as separate documents in the database

  • is true if all instance_variables are the same

Takes:

  • other_object which is any object one wishes to compare self to

Returns:

  • true or false



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/couch_object/persistable/overloaded_methods.rb', line 51

def ==(other_object)
  return false unless self.class == other_object.class
  return true if self.object_id == other_object.object_id
  return false if self.id != other_object.id || \
                  self.revision != other_object.revision
  return false if self.new? || other_object.new?
  
  # All relations should be loaded so that the comparison can be realistic
  has.each do |what_is_has|
    self.send(what_is_has)
    other_object.send(what_is_has) if other_object.
      respond_to?(what_is_has)
  end
  belongs_to.each do |what_it_belongs_to|
    self.send(what_it_belongs_to)
    other_object.send(what_it_belongs_to) if other_object.
      respond_to?(what_it_belongs_to)
  end
  
  self.instance_variables.each do |var|
    return false if eval(var).to_s != \
      other_object.instance_variable_get(var).to_s
  end
  
  # has to be true because else it would already have failed :)
  true      
end

#>(other_object) ⇒ Object

Goal: The goal is to return true for the object which has the newest representation in the database.

Requires:

  • that both objects have timestamps

Returns:

  • false if they are equal objects.

  • true if self has been saved while the other_object has not.

  • false if self hasn’t been saved but the other_object has

  • true if both have been saved and self has been saved more recently.

  • false if both have been saved but the other object more recently.

Takes:

  • other_object to compare size with

Raises:

  • CantCompareSize when trying to compare two object that have not been saved or saved but do not have timestamps

Returns:

  • true or false



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/couch_object/persistable/overloaded_methods.rb', line 104

def >(other_object)
  return false if self == other_object
  unless self.new? && other_object.new?
    if self.class.couch_object_timestamp_on_update? and \
        other_object.class.couch_object_timestamp_on_update?
      return ((self.updated_at || 0).to_i \
              > (other_object.updated_at || 0).to_i) \
              ? true : false
    elsif self.class.couch_object_timestamp_on_create? and \
        other_object.class.couch_object_timestamp_on_create?
      return ((self.created_at || 0).to_i \
              > (other_object.created_at || 0).to_i) \
              ? true : false
    else
      # Both or one of them does not have a timestamp, 
      # so we can't really compare them
      raise CouchObject::Errors::CantCompareSize
    end
  else
    raise CouchObject::Errors::CantCompareSize
  end
  
end

#>=(other_object) ⇒ Object



148
149
150
# File 'lib/couch_object/persistable/overloaded_methods.rb', line 148

def >=(other_object)
  other_object <= self
end

#cloneObject



20
21
22
23
24
25
26
27
# File 'lib/couch_object/persistable/overloaded_methods.rb', line 20

def clone
  new_clone = self.couch_object_origianl_clone
  new_clone.instance_variable_set("@id", nil)
  new_clone.instance_variable_set("@revision", nil)

  # return the new clone
  new_clone
end

#couch_force_smart_saveObject

Forces the instance object into smart save mode



331
332
333
334
335
# File 'lib/couch_object/persistable.rb', line 331

def couch_force_smart_save
  def self.use_smart_save
    true
  end
end

#couch_object_origianl_cloneObject

When saved, clones and dupes are stored as separate documents from the object it originated from

Returns:

  • A new instance of itself where the ID and revision number is set to nil

  • the new instance shares the same storage location as the object it originates from.



19
# File 'lib/couch_object/persistable/overloaded_methods.rb', line 19

alias couch_object_origianl_clone clone

#couch_set_initial_stateObject

Stores the initial value of the instance to a variable for later reference by the unsaved_changes? method



315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/couch_object/persistable.rb', line 315

def couch_set_initial_state
  # For the unsaved_changes? instance method to work, we have to
  # supply a snapshot of what the fresh object looked like.
  # BUT ONLY if the user has activated the smart_save option
  if use_smart_save

    @couch_initial_load = true
    @couch_object_original_state = to_json
    @couch_initial_load = false
    
  end
end

#delete(db_uri = location) ⇒ Object

Any instance should be able to delete itself

Takes:

  • db_uri if not set in the location variable

Returns:

  • true on success

  • false on failure

Note:

  • it also deletes all has_many relations from the database

  • it removes itself from object it belongs to

Raises:

  • CouchObject::Errors::NoDatabaseLocationSet if db_uri is blank AND has not been set on class level



585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
# File 'lib/couch_object/persistable.rb', line 585

def delete(db_uri = location)
  
  perform_callback(:before_delete)
  
  unless new?
    # Raises an error if the location variable hasn't been set
    raise CouchObject::Errors::NoDatabaseLocationSet unless db_uri

    db = CouchObject::Database.open(db_uri)

    # Removes itself from the database
    db.delete(id, revision)
  end
  
  # Remove all relations
  has_many.each do |what_it_has|
    self.send(what_it_has).dup.each do |related_object|
      related_object.delete
    end
  end
  
  # Remove the relationship with it's has many master object
  belongs_to.each do |what_it_belongs_to|
    self_belongs_to_classtype = what_it_belongs_to
            
    self_belongs_to_classtype_as = self.
      send("belongs_to_#{what_it_belongs_to}_as")

    self_belongs_to_class = self.send(self_belongs_to_classtype) unless \
      self_belongs_to_classtype.nil?

    self_belongs_to_class.end_relationsship_with(self,
      self_belongs_to_classtype_as) unless self_belongs_to_class.nil?

    # Remove the relationship with it's belongs_to master from itself
    remove_call = "#{self_belongs_to_classtype.to_s}" + \
      "_without_call_back="
    self.send(remove_call.to_sym, nil) if self_belongs_to_classtype
    
  end
  
  # Reset itself
  @id = nil
  @revision = nil
  @location = nil
  
  perform_callback(:after_delete)
  
  true

end

#do_load_belongs_to_relationsObject Also known as: do_load_belongs_to_relation



838
839
840
# File 'lib/couch_object/persistable.rb', line 838

def do_load_belongs_to_relations
  @do_not_load_belongs_to_relations = false
end

#do_load_has_many_relationsObject Also known as: do_load_has_one_relations, do_load_has_one_relation



820
821
822
# File 'lib/couch_object/persistable.rb', line 820

def do_load_has_many_relations  
  @do_not_load_has_many_relations = false
end

#do_not_load_belongs_to_relationsObject

If you need to access the belongs_to variable without loading the relation if it hasn’t already been loaded, you can call the instance method do_not_load_belongs_to_relations. To reactivate loading so the relation is loaded the next time it is needed, call the instance method do_load_belongs_to_relations.



835
836
837
# File 'lib/couch_object/persistable.rb', line 835

def do_not_load_belongs_to_relations
  @do_not_load_belongs_to_relations = true
end

#do_not_load_has_many_relationsObject Also known as: do_not_load_has_one_relations, do_not_load_has_one_relation, do_not_load_has_many_relation

Sometimes you might want to add an object to a has_many relation without interacting with the other relations at all. In cases like that, when loading all the relations would just cause unnecessary traffic to the database, you can tell the object not to load it has_many relations using this method



817
818
819
# File 'lib/couch_object/persistable.rb', line 817

def do_not_load_has_many_relations
  @do_not_load_has_many_relations = true
end

#dupObject



28
29
30
# File 'lib/couch_object/persistable/overloaded_methods.rb', line 28

def dup
  clone
end

#end_relationsship_with(undersired_object, which_is_stored_as) ⇒ Object

Breaks relations if existing

Takes:

  • undesired_object as a reference to the object the relationship should be broken with

  • which_is_stored_as (string) which is what the relation is stored as.



646
647
648
# File 'lib/couch_object/persistable.rb', line 646

def end_relationsship_with(undersired_object, which_is_stored_as)
  self.send(which_is_stored_as.to_sym).perform_remove(undersired_object)
end

#new?Boolean

Returns:

  • true if the object hasn’t been saved

  • false if the object has previously been stored or is loaded from the document store

Returns:

  • (Boolean)


307
308
309
# File 'lib/couch_object/persistable.rb', line 307

def new?
  id.nil? || revision.nil?
end

#save(db_uri = location) ⇒ Object

Saves the object to the db_uri supplied, or if not set, to the location the object has previously been saved to.

Takes:

Raises:

  • CouchObject::Errors::NoDatabaseLocationSet error if the object doesn’t have a previously set location and the db_uri is nil

Returns:

  • Hash with the id and revision: => “1234”, :revision => “ABC123”

Sub methods might raise:

  • CouchObject::Errors::DatabaseSaveFailed if the save fails



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/couch_object/persistable.rb', line 357

def save(db_uri = location)
  # if the location hasn't been set, set it
  @location ||= db_uri
  
  # Raises an error if the location variable hasn't been set
  raise CouchObject::Errors::NoDatabaseLocationSet unless location
  
  # If it's belongs_to relationships haven't been saved
  # then it has be done first.
  # Saving the master will also automatically save the child
  performed_save = false
  
  # If the belongs_to relationships haven't already been loaded,
  # there is reason to believe that:
  # * The object is new and doesn't have any relation set
  # * The object already knows about it's belongs_to relations
  #   and doesn't need aditional information about them for saving
  # We therefore deactivate the loading of belongs_to relations
  original_state_of_load_belongs_to_relations = \
    @do_not_load_belongs_to_relations
  @do_not_load_belongs_to_relations = true
  
  belongs_to.each do |what_it_belongs_to|
    master_class = self.send(what_it_belongs_to)
    unless master_class == nil || !master_class.new?
      master_class.save
      performed_save = true          
    end
  end
    
  # Reset the do_not_load_belongs_to_relations variable to its 
  # original state
  @do_not_load_belongs_to_relations = \
    original_state_of_load_belongs_to_relations
  
  # If none of the master classes were saved, meaning they weren't new
  # or didn't exist, then we have to manually save this object.
  couch_perform_save unless performed_save

  {:id => @id, :revision => @revision}
  
end

#set_location=(db_uri) ⇒ Object Also known as: set_storage_location=

Sets the location variable manually

Takes:



657
658
659
# File 'lib/couch_object/persistable.rb', line 657

def set_location=(db_uri)
  @location = db_uri == "" ? nil : db_uri
end

#to_jsonObject

serializes this object into JSON

Returns:

  • The values of the class in json format

    Example “class”:“Bike”,“attributes”:{“wheels”:2}



671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
# File 'lib/couch_object/persistable.rb', line 671

def to_json
  parameters = {}
  
  parameters["class"] = self.class
             
  if respond_to?(:to_couch)
    parameters["attributes"] = self.to_couch
  else
    # Find all the instance variables and add them to the 
    # attributes parameter
    p_attributes = {}
    instance_variables = \
      self.instance_variables - ["@location",
                                 "@created_at",
                                 "@updated_at",
                                 "@id",
                                 "@revision",
                                 "@do_not_load_has_many_relations",
                                 "@do_not_load_belongs_to_relations",
                                 "@couch_object_original_state",
                                 "@couch_initial_load",
                                 "@belongs_to"]
    
    # We also have to remove all the objects that are related
    # through belongs_to and has_many relations. They have to be called
    # saved separately
    has.each do |thing_it_has|
      instance_variables = instance_variables - \
        ["@couch_object_#{thing_it_has.to_s}"]
    end
    
    belongs_to.each do |things_it_belongs_to|
      instance_variables = instance_variables - \
        ["@couch_object_#{things_it_belongs_to.to_s}"]  
    end
            
    instance_variables.each do |var|
      p_attributes[var[1..(var.length)]] = self.instance_variable_get(var)
    end
    parameters["attributes"] = p_attributes
  end    
            
  parameters["updated_at"] = Time.now \
    if self.class::couch_object_timestamp_on_update?
  
  unless new?
    parameters["_id"] = id
    parameters["_rev"] = revision
    parameters["created_at"] = created_at \
      if self.class::couch_object_timestamp_on_create?
  else
    parameters["created_at"] = Time.now \
      if self.class::couch_object_timestamp_on_create?
  end
  
  # If it is in belongs_to relationship(s), then that fact 
  # has to be stored in the database
  if belongs_to != [] and !@couch_initial_load # the couch_initial_load
                                               # is to get the initial
                                               # state of the object
                                               # without loading the
                                               # belongs_to relations
                                               # which would start an
                                               # infinite loop.
                                               # NOTE: this is only for
                                               # cases where smart_save
                                               # has been activated.
    
    # No reason to load the belongs_to relations if they haven't
    # already been loaded from the database!
    original_state_of_load_belongs_to_relations = \
      @do_not_load_belongs_to_relations
    @do_not_load_belongs_to_relations = true

    what_it_belongs_to = {}
    self.send(:belongs_to).each do |relation|
      as_what = self.send("belongs_to_#{relation}_as")
      object_it_belongs_to = self.send(relation)
  
      # Unless the relation is unset, in which case it will be of
      # type NilClass, set it.
      what_it_belongs_to[as_what.to_s] = object_it_belongs_to.id || "new" \
        unless object_it_belongs_to.class == NilClass
    end
    
    # Reset the value so they are loaded the next time when needed
    # if that is what the user wants.
    @do_not_load_belongs_to_relations = \
      original_state_of_load_belongs_to_relations


    # We have to make sure the @belongs_to variable contains all changes
    # and all the original values for the keys that haven't changed/
    # relations that haven't been loaded
    original_belongs_to = @belongs_to || {}
    @belongs_to = what_it_belongs_to
    times_through = 1

    # LOOP 1... see below for problem description
    original_belongs_to.each_pair do |key, value|
      times_through += 1
      @belongs_to[key.to_s] = value unless @belongs_to[key.to_s]
    end

    # FIXME:
    # Now... this is a really hacky way to solve this problem
    # and should be improved... Feel free to come up with sollutions
    # Case:
    # If it has a belongs_to relationship that is new and therefore 
    # doesn't have an ID it would normally be written in the @belongs_to 
    # variable as nil. The problem is that if self has previously
    # been saved with another parent object this ID would still come
    # through in the @belongs_to variable updater (see "LOOP 1" above).
    # We therefore assign the ID "new" to all unsaved relations, which we
    # now have to nilify. If we don't the smart save wont work for this 
    # type of cases.

  end
  
  parameters["belongs_to"] = @belongs_to 
  parameters.delete("belongs_to") if @belongs_to == {} or @belongs_to == nil

   # if @couch_initial_load && @belongs_to
  
  begin
    parameters.to_json
  rescue JSON::GeneratorError
    # All strings aren't encoded properly, so we have to force them into
    # UTF-8. 
    # FIXME: The kconv library has some weird artefacts though where
    # a lot of Norwegian (Scandianavian?) letters get turned into
    # asian characters of some sort!
    CouchObject::Utils::decode_strings(parameters).to_json  
  end
  
end

#unsaved_changes?Boolean

Classes WITH smart_save activated: Any instance should be able to know if it has unsaved changes or not. When an instance is loaded from the DB it creates a snapshot of what its variables contain. Based on a comparison between the snapshot and the contents of the instance this method returns true or false.

Classes WITHOUT smart_save activated: Will always return true regardless of what state it is in

A new object will always return true

Returns:

  • true: if it has changes that haven’t been saved to the database

  • fase: if the nothing has changed since it was loaded from the database.

Returns:

  • (Boolean)


559
560
561
562
563
564
565
# File 'lib/couch_object/persistable.rb', line 559

def unsaved_changes?
  return true if new?
  return true unless use_smart_save
  
  @couch_object_original_state == self.to_json ? false : true
  
end