Skip to content

Commit

Permalink
Make MiqProductFeature seeding pluggable
Browse files Browse the repository at this point in the history
  • Loading branch information
Fryguy committed May 23, 2019
1 parent cb61dd4 commit ed02420
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 67 deletions.
39 changes: 22 additions & 17 deletions app/models/miq_product_feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class MiqProductFeature < ApplicationRecord
ALL_TASKS_FEATURE = "miq_task_all_ui".freeze
TENANT_ADMIN_FEATURE = "rbac_tenant".freeze

include_concern "Seeding"

acts_as_tree

has_and_belongs_to_many :miq_user_roles, :join_table => :miq_roles_features
Expand All @@ -18,8 +20,6 @@ class MiqProductFeature < ApplicationRecord
validates_presence_of :identifier
validates_uniqueness_of :identifier

FIXTURE_PATH = Rails.root.join(*["db", "fixtures", table_name])

DETAIL_ATTRS = [
:name,
:description,
Expand Down Expand Up @@ -62,10 +62,6 @@ def self.current_tenant_identifier(identifier)
tenant_identifier(identifier, User.current_tenant.id) if identifier && feature_details(identifier) && root_tenant_identifier?(identifier)
end

def self.feature_yaml(path = FIXTURE_PATH)
"#{path}.yml".freeze
end

def self.feature_root
features.keys.detect { |k| feature_parent(k).nil? }
end
Expand Down Expand Up @@ -169,21 +165,30 @@ def self.seed_tenant_miq_product_features
Tenant.in_my_region.all.flat_map { |t| seed_single_tenant_miq_product_features(t) }
end

def self.seed_features(path = FIXTURE_PATH)
fixture_yaml = feature_yaml(path)
def self.seed_features
transaction do
features = all.index_by(&:identifier)

root_file, other_files = seed_files

features = all.to_a.index_by(&:identifier)
seen = seed_from_hash(YAML.load_file(fixture_yaml), seen, nil, features)
seen = seed_from_hash(YAML.load_file(root_file), seen, nil, features)
root_feature = find_by(:identifier => SUPER_ADMIN_FEATURE)

root_feature = MiqProductFeature.find_by(:identifier => SUPER_ADMIN_FEATURE)
Dir.glob(path.join("*.yml")).each do |fixture|
seed_from_hash(YAML.load_file(fixture), seen, root_feature)
other_files.each do |file|
seed_from_array(YAML.load_file(file), seen, root_feature)
end

tenant_identifiers = seed_tenant_miq_product_features
deletes = where.not(:identifier => seen.values.flatten + tenant_identifiers).destroy_all
_log.info("Deleting product features: #{deletes.collect(&:identifier).inspect}") unless deletes.empty?
seen
end
end

tenant_identifiers = seed_tenant_miq_product_features
deletes = where.not(:identifier => seen.values.flatten + tenant_identifiers).destroy_all
_log.info("Deleting product features: #{deletes.collect(&:identifier).inspect}") unless deletes.empty?
seen
def self.seed_from_array(array, seen = nil, parent = nil, features = nil)
Array.wrap(array).each do |hash|
seed_from_hash(hash, seen, parent, features)
end
end

def self.seed_from_hash(hash, seen = nil, parent = nil, features = nil)
Expand Down
30 changes: 30 additions & 0 deletions app/models/miq_product_feature/seeding.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class MiqProductFeature < ApplicationRecord
module Seeding
extend ActiveSupport::Concern

RELATIVE_FIXTURE_PATH = "db/fixtures/miq_product_features".freeze
FIXTURE_PATH = Rails.root.join(RELATIVE_FIXTURE_PATH).freeze

module ClassMethods
def seed_files
return seed_root_filename, seed_core_files + seed_plugin_files
end

private

def seed_root_filename
"#{FIXTURE_PATH}.yml"
end

def seed_core_files
Dir.glob("#{FIXTURE_PATH}/*.y{,a}ml").sort
end

def seed_plugin_files
Vmdb::Plugins.flat_map do |plugin|
Dir.glob("#{plugin.root.join(RELATIVE_FIXTURE_PATH)}{.yml,.yaml,/*.yml,/*.yaml}").sort
end
end
end
end
end
115 changes: 66 additions & 49 deletions spec/models/miq_product_feature_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
require 'tmpdir'
require 'pathname'

describe MiqProductFeature do
let(:miq_product_feature_class) do
Class.new(described_class) do
Expand All @@ -26,40 +23,51 @@ def self.tenant_features_in_hash
)
end

it "is properly configured" do
everything = YAML.load_file(described_class.feature_yaml)
traverse_product_features(everything) do |pf|
expect(pf).to include(*described_class::REQUIRED_ATTRIBUTES)
expect(pf.keys - described_class::ALLOWED_ATTRIBUTES).to be_empty
expect(pf[:children]).not_to be_empty if pf.key?(:children)
def assert_product_feature_attributes(pf)
expect(pf).to include(*described_class::REQUIRED_ATTRIBUTES)
expect(pf.keys - described_class::ALLOWED_ATTRIBUTES).to be_empty
expect(pf[:children]).not_to be_empty if pf.key?(:children)
end

def traverse_product_feature_children(pfs, &block)
pfs.each do |pf|
yield pf
traverse_product_feature_children(pf[:children], &block) if pf.key?(:children)
end
end

def traverse_product_features(product_feature, &block)
block.call(product_feature)
if product_feature.key?(:children)
product_feature[:children].each { |child| traverse_product_features(child, &block) }
it "is properly configured" do
root_file, other_files = described_class.seed_files

hash = YAML.load_file(root_file)
assert_product_feature_attributes(hash)

traverse_product_feature_children(hash[:children]) do |pf|
assert_product_feature_attributes(pf)
end

other_files.each do |f|
hash = YAML.load_file(f)
traverse_product_feature_children(Array.wrap(hash)) do |pf|
assert_product_feature_attributes(pf)
end
end
end

context ".seed" do
it "creates feature identifiers once on first seed, changes nothing on second seed" do
status_seed1 = nil
expect { status_seed1 = MiqProductFeature.seed }.to change(MiqProductFeature, :count)
expect(status_seed1[:created]).to match_array status_seed1[:created].uniq
expect(status_seed1[:updated]).to match_array []
expect(status_seed1[:unchanged]).to match_array []

status_seed2 = nil
expect { status_seed2 = MiqProductFeature.seed }.not_to change(MiqProductFeature, :count)
expect(status_seed2[:created]).to match_array []
expect(status_seed2[:updated]).to match_array []
expect(status_seed2[:unchanged]).to match_array status_seed1[:created]
expect { MiqProductFeature.seed }.to change(MiqProductFeature, :count)
orig_ids = MiqProductFeature.pluck(:id)
expect { MiqProductFeature.seed }.not_to change(MiqProductFeature, :count)
expect(MiqProductFeature.pluck(:id)).to match_array orig_ids
end
end

context ".seed_features" do
let(:feature_path) { Pathname.new(@tempfile.path.sub(/\.yml/, '')) }
let(:tempdir) { Dir.mktmpdir }
let(:feature_path) { File.join(tempdir, "miq_product_features") }
let(:root_file) { File.join(tempdir, "miq_product_features.yml") }

let(:base) do
{
:feature_type => "node",
Expand All @@ -76,47 +84,56 @@ def traverse_product_features(product_feature, &block)
end

before do
@tempdir = Dir.mktmpdir
@tempfile = Tempfile.new(['feature', '.yml'], @tempdir)
@tempfile.write(YAML.dump(base))
@tempfile.close
File.write(root_file, base.to_yaml)

expect(described_class).to receive(:seed_files).at_least(:once) do
[root_file, Dir.glob(File.join(feature_path, "*.yml"))]
end
end

after do
@tempfile.unlink
Dir.rmdir(@tempdir)
FileUtils.rm_rf(tempdir)
end

it "existing records" do
it "creates/updates/deletes records" do
deleted = FactoryBot.create(:miq_product_feature, :identifier => "xxx")
changed = FactoryBot.create(:miq_product_feature, :identifier => "dialog_new_editor", :name => "XXX")
unchanged = FactoryBot.create(:miq_product_feature_everything)
unchanged_orig_updated_at = unchanged.updated_at

MiqProductFeature.seed_features(feature_path)
MiqProductFeature.seed_features

expect { deleted.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect(changed.reload.name).to eq("One")
expect(unchanged.reload.updated_at).to be_within(0.1).of unchanged_orig_updated_at
end

it "additional yaml feature" do
additional = {
:feature_type => "node",
:identifier => "dialog_edit_editor",
:children => []
}
context "additional yaml feature" do
before do
additional_hash = {
:feature_type => "node",
:identifier => "dialog_edit_editor",
:children => []
}

additional_array = [
{
:feature_type => "node",
:identifier => "policy_edit_editor",
:children => []
}
]

Dir.mkdir(feature_path)
additional_file = Tempfile.new(['additional', '.yml'], feature_path)
additional_file.write(YAML.dump(additional))
additional_file.close
FileUtils.mkdir_p(feature_path)
File.write(File.join(feature_path, "additional_hash.yml"), additional_hash.to_yaml)
File.write(File.join(feature_path, "additional_array.yml"), additional_array.to_yaml)
end

status_seed = MiqProductFeature.seed_features(feature_path)
expect(MiqProductFeature.count).to eq(3)
expect(status_seed[:created]).to match_array %w(everything dialog_new_editor dialog_edit_editor)
it "creates/updates/deletes records" do
MiqProductFeature.seed_features

additional_file.unlink
Dir.rmdir(feature_path)
expect(MiqProductFeature.pluck(:identifier)).to match_array %w(everything dialog_new_editor dialog_edit_editor policy_edit_editor)
end
end

context 'dynamic product features' do
Expand Down Expand Up @@ -151,7 +168,7 @@ def traverse_product_features(product_feature, &block)
let!(:tenant) { FactoryBot.create(:tenant, :parent => root_tenant) }

before do
MiqProductFeature.seed_features(feature_path)
MiqProductFeature.seed_features
end

it "creates tenant features" do
Expand Down
9 changes: 8 additions & 1 deletion spec/support/evm_spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,14 @@ def self.specific_product_features(*features)

def self.seed_specific_product_features(*features)
features.flatten!
hashes = YAML.load_file(MiqProductFeature.feature_yaml)

root_file, other_files = MiqProductFeature.seed_files

hashes = YAML.load_file(root_file)
other_files.each do |f|
hashes[:children] += Array.wrap(YAML.load_file(f))
end

filtered = filter_specific_features([hashes], features).first
MiqProductFeature.seed_from_hash(filtered)
MiqProductFeature.seed_tenant_miq_product_features
Expand Down

0 comments on commit ed02420

Please sign in to comment.