diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3604d72..079aa6c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,18 +17,15 @@ jobs: fail-fast: false matrix: ruby-version: - - "3.1" - "3.2" - "3.3" + - "3.4" alchemy: - - "7.2-stable" - - "7.3-stable" - "7.4-stable" + - "8.0-stable" solidus: - - "v4.1" - - "v4.2" - - "v4.3" - - "v4.4" + - "v4.5" + - "v4.6" env: ALCHEMY_BRANCH: ${{ matrix.alchemy }} SOLIDUS_BRANCH: ${{ matrix.solidus }} diff --git a/Gemfile b/Gemfile index 8e90638..8c492da 100644 --- a/Gemfile +++ b/Gemfile @@ -1,19 +1,18 @@ source "https://rubygems.org" -solidus_branch = ENV.fetch("SOLIDUS_BRANCH", "v4.4") +solidus_branch = ENV.fetch("SOLIDUS_BRANCH", "v4.6") gem "solidus_core", github: "solidusio/solidus", branch: solidus_branch gem "solidus_backend", github: "solidusio/solidus", branch: solidus_branch gem "solidus_frontend", github: "solidusio/solidus_frontend", branch: "main" -alchemy_branch = ENV.fetch("ALCHEMY_BRANCH", "7.4-stable") +alchemy_branch = ENV.fetch("ALCHEMY_BRANCH", "8.0-stable") gem "alchemy_cms", github: "AlchemyCMS/alchemy_cms", branch: alchemy_branch - -gem "alchemy-devise", github: "AlchemyCMS/alchemy-devise", branch: "7.4-stable" +gem "alchemy-devise", github: "AlchemyCMS/alchemy-devise", branch: alchemy_branch # Specify your gem's dependencies in alchemy-solidus.gemspec gemspec -gem "sqlite3", "~> 1.4" +gem "sqlite3", "~> 2.7" gem "pry-rails" gem "sprockets", "~> 4.0" gem "jsbundling-rails", "~> 1.1" @@ -36,3 +35,5 @@ group :lint do gem "rubocop", require: false gem "standard", "~> 1.25", require: false end + +gem "ruby-lsp-rspec", "~> 0.1.28" diff --git a/alchemy-solidus.gemspec b/alchemy-solidus.gemspec index 9fe734a..e022bcd 100644 --- a/alchemy-solidus.gemspec +++ b/alchemy-solidus.gemspec @@ -14,13 +14,14 @@ Gem::Specification.new do |gem| gem.require_paths = ["lib"] gem.version = Alchemy::Solidus::VERSION - gem.add_dependency("alchemy_cms", [">= 7.2.0", "< 8"]) + gem.add_dependency("alchemy_cms", [">= 7.2.0", "< 9"]) + gem.add_dependency("solidus_api", [">= 4.0.0", "< 5"]) gem.add_dependency("solidus_core", [">= 4.0.0", "< 5"]) gem.add_dependency("solidus_backend", [">= 4.0.0", "< 5"]) gem.add_dependency("solidus_support", [">= 0.14.0", "< 1"]) gem.add_dependency("deface", ["~> 1.0"]) - gem.add_development_dependency("rspec-rails", ["~> 6.0"]) + gem.add_development_dependency("rspec-rails", ["~> 8.0"]) gem.add_development_dependency("shoulda-matchers", ["~> 4.0"]) gem.add_development_dependency("capybara", ["~> 3.0"]) gem.add_development_dependency("capybara-screenshot", ["~> 1.0"]) diff --git a/app/components/alchemy/admin/link_dialog/product_tab.rb b/app/components/alchemy/admin/link_dialog/product_tab.rb index 5f5f970..d944c72 100644 --- a/app/components/alchemy/admin/link_dialog/product_tab.rb +++ b/app/components/alchemy/admin/link_dialog/product_tab.rb @@ -33,10 +33,7 @@ def message private def product - slug = url&.match(/products\/(?[\w-]+)/)&.captures - return unless slug - - @_product ||= Spree::Product.find_by(slug: slug) + @_product ||= Alchemy::Solidus.config.product_finder.call(url) end def product_select @@ -48,7 +45,7 @@ def product_select current_alchemy_user.spree_api_key, product: product, url: spree.api_products_path, - value_attribute: :slug + value_attribute: Alchemy::Solidus.config.product_url_attribute ).with_content(input) content_tag("div", label + select, class: "input select") end diff --git a/app/components/alchemy/admin/product_select.rb b/app/components/alchemy/admin/product_select.rb index d814e49..51ea518 100644 --- a/app/components/alchemy/admin/product_select.rb +++ b/app/components/alchemy/admin/product_select.rb @@ -1,11 +1,9 @@ module Alchemy module Admin class ProductSelect < ViewComponent::Base - VALUE_ATTRIBUTES = %i[id slug].freeze - delegate :spree, to: :helpers - attr_reader :api_key, :product, :url, :query_params, :placeholder + attr_reader :api_key, :product, :url, :query_params, :placeholder, :value_attribute def initialize(api_key, product: nil, url: nil, query_params: nil, placeholder: nil, value_attribute: nil) @api_key = api_key @@ -13,7 +11,7 @@ def initialize(api_key, product: nil, url: nil, query_params: nil, placeholder: @url = url @query_params = query_params @placeholder = placeholder - @value_attribute = value_attribute + @value_attribute = value_attribute || :id end def call @@ -22,10 +20,6 @@ def call private - def value_attribute - @value_attribute.in?(VALUE_ATTRIBUTES) ? @value_attribute : :id - end - def attributes attrs = { placeholder: placeholder || Alchemy.t(:search_product, scope: "solidus"), @@ -45,7 +39,7 @@ def attributes def serialized_selection { - id: product.send(value_attribute), + id: product.public_send(value_attribute), name: product.name }.to_json end diff --git a/app/javascript/alchemy_solidus/components/product_select.js b/app/javascript/alchemy_solidus/components/product_select.js index e91855b..5493d8f 100644 --- a/app/javascript/alchemy_solidus/components/product_select.js +++ b/app/javascript/alchemy_solidus/components/product_select.js @@ -36,15 +36,11 @@ export default class ProductSelect extends RemoteSelect { * The value used for the select send to the server * after submitting the form this select is placed in. * - * Note: Returning an url if the `value-attribute` is "slug". - * * @param {object} product * @returns {string} */ _parsedValue(product) { - return this.valueAttribute === "slug" - ? `${Spree.mountedAt()}products/${product.slug}` - : product.id + return product[this.valueAttribute] } /** diff --git a/app/models/alchemy/solidus/product_finder.rb b/app/models/alchemy/solidus/product_finder.rb new file mode 100644 index 0000000..a4db286 --- /dev/null +++ b/app/models/alchemy/solidus/product_finder.rb @@ -0,0 +1,18 @@ +module Alchemy + module Solidus + # Finds a Spree::Product by its slug. + # + module ProductFinder + extend self + + # @param url [String] The URL to find the product by. + # @return [Spree::Product, nil] The found product or nil if not found. + def call(url) + slug = url&.match(/products\/(?[\w-]+)/)&.captures + return unless slug + + Spree::Product.find_by(slug:) + end + end + end +end diff --git a/app/patches/models/alchemy/solidus/spree_product_patch.rb b/app/patches/models/alchemy/solidus/spree_product_patch.rb index 68faef9..9238401 100644 --- a/app/patches/models/alchemy/solidus/spree_product_patch.rb +++ b/app/patches/models/alchemy/solidus/spree_product_patch.rb @@ -9,6 +9,10 @@ def self.prepended(base) base.has_many :alchemy_ingredients, class_name: "Alchemy::Ingredients::SpreeProduct", as: :related_object, dependent: :nullify end + def url_path + Spree::Core::Engine.routes.url_helpers.try(:product_path, self) + end + private # Overwritten Solidus' default behavior diff --git a/config/initializers/alchemy.rb b/config/initializers/alchemy.rb index 1619fab..1f5cbde 100644 --- a/config/initializers/alchemy.rb +++ b/config/initializers/alchemy.rb @@ -47,5 +47,5 @@ Rails.application.config.after_initialize do Alchemy::Modules.register_module(alchemy_module) - Alchemy.link_dialog_tabs.add(Alchemy::Admin::LinkDialog::ProductTab) + Alchemy.config.link_dialog_tabs.add("Alchemy::Admin::LinkDialog::ProductTab") end diff --git a/config/initializers/spree.rb b/config/initializers/spree.rb index 0201890..30d5eec 100644 --- a/config/initializers/spree.rb +++ b/config/initializers/spree.rb @@ -3,6 +3,7 @@ end Rails.application.config.after_initialize do + Spree::Api::Config.product_attributes << :url_path Spree::Backend::Config.configure do |config| alchemy_menu_item = if Spree.solidus_gem_version >= Gem::Version.new("4.2.0") config.class::MenuItem.new( diff --git a/lib/alchemy-solidus.rb b/lib/alchemy-solidus.rb index b279953..04670b3 100644 --- a/lib/alchemy-solidus.rb +++ b/lib/alchemy-solidus.rb @@ -1 +1,13 @@ +require "alchemy/solidus/configuration" + +module Alchemy + module Solidus + extend self + + def config + @_config ||= Alchemy::Solidus::Configuration.new + end + end +end + require "alchemy/solidus/engine" diff --git a/lib/alchemy/solidus/configuration.rb b/lib/alchemy/solidus/configuration.rb new file mode 100644 index 0000000..481971f --- /dev/null +++ b/lib/alchemy/solidus/configuration.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "alchemy/configuration" + +module Alchemy + module Solidus + class Configuration < Alchemy::Configuration + # == This is the Alchemy Solidus configuration class + + # === Product URL Attribute + # + # The Spree::Product attribute we use for url. + # + # NOTE: Used in the Product Select. + # + option :product_url_attribute, :string, default: "url_path" + + # === Product URL Finder + # + # The class responsible to load the product by url. + # + # NOTE: Used in the Page Link Dialogs Product Tab. + # + option :product_finder, :class, default: "Alchemy::Solidus::ProductFinder" + end + end +end diff --git a/lib/alchemy/solidus/engine.rb b/lib/alchemy/solidus/engine.rb index 075f218..fb6218d 100644 --- a/lib/alchemy/solidus/engine.rb +++ b/lib/alchemy/solidus/engine.rb @@ -1,5 +1,6 @@ require "alchemy_cms" require "alchemy/version" +require "solidus_api" require "solidus_core" require "solidus_backend" require "solidus_support" @@ -17,7 +18,7 @@ class Engine < ::Rails::Engine engine_name "alchemy_solidus" initializer "alchemy_solidus.assets", before: "alchemy.importmap" do |app| - Alchemy.admin_importmaps.add({ + Alchemy.config.admin_importmaps.add({ importmap_path: root.join("config/importmap.rb"), source_paths: [ root.join("app/javascript") diff --git a/spec/components/alchemy/admin/link_dialog/product_tab_spec.rb b/spec/components/alchemy/admin/link_dialog/product_tab_spec.rb index 54c59eb..8cf0253 100644 --- a/spec/components/alchemy/admin/link_dialog/product_tab_spec.rb +++ b/spec/components/alchemy/admin/link_dialog/product_tab_spec.rb @@ -22,8 +22,8 @@ expect(page).to have_selector("alchemy-product-select [name=product_link]") end - it "sets the value-attribute to slug" do - expect(page).to have_selector("alchemy-product-select[value-attribute=slug]") + it "sets the value-attribute" do + expect(page).to have_selector("alchemy-product-select[value-attribute=url_path]") end context "with product found by url" do diff --git a/spec/components/alchemy/admin/product_select_spec.rb b/spec/components/alchemy/admin/product_select_spec.rb index ec30e48..2fc4d49 100644 --- a/spec/components/alchemy/admin/product_select_spec.rb +++ b/spec/components/alchemy/admin/product_select_spec.rb @@ -67,8 +67,8 @@ end end - context "with value_attribute set to evil" do - let(:value_attribute) { :evil } + context "with value_attribute set to nil" do + let(:value_attribute) { nil } it "sets value-attribute to 'id'" do expect(page).to have_selector( diff --git a/spec/features/alchemy/link_overlay_spec.rb b/spec/features/alchemy/link_overlay_spec.rb index 6378bdb..3faabd1 100644 --- a/spec/features/alchemy/link_overlay_spec.rb +++ b/spec/features/alchemy/link_overlay_spec.rb @@ -26,12 +26,12 @@ authorize_user admin_user allow(element).to receive(:definition) do - { + Alchemy::ElementDefinition.new( name: "article", ingredients: [ {role: "headline", type: "Text", settings: {linkable: true}} ] - } + ) end element.save! diff --git a/spec/models/alchemy/solidus/product_finder_spec.rb b/spec/models/alchemy/solidus/product_finder_spec.rb new file mode 100644 index 0000000..33cd8f8 --- /dev/null +++ b/spec/models/alchemy/solidus/product_finder_spec.rb @@ -0,0 +1,32 @@ +require "rails_helper" + +RSpec.describe Alchemy::Solidus::ProductFinder do + let(:product) { create(:product) } + let(:finder) { described_class.call(url) } + + context "with url matching product slug" do + let(:url) do + Spree::Core::Engine.routes.url_helpers.product_path(product) + end + + it "returns product" do + expect(finder).to eq(product) + end + end + + context "with non matching url" do + let(:url) { "some/product/22" } + + it "returns nil" do + expect(finder).to be_nil + end + end + + context "with nil as url" do + let(:url) { nil } + + it "returns nil" do + expect(finder).to be_nil + end + end +end diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 5a7df09..70e2c01 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -5,6 +5,14 @@ RSpec.describe Spree::Product, type: :model do it { is_expected.to have_many(:alchemy_ingredients) } + describe "#url_path" do + let(:product) { create(:product) } + + it "returns the product path" do + expect(product.url_path).to eq(Spree::Core::Engine.routes.url_helpers.product_path(product)) + end + end + describe "cache invalidation" do let(:page) { create(:alchemy_page) } let(:page_version) { create(:alchemy_page_version, page: page) } diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index caa9660..ffb559b 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -75,8 +75,6 @@ Capybara.server = :puma, {Silent: true} RSpec.configure do |config| - # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures - config.fixture_path = "#{::Rails.root}/spec/fixtures" config.include Alchemy::TestSupport::IntegrationHelpers, type: :feature config.include Alchemy::TestSupport::CapybaraHelpers, type: :feature config.include ActiveSupport::Testing::TimeHelpers, type: :model diff --git a/spec/views/alchemy/ingredients/spree_product_editor_spec.rb b/spec/views/alchemy/ingredients/spree_product_editor_spec.rb index 39c6ce1..79adcfc 100644 --- a/spec/views/alchemy/ingredients/spree_product_editor_spec.rb +++ b/spec/views/alchemy/ingredients/spree_product_editor_spec.rb @@ -9,7 +9,7 @@ before do allow(element).to receive(:definition) do - { + Alchemy::ElementDefinition.new( name: "all_you_can_eat", ingredients: [ {role: "product", @@ -19,7 +19,7 @@ {role: "taxon", type: "SpreeTaxon"} ] - } + ) end allow(element_editor).to receive(:ingredients) { [Alchemy::IngredientEditor.new(ingredient)] } diff --git a/spec/views/alchemy/ingredients/spree_taxon_editor_spec.rb b/spec/views/alchemy/ingredients/spree_taxon_editor_spec.rb index bfdaa0b..5703a5f 100644 --- a/spec/views/alchemy/ingredients/spree_taxon_editor_spec.rb +++ b/spec/views/alchemy/ingredients/spree_taxon_editor_spec.rb @@ -9,7 +9,7 @@ before do allow(element).to receive(:definition) do - { + Alchemy::ElementDefinition.new( name: "all_you_can_eat", ingredients: [ {role: "product", @@ -19,7 +19,7 @@ {role: "taxon", type: "SpreeTaxon"} ] - } + ) end allow(element_editor).to receive(:ingredients) { [Alchemy::IngredientEditor.new(ingredient)] } diff --git a/spec/views/alchemy/ingredients/spree_variant_editor_spec.rb b/spec/views/alchemy/ingredients/spree_variant_editor_spec.rb index 5876597..04d4960 100644 --- a/spec/views/alchemy/ingredients/spree_variant_editor_spec.rb +++ b/spec/views/alchemy/ingredients/spree_variant_editor_spec.rb @@ -9,7 +9,7 @@ before do allow(element).to receive(:definition) do - { + Alchemy::ElementDefinition.new( name: "all_you_can_eat", ingredients: [ {role: "product", @@ -19,7 +19,7 @@ {role: "taxon", type: "SpreeTaxon"} ] - } + ) end allow(element_editor).to receive(:ingredients) { [Alchemy::IngredientEditor.new(ingredient)] }