acts_as_multi_versioned

Hi, I've been playing around with something I call acts_as_multi_versioned. The difference from acts_as_versioned is that you can work with different versions simultaneously. For example if one session sees one version, and another session sees another. It's designed like this:

All tables need to have two primary-keys, id (autoincrement) and version (not autoincrement). There is no extra table for older versions, everything is in the same table.

The variable ActiveRecord::Base.current_version is used to define what version use currently use. That version will be used for all tables. When an object, for example with id 3, don't exists for version 4, the system will search for the closest version available (which means max(version < current_version). So, a new version only takes extra space for the modified objects.

Currently it looks like obj.destroy is like doing revert on a revision system :). Needs to be fixed. Currently it's just a proof of concept, but it would be nice with comments and suggestions.

Have a nice day! Tobias Nurmiranta

Minimal example (the function ver sets the current version):

Hi. I've updated the code mentioned below, if anyone is interested or want to comment. Tables with version-control now have the extra columns version (primary key) and version_deleted (boolean indicating if the object is deleted in this version). I needed some complicated SQL to get it working with find(:all) and find(:first) :). More details in the old mail.

Have a nice day! Tobias

-- code -- # acts_as_multi_versioned class ActiveRecord::Base   class << self     attr_accessor :current_version   end   self.current_version = 0

  def self.acts_as_multi_versioned     class_eval %q{       class << self         def find_version(obj, version, args = {})           # Adding a join-statement which selects the rows with highest           # version =< current_version for every unique value of id.           args[:joins] = "INNER JOIN (SELECT #{primary_key},MAX(version) as version FROM #{table_name} WHERE version < #{version+1} GROUP BY #{primary_key}) #{table_name}_versions ON #{table_name}_versions.#{primary_key} = #{table_name}.#{primary_key} AND #{table_name}_versions.version = #{table_name}.version AND #{table_name}.version_deleted = 0 #{args[:joins]}"           args[:readonly] ||= false           find_without_version(obj, args)         end         def find_with_version(obj, args = {})           find_version(obj, ActiveRecord::Base.current_version, args)         end         alias_method_chain :find, :version       end

      def before_create         self[:version] = ActiveRecord::Base.current_version       end

      def update_without_callbacks         if self.version != ActiveRecord::Base.current_version           self[:version] = ActiveRecord::Base.current_version           # Must create a new object when storing a new version- number. But the           # old record will work correctly for the next update.           o = self.clone           o[self.class.primary_key] = self[self.class.primary_key] # why is this needed?           o.save!         else           # Modified update_without_callbacks from composite_primary_keys.           where_class = "(#{self.class.primary_key} = #{quoted_id}) AND (version = #{self[:version]})"

          connection.update(

            "UPDATE #{self.class.table_name} " +

            "SET #{quoted_comma_pair_list(connection, attributes_with_quotes_versioned)} " +

            "WHERE #{where_class}",

            "#{self.class.name} Update"

          )

          return true         end       end

      def destroy_without_callbacks         begin           self.class.find_version(self.id, self.version - 1) # older version exists?           self.version_deleted = true           self.save! # (this will clone and create a deleted record if only readaccess was made earlier)

        rescue ActiveRecord::RecordNotFound # older record doesn't exist, remove it from database.           where_class = "(#{self.class.primary_key} = #{quoted_id}) AND (version = #{self[:version]})"           unless new_record?

            connection.delete(

              "DELETE FROM #{self.class.table_name} " +

              "WHERE #{where_class}",

              "#{self.class.name} Destroy"

            )

          end         end         freeze       end

      def attributes_with_quotes_versioned # All attributes except primary-key and 'version'.         x = attributes_with_quotes(false)         x.delete "version"         x       end     }   end end

ActiveRecord::Base.current_version = 3