app/models/miq_ae_class.rb
class MiqAeClass < ApplicationRecord
include MiqAeSetUserInfoMixin
include MiqAeYamlImportExportMixin
include RelativePathMixin
belongs_to :ae_namespace, :class_name => "MiqAeNamespace", :foreign_key => :namespace_id
belongs_to :domain, :class_name => "MiqAeDomain", :inverse_of => false
has_many :ae_fields, -> { order(:priority) }, :class_name => "MiqAeField", :foreign_key => :class_id,
:dependent => :destroy, :autosave => true, :inverse_of => :ae_class
has_many :ae_instances, -> { preload(:ae_values) }, :class_name => "MiqAeInstance", :foreign_key => :class_id,
:dependent => :destroy, :inverse_of => :ae_class
has_many :ae_methods, :class_name => "MiqAeMethod", :foreign_key => :class_id,
:dependent => :destroy, :inverse_of => :ae_class
validates :namespace_id, :domain_id, :presence => true
validates :name, :presence => true,
:uniqueness_when_changed => {:case_sensitive => false, :scope => :namespace_id},
:format => {:with => /\A[\w.-]+\z/i, :message => N_("may contain only alphanumeric and _ . - characters")}
before_validation :set_relative_path
after_save :set_children_relative_path
virtual_attribute :fqname, :string
def self.lookup_by_fqname(fqname, _args = {})
return if fqname.blank?
dname, *partial = split_fqname(fqname)
domain_id = MiqAeDomain.unscoped.where(MiqAeDomain.arel_table[:name].lower.eq(dname.downcase)).where(:domain_id => nil).select(:id)
find_by(:lower_relative_path => partial.join("/").downcase, :domain_id => domain_id)
end
singleton_class.send(:alias_method, :find_by_fqname, :lookup_by_fqname)
Vmdb::Deprecation.deprecate_methods(singleton_class, :find_by_fqname => :lookup_by_fqname)
def self.lookup_by_namespace_and_name(name_space, name, _args = {})
lookup_by_fqname(name_space + "/" + name)
end
singleton_class.send(:alias_method, :find_by_namespace_and_name, :lookup_by_namespace_and_name)
Vmdb::Deprecation.deprecate_methods(singleton_class, :find_by_namespace_and_name => :lookup_by_namespace_and_name)
def self.lookup_by_namespace_id_and_name(ns_id, name)
where(:namespace_id => ns_id).find_by(:lower_name => name.downcase)
end
singleton_class.send(:alias_method, :find_by_namespace_id_and_name, :lookup_by_namespace_id_and_name)
Vmdb::Deprecation.deprecate_methods(singleton_class, :find_by_namespace_id_and_name => :lookup_by_namespace_id_and_name)
def self.lookup_by_name(name)
find_by(:lower_name => name.downcase)
end
singleton_class.send(:alias_method, :find_by_name, :lookup_by_name)
Vmdb::Deprecation.deprecate_methods(singleton_class, :find_by_name => :lookup_by_name)
def self.fqname(ns, name)
"#{ns}/#{name}"
end
def export_schema
ae_fields.sort_by(&:priority).collect(&:to_export_yaml)
end
def self.parse_fqname(fqname)
parts = fqname.split('/')
name = parts.pop
ns = parts.join('/')
return ns, name
end
def to_export_xml(options = {})
require 'builder'
xml = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
xml_attrs = {:name => name, :namespace => namespace}
self.class.column_names.each do |cname|
# Remove any columns that we do not want to export
next if %w[id created_on updated_on updated_by].include?(cname) || cname.ends_with?("_id")
# Skip any columns that we process explicitly
next if %w[name namespace].include?(cname)
# Process the column
xml_attrs[cname.to_sym] = send(cname) if send(cname).present?
end
xml.MiqAeClass(xml_attrs) do
ae_methods.sort_by(&:fqname).each { |m| m.to_export_xml(:builder => xml) }
xml.MiqAeSchema do
ae_fields.sort_by(&:priority).each { |f| f.to_export_xml(:builder => xml) }
end unless ae_fields.empty?
ae_instances.sort_by(&:fqname).each { |i| i.to_export_xml(:builder => xml) }
end
end
# my class's fqname is /domain/namespace1/namespace2/class
def namespace
return nil if ae_namespace.nil?
fqname.split("/")[0..-2].join("/")
end
def namespace=(ns)
raise ArgumentError, "ns cannot be blank" if ns.blank?
self.ae_namespace = MiqAeNamespace.find_or_create_by_fqname(ns)
end
def instance_methods
@instance_methods ||= scoped_methods("instance")
end
def class_methods
@class_methods ||= scoped_methods("class")
end
def state_machine?
ae_fields.any? { |f| f.aetype == 'state' }
end
def self.get_homonymic_across_domains(user, fqname, enabled = nil)
MiqAeDatastore.get_homonymic_across_domains(user, ::MiqAeClass, fqname, enabled)
end
def self.find_homonymic_instances_across_domains(user, fqname)
return [] if fqname.blank?
path = MiqAeEngine::MiqAeUri.path(fqname, "miqaedb")
ns, klass, inst = MiqAeEngine::MiqAePath.split(path)
return [] if ns.blank? || klass.blank? || inst.blank?
get_same_instance_from_classes(get_sorted_homonym_class_across_domains(user, ns, klass), inst)
end
def self.find_distinct_instances_across_domains(user, fqname)
return [] if fqname.blank?
ns, klass = fqname.starts_with?('/') ? parse_fqname(fqname[1..-1]) : parse_fqname(fqname)
return [] if ns.blank? || klass.blank?
get_unique_instances_from_classes(get_sorted_homonym_class_across_domains(user, ns, klass))
end
delegate :editable?, :to => :ae_namespace
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
def self.copy(options)
if options[:new_name]
MiqAeClassCopy.new(options[:fqname]).as(options[:new_name],
options[:namespace],
options[:overwrite_location]
)
else
MiqAeClassCopy.copy_multiple(options[:ids],
options[:domain],
options[:namespace],
options[:overwrite_location]
)
end
end
def self.waypoint_ids_for_state_machines
MiqAeClass.all.select(&:state_machine?).each_with_object([]) do |klass, ids|
ids << "#{klass.class.name}::#{klass.id}"
sub_namespaces(klass.ae_namespace, ids)
end
end
def self.display_name(number = 1)
n_('Automate Class', 'Automate Classes', number)
end
private
def set_relative_path
self.domain_id ||= domain&.id || ae_namespace&.domain_id
self.domain_id ||= ae_namespace.id if ae_namespace&.root?
self.relative_path = [ae_namespace.relative_path, name].compact.join("/") if (name_changed? || relative_path.nil?) && ae_namespace
end
def set_children_relative_path
return unless saved_change_to_relative_path?
ae_instances.each { |instance| instance.update!(:ae_class => self, :relative_path => nil) }
ae_methods.each { |method| method.update!(:ae_class => self, :relative_path => nil) }
end
def self.sub_namespaces(ns_obj, ids)
loop do
break if ns_obj.nil? || ids.include?("#{ns_obj.class.name}::#{ns_obj.id}")
ids << "#{ns_obj.class.name}::#{ns_obj.id}"
ns_obj = ns_obj.parent
end
end
private_class_method :sub_namespaces
def scoped_methods(s)
ae_methods.select { |m| m.scope == s }
end
def self.get_sorted_homonym_class_across_domains(user, ns = nil, klass)
ns_obj = MiqAeNamespace.lookup_by_fqname(ns) unless ns.nil?
partial_ns = ns_obj.nil? ? ns : remove_domain_from_fqns(ns)
class_array = user.current_tenant.visible_domains.pluck(:name).collect do |domain|
fq_ns = domain + "/" + partial_ns
ae_ns = MiqAeNamespace.lookup_by_fqname(fq_ns)
next if ae_ns.nil?
ae_ns.ae_classes.select { |c| File.fnmatch(klass, c.name, File::FNM_CASEFOLD) }
end.compact.flatten
if class_array.empty? && ns_obj
class_array = ns_obj.ae_classes.select { |c| File.fnmatch(klass, c.name, File::FNM_CASEFOLD) }
end
class_array
end
private_class_method :get_sorted_homonym_class_across_domains
def self.remove_domain_from_fqns(fqname)
parts = fqname.split('/')
parts.shift
parts.join('/')
end
private_class_method :remove_domain_from_fqns
def self.get_unique_instances_from_classes(klass_array)
name_set = Set.new
klass_array.collect do |klass|
cls = find_by(:id => klass.id)
next if cls.nil?
cls.ae_instances.sort_by(&:fqname).collect do |inst|
next if name_set.include?(inst.name)
name_set << inst.name
inst
end.compact.flatten
end.compact.flatten
end
private_class_method :get_unique_instances_from_classes
def self.get_same_instance_from_classes(klass_array, instance)
klass_array.collect do |klass|
cls = find_by(:id => klass.id)
next if cls.nil?
cls.ae_instances.select { |a| File.fnmatch(instance, a.name, File::FNM_CASEFOLD) }
end.compact.flatten
end
private_class_method :get_same_instance_from_classes
end