-
Notifications
You must be signed in to change notification settings - Fork 897
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
class MiqAeClassCompareFields | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Fryguy |
||
|
||
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 |
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 |
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the inconsistent naming? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Fryguy |
||
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) | ||
|
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.