Michael Daines
2005-Apr-25  03:10 UTC
Help?/Question about instance methods in ActiveRecord (and probably generally)
I''m working on a method of saving previous versions of only certain 
parts of my tables when the main record is saved. To do this, I''ve 
written something like the ActiveRecord::Acts things that I''ve put in 
my lib/ directory, and then included from the ActiveRecord class I wish 
to use it in.
So, I put the following code in my "Article" class, and versions of
the
fields ''body'', ''short_body'', and
''published'' are saved in a table
called "article_versions":
   saves_versions :of => [:body, :short_body, :published]
This works fine, but I also wanted a way to prevent versions from being 
saved if certain parts of the record are unchanged. If my
''published''
field is toggled and the ''body'' and
''short_body'' fields remain the
same, I don''t want a version with duplicates of those and a different 
''published'' field, because it wastes space in the database.
What I did, then, in the function that saves the versions, is call a 
function called ''save_version?'', which returns true if a
version is to
be saved. In the Article class, I wish to override it with something 
like this:
   def save_version?
     self.attributes_modified?(:body, :short_body)
   end
The "attributes_modified?" function, which looks at the attributes of 
the last version that was saved and the attributes of the current 
version and tells us if any are different, should go with the instance 
methods of this Acts-like thing. (At least, that seems like the most 
appropriate place for it.)
However, if I put it there, it can''t be found when called in the 
context of actually using this stuff in a running application! I get 
"undefined method `attributes_modified?'' for
#<Article:0x54697ec>". If
I put it actually in the Article class, then it works! The whole thing 
tests just fine, though, which is maddening! I''m really not sure what 
I''m doing wrong here, because this seems to be the way the Acts work? 
Please help!
Here is the source, with my apologies for posting all of it: (maybe 
this should be Acts::VersionSaver, but whatever...)
module ActiveRecord
   module SavesVersions
     def self.append_features(base)
       super
       base.extend(ClassMethods)
     end
     module ClassMethods
       def saves_versions(options = {})
         class_eval <<-EOV
           include ActiveRecord::SavesVersions::InstanceMethods
           has_many :versions, :class_name => 
"#{self.name}::#{self.name}Version", :order => ''id
desc'', :dependent =>
true
           def versioned_attributes
             [:#{options[:of].join('', :'')}]
           end
           def versions_class
             #{self.name}::#{self.name}Version
           end
           class #{self.name}Version < ActiveRecord::Base
             belongs_to :#{self.table_name.singularize}
           end
           after_save :save_version
         EOV
       end
     end
     module InstanceMethods
       def save_version
         unless self.save_version? || self.versions.length == 0
           self.versions.delete(self.versions.last)
         end
         self.versions << copy_version_attrs(self, versions_class.new)
       end
       def save_version?
         true
       end
       def attributes_modified?(*attrs)
         return true if self.versions.length == 0
         attrs.each do |attr|
           return true if self[attr] != self.versions.last[attr]
         end
         return false
       end
     private
       def copy_version_attrs(from, to=self.dup)
         self.versioned_attributes.each{|attr| to[attr] = from[attr]}
         to
       end
     end
   end
end