Commit b13a7b3b4b7a911f89376868b68b11f983e57ff6

Performance improvement. Doesn't load belongs_to relationships when it doesn't have to

Commit diff

lib/couch_object/persistable.rb

 
364364 # If it's belongs_to relationships haven't been saved
365365 # then it has be done first.
366366 # Saving the master will also automatically save the child
367
368367 performed_save = false
368
369 # If the belongs_to relationships haven't already been loaded,
370 # there is reason to believe that:
371 # * The object is new and doesn't have any relation set
372 # * The object already knows about it's belongs_to relations
373 # and doesn't need aditional information about them for saving
374 # We therefore deactivate the loading of belongs_to relations
375 original_state_of_load_belongs_to_relations = \
376 @do_not_load_belongs_to_relations
377 @do_not_load_belongs_to_relations = true
378
369379 belongs_to.each do |what_it_belongs_to|
370380 master_class = self.send(what_it_belongs_to)
371381 unless master_class == nil || !master_class.new?
384384 end
385385 end
386386
387 # Reset the do_not_load_belongs_to_relations variable to its
388 # original state
389 @do_not_load_belongs_to_relations = \
390 original_state_of_load_belongs_to_relations
391
387392 # If none of the master classes were saved, meaning they weren't new
388393 # or didn't exist, then we have to manually save this object.
389394 couch_perform_save unless performed_save
680680 "@id",
681681 "@revision",
682682 "@do_not_load_has_many_relations",
683 "@do_not_load_belongs_to_relations",
683684 "@couch_object_original_state",
684685 "@couch_initial_load",
685686 "@belongs_to"]
729729 # NOTE: this is only for
730730 # cases where smart_save
731731 # has been activated.
732
733 # No reason to load the belongs_to relations if they haven't
734 # already been loaded from the database!
735 original_state_of_load_belongs_to_relations = \
736 @do_not_load_belongs_to_relations
737 @do_not_load_belongs_to_relations = true
738
732739 what_it_belongs_to = {}
733740 self.send(:belongs_to).each do |relation|
734741 as_what = self.send("belongs_to_#{relation}_as")
743743
744744 # Unless the relation is unset, in which case it will be of
745745 # type NilClass, set it.
746 what_it_belongs_to[as_what] = object_it_belongs_to.id \
746 what_it_belongs_to[as_what.to_s] = object_it_belongs_to.id || "new" \
747747 unless object_it_belongs_to.class == NilClass
748748 end
749 parameters["belongs_to"] = what_it_belongs_to
749
750 # Reset the value so they are loaded the next time when needed
751 # if that is what the user wants.
752 @do_not_load_belongs_to_relations = \
753 original_state_of_load_belongs_to_relations
754
755
756 # We have to make sure the @belongs_to variable contains all changes
757 # and all the original values for the keys that haven't changed/
758 # relations that haven't been loaded
759 original_belongs_to = @belongs_to || {}
760 @belongs_to = what_it_belongs_to
761 times_through = 1
762
763 # LOOP 1... see below for problem description
764 original_belongs_to.each_pair do |key, value|
765 times_through += 1
766 @belongs_to[key.to_s] = value unless @belongs_to[key.to_s]
767 end
768
769 # FIXME:
770 # Now... this is a really hacky way to solve this problem
771 # and should be improved... Feel free to come up with sollutions
772 # Case:
773 # If it has a belongs_to relationship that is new and therefore
774 # doesn't have an ID it would normally be written in the @belongs_to
775 # variable as nil. The problem is that if self has previously
776 # been saved with another parent object this ID would still come
777 # through in the @belongs_to variable updater (see "LOOP 1" above).
778 # We therefore assign the ID "new" to all unsaved relations, which we
779 # now have to nilify. If we don't the smart save wont work for this
780 # type of cases.
781
750782 end
751783
752 parameters["belongs_to"] = @belongs_to if @couch_initial_load && @belongs_to
784 parameters["belongs_to"] = @belongs_to
785 parameters.delete("belongs_to") if @belongs_to == {} or @belongs_to == nil
786
787 # if @couch_initial_load && @belongs_to
753788
754789 begin
755790 parameters.to_json
814814 def do_load_has_many_relations
815815 @do_not_load_has_many_relations = false
816816 end
817 alias do_not_load_has_one_relations do_not_load_has_many_relations
818 alias do_load_has_one_relations do_load_has_many_relations
819 alias do_not_load_has_one_relation do_not_load_has_many_relations
820 alias do_load_has_one_relation do_load_has_many_relations
821
822 #
823 # If you need to access the belongs_to variable without loading the
824 # relation if it hasn't already been loaded, you can call the instance
825 # method +do_not_load_belongs_to_relations+. To reactivate loading so
826 # the relation is loaded the next time it is needed, call the instance
827 # method +do_load_belongs_to_relations+.
828 #
829 def do_not_load_belongs_to_relations
830 @do_not_load_belongs_to_relations = true
831 end
832 def do_load_belongs_to_relations
833 @do_not_load_belongs_to_relations = false
834 end
835 alias do_not_load_has_many_relation do_not_load_has_many_relations
836 alias do_load_belongs_to_relation do_load_belongs_to_relations
817837
818838
819839 protected
910910 #
911911 def couch_load_belongs_to_relation(that_is_called)
912912
913 return nil if new? || @do_not_load_belongs_to_relations
914
913915 # If it doesn't have a belongs to ID then there is no
914916 # related object in the database
915917 if @belongs_to && @belongs_to[that_is_called]
toggle raw diff

lib/couch_object/persistable/meta_classes.rb

 
301301 else
302302 is_a_has_many_relationship = true
303303 end
304
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
305313
306314 if is_a_has_many_relationship
307315
339339 if object_to_add
340340
341341 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
342348 end
343349
344350 # Setter without callback for new objects
toggle raw diff

lib/couch_object/utils.rb

 
4343 case p.class.to_s.downcase
4444 when "fixnum", "bignum", "trueclass", "falseclass"
4545 "#{p.to_s}"
46 when "nilclass"
47 nil
4648 else
4749 # Sanitize forward slashes
4850 # # / => \/
toggle raw diff

spec/persistable/relations.rb

 
268268 @apartment.save("foo")
269269 @apartment.new?.should == false
270270 end
271
272 it "should not load the belongs_to relations if they are not already loaded before saving" do
273 @inhabitant_content = %{ {"_id":"50F1A07A283C1879057AFE4C4B727D35","_rev":"1957693345","belongs_to":{"inhabitants":"12A64B5156002BFD66F23F559C5AD5FA"},"class":"Inhabitant","attributes":{}}
274 }
275
276 response = HTTPResponse.new(@inhabitant_content)
277
278 CouchObject::Database.should_receive(:open).exactly(1).and_return(@db)
279
280 @db.should_receive(:get).once.with("foo").and_return(response)
281
282 inhabitant = Inhabitant.get_with_smart_save("foo")
283 puts "\boriginal: #{inhabitant.instance_variable_get("@couch_object_original_state")}"
284 puts " now: #{inhabitant.to_json}"
285 inhabitant.location.should_not == nil
286
287 # Telling the object not to load its relatives
288 inhabitant.save
289
290 end
271291end
272292
273293describe CouchObject::Persistable, "loading object relations: " do
369369
370370 end
371371
372 it "should be possible to tell the object not to load its relatives" do
372 it "should be possible to tell the object not to load its has many relations" do
373373 response = HTTPResponse.new(@apartment_building_content)
374374
375375 CouchObject::Database.should_receive(:open).exactly(1).and_return(@db)
386386 apartment_building.inhabitants.size.should == 0
387387
388388 end
389
390 it "should be possible to tell the object not to load its has many relations" do
391 response = HTTPResponse.new(@inhabitant_content)
392
393 CouchObject::Database.should_receive(:open).exactly(1).and_return(@db)
394
395 @db.should_receive(:get).once.with("foo").and_return(response)
396
397 inhabitant = Inhabitant.get("foo")
398 inhabitant.location.should_not == nil
399
400 # Telling the object not to load its relatives
401 inhabitant.do_not_load_belongs_to_relations
402
403 # The content should be loaded on first request!
404 inhabitant.apartment_building.should == nil
405
406 end
407
389408end
390409
391410describe CouchObject::Persistable, "has_one relations" do
toggle raw diff

spec/persistable/unsaved_changes.rb

 
8888
8989 end
9090
91
9291 it "should save an object that does not unsaved changes if it hasn't activated smart_save" do
9392 response = HTTPResponse.new(@normal_content_motor_bike)
9493
133133
134134 end
135135
136
137136 it "should only save relations that need to be saved" do
138137
139138 response = HTTPResponse.new(@apartment_building_content)
164164
165165 end
166166
167 it "a class in a belongs_to relationship should notice if it parent changes" do
167 it "a class in a belongs_to relationship should notice if its parent changes" do
168168
169169 response = HTTPResponse.new(@apartment_building_content)
170170 response_relation = HTTPResponse.new(@apartment_building_inhabitants)
198198 second_apartment_building.inhabitants.size.should == 1
199199 apartment_building.inhabitants.size.should == 1
200200 apartment_building.inhabitants.first.unsaved_changes?.should == false
201 puts "Original: #{second_apartment_building.inhabitants.first.instance_variable_get("@couch_object_original_state")}"
202 puts " Now: #{second_apartment_building.inhabitants.first.to_json}"
203
204 puts "second_apartment_building.id '#{second_apartment_building.id == nil}'"
201205 second_apartment_building.inhabitants.first.unsaved_changes?.should == true
206
202207
203208 # Should not save anything at all...
204209 apartment_building.save
toggle raw diff