lib/dcmgr/models/base_new.rb in wakame-vdc-dcmgr-10.11.0 vs lib/dcmgr/models/base_new.rb in wakame-vdc-dcmgr-10.12.0

- old
+ new

@@ -48,10 +48,11 @@ unless has_column?(:uuid) # add :uuid column with unique index constraint. column(:uuid, String, :size=>8, :null=>false, :fixed=>true, :unique=>true) end } + model.many_to_many :tags, :dataset=>lambda { Tag.join(TagMapping.table_name, :tag_id=>:id, :uuid=>self.canonical_uuid); } end module InstanceMethods # read-only instance method to retrieve @uuid_prefix class # variable. @@ -63,49 +64,75 @@ super # set random generated uuid value self[:uuid] ||= Array.new(8) do UUID_TABLE[rand(UUID_TABLE.size)]; end.join end + # model hook + def after_destroy + super + TagMapping.filter(:uuid=>self.canonical_uuid).delete + end + # Returns canonicalized uuid which has the form of # "{uuid_prefix}-{uuid}". def canonical_uuid "#{self.uuid_prefix}-#{self[:uuid]}" end alias_method :cuuid, :canonical_uuid # Put the tag on the object. # - # This method just delegates the method call of Tag#label(). - # @params [Models::Tag,String] tag_or_tag_uuid 'tag-xxxx' is expected when the type is string - def label_tag(tag_or_tag_uuid) - tag = case tag_or_tag_uuid + # @params [Models::Tag,String,Symbol] arg1 + # @params [String,NilClass] arg2 + # @params [String,NilClass] arg3 + # + # @example + # lable_tag('tag-xxxxx') + # t = Tag['tag-xxxx'] + # label_tag(t) + # label_tag(:NetworkPool, 'newname1', 'account_id') + def label_tag(arg1, arg2=nil, arg3=nil) + tag = case arg1 when String - Tag[tag_or_tag_uuid] + Tag[arg1] + when Symbol + acctid = arg3 || self.respond_to?(:account_id) ? self.account_id : raise("Unknown Account ID") + Dcmgr::Tags.const_get(arg1).find_or_create(:account_id=>acctid, :name=>arg2) when Tag - tag_or_tag_uuid + arg1 else - raise ArgumentError, "Invalid type: #{tag_or_tag_uuid.class}" + raise ArgumentError, "Invalid type: #{arg1.class}" end - - tag.label(self.uuid) + raise "Root Tag class can not be used" unless tag.class < Tag + tag.label(self.canonical_uuid) end # Remove the labeled tag from the object # - # This method just delegates the method call of Tag#unlabel(). - # @params [Models::Tag,String] tag_or_tag_uuid 'tag-xxxx' is expected when the type is string - def unlabel_tag(tag_or_tag_uuid) - tag = case tag_or_tag_uuid + # @params [Models::Tag,String,Symbol] arg1 + # @params [String,NilClass] arg2 + # @params [String,NilClass] arg3 + # + # @example + # unlable_tag('tag-xxxxx') + # t = Tag['tag-xxxx'] + # unlabel_tag(t) + # unlabel_tag(:NetworkPool, 'newname1', 'account_id') + def unlabel_tag(arg1, arg2=nil, arg3=nil) + tag = case arg1 when String - Tag[tag_or_tag_uuid] + Tag[arg1] + when Symbol + acctid = arg3 || self.respond_to?(:account_id) ? self.account_id : raise("Unknown Account ID") + Dcmgr::Tags.const_get(arg1).find(:account_id=>acctid, :name=>arg2) when Tag - tag_or_tag_uuid + arg1 else - raise ArgumentError, "Invalid type: #{tag_or_tag_uuid.class}" + raise ArgumentError, "Invalid type: #{arg1.class}" end - tag.unlabel(self.uuid) + tag.unlabel(self.canonical_uuid) end def to_hash() self.values.dup.merge({:id=>canonical_uuid, :uuid=>canonical_uuid}) end @@ -254,10 +281,119 @@ self.schema_builders << blk end end end - + + # This plugin is to archive the changes on each column of the model + # to a history table. + # + # plugin ArchiveChangedColumn, :your_history_table + # or + # plugin ArchiveChangedColumn + # history_dataset = DB[:history_table] + # + # The history table should have the schema below: + # schema do + # Fixnum :id, :null=>false, :primary_key=>true + # String :uuid, :size=>50, :null=>false + # String :attr, :null=>false + # String :vchar_value, :null=>true + # String :blob_value, :null=>true, :text=>true + # Time :created_at, :null=>false + # index [:uuid, :created_at] + # index [:uuid, :attr] + # end + module ArchiveChangedColumn + def self.configure(model, history_table=nil) + model.history_dataset = case history_table + when NilClass + nil + when String,Symbol + model.db.from(history_table) + when Class + raise "Unknown type" unless history_table < Sequel::Model + history_table.dataset + when Sequel::Dataset + history_table + else + raise "Unknown type" + end + end + + module ClassMethods + def history_dataset=(ds) + @history_ds = ds + end + + def history_dataset + @history_ds + end + end + + module InstanceMethods + def history_snapshot(at) + raise TypeError unless at.is_a?(Time) + + if self.created_at > at || (!self.terminated_at.nil? && self.terminated_at < at) + raise "#{at} is not in the range of the object's life span." + end + + ss = self.dup + # SELECT * FROM (SELECT * FROM `instance_histories` WHERE + # (`uuid` = 'i-ezsrs132') AND created_at <= '2010-11-30 23:08:05' + # ORDER BY created_at DESC) AS a GROUP BY a.attr; + ds = self.class.history_dataset.filter('uuid=? AND created_at <= ?', self.canonical_uuid, at).order(:created_at.desc) + ds = ds.from_self.group_by(:attr) + ds.all.each { |h| + if !h[:blob_value].nil? + ss.send("#{h[:attr]}=", typecast_value(h[:attr], h[:blob_value])) + else + ss.send("#{h[:attr]}=", typecast_value(h[:attr], h[:vchar_value])) + end + } + # take care for serialized columns by serialization plugin. + ss.deserialized_values.clear if ss.respond_to?(:deserialized_values) + + ss + end + + def before_create + return false if super == false + store_changes(self.columns) + true + end + + def before_update + return false if super == false + store_changes(self.changed_columns) + true + end + + private + def store_changes(cols_stored) + return if cols_stored.nil? || cols_stored.empty? + common_rec = { + :uuid=>self.canonical_uuid, + :created_at => Time.now, + } + + cols_stored.each { |c| + hist_rec = common_rec.dup + hist_rec[:attr] = c.to_s + + coldef = self.class.db_schema[c] + case coldef[:type] + when :text,:blob + hist_rec[:blob_value]= (new? ? (self[c] || coldef[:default]) : self[c]) + else + hist_rec[:vchar_value]=(new? ? (self[c] || coldef[:default]) : self[c]) + end + self.class.history_dataset.insert(hist_rec) + } + end + end + end class BaseNew < Sequel::Model LOCK_TABLES_KEY='__locked_tables'