# HG changeset patch # User Tyler Rick # Date 1267166651 28800 # Node ID 160ee2cae2ae0c8c01ec146d268b7d532b03c115 # Parent 80300ab7a55f3b59856cfb84b9ccc1310e69b9e8 Made it so that find, first, last, and any missing methods (this would include the "dynamic" finders such as find_by_name) called on the collection_with_deferred_save collection are passed on to the collection_without_deferred_save collection. (Which means they will operate strictly on what's in the database and not take into account what's in unsaved_collection.) diff -r 80300ab7a55f3b59856cfb84b9ccc1310e69b9e8 -r 160ee2cae2ae0c8c01ec146d268b7d532b03c115 .gitignore --- a/.gitignore Thu Feb 25 17:39:17 2010 -0800 +++ b/.gitignore Thu Feb 25 22:44:11 2010 -0800 @@ -1,1 +1,2 @@ pkg +*.db diff -r 80300ab7a55f3b59856cfb84b9ccc1310e69b9e8 -r 160ee2cae2ae0c8c01ec146d268b7d532b03c115 lib/has_and_belongs_to_many_with_deferred_save.rb --- a/lib/has_and_belongs_to_many_with_deferred_save.rb Thu Feb 25 17:39:17 2010 -0800 +++ b/lib/has_and_belongs_to_many_with_deferred_save.rb Thu Feb 25 22:44:11 2010 -0800 @@ -21,7 +21,7 @@ has_and_belongs_to_many *args collection_name = args[0].to_s collection_singular_ids = collection_name.singularize + "_ids" - + # this will delete all the assocation into the join table after obj.destroy after_destroy { |record| record.save } @@ -63,18 +63,18 @@ define_method "before_save_with_deferred_save_for_#{collection_name}" do # Question: Why do we need this @use_original_collection_reader_behavior stuff? - # Answer: Because AssociationCollection#replace(other_array) performs a diff between current_array and other_array and deletes/adds only + # Answer: Because AssociationCollection#replace(other_array) performs a diff between current_array and other_array and deletes/adds only # records that have changed. - # In order to perform that diff, it needs to figure out what "current_array" is, so it calls our collection_with_deferred_save, not + # In order to perform that diff, it needs to figure out what "current_array" is, so it calls our collection_with_deferred_save, not # knowing that we've changed its behavior. It expects that method to return the elements of that collection that are in the *database* # (the original behavior), so we have to provide that behavior... If we didn't provide it, it would end up trying to take the diff of # two identical collections so nothing would ever get saved. - # But we only want the old behavior in this case -- most of the time we want the *new* behavior -- so we use + # But we only want the old behavior in this case -- most of the time we want the *new* behavior -- so we use # @use_original_collection_reader_behavior as a switch. - + if self.respond_to? :"before_save_without_deferred_save_for_#{collection_name}" - self.send("before_save_without_deferred_save_for_#{collection_name}") - end + self.send("before_save_without_deferred_save_for_#{collection_name}") + end self.send "use_original_collection_reader_behavior_for_#{collection_name}=", true if self.send("unsaved_#{collection_name}").nil? @@ -103,20 +103,20 @@ define_method "initialize_unsaved_#{collection_name}" do |*args| #puts "Initialized to #{self.send("#{collection_name}_without_deferred_save").clone.inspect}" self.send "unsaved_#{collection_name}=", self.send("#{collection_name}_without_deferred_save", *args).clone - # /\ We initialize it to collection_without_deferred_save in case they just loaded the object from the + # /\ We initialize it to collection_without_deferred_save in case they just loaded the object from the # database, in which case we want unsaved_collection to start out with the "saved collection". - # If they just constructed a *new* object, this will still work, because self.collection_without_deferred_save.clone + # If they just constructed a *new* object, this will still work, because self.collection_without_deferred_save.clone # will return a new HasAndBelongsToManyAssociation (which acts like an empty array, []). # Important: If we don't use clone, then it does an assignment by reference and any changes to unsaved_collection # will also change *collection_without_deferred_save*! (Not what we want! Would result in us saving things # immediately, which is exactly what we're trying to avoid.) - + # trick collection_name.include?(obj) # If you use a collection of SignelTableInheritance and didn't :select 'type' the # include? method will not find any subclassed object. class << eval("@unsaved_#{collection_name}") def include_with_deferred_save?(obj) - if self.find { |itm| itm == obj || (itm[:id] == obj[:id] && obj.is_a?(itm.class) ) } + if self.detect { |itm| itm == obj || (itm[:id] == obj[:id] && obj.is_a?(itm.class) ) } return true else return false @@ -124,9 +124,32 @@ end alias_method_chain :include?, 'deferred_save' end + + + collection_without_deferred_save = self.send("#{collection_name}_without_deferred_save") + # (We don't have access to locals inside a normal class << object block, so we have to do it this way instead.) + (class << eval("@unsaved_#{collection_name}"); self end).class_eval do + define_method :find do |*args| + collection_without_deferred_save.send(:find, *args) + end + # We have to override these so that it doesn't call Array's version of these methods. + # Otherwise user will get a "can't convert Hash into Integer" error + define_method :first do |*args| + collection_without_deferred_save.send(:first, *args) + end + define_method :last do |*args| + collection_without_deferred_save.send(:first, *args) + end + + define_method :method_missing do |method, *args| + #puts "#{self.class}.method_missing(#{method}) (#{collection_without_deferred_save.inspect})" + collection_without_deferred_save.send(method, *args) + end + end + end private :"initialize_unsaved_#{collection_name}" - + end end end diff -r 80300ab7a55f3b59856cfb84b9ccc1310e69b9e8 -r 160ee2cae2ae0c8c01ec146d268b7d532b03c115 spec/has_and_belongs_to_many_with_deferred_save_spec.rb --- a/spec/has_and_belongs_to_many_with_deferred_save_spec.rb Thu Feb 25 17:39:17 2010 -0800 +++ b/spec/has_and_belongs_to_many_with_deferred_save_spec.rb Thu Feb 25 22:44:11 2010 -0800 @@ -5,9 +5,9 @@ describe "room maximum_occupancy" do before :all do @people = [] - @people << Person.create - @people << Person.create - @people << Person.create + @people << Person.create(:name => 'Filbert') + @people << Person.create(:name => 'Miguel') + @people << Person.create(:name => 'Rainer') @room = Room.new(:maximum_occupancy => 2) end after :all do @@ -118,6 +118,19 @@ @people[2].errors.on(:rooms).should == "This room has reached its maximum occupancy" @room.reload.people.size.should == 2 end + + it "still lets you do find" do + #@room.people2. find(:first, :conditions => {:name => 'Filbert'}).should == @people[0] + #@room.people_without_deferred_save.find(:first, :conditions => {:name => 'Filbert'}).should == @people[0] + #@room.people2.first(:conditions => {:name => 'Filbert'}).should == @people[0] + #@room.people_without_deferred_save.first(:conditions => {:name => 'Filbert'}).should == @people[0] + #@room.people_without_deferred_save.find_by_name('Filbert').should == @people[0] + + @room.people.find(:first, :conditions => {:name => 'Filbert'}).should == @people[0] + @room.people.first(:conditions => {:name => 'Filbert'}). should == @people[0] + @room.people.last(:conditions => {:name => 'Filbert'}). should == @people[0] + @room.people.find_by_name('Filbert'). should == @people[0] + end end describe "doors" do diff -r 80300ab7a55f3b59856cfb84b9ccc1310e69b9e8 -r 160ee2cae2ae0c8c01ec146d268b7d532b03c115 spec/models/person.rb --- a/spec/models/person.rb Thu Feb 25 17:39:17 2010 -0800 +++ b/spec/models/person.rb Thu Feb 25 22:44:11 2010 -0800 @@ -1,7 +1,3 @@ class Person < ActiveRecord::Base has_and_belongs_to_many_with_deferred_save :rooms, :validate => true - - def validate - - end end diff -r 80300ab7a55f3b59856cfb84b9ccc1310e69b9e8 -r 160ee2cae2ae0c8c01ec146d268b7d532b03c115 spec/models/room.rb --- a/spec/models/room.rb Thu Feb 25 17:39:17 2010 -0800 +++ b/spec/models/room.rb Thu Feb 25 22:44:11 2010 -0800 @@ -1,5 +1,6 @@ class Room < ActiveRecord::Base has_and_belongs_to_many_with_deferred_save :people, :before_add => :before_adding_person + has_and_belongs_to_many :people2, :class_name => 'Person' has_and_belongs_to_many_with_deferred_save :doors def validate