spree_core 5.1.8 → 5.2.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/images/payment_icons/ach.svg +1 -0
- data/app/assets/images/payment_icons/achdirectdebit.svg +1 -0
- data/app/assets/images/payment_icons/ada.svg +1 -0
- data/app/assets/images/payment_icons/adyen.svg +1 -0
- data/app/assets/images/payment_icons/affinbank.svg +1 -0
- data/app/assets/images/payment_icons/affirm.svg +1 -0
- data/app/assets/images/payment_icons/afterpay.svg +1 -0
- data/app/assets/images/payment_icons/airtel_money.svg +1 -0
- data/app/assets/images/payment_icons/airteltigo_mobile_money.svg +1 -0
- data/app/assets/images/payment_icons/alandsbanken.svg +1 -0
- data/app/assets/images/payment_icons/alipay.svg +1 -0
- data/app/assets/images/payment_icons/alipay_hk.svg +1 -0
- data/app/assets/images/payment_icons/alliancebank.svg +1 -0
- data/app/assets/images/payment_icons/alma.svg +1 -0
- data/app/assets/images/payment_icons/amazon.svg +1 -0
- data/app/assets/images/payment_icons/ambank.svg +1 -0
- data/app/assets/images/payment_icons/american_express.svg +1 -0
- data/app/assets/images/payment_icons/apecoin.svg +1 -0
- data/app/assets/images/payment_icons/apple_pay.svg +1 -0
- data/app/assets/images/payment_icons/arbitrum.svg +1 -0
- data/app/assets/images/payment_icons/atome.svg +1 -0
- data/app/assets/images/payment_icons/authorizenet.svg +1 -0
- data/app/assets/images/payment_icons/avalanche.svg +1 -0
- data/app/assets/images/payment_icons/bacs.svg +1 -0
- data/app/assets/images/payment_icons/bancontact.svg +1 -0
- data/app/assets/images/payment_icons/bangkokbank.svg +1 -0
- data/app/assets/images/payment_icons/bankislam.svg +1 -0
- data/app/assets/images/payment_icons/bankmuamalat.svg +1 -0
- data/app/assets/images/payment_icons/bankofamerica.svg +1 -0
- data/app/assets/images/payment_icons/bankrakyat.svg +1 -0
- data/app/assets/images/payment_icons/base.svg +1 -0
- data/app/assets/images/payment_icons/becs.svg +1 -0
- data/app/assets/images/payment_icons/benefit.svg +1 -0
- data/app/assets/images/payment_icons/billie.svg +1 -0
- data/app/assets/images/payment_icons/bitcoin.svg +1 -0
- data/app/assets/images/payment_icons/bitcoin_cash.svg +1 -0
- data/app/assets/images/payment_icons/bizum.svg +1 -0
- data/app/assets/images/payment_icons/blik.svg +1 -0
- data/app/assets/images/payment_icons/bnbchain.svg +1 -0
- data/app/assets/images/payment_icons/bogus.svg +1 -0
- data/app/assets/images/payment_icons/bogus_app_coin.svg +1 -0
- data/app/assets/images/payment_icons/boleto.svg +1 -0
- data/app/assets/images/payment_icons/boost.svg +1 -0
- data/app/assets/images/payment_icons/braintree.svg +1 -0
- data/app/assets/images/payment_icons/buckaroopaybybank.svg +1 -0
- data/app/assets/images/payment_icons/busd.svg +1 -0
- data/app/assets/images/payment_icons/cartes_bancaires.svg +1 -0
- data/app/assets/images/payment_icons/cash.svg +1 -0
- data/app/assets/images/payment_icons/cashappafterpay.svg +1 -0
- data/app/assets/images/payment_icons/cashapppay.svg +1 -0
- data/app/assets/images/payment_icons/checkout_finance.svg +1 -0
- data/app/assets/images/payment_icons/chinabank.svg +1 -0
- data/app/assets/images/payment_icons/clearpay.svg +1 -0
- data/app/assets/images/payment_icons/collector_bank.svg +1 -0
- data/app/assets/images/payment_icons/dai.svg +1 -0
- data/app/assets/images/payment_icons/dana.svg +1 -0
- data/app/assets/images/payment_icons/dankort.svg +1 -0
- data/app/assets/images/payment_icons/danske_bank.svg +1 -0
- data/app/assets/images/payment_icons/deutschebank.svg +1 -0
- data/app/assets/images/payment_icons/diners_club.svg +1 -0
- data/app/assets/images/payment_icons/directbanktransferlatinamerica.svg +1 -0
- data/app/assets/images/payment_icons/discover.svg +1 -0
- data/app/assets/images/payment_icons/dogecoin.svg +1 -0
- data/app/assets/images/payment_icons/duitnow.svg +1 -0
- data/app/assets/images/payment_icons/eft_secure.svg +1 -0
- data/app/assets/images/payment_icons/eftpos_au.svg +1 -0
- data/app/assets/images/payment_icons/eftposau.svg +1 -0
- data/app/assets/images/payment_icons/elo.svg +1 -0
- data/app/assets/images/payment_icons/eos.svg +1 -0
- data/app/assets/images/payment_icons/eps.svg +1 -0
- data/app/assets/images/payment_icons/ethereum.svg +1 -0
- data/app/assets/images/payment_icons/eurobonuscheckout.svg +1 -0
- data/app/assets/images/payment_icons/ezcash.svg +1 -0
- data/app/assets/images/payment_icons/fantom.svg +1 -0
- data/app/assets/images/payment_icons/fpx.svg +1 -0
- data/app/assets/images/payment_icons/gcash.svg +1 -0
- data/app/assets/images/payment_icons/generic.svg +1 -0
- data/app/assets/images/payment_icons/genericbank.svg +1 -0
- data/app/assets/images/payment_icons/gift-card.svg +1 -0
- data/app/assets/images/payment_icons/giropay.svg +1 -0
- data/app/assets/images/payment_icons/glbepaypal.svg +1 -0
- data/app/assets/images/payment_icons/gmobanktransfer.svg +1 -0
- data/app/assets/images/payment_icons/gnosis.svg +1 -0
- data/app/assets/images/payment_icons/google_pay.svg +1 -0
- data/app/assets/images/payment_icons/google_wallet.svg +1 -0
- data/app/assets/images/payment_icons/gopay.svg +1 -0
- data/app/assets/images/payment_icons/grabpay.svg +1 -0
- data/app/assets/images/payment_icons/gusd.svg +1 -0
- data/app/assets/images/payment_icons/handelsbanken.svg +1 -0
- data/app/assets/images/payment_icons/hongleongbank.svg +1 -0
- data/app/assets/images/payment_icons/hypercard.svg +1 -0
- data/app/assets/images/payment_icons/hypercash.svg +1 -0
- data/app/assets/images/payment_icons/ideal.svg +1 -0
- data/app/assets/images/payment_icons/in3viaideal.svg +1 -0
- data/app/assets/images/payment_icons/inbank.svg +1 -0
- data/app/assets/images/payment_icons/jcb.svg +1 -0
- data/app/assets/images/payment_icons/kakao_pay.svg +1 -0
- data/app/assets/images/payment_icons/kasikornbank.svg +1 -0
- data/app/assets/images/payment_icons/klarna-pay-later.svg +1 -0
- data/app/assets/images/payment_icons/klarna-pay-now.svg +1 -0
- data/app/assets/images/payment_icons/klarna-slice-it.svg +1 -0
- data/app/assets/images/payment_icons/klarna.svg +1 -0
- data/app/assets/images/payment_icons/knet.svg +1 -0
- data/app/assets/images/payment_icons/krungthaibank.svg +1 -0
- data/app/assets/images/payment_icons/landbank.svg +1 -0
- data/app/assets/images/payment_icons/laybuybyklarna.svg +1 -0
- data/app/assets/images/payment_icons/litecoin.svg +1 -0
- data/app/assets/images/payment_icons/lunchcheck.svg +1 -0
- data/app/assets/images/payment_icons/mach.svg +1 -0
- data/app/assets/images/payment_icons/mada.svg +1 -0
- data/app/assets/images/payment_icons/maestro.svg +1 -0
- data/app/assets/images/payment_icons/master.svg +1 -0
- data/app/assets/images/payment_icons/mayabank.svg +1 -0
- data/app/assets/images/payment_icons/maybank.svg +1 -0
- data/app/assets/images/payment_icons/maybankm2u.svg +1 -0
- data/app/assets/images/payment_icons/maybankqrpay.svg +1 -0
- data/app/assets/images/payment_icons/mbway.svg +1 -0
- data/app/assets/images/payment_icons/mcash.svg +1 -0
- data/app/assets/images/payment_icons/medicinosbankas.svg +1 -0
- data/app/assets/images/payment_icons/mercadopago.svg +1 -0
- data/app/assets/images/payment_icons/metrobank.svg +1 -0
- data/app/assets/images/payment_icons/mobilepay.svg +1 -0
- data/app/assets/images/payment_icons/momopay.svg +1 -0
- data/app/assets/images/payment_icons/monero.svg +1 -0
- data/app/assets/images/payment_icons/mtn_mobile_money.svg +1 -0
- data/app/assets/images/payment_icons/mybank.svg +1 -0
- data/app/assets/images/payment_icons/naver_pay.svg +1 -0
- data/app/assets/images/payment_icons/nelo.svg +1 -0
- data/app/assets/images/payment_icons/netbanking.svg +1 -0
- data/app/assets/images/payment_icons/novalnetdirectdebitach.svg +1 -0
- data/app/assets/images/payment_icons/nubank.svg +1 -0
- data/app/assets/images/payment_icons/ocbcbank.svg +1 -0
- data/app/assets/images/payment_icons/offlinebanktransferlatinamerica.svg +1 -0
- data/app/assets/images/payment_icons/ola_money.svg +1 -0
- data/app/assets/images/payment_icons/oney.svg +1 -0
- data/app/assets/images/payment_icons/onlinebanking.svg +1 -0
- data/app/assets/images/payment_icons/onlinebanktransfer.svg +1 -0
- data/app/assets/images/payment_icons/orangemobilemoney.svg +1 -0
- data/app/assets/images/payment_icons/otpbank.svg +1 -0
- data/app/assets/images/payment_icons/oxxo.svg +1 -0
- data/app/assets/images/payment_icons/paybybank.svg +1 -0
- data/app/assets/images/payment_icons/paybybankmollie.svg +1 -0
- data/app/assets/images/payment_icons/paybybankus.svg +1 -0
- data/app/assets/images/payment_icons/paycash.svg +1 -0
- data/app/assets/images/payment_icons/payco.svg +1 -0
- data/app/assets/images/payment_icons/payeverpaybybank.svg +1 -0
- data/app/assets/images/payment_icons/payme.svg +1 -0
- data/app/assets/images/payment_icons/paynow.svg +1 -0
- data/app/assets/images/payment_icons/paynowmbank.svg +1 -0
- data/app/assets/images/payment_icons/paypal.svg +1 -0
- data/app/assets/images/payment_icons/paypo.svg +1 -0
- data/app/assets/images/payment_icons/payrexxbanktransfer.svg +1 -0
- data/app/assets/images/payment_icons/payrexxpaybybank.svg +1 -0
- data/app/assets/images/payment_icons/paysafecash.svg +1 -0
- data/app/assets/images/payment_icons/pix.svg +1 -0
- data/app/assets/images/payment_icons/polygon.svg +1 -0
- data/app/assets/images/payment_icons/przelewy24.svg +1 -0
- data/app/assets/images/payment_icons/publicbank.svg +1 -0
- data/app/assets/images/payment_icons/publicbank_pbe.svg +1 -0
- data/app/assets/images/payment_icons/qr_promptpay.svg +1 -0
- data/app/assets/images/payment_icons/rabobank.svg +1 -0
- data/app/assets/images/payment_icons/revolut.svg +1 -0
- data/app/assets/images/payment_icons/rhbbank.svg +1 -0
- data/app/assets/images/payment_icons/riverty.svg +1 -0
- data/app/assets/images/payment_icons/safetypaybanktransfer.svg +1 -0
- data/app/assets/images/payment_icons/samsung_pay.svg +1 -0
- data/app/assets/images/payment_icons/satispay.svg +1 -0
- data/app/assets/images/payment_icons/scalapay.svg +1 -0
- data/app/assets/images/payment_icons/scotiabank.svg +1 -0
- data/app/assets/images/payment_icons/seabankid.svg +1 -0
- data/app/assets/images/payment_icons/seabankph.svg +1 -0
- data/app/assets/images/payment_icons/sepa_bank_transfer.svg +1 -0
- data/app/assets/images/payment_icons/sepadirectdebit.svg +1 -0
- data/app/assets/images/payment_icons/seveneleven.svg +1 -0
- data/app/assets/images/payment_icons/shib.svg +1 -0
- data/app/assets/images/payment_icons/shopcash.svg +1 -0
- data/app/assets/images/payment_icons/siauliubankas.svg +1 -0
- data/app/assets/images/payment_icons/skeps.svg +1 -0
- data/app/assets/images/payment_icons/snap_checkout.svg +1 -0
- data/app/assets/images/payment_icons/sofort.svg +1 -0
- data/app/assets/images/payment_icons/softbank.svg +1 -0
- data/app/assets/images/payment_icons/spei.svg +1 -0
- data/app/assets/images/payment_icons/storecredit.svg +1 -0
- data/app/assets/images/payment_icons/stripe.svg +1 -0
- data/app/assets/images/payment_icons/sveacheckout.svg +1 -0
- data/app/assets/images/payment_icons/swedbank.svg +1 -0
- data/app/assets/images/payment_icons/swish.svg +1 -0
- data/app/assets/images/payment_icons/tbibank.svg +1 -0
- data/app/assets/images/payment_icons/thanachartbank.svg +1 -0
- data/app/assets/images/payment_icons/tnmmoney.svg +1 -0
- data/app/assets/images/payment_icons/touchngo.svg +1 -0
- data/app/assets/images/payment_icons/trustly.svg +1 -0
- data/app/assets/images/payment_icons/twint.svg +1 -0
- data/app/assets/images/payment_icons/uaevisa.svg +1 -0
- data/app/assets/images/payment_icons/unionpay.svg +1 -0
- data/app/assets/images/payment_icons/unzerbanktransfer.svg +1 -0
- data/app/assets/images/payment_icons/upi.svg +1 -0
- data/app/assets/images/payment_icons/usbank.svg +1 -0
- data/app/assets/images/payment_icons/usdc.svg +1 -0
- data/app/assets/images/payment_icons/usdt.svg +1 -0
- data/app/assets/images/payment_icons/venmo.svg +1 -0
- data/app/assets/images/payment_icons/vipps.svg +1 -0
- data/app/assets/images/payment_icons/visa.svg +1 -0
- data/app/assets/images/payment_icons/volksbank.svg +1 -0
- data/app/assets/images/payment_icons/waavepaybybank.svg +1 -0
- data/app/assets/images/payment_icons/walley.svg +1 -0
- data/app/assets/images/payment_icons/wbtc.svg +1 -0
- data/app/assets/images/payment_icons/wechatpay.svg +1 -0
- data/app/assets/images/payment_icons/whishcheckout.svg +1 -0
- data/app/assets/images/payment_icons/wingbank.svg +1 -0
- data/app/assets/images/payment_icons/xrp.svg +1 -0
- data/app/assets/images/payment_icons/zip.svg +1 -0
- data/app/finders/spree/posts/find.rb +137 -0
- data/app/helpers/spree/addresses_helper.rb +9 -3
- data/app/helpers/spree/base_helper.rb +8 -4
- data/app/jobs/spree/images/save_from_url_job.rb +70 -0
- data/app/jobs/spree/imports/create_rows_job.rb +72 -0
- data/app/jobs/spree/imports/process_rows_job.rb +19 -0
- data/app/models/concerns/spree/adjustment_source.rb +3 -3
- data/app/models/concerns/spree/admin_user_methods.rb +52 -0
- data/app/models/concerns/spree/display_on.rb +7 -1
- data/app/models/concerns/spree/has_image_alt_text.rb +18 -0
- data/app/models/concerns/spree/has_one_link.rb +1 -1
- data/app/models/concerns/spree/has_page_links.rb +1 -1
- data/app/models/concerns/spree/linkable.rb +9 -0
- data/app/models/concerns/spree/metadata.rb +2 -0
- data/app/models/concerns/spree/metafields.rb +94 -0
- data/app/models/concerns/spree/named_type.rb +1 -1
- data/app/models/concerns/spree/user_methods.rb +20 -7
- data/app/models/concerns/spree/user_payment_source.rb +1 -1
- data/app/models/concerns/spree/user_roles.rb +3 -2
- data/app/models/spree/ability.rb +4 -0
- data/app/models/spree/address.rb +3 -1
- data/app/models/spree/adjustable/adjuster/tax.rb +2 -2
- data/app/models/spree/adjustable/adjustments_updater.rb +26 -3
- data/app/models/spree/adjustment.rb +4 -1
- data/app/models/spree/asset.rb +16 -0
- data/app/models/spree/base.rb +1 -1
- data/app/models/spree/credit_card.rb +1 -0
- data/app/models/spree/current.rb +5 -1
- data/app/models/spree/custom_domain.rb +5 -0
- data/app/models/spree/customer_return.rb +1 -0
- data/app/models/spree/export.rb +39 -2
- data/app/models/spree/exports/customers.rb +17 -0
- data/app/models/spree/exports/gift_cards.rb +13 -0
- data/app/models/spree/exports/newsletter_subscribers.rb +13 -0
- data/app/models/spree/exports/orders.rb +6 -4
- data/app/models/spree/exports/products.rb +9 -3
- data/app/models/spree/gift_card.rb +7 -1
- data/app/models/spree/gift_card_batch.rb +0 -1
- data/app/models/spree/import.rb +279 -0
- data/app/models/spree/import_mapping.rb +46 -0
- data/app/models/spree/import_row.rb +108 -0
- data/app/models/spree/import_schema.rb +25 -0
- data/app/models/spree/import_schemas/products.rb +45 -0
- data/app/models/spree/imports/products.rb +9 -0
- data/app/models/spree/invitation.rb +1 -1
- data/app/models/spree/legacy_user.rb +0 -1
- data/app/models/spree/line_item.rb +2 -7
- data/app/models/spree/metafield.rb +53 -0
- data/app/models/spree/metafield_definition.rb +91 -0
- data/app/models/spree/metafields/boolean.rb +15 -0
- data/app/models/spree/metafields/json.rb +27 -0
- data/app/models/spree/metafields/long_text.rb +7 -0
- data/app/models/spree/metafields/number.rb +15 -0
- data/app/models/spree/metafields/rich_text.rb +18 -0
- data/app/models/spree/metafields/short_text.rb +7 -0
- data/app/models/spree/newsletter_subscriber/emails.rb +12 -0
- data/app/models/spree/newsletter_subscriber.rb +64 -0
- data/app/models/spree/option_type.rb +2 -0
- data/app/models/spree/option_value.rb +1 -0
- data/app/models/spree/order/checkout.rb +7 -0
- data/app/models/spree/order.rb +8 -3
- data/app/models/spree/order_merger.rb +2 -4
- data/app/models/spree/order_promotion.rb +3 -0
- data/app/models/spree/order_updater.rb +2 -2
- data/app/models/spree/page.rb +5 -1
- data/app/models/spree/page_block.rb +4 -0
- data/app/models/spree/page_blocks/image.rb +3 -0
- data/app/models/spree/page_blocks/metafields.rb +18 -0
- data/app/models/spree/page_blocks/products/brand.rb +15 -0
- data/app/models/spree/page_blocks/products/buy_buttons.rb +5 -1
- data/app/models/spree/page_blocks/products/description.rb +18 -0
- data/app/models/spree/page_blocks/products/price.rb +1 -1
- data/app/models/spree/page_blocks/products/quantity_selector.rb +6 -2
- data/app/models/spree/page_blocks/products/title.rb +5 -1
- data/app/models/spree/page_blocks/products/variant_picker.rb +1 -1
- data/app/models/spree/page_section.rb +2 -2
- data/app/models/spree/page_sections/breadcrumbs.rb +12 -0
- data/app/models/spree/page_sections/footer.rb +20 -13
- data/app/models/spree/page_sections/image_banner.rb +6 -1
- data/app/models/spree/page_sections/image_with_text.rb +5 -0
- data/app/models/spree/page_sections/newsletter.rb +7 -0
- data/app/models/spree/page_sections/product_details.rb +38 -1
- data/app/models/spree/pages/account.rb +4 -0
- data/app/models/spree/pages/cart.rb +10 -0
- data/app/models/spree/pages/checkout.rb +6 -0
- data/app/models/spree/pages/custom.rb +4 -0
- data/app/models/spree/pages/homepage.rb +4 -0
- data/app/models/spree/pages/login.rb +4 -0
- data/app/models/spree/pages/password.rb +2 -0
- data/app/models/spree/pages/post_list.rb +4 -0
- data/app/models/spree/pages/product_details.rb +1 -0
- data/app/models/spree/pages/shop_all.rb +4 -0
- data/app/models/spree/pages/wishlist.rb +4 -0
- data/app/models/spree/payment.rb +1 -0
- data/app/models/spree/payment_method.rb +4 -5
- data/app/models/spree/payment_source.rb +1 -0
- data/app/models/spree/policy.rb +70 -0
- data/app/models/spree/post.rb +4 -2
- data/app/models/spree/post_category.rb +1 -0
- data/app/models/spree/product.rb +16 -9
- data/app/models/spree/promotion/actions/free_shipping.rb +3 -3
- data/app/models/spree/promotion.rb +24 -8
- data/app/models/spree/promotion_action.rb +1 -1
- data/app/models/spree/promotion_handler/cart.rb +1 -1
- data/app/models/spree/promotion_handler/coupon.rb +1 -1
- data/app/models/spree/promotion_rule.rb +1 -1
- data/app/models/spree/property.rb +18 -0
- data/app/models/spree/refund.rb +2 -1
- data/app/models/spree/role_user.rb +1 -1
- data/app/models/spree/shipment.rb +1 -0
- data/app/models/spree/shipping_method.rb +1 -0
- data/app/models/spree/stock_item.rb +1 -0
- data/app/models/spree/stock_transfer.rb +1 -0
- data/app/models/spree/store.rb +45 -3
- data/app/models/spree/store_credit.rb +2 -1
- data/app/models/spree/tax_category.rb +1 -1
- data/app/models/spree/tax_rate.rb +18 -3
- data/app/models/spree/taxon.rb +12 -2
- data/app/models/spree/taxonomy.rb +11 -0
- data/app/models/spree/theme.rb +1 -1
- data/app/models/spree/variant.rb +17 -0
- data/app/presenters/spree/csv/customer_presenter.rb +59 -0
- data/app/presenters/spree/csv/gift_card_presenter.rb +49 -0
- data/app/presenters/spree/csv/metafields_helper.rb +17 -0
- data/app/presenters/spree/csv/newsletter_subscriber_presenter.rb +39 -0
- data/app/presenters/spree/csv/order_line_item_presenter.rb +12 -5
- data/app/presenters/spree/csv/product_variant_presenter.rb +9 -5
- data/app/services/spree/cart/recalculate.rb +6 -4
- data/app/services/spree/cart/remove_line_item.rb +1 -2
- data/app/services/spree/imports/row_processors/base.rb +19 -0
- data/app/services/spree/imports/row_processors/product_variant.rb +228 -0
- data/app/services/spree/line_items/destroy.rb +1 -1
- data/app/services/spree/newsletter/subscribe.rb +43 -0
- data/app/services/spree/newsletter/verify.rb +30 -0
- data/app/sorters/spree/posts/sort.rb +40 -0
- data/app/views/spree/shared/_payment.html.erb +3 -3
- data/config/locales/en.yml +56 -2
- data/db/migrate/20250605131334_add_missing_fields_to_users.rb +23 -13
- data/db/migrate/20250728095629_create_spree_metafields.rb +27 -0
- data/db/migrate/20250730102644_add_missing_unique_indexes_to_spree_option_types_and_values.rb +40 -0
- data/db/migrate/20250730154601_add_unique_index_on_spree_properties_name.rb +24 -0
- data/db/migrate/20250811112056_create_spree_policies.rb +22 -0
- data/db/migrate/20250821211705_fix_unique_index_on_spree_order_promotions.rb +20 -0
- data/db/migrate/20250825175217_add_missing_page_builder_indexes.rb +7 -0
- data/db/migrate/20250826093602_create_spree_newsletter_subscribers.rb +12 -0
- data/db/migrate/20250902143122_fix_policies_store_association.rb +18 -0
- data/db/migrate/20250911205659_add_missing_index_on_spree_products_promotionable.rb +5 -0
- data/db/migrate/20250913130044_add_page_links_counter_cache_to_spree_stores.rb +10 -0
- data/db/migrate/20250914101955_add_missing_indexes_on_spree_adjustments.rb +6 -0
- data/db/migrate/20250915093930_add_metadata_to_spree_newsletter_subscribers.rb +20 -0
- data/db/migrate/20250923141845_create_spree_imports.rb +47 -0
- data/lib/generators/spree/cursor_rules/cursor_rules_generator.rb +19 -0
- data/lib/generators/spree/cursor_rules/templates/spree_rules.mdc +387 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +0 -1
- data/lib/generators/spree/install/templates/config/initializers/spree.rb +5 -0
- data/lib/spree/core/configuration.rb +1 -0
- data/lib/spree/core/dependencies.rb +3 -0
- data/lib/spree/core/engine.rb +57 -2
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +3 -1
- data/lib/spree/permitted_attributes.rb +18 -3
- data/lib/spree/testing_support/capybara_config.rb +9 -3
- data/lib/spree/testing_support/factories/export_factory.rb +5 -17
- data/lib/spree/testing_support/factories/import_factory.rb +20 -0
- data/lib/spree/testing_support/factories/import_mapping_factory.rb +7 -0
- data/lib/spree/testing_support/factories/import_row_factory.rb +7 -0
- data/lib/spree/testing_support/factories/metafield_definition_factory.rb +72 -0
- data/lib/spree/testing_support/factories/metafield_factory.rb +68 -0
- data/lib/spree/testing_support/factories/newsletter_subscriber_factory.rb +18 -0
- data/lib/spree/testing_support/factories/policy_factory.rb +8 -0
- data/lib/spree/testing_support/factories/post_factory.rb +8 -0
- data/lib/spree/testing_support/store.rb +11 -0
- data/lib/tasks/core.rake +128 -0
- metadata +279 -6
- data/app/models/action_text/rich_text_decorator.rb +0 -11
- data/app/presenters/spree/csv/product_presenter.rb +0 -63
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
class AddMissingUniqueIndexesToSpreeOptionTypesAndValues < ActiveRecord::Migration[7.2]
|
|
2
|
+
def change
|
|
3
|
+
# we don't need to run this migration in multi-tenant mode as it's already handled there
|
|
4
|
+
return if defined?(SpreeMultiTenant)
|
|
5
|
+
|
|
6
|
+
# Rename duplicated option types
|
|
7
|
+
duplicates = Spree::OptionType.select(:name).group(:name).having('COUNT(*) > 1').reorder('').pluck(:name)
|
|
8
|
+
|
|
9
|
+
duplicates.each do |duplicate_name|
|
|
10
|
+
option_types = Spree::OptionType.where(name: duplicate_name)
|
|
11
|
+
|
|
12
|
+
option_types.each_with_index do |option_type, index|
|
|
13
|
+
next if index == 0 # Keep the first one unchanged
|
|
14
|
+
|
|
15
|
+
new_name = "#{duplicate_name}_#{index}"
|
|
16
|
+
option_type.update_columns(name: new_name, updated_at: Time.current)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Handle duplicates in spree_option_values
|
|
21
|
+
duplicates = Spree::OptionValue.select(:option_type_id, :name).group(:option_type_id, :name).having('COUNT(*) > 1').reorder('')
|
|
22
|
+
|
|
23
|
+
duplicates.each do |dup|
|
|
24
|
+
option_values = Spree::OptionValue.where(option_type_id: dup.option_type_id, name: dup.name)
|
|
25
|
+
|
|
26
|
+
option_values.each_with_index do |option_value, index|
|
|
27
|
+
next if index == 0 # Keep the first one unchanged
|
|
28
|
+
|
|
29
|
+
new_name = "#{option_value.name}_#{index}"
|
|
30
|
+
option_value.update_columns(name: new_name, updated_at: Time.current)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Add indexes
|
|
35
|
+
remove_index :spree_option_types, :name, if_exists: true
|
|
36
|
+
add_index :spree_option_types, :name, unique: true, if_not_exists: true
|
|
37
|
+
|
|
38
|
+
add_index :spree_option_values, %i[option_type_id name], unique: true, if_not_exists: true
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class AddUniqueIndexOnSpreePropertiesName < ActiveRecord::Migration[7.2]
|
|
2
|
+
def change
|
|
3
|
+
# we don't need to run this migration in multi-tenant mode as it's already handled there
|
|
4
|
+
return if defined?(SpreeMultiTenant)
|
|
5
|
+
|
|
6
|
+
# Rename duplicated properties
|
|
7
|
+
duplicates = Spree::Property.select(:name).group(:name).having('COUNT(*) > 1').reorder('').pluck(:name)
|
|
8
|
+
|
|
9
|
+
duplicates.each do |duplicate_name|
|
|
10
|
+
properties = Spree::Property.where(name: duplicate_name)
|
|
11
|
+
|
|
12
|
+
properties.each_with_index do |property, index|
|
|
13
|
+
next if index == 0 # Keep the first one unchanged
|
|
14
|
+
|
|
15
|
+
new_name = "#{duplicate_name}_#{index}"
|
|
16
|
+
property.update_columns(name: new_name, updated_at: Time.current)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Add indexes
|
|
21
|
+
remove_index :spree_properties, :name, if_exists: true
|
|
22
|
+
add_index :spree_properties, :name, unique: true, if_not_exists: true
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class CreateSpreePolicies < ActiveRecord::Migration[7.2]
|
|
2
|
+
def change
|
|
3
|
+
create_table :spree_policies do |t|
|
|
4
|
+
t.belongs_to :store, null: false
|
|
5
|
+
t.string :slug, null: false
|
|
6
|
+
t.string :name, null: false
|
|
7
|
+
|
|
8
|
+
t.timestamps
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
add_index :spree_policies, [:store_id, :slug], unique: true
|
|
12
|
+
create_table :spree_policy_translations do |t|
|
|
13
|
+
t.string :locale, null: false
|
|
14
|
+
t.string :name
|
|
15
|
+
t.references :spree_policy, null: false
|
|
16
|
+
|
|
17
|
+
t.timestamps
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
add_index :spree_policy_translations, [:spree_policy_id, :locale], unique: true
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class FixUniqueIndexOnSpreeOrderPromotions < ActiveRecord::Migration[7.2]
|
|
2
|
+
def change
|
|
3
|
+
remove_index :spree_order_promotions, name: 'index_spree_order_promotions_on_promotion_id_and_order_id', if_exists: true
|
|
4
|
+
|
|
5
|
+
# Remove duplicate records before adding unique index
|
|
6
|
+
duplicates = Spree::OrderPromotion.select(:promotion_id, :order_id).group(:promotion_id, :order_id).having('COUNT(*) > 1').reorder('').pluck(:promotion_id, :order_id)
|
|
7
|
+
|
|
8
|
+
duplicates.each do |duplicate_promotion_id, duplicate_order_id|
|
|
9
|
+
order_promotions = Spree::OrderPromotion.where(promotion_id: duplicate_promotion_id, order_id: duplicate_order_id)
|
|
10
|
+
|
|
11
|
+
order_promotions.each_with_index do |order_promotion, index|
|
|
12
|
+
next if index == 0 # Keep the first one unchanged
|
|
13
|
+
|
|
14
|
+
order_promotion.destroy
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
add_index :spree_order_promotions, [:promotion_id, :order_id], unique: true, name: 'index_spree_order_promotions_on_promotion_id_and_order_id'
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
class AddMissingPageBuilderIndexes < ActiveRecord::Migration[7.2]
|
|
2
|
+
def change
|
|
3
|
+
add_index :spree_themes, %w[store_id default], name: 'index_spree_themes_on_store_id_and_default', if_not_exists: true
|
|
4
|
+
add_index :spree_page_links, %w[parent_type parent_id position], name: 'index_spree_page_links_parent_with_position', if_not_exists: true
|
|
5
|
+
add_index :spree_page_blocks, :deleted_at, if_not_exists: true
|
|
6
|
+
end
|
|
7
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class CreateSpreeNewsletterSubscribers < ActiveRecord::Migration[7.2]
|
|
2
|
+
def change
|
|
3
|
+
create_table :spree_newsletter_subscribers do |t|
|
|
4
|
+
t.string :email, index: { unique: true }, null: false
|
|
5
|
+
t.references :user, index: true
|
|
6
|
+
t.datetime :verified_at, index: true
|
|
7
|
+
t.string :verification_token, index: { unique: true }
|
|
8
|
+
|
|
9
|
+
t.timestamps
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class FixPoliciesStoreAssociation < ActiveRecord::Migration[7.2]
|
|
2
|
+
def change
|
|
3
|
+
add_reference :spree_policies, :owner, polymorphic: true, index: true
|
|
4
|
+
|
|
5
|
+
Spree::Policy.reset_column_information
|
|
6
|
+
Spree::Policy.all.each do |policy|
|
|
7
|
+
policy.update(owner_id: policy.store_id, owner_type: 'Spree::Store')
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
remove_index :spree_policies, [:store_id, :slug], unique: true, if_exists: true
|
|
11
|
+
remove_index :spree_policies, [:store_id, :position], if_exists: true
|
|
12
|
+
remove_column :spree_policies, :store_id, if_exists: true
|
|
13
|
+
remove_column :spree_policies, :show_in_checkout_footer, if_exists: true
|
|
14
|
+
remove_column :spree_policies, :position, if_exists: true
|
|
15
|
+
|
|
16
|
+
add_index :spree_policies, [:owner_id, :owner_type, :slug], unique: true
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class AddPageLinksCounterCacheToSpreeStores < ActiveRecord::Migration[7.2]
|
|
2
|
+
def change
|
|
3
|
+
add_column :spree_stores, :page_links_count, :integer, default: 0, null: false
|
|
4
|
+
|
|
5
|
+
Spree::Store.reset_column_information
|
|
6
|
+
Spree::Store.find_each do |store|
|
|
7
|
+
Spree::Store.reset_counters(store.id, :links)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class AddMetadataToSpreeNewsletterSubscribers < ActiveRecord::Migration[7.2]
|
|
2
|
+
def up
|
|
3
|
+
change_table :spree_newsletter_subscribers do |t|
|
|
4
|
+
if t.respond_to? :jsonb
|
|
5
|
+
t.jsonb :public_metadata
|
|
6
|
+
t.jsonb :private_metadata
|
|
7
|
+
else
|
|
8
|
+
t.json :public_metadata
|
|
9
|
+
t.json :private_metadata
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def down
|
|
15
|
+
change_table :spree_newsletter_subscribers do |t|
|
|
16
|
+
t.remove :public_metadata
|
|
17
|
+
t.remove :private_metadata
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
class CreateSpreeImports < ActiveRecord::Migration[7.2]
|
|
2
|
+
def change
|
|
3
|
+
create_table :spree_imports do |t|
|
|
4
|
+
t.belongs_to :user, null: false
|
|
5
|
+
t.belongs_to :owner, polymorphic: true, null: false
|
|
6
|
+
|
|
7
|
+
t.string :status, null: false, index: true
|
|
8
|
+
|
|
9
|
+
t.string :number, limit: 32, null: false, index: { unique: true }
|
|
10
|
+
t.string :type, null: false, index: true
|
|
11
|
+
|
|
12
|
+
t.text :processing_errors
|
|
13
|
+
|
|
14
|
+
t.text :preferences
|
|
15
|
+
|
|
16
|
+
t.integer :rows_count, null: false, default: 0
|
|
17
|
+
|
|
18
|
+
t.timestamps
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
create_table :spree_import_rows do |t|
|
|
22
|
+
t.belongs_to :import, null: false
|
|
23
|
+
t.belongs_to :item, polymorphic: true
|
|
24
|
+
|
|
25
|
+
t.string :status, null: false, index: true
|
|
26
|
+
t.integer :row_number, null: false
|
|
27
|
+
|
|
28
|
+
t.text :data, null: false
|
|
29
|
+
t.text :validation_errors
|
|
30
|
+
|
|
31
|
+
t.timestamps
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
add_index :spree_import_rows, [:import_id, :row_number], unique: true
|
|
35
|
+
|
|
36
|
+
create_table :spree_import_mappings do |t|
|
|
37
|
+
t.belongs_to :import, null: false
|
|
38
|
+
|
|
39
|
+
t.string :schema_field, null: false
|
|
40
|
+
t.string :file_column, index: true
|
|
41
|
+
|
|
42
|
+
t.timestamps
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
add_index :spree_import_mappings, [:import_id, :schema_field], unique: true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class CursorRulesGenerator < Rails::Generators::Base
|
|
3
|
+
desc 'Set up Cursor Rules - copies all Spree cursor rules to .cursor/rules directory'
|
|
4
|
+
|
|
5
|
+
def self.source_paths
|
|
6
|
+
paths = superclass.source_paths
|
|
7
|
+
paths << File.expand_path('templates', __dir__)
|
|
8
|
+
paths.flatten
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def create_cursor_rules_directory
|
|
12
|
+
empty_directory '.cursor/rules'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def copy_cursor_rules
|
|
16
|
+
copy_file 'spree_rules.mdc', '.cursor/rules/spree_rules.mdc'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Cursor Rules for Spree Commerce Development
|
|
6
|
+
|
|
7
|
+
## General Development Guidelines
|
|
8
|
+
|
|
9
|
+
### Framework & Architecture
|
|
10
|
+
|
|
11
|
+
- Spree is built on Ruby on Rails and follows MVC architecture
|
|
12
|
+
- All Spree code must be namespaced under `Spree::` module
|
|
13
|
+
- Spree is distributed as Rails engines with separate gems (core, admin, api, storefront, emails, etc.)
|
|
14
|
+
- Follow Rails conventions and the Rails Security Guide
|
|
15
|
+
- Prefer Rails idioms and standard patterns over custom solutions
|
|
16
|
+
|
|
17
|
+
### Code Organization
|
|
18
|
+
|
|
19
|
+
- Place all models in `app/models/spree/` directory
|
|
20
|
+
- Place all controllers in `app/controllers/spree/` directory
|
|
21
|
+
- Place all views in `app/views/spree/` directory
|
|
22
|
+
- Place all services in `app/services/spree/` directory
|
|
23
|
+
- Place all mailers in `app/mailers/spree/` directory
|
|
24
|
+
- Place all API serializers in `app/serializers/spree/` directory
|
|
25
|
+
- Place all helpers in `app/helpers/spree/` directory
|
|
26
|
+
- Place all jobs in `app/jobs/spree/` directory
|
|
27
|
+
- Place all presenters in `app/presenters/spree/` directory
|
|
28
|
+
- Use consistent file naming: `spree/product.rb` for `Spree::Product` class
|
|
29
|
+
- Group related functionality into concerns when appropriate
|
|
30
|
+
- Do not call `Spree::User` directly, use `Spree.user_class` instead
|
|
31
|
+
- Do not call `Spree::AdminUser` directly, use `Spree.admin_user_class` instead
|
|
32
|
+
|
|
33
|
+
## Naming Conventions & Structure
|
|
34
|
+
|
|
35
|
+
### Classes & Modules
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
# ✅ Correct naming
|
|
39
|
+
module Spree
|
|
40
|
+
class Product < Spree.base_class
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
module Spree
|
|
45
|
+
module Admin
|
|
46
|
+
class ProductsController < ResourceController
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# ❌ Incorrect - missing namespace
|
|
52
|
+
class Product < ApplicationRecord
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Always inherit from `Spree.base_class` when creating models.
|
|
57
|
+
|
|
58
|
+
### File Paths
|
|
59
|
+
|
|
60
|
+
- Models: `app/models/spree/product.rb`
|
|
61
|
+
- Controllers: `app/controllers/spree/admin/products_controller.rb`
|
|
62
|
+
- Views: `app/views/spree/admin/products/`
|
|
63
|
+
- Decorators: `app/models/spree/product_decorator.rb`
|
|
64
|
+
|
|
65
|
+
## Model Development
|
|
66
|
+
|
|
67
|
+
### Model Patterns
|
|
68
|
+
|
|
69
|
+
- Use ActiveRecord associations appropriately, always pass `class_name` and `dependent` options
|
|
70
|
+
- Implement concerns for shared functionality
|
|
71
|
+
- Use scopes for reusable query patterns
|
|
72
|
+
- Include `Spree::Metafields` concern for models that need metadata support
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
# ✅ Good model structure
|
|
76
|
+
class Spree::Product < Spree.base_class
|
|
77
|
+
include Spree::Metafields
|
|
78
|
+
|
|
79
|
+
has_many :variants, class_name: 'Spree::Variant', dependent: :destroy
|
|
80
|
+
has_many :product_properties, class_name: 'Spree::ProductProperty', dependent: :destroy
|
|
81
|
+
has_many :properties, through: :product_properties, source: :property
|
|
82
|
+
|
|
83
|
+
scope :available, -> { where(available_on: ..Time.current) }
|
|
84
|
+
|
|
85
|
+
validates :name, presence: true
|
|
86
|
+
validates :slug, presence: true, uniqueness: { scope: spree_base_uniqueness_scope }
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
For uniqueness validation, always use `scope: spree_base_uniqueness_scope`
|
|
91
|
+
|
|
92
|
+
## Controller Development
|
|
93
|
+
|
|
94
|
+
### Controller Inheritance
|
|
95
|
+
|
|
96
|
+
- Admin controllers inherit from `Spree::Admin::ResourceController` which handles most of CRUD operations
|
|
97
|
+
- API controllers inherit from `Spree::Api::V2::BaseController`
|
|
98
|
+
- Storefront controllers inherit from `Spree::StoreController`
|
|
99
|
+
|
|
100
|
+
### Parameter Handling
|
|
101
|
+
|
|
102
|
+
- Always use strong parameters
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
# ✅ Proper parameter handling
|
|
106
|
+
def permitted_product_params
|
|
107
|
+
params.require(:product).permit(:name, :description, :price)
|
|
108
|
+
end
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Customization & Extensions
|
|
112
|
+
|
|
113
|
+
### Spree::Dependencies System (Preferred Method)
|
|
114
|
+
|
|
115
|
+
Dependencies allow you to replace parts of Spree core with custom implementations. This is the preferred method for customization.
|
|
116
|
+
|
|
117
|
+
#### Global Customization
|
|
118
|
+
|
|
119
|
+
In `config/initializers/spree.rb`:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
# Single service replacement
|
|
123
|
+
Spree::Dependencies.cart_add_item_service = 'MyAddToCartService'
|
|
124
|
+
|
|
125
|
+
# Or using block syntax
|
|
126
|
+
Spree.dependencies do |dependencies|
|
|
127
|
+
dependencies.cart_add_item_service = 'MyAddToCartService'
|
|
128
|
+
dependencies.checkout_complete_service = 'MyCheckoutCompleteService'
|
|
129
|
+
end
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### API Level Customization
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
# Storefront API specific
|
|
136
|
+
Spree::Api::Dependencies.storefront_cart_serializer = 'MyCartSerializer'
|
|
137
|
+
Spree::Api::Dependencies.storefront_cart_add_item_service = 'MyAddToCartService'
|
|
138
|
+
|
|
139
|
+
# Platform API specific
|
|
140
|
+
Spree::Api::Dependencies.platform_product_serializer = 'MyProductSerializer'
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Service Implementation
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
# ✅ Proper service inheritance
|
|
147
|
+
class MyAddToCartService < Spree::Cart::AddItem
|
|
148
|
+
def call(order:, variant:, quantity: nil, public_metadata: {}, private_metadata: {}, options: {})
|
|
149
|
+
ApplicationRecord.transaction do
|
|
150
|
+
run :add_to_line_item
|
|
151
|
+
run Spree::Dependencies.cart_recalculate_service.constantize
|
|
152
|
+
run :update_external_system
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
def update_external_system(result)
|
|
159
|
+
# Custom logic here
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### Available Injection Points
|
|
165
|
+
|
|
166
|
+
Common dependencies you can override:
|
|
167
|
+
- Cart services: `cart_add_item_service`, `cart_update_service`, `cart_remove_item_service`
|
|
168
|
+
- Checkout services: `checkout_next_service`, `checkout_complete_service`
|
|
169
|
+
- Order services: `order_approve_service`, `order_cancel_service`
|
|
170
|
+
- Payment services: `payment_create_service`, `payment_process_service`
|
|
171
|
+
- Ability classes: `ability_class`
|
|
172
|
+
- Serializers: Various API serializers for different endpoints
|
|
173
|
+
|
|
174
|
+
### Decorators (Use Sparingly)
|
|
175
|
+
|
|
176
|
+
Decorators should be a last resort - they make upgrades difficult. Use `Module.prepend` pattern for decorators.
|
|
177
|
+
|
|
178
|
+
#### Model Decorators
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
# ✅ Proper decorator structure
|
|
182
|
+
module Spree
|
|
183
|
+
module ProductDecorator
|
|
184
|
+
def self.prepended(base)
|
|
185
|
+
base.has_many :videos, class_name: 'Spree::Video', dependent: :destroy
|
|
186
|
+
base.before_validation :strip_whitespaces
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def custom_name
|
|
190
|
+
name.upcase
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
private
|
|
194
|
+
|
|
195
|
+
def strip_whitespaces
|
|
196
|
+
self.name = name.strip if name.present?
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
Product.prepend(ProductDecorator)
|
|
201
|
+
end
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### Controller Decorators
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
# ✅ Controller decorator with dependency injection
|
|
208
|
+
module Spree
|
|
209
|
+
module Admin
|
|
210
|
+
module ProductsControllerDecorator
|
|
211
|
+
def self.prepended(base)
|
|
212
|
+
base.before_action :load_custom_data, only: [:show, :edit]
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def custom_action
|
|
216
|
+
# Custom action implementation
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
private
|
|
220
|
+
|
|
221
|
+
def load_custom_data
|
|
222
|
+
@custom_data = fetch_custom_data
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
ProductsController.prepend(ProductsControllerDecorator)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### View Customization
|
|
232
|
+
|
|
233
|
+
#### Admin Panel Injection Points
|
|
234
|
+
|
|
235
|
+
Use partial injection for admin customization:
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
# In config/initializers/spree.rb
|
|
239
|
+
Rails.application.config.spree_admin.head_partials << 'spree/admin/shared/custom_head'
|
|
240
|
+
Rails.application.config.spree_admin.body_end_partials << 'spree/admin/shared/custom_footer'
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Available injection points:
|
|
244
|
+
- `head_partials` - Injects into `<head>` tag
|
|
245
|
+
- `body_start_partials` - Injects at start of `<body>`
|
|
246
|
+
- `body_end_partials` - Injects at end of `<body>`
|
|
247
|
+
|
|
248
|
+
#### Storefront Themes
|
|
249
|
+
|
|
250
|
+
Create custom themes for storefront customization:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
bin/rails g spree:storefront:theme MyTheme
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
# Register theme in config/initializers/spree.rb
|
|
258
|
+
Rails.application.config.spree.themes << Spree::Themes::MyTheme
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### View Overrides
|
|
262
|
+
|
|
263
|
+
Override specific views by creating files in your app:
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
app/views/themes/my_theme/spree/products/index.html.erb
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Do not override views for admin, only for storefront. Avoid overriding any views for Checkout or Cart.
|
|
270
|
+
|
|
271
|
+
### Authentication Integration
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
# In config/initializers/spree.rb
|
|
275
|
+
Spree.user_class = 'User'
|
|
276
|
+
Spree.admin_user_class = 'AdminUser'
|
|
277
|
+
|
|
278
|
+
# Custom authentication
|
|
279
|
+
Rails.application.config.to_prepare do
|
|
280
|
+
Spree::ApplicationController.include MyAuthenticationModule
|
|
281
|
+
end
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Testing Guidelines
|
|
285
|
+
|
|
286
|
+
### Test Structure
|
|
287
|
+
|
|
288
|
+
- Use RSpec for testing
|
|
289
|
+
- Place specs in `spec/` directory following Rails conventions
|
|
290
|
+
- Use Spree's testing helpers and factories
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
# ✅ Proper test structure
|
|
294
|
+
require 'spec_helper'
|
|
295
|
+
|
|
296
|
+
RSpec.describe Spree::Product, type: :model do
|
|
297
|
+
let(:product) { create(:product) }
|
|
298
|
+
|
|
299
|
+
describe '#available?' do
|
|
300
|
+
it 'returns true when product is available' do
|
|
301
|
+
expect(product.available?).to be true
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Factory Usage
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
# Use Spree factories
|
|
311
|
+
create(:product, name: 'Test Product')
|
|
312
|
+
create(:order_with_line_items)
|
|
313
|
+
create(:user)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Performance & Security
|
|
317
|
+
|
|
318
|
+
### Database Queries
|
|
319
|
+
|
|
320
|
+
- Use includes/joins to avoid N+1 queries
|
|
321
|
+
- Add database indexes for frequently queried fields
|
|
322
|
+
- Use counter caches for associations that are counted frequently
|
|
323
|
+
|
|
324
|
+
### Security
|
|
325
|
+
|
|
326
|
+
- Always use strong parameters in controllers
|
|
327
|
+
- Sanitize user input
|
|
328
|
+
- Use Spree's built-in authorization system (CanCanCan)
|
|
329
|
+
- Validate file uploads and restrict file types
|
|
330
|
+
|
|
331
|
+
## Common Patterns
|
|
332
|
+
|
|
333
|
+
### Service Objects
|
|
334
|
+
|
|
335
|
+
```ruby
|
|
336
|
+
# ✅ Spree service pattern
|
|
337
|
+
module Spree
|
|
338
|
+
class MyCustomService
|
|
339
|
+
prepend Spree::ServiceModule::Base
|
|
340
|
+
|
|
341
|
+
def call(param1:, param2: nil)
|
|
342
|
+
# Service logic here
|
|
343
|
+
success(result_data)
|
|
344
|
+
rescue StandardError => e
|
|
345
|
+
failure(e.message)
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### API Development
|
|
352
|
+
|
|
353
|
+
```ruby
|
|
354
|
+
# ✅ Custom API endpoint
|
|
355
|
+
module Spree
|
|
356
|
+
module Api
|
|
357
|
+
module V2
|
|
358
|
+
class CustomController < Spree::Api::V2::BaseController
|
|
359
|
+
def index
|
|
360
|
+
render json: serialized_collection
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
private
|
|
364
|
+
|
|
365
|
+
def serialized_collection
|
|
366
|
+
Spree::Api::Dependencies.storefront_product_serializer.constantize.new(
|
|
367
|
+
collection,
|
|
368
|
+
include: resource_includes,
|
|
369
|
+
fields: sparse_fields
|
|
370
|
+
).serializable_hash
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Avoid These Patterns
|
|
379
|
+
|
|
380
|
+
❌ Direct inheritance from Rails classes without Spree namespace
|
|
381
|
+
❌ Monkey patching without using decorators or dependencies
|
|
382
|
+
❌ Hard-coding configuration values
|
|
383
|
+
❌ Direct SQL queries without using ActiveRecord
|
|
384
|
+
❌ Creating models outside Spree namespace when extending core functionality
|
|
385
|
+
❌ Using class_eval decorators (use Module.prepend instead)
|
|
386
|
+
❌ Overriding entire view files when partial injection would work
|
|
387
|
+
❌ Modifying core Spree files directly
|
|
@@ -38,7 +38,6 @@ module Spree
|
|
|
38
38
|
opts[:skip_spring] = true
|
|
39
39
|
opts[:skip_test] = true
|
|
40
40
|
opts[:skip_bootsnap] = true
|
|
41
|
-
opts[:skip_asset_pipeline] = true # skip installing propshaft, we're still using sprockets as a dependency
|
|
42
41
|
opts[:skip_docker] = true
|
|
43
42
|
opts[:skip_rubocop] = true
|
|
44
43
|
opts[:skip_brakeman] = true
|
|
@@ -29,6 +29,11 @@ end
|
|
|
29
29
|
# Default is nil and your application host will be used
|
|
30
30
|
# Spree.cdn_host = 'cdn.example.com'
|
|
31
31
|
|
|
32
|
+
# Multi-store setup
|
|
33
|
+
# You need to set a wildcard `root_domain` on the store to enable multi-store setup
|
|
34
|
+
# all new stores will be created in a subdomain of the root domain, eg. store1.lvh.me, store2.lvh.me, etc.
|
|
35
|
+
# Spree.root_domain = ENV.fetch('SPREE_ROOT_DOMAIN', 'lvh.me')
|
|
36
|
+
|
|
32
37
|
# Use a different service for storage (S3, google, etc)
|
|
33
38
|
# unless Rails.env.test?
|
|
34
39
|
# Spree.private_storage_service_name = :amazon_public # public assets, such as product images
|
|
@@ -50,6 +50,7 @@ module Spree
|
|
|
50
50
|
preference :mailer_logo, :string, deprecated: true
|
|
51
51
|
preference :max_level_in_taxons_menu, :integer, deprecated: true
|
|
52
52
|
preference :non_expiring_credit_types, :array, default: []
|
|
53
|
+
preference :product_properties_enabled, :boolean, default: false # enable legacy product properties
|
|
53
54
|
preference :products_per_page, :integer, default: 12
|
|
54
55
|
preference :require_master_price, :boolean, default: false
|
|
55
56
|
preference :restock_inventory, :boolean, default: true # Determines if a return item is restocked automatically once it has been received
|