Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for copy and compare of automate class, instance and methods #73

Merged
merged 1 commit into from
Jul 1, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions vmdb/app/models/miq_ae_class.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ def editable?
ae_namespace.editable?
end

def field_names
ae_fields.collect { |x| x.name.downcase }
end

def field_hash(name)
field = ae_fields.detect { |f| f.name.casecmp(name) == 0 }
raise "field #{name} not found in class #{@name}" if field.nil?
field.attributes
end

private

def scoped_methods(s)
Expand Down
87 changes: 87 additions & 0 deletions vmdb/app/models/miq_ae_class_compare_fields.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
class MiqAeClassCompareFields
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these are specific to MiqAeClass, why not namespace them with MiqAeClass::CompareFields?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Fryguy
The Compare works with both MiqAeClass (AR) and MiqAeClassYaml (YAML). My goal is to switch MiqAeClass with MiqAeClassYaml down the road when we can have all AR functionality in the MiqAeClassYaml.

attr_reader :compatibilities
attr_reader :incompatibilities
attr_reader :fields_in_use
attr_reader :adds
IGNORE_PROPERTY_NAMES = %w(name owner created_on updated_on updated_by
updated_by_user_id id class_id)
WARNING_PROPERTY_NAMES = %w(priority message display_name default_value substitute
visibility collect scope description condition on_entry
on_exit on_error max_retries max_time)
ERROR_PROPERTY_NAMES = %w(aetype datatype)

CONGRUENT_SCHEMA = 1
COMPATIBLE_SCHEMA = 2
INCOMPATIBLE_SCHEMA = 4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for sorting purposes? If so, why use 1, 2, 4...these aren't bitfields.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's just for naming purposes, the code would be a lot clearer with just 3 symbols... :congruent, :compatible, :incompatible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Fryguy
During a copy operation these values can be combined e.g.
flags = CONGRUENT | COMPATIBLE
It is a simple implementation of bit fields.


def initialize(new_class, old_class)
@new_class = new_class
@old_class = old_class
end

def compare
load_field_names
initialize_results
venn_list
validate_similar
status
end

def status
return CONGRUENT_SCHEMA if congruent?
return COMPATIBLE_SCHEMA if compatible?
INCOMPATIBLE_SCHEMA
end

def congruent?
@adds.empty? && @incompatibilities.empty? &&
@compatibilities.empty? && @fields_in_use.empty? &&
@deletes.empty?
end

def compatible?
@incompatibilities.empty? && @deletes.empty?
end

private

def initialize_results
@incompatibilities = []
@compatibilities = []
@fields_in_use = []
@adds = []
@deletes = []
end

def load_field_names
@old_names = @old_class.field_names
@new_names = @new_class.field_names
end

def venn_list
@similar = @new_names & @old_names
@adds = @new_names - @old_names
@deletes = @old_names - @new_names
end

def validate_similar
@similar.each do |name|
old_field = @old_class.field_hash(name)
new_field = @new_class.field_hash(name)
compare_field_properties(name, old_field, new_field)
end
end

def compare_field_properties(field_name, old_field, new_field)
old_field.each do |property, value|
next if IGNORE_PROPERTY_NAMES.include?(property)
next if value == new_field[property]
hash = {'property' => property,
'old_value' => value,
'new_value' => new_field[property],
'field_name' => field_name}
@compatibilities << hash if WARNING_PROPERTY_NAMES.include?(property)
@incompatibilities << hash if ERROR_PROPERTY_NAMES.include?(property)
end
end
end
80 changes: 80 additions & 0 deletions vmdb/app/models/miq_ae_class_copy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
class MiqAeClassCopy
include MiqAeCopyMixin
DELETE_PROPERTIES = %w(updated_by updated_by_user_id updated_on id
created_on updated_on method_id owner class_id)

def initialize(class_fqname)
@class_fqname = class_fqname
@src_domain, @partial_ns, @ae_class = MiqAeClassCopy.split(@class_fqname, false)
@src_class = MiqAeClass.find_by_fqname(@class_fqname)
raise "Source class not found #{@class_fqname}" unless @src_class
end

def to_domain(domain, ns = nil, overwrite = false)
check_duplicity(domain, ns, @src_class.name)
@overwrite = overwrite
@target_ns_fqname = target_ns(domain, ns)
@target_name = @src_class.name
copy
end

def as(new_name, ns = nil, overwrite = false)
check_duplicity(@src_domain, ns, new_name)
@overwrite = overwrite
@target_ns_fqname = target_ns(@src_domain, ns)
@target_name = new_name
copy
end

private

def target_ns(domain, ns)
return "#{domain}/#{@partial_ns}" if ns.nil?
MiqAeNamespace.find_by_fqname(ns, false).nil? ? "#{domain}/#{ns}" : ns
end

def copy
validate
create_class
copy_schema
@dest_class
end

def create_class
ns = MiqAeNamespace.find_or_create_by_fqname(@target_ns_fqname, false)
ns.save!
@dest_class = MiqAeClass.create!(:namespace_id => ns.id,
:name => @target_name,
:description => @src_class.description,
:type => @src_class.type,
:display_name => @src_class.display_name,
:inherits => @src_class.inherits,
:visibility => @src_class.visibility)
end

def copy_schema
@dest_class.ae_fields = add_fields
@dest_class.save!
end

def add_fields
@src_class.ae_fields.collect do |src_field|
attrs = src_field.attributes.reject { |k, _| DELETE_PROPERTIES.include?(k) }
MiqAeField.new(attrs)
end
end

def validate
dest_class = MiqAeClass.find_by_fqname("#{@target_ns_fqname}/#{@target_name}")
if dest_class
dest_class.destroy if @overwrite
raise "Destination Class already exists #{dest_class.fqname}" unless @overwrite
end
end

def check_duplicity(domain, ns, classname)
if domain.downcase == @src_domain.downcase && classname.downcase == @ae_class.downcase
raise "Cannot copy class onto itself" if ns.nil? || ns.downcase == @partial_ns.downcase
end
end
end
19 changes: 19 additions & 0 deletions vmdb/app/models/miq_ae_class_yaml.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class MiqAeClassYaml
attr_accessor :ae_class_obj
def initialize(filename = nil)
@filename = filename
@ae_class_obj = YAML.load_file(filename) if filename
end

def field_names
raise "class object has not been set" unless @ae_class_obj
@ae_class_obj['object']['schema'].collect { |x| x['field']['name'].downcase }
end

def field_hash(name)
raise "class object has not been set" unless @ae_class_obj
field = @ae_class_obj['object']['schema'].detect { |f| f['field']['name'].casecmp(name) == 0 }
raise "field #{name} not found in yaml class #{@filename}" if field.nil?
field['field']
end
end
14 changes: 13 additions & 1 deletion vmdb/app/models/miq_ae_instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,20 @@ def editable?
ae_class.ae_namespace.editable?
end

private
def field_names
fields = ae_values.collect { |v| v.field_id }
ae_class.ae_fields.select { |x| fields.include?(x.id) }.collect { |f| f.name.downcase }
end

def field_value_hash(name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the inconsistent naming?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Fryguy
When you get a field value we get back an AR object called MiqAeValue. This function can be used by both the YAML and AR object so it wants to let the intention be known that it wants the value back as a hash for comparison purposes.

field = ae_class.ae_fields.detect { |f| f.name.casecmp(name) == 0 }
raise "Field #{name} not found in class #{ae_class.fqname}" if field.nil?
value = ae_values.detect { |v| v.field_id == field.id }
raise "Field #{name} not found in instance #{self.name} in class #{ae_class.fqname}" if value.nil?
value.attributes
end

private

def validate_field(field)
if field.kind_of?(MiqAeField)
Expand Down
87 changes: 87 additions & 0 deletions vmdb/app/models/miq_ae_instance_compare_values.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
class MiqAeInstanceCompareValues
attr_reader :compatibilities
attr_reader :incompatibilities
attr_reader :fields_in_use
attr_reader :adds
IGNORE_PROPERTY_NAMES = %w(name owner created_on updated_on updated_by
updated_by_user_id id class_id instance_id field_id)
WARNING_PROPERTY_NAMES = %w(priority message display_name default_value substitute
visibility collect scope description condition on_entry
on_exit on_error max_retries max_time)
ERROR_PROPERTY_NAMES = %w(aetype datatype)

CONGRUENT_INSTANCE = 1
COMPATIBLE_INSTANCE = 2
INCOMPATIBLE_INSTANCE = 4

def initialize(new_instance, old_instance)
@new_instance = new_instance
@old_instance = old_instance
end

def compare
load_field_names
initialize_results
venn_list
validate_similar
status
end

def status
return CONGRUENT_INSTANCE if congruent?
return COMPATIBLE_INSTANCE if compatible?
INCOMPATIBLE_INSTANCE
end

def congruent?
@adds.empty? && @incompatibilities.empty? &&
@compatibilities.empty? && @fields_in_use.empty? &&
@deletes.empty?
end

def compatible?
@incompatibilities.empty? && @deletes.empty?
end

private

def initialize_results
@incompatibilities = []
@compatibilities = []
@fields_in_use = []
@adds = []
@deletes = []
end

def load_field_names
@old_names = @old_instance.field_names
@new_names = @new_instance.field_names
end

def venn_list
@similar = @new_names & @old_names
@adds = @new_names - @old_names
@deletes = @old_names - @new_names
end

def validate_similar
@similar.each do |name|
old_value = @old_instance.field_value_hash(name)
new_value = @new_instance.field_value_hash(name)
compare_value_properties(name, old_value, new_value)
end
end

def compare_value_properties(field_name, old_value, new_value)
old_value.each do |property, data|
next if IGNORE_PROPERTY_NAMES.include?(property)
next if data == new_value[property]
hash = {'property' => property,
'old_data' => data,
'new_data' => new_value[property],
'field_name' => field_name}
@compatibilities << hash if WARNING_PROPERTY_NAMES.include?(property)
@incompatibilities << hash if ERROR_PROPERTY_NAMES.include?(property)
end
end
end
Loading