diff --git a/app/models/product.rb b/app/models/product.rb index 2842f6365..fb60e7971 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -16,13 +16,21 @@ class Product < ApplicationRecord class_name: 'ProductsExtensionsAssociation', foreign_key: :product_id - has_many :extensions, - through: :extension_products_associations, - source: :extension + has_many :extensions, -> { distinct }, + through: :extension_products_associations, + source: :extension do + def for_root_product(root_product) + where('products_extensions.root_product_id = %s', root_product.id) + end + end has_many :mirrored_extensions, -> { mirrored }, through: :extension_products_associations, - source: :extension + source: :extension do + def for_root_product(root_product) + where('products_extensions.root_product_id = %s', root_product.id) + end + end has_and_belongs_to_many :predecessors, class_name: 'Product', join_table: :product_predecessors, association_foreign_key: :predecessor_id diff --git a/app/models/products_extensions_association.rb b/app/models/products_extensions_association.rb index 60bd09285..6b1994b2e 100644 --- a/app/models/products_extensions_association.rb +++ b/app/models/products_extensions_association.rb @@ -2,6 +2,7 @@ class ProductsExtensionsAssociation < ApplicationRecord self.table_name = 'products_extensions' belongs_to :product, class_name: 'Product', foreign_key: :product_id + belongs_to :root_product, class_name: 'Product', foreign_key: :root_product_id belongs_to :extension, class_name: 'Product', foreign_key: :extension_id end diff --git a/app/serializers/v3/product_serializer.rb b/app/serializers/v3/product_serializer.rb index bef0b0bf1..58927d2e3 100644 --- a/app/serializers/v3/product_serializer.rb +++ b/app/serializers/v3/product_serializer.rb @@ -14,7 +14,7 @@ def repositories :friendly_name, :product_class, :cpe, :free, :description, :eula_url, :repositories, :product_type, :extensions def extensions - object.mirrored_extensions.map do |extension| + object.mirrored_extensions.for_root_product(root_product).map do |extension| ::V3::ProductSerializer.new(extension, base_url: base_url).attributes end end @@ -37,4 +37,8 @@ def free true end + def root_product + @instance_options[:root_product] ||= object + end + end diff --git a/db/migrate/20180102134941_add_root_and_recommended_to_products_extensions.rb b/db/migrate/20180102134941_add_root_and_recommended_to_products_extensions.rb new file mode 100644 index 000000000..ca815c1ca --- /dev/null +++ b/db/migrate/20180102134941_add_root_and_recommended_to_products_extensions.rb @@ -0,0 +1,22 @@ +class AddRootAndRecommendedToProductsExtensions < ActiveRecord::Migration[5.1] + + def change + add_column :products_extensions, :recommended, :boolean + add_column :products_extensions, :root_product_id, :integer + add_index :products_extensions, %i[product_id extension_id root_product_id], + unique: true, name: 'index_products_extensions_on_product_extension_root' + + reversible do |dir| + dir.up do + ProductsExtensionsAssociation.find_each.each do |pa| + base = pa.product + pa.root_product = base.bases.present? ? base.bases.first : base + pa.recommended = false + pa.save! + end + change_column_null(:products_extensions, :root_product_id, false) + end + end + end + +end diff --git a/db/schema.rb b/db/schema.rb index 890412e97..d07dc8b99 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171214093115) do +ActiveRecord::Schema.define(version: 20180102134941) do create_table "activations", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t| t.integer "service_id", null: false @@ -56,7 +56,10 @@ create_table "products_extensions", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t| t.bigint "product_id", null: false t.bigint "extension_id", null: false + t.boolean "recommended" + t.integer "root_product_id", null: false t.index ["extension_id"], name: "index_products_extensions_on_extension_id" + t.index ["product_id", "extension_id", "root_product_id"], name: "index_products_extensions_on_product_extension_root", unique: true t.index ["product_id"], name: "index_products_extensions_on_product_id" end diff --git a/lib/rmt/scc.rb b/lib/rmt/scc.rb index b00401a5d..d1be6c46b 100644 --- a/lib/rmt/scc.rb +++ b/lib/rmt/scc.rb @@ -126,6 +126,7 @@ def create_product(item) association = ProductsExtensionsAssociation.new association.product_id = product.id association.extension_id = extension.id + association.root_product_id = product.id # FIXME: correct root_product_id !!! association.save! end diff --git a/spec/factories/products.rb b/spec/factories/products.rb index 70b8e806a..d1448fa52 100644 --- a/spec/factories/products.rb +++ b/spec/factories/products.rb @@ -17,11 +17,15 @@ transient do base_products [] predecessor nil + root_product nil end after :create do |product, evaluator| evaluator.base_products.each do |base_product| - product.product_extensions_associations << ProductsExtensionsAssociation.create(product: base_product) + product.product_extensions_associations << ProductsExtensionsAssociation.create( + product: base_product, + root_product: evaluator.root_product || base_product + ) end product.predecessors << evaluator.predecessor if evaluator.predecessor @@ -38,8 +42,7 @@ trait :with_extensions do after :create do |product, _evaluator| 5.times do - extension = create :product, :extension - product.extensions << extension + create(:product, :extension, base_products: [product]) end end end @@ -47,8 +50,7 @@ trait :with_mirrored_extensions do after :create do |product, _evaluator| 5.times do - extension = create :product, :extension, :with_mirrored_repositories - product.extensions << extension + create(:product, :extension, :with_mirrored_repositories, base_products: [product]) end end end diff --git a/spec/models/product_spec.rb b/spec/models/product_spec.rb index 5c628e7cd..3c39ff187 100644 --- a/spec/models/product_spec.rb +++ b/spec/models/product_spec.rb @@ -10,14 +10,14 @@ subject { product.has_extension? } let(:product) { create :product } - let(:extension) { create :product } + context 'when has no extensions' do it { is_expected.to eq false } end context 'when has extension' do - before { product.extensions << extension } + before { create(:product, :extension, base_products: [product]) } it { is_expected.to eq true } end end @@ -65,4 +65,54 @@ it { is_expected.to eq('42') } end end + + describe '#extensions' do + let(:base_a) { FactoryGirl.create(:product) } + let(:base_b) { FactoryGirl.create(:product) } + + let(:level_one_extension) { FactoryGirl.create(:product, :extension, base_products: [base_a, base_b]) } + + let!(:level_two_extension_a) { FactoryGirl.create(:product, :extension, base_products: [level_one_extension], root_product: base_a) } + let!(:level_two_extension_b) { FactoryGirl.create(:product, :extension, base_products: [level_one_extension], root_product: base_b) } + + it 'returns correct extensions for base A' do + expect(level_one_extension.extensions.for_root_product(base_a)).to eq([level_two_extension_a]) + end + + it 'returns correct extensions for base B' do + expect(level_one_extension.extensions.for_root_product(base_b)).to eq([level_two_extension_b]) + end + end + + describe '#mirrored_extensions' do + let(:base_a) { FactoryGirl.create(:product) } + let(:base_b) { FactoryGirl.create(:product) } + + let(:level_one_extension) { FactoryGirl.create(:product, :extension, base_products: [base_a, base_b]) } + + let(:level_two_extension_a_not_mirrored) { FactoryGirl.create(:product, :extension, base_products: [level_one_extension], root_product: base_a) } + let(:level_two_extension_b_not_mirrored) { FactoryGirl.create(:product, :extension, base_products: [level_one_extension], root_product: base_b) } + + let(:level_two_extension_a_mirrored) do + FactoryGirl.create(:product, :extension, :with_mirrored_repositories, base_products: [level_one_extension], root_product: base_a) + end + let(:level_two_extension_b_mirrored) do + FactoryGirl.create(:product, :extension, :with_mirrored_repositories, base_products: [level_one_extension], root_product: base_b) + end + + before do + level_two_extension_a_not_mirrored + level_two_extension_b_not_mirrored + level_two_extension_a_mirrored + level_two_extension_b_mirrored + end + + it 'returns correct extensions for base A' do + expect(level_one_extension.mirrored_extensions.for_root_product(base_a)).to eq([level_two_extension_a_mirrored]) + end + + it 'returns correct extensions for base B' do + expect(level_one_extension.mirrored_extensions.for_root_product(base_b)).to eq([level_two_extension_b_mirrored]) + end + end end diff --git a/spec/requests/api/connect/v4/systems/products_controller_spec.rb b/spec/requests/api/connect/v4/systems/products_controller_spec.rb index 3a0b1e7a0..c2e3bd332 100644 --- a/spec/requests/api/connect/v4/systems/products_controller_spec.rb +++ b/spec/requests/api/connect/v4/systems/products_controller_spec.rb @@ -53,10 +53,7 @@ context 'has products depending on it and is activated' do let(:product) do product = FactoryGirl.create(:product, :extension, :with_mirrored_repositories, :activated, system: system) - ext_product = FactoryGirl.create(:product, :extension, :with_mirrored_repositories, :activated, system: system) - ext_product.bases << product - ext_product.save! - + FactoryGirl.create(:product, :extension, :with_mirrored_repositories, :activated, system: system, base_products: [product]) product end