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.
Files changed (389) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/payment_icons/ach.svg +1 -0
  3. data/app/assets/images/payment_icons/achdirectdebit.svg +1 -0
  4. data/app/assets/images/payment_icons/ada.svg +1 -0
  5. data/app/assets/images/payment_icons/adyen.svg +1 -0
  6. data/app/assets/images/payment_icons/affinbank.svg +1 -0
  7. data/app/assets/images/payment_icons/affirm.svg +1 -0
  8. data/app/assets/images/payment_icons/afterpay.svg +1 -0
  9. data/app/assets/images/payment_icons/airtel_money.svg +1 -0
  10. data/app/assets/images/payment_icons/airteltigo_mobile_money.svg +1 -0
  11. data/app/assets/images/payment_icons/alandsbanken.svg +1 -0
  12. data/app/assets/images/payment_icons/alipay.svg +1 -0
  13. data/app/assets/images/payment_icons/alipay_hk.svg +1 -0
  14. data/app/assets/images/payment_icons/alliancebank.svg +1 -0
  15. data/app/assets/images/payment_icons/alma.svg +1 -0
  16. data/app/assets/images/payment_icons/amazon.svg +1 -0
  17. data/app/assets/images/payment_icons/ambank.svg +1 -0
  18. data/app/assets/images/payment_icons/american_express.svg +1 -0
  19. data/app/assets/images/payment_icons/apecoin.svg +1 -0
  20. data/app/assets/images/payment_icons/apple_pay.svg +1 -0
  21. data/app/assets/images/payment_icons/arbitrum.svg +1 -0
  22. data/app/assets/images/payment_icons/atome.svg +1 -0
  23. data/app/assets/images/payment_icons/authorizenet.svg +1 -0
  24. data/app/assets/images/payment_icons/avalanche.svg +1 -0
  25. data/app/assets/images/payment_icons/bacs.svg +1 -0
  26. data/app/assets/images/payment_icons/bancontact.svg +1 -0
  27. data/app/assets/images/payment_icons/bangkokbank.svg +1 -0
  28. data/app/assets/images/payment_icons/bankislam.svg +1 -0
  29. data/app/assets/images/payment_icons/bankmuamalat.svg +1 -0
  30. data/app/assets/images/payment_icons/bankofamerica.svg +1 -0
  31. data/app/assets/images/payment_icons/bankrakyat.svg +1 -0
  32. data/app/assets/images/payment_icons/base.svg +1 -0
  33. data/app/assets/images/payment_icons/becs.svg +1 -0
  34. data/app/assets/images/payment_icons/benefit.svg +1 -0
  35. data/app/assets/images/payment_icons/billie.svg +1 -0
  36. data/app/assets/images/payment_icons/bitcoin.svg +1 -0
  37. data/app/assets/images/payment_icons/bitcoin_cash.svg +1 -0
  38. data/app/assets/images/payment_icons/bizum.svg +1 -0
  39. data/app/assets/images/payment_icons/blik.svg +1 -0
  40. data/app/assets/images/payment_icons/bnbchain.svg +1 -0
  41. data/app/assets/images/payment_icons/bogus.svg +1 -0
  42. data/app/assets/images/payment_icons/bogus_app_coin.svg +1 -0
  43. data/app/assets/images/payment_icons/boleto.svg +1 -0
  44. data/app/assets/images/payment_icons/boost.svg +1 -0
  45. data/app/assets/images/payment_icons/braintree.svg +1 -0
  46. data/app/assets/images/payment_icons/buckaroopaybybank.svg +1 -0
  47. data/app/assets/images/payment_icons/busd.svg +1 -0
  48. data/app/assets/images/payment_icons/cartes_bancaires.svg +1 -0
  49. data/app/assets/images/payment_icons/cash.svg +1 -0
  50. data/app/assets/images/payment_icons/cashappafterpay.svg +1 -0
  51. data/app/assets/images/payment_icons/cashapppay.svg +1 -0
  52. data/app/assets/images/payment_icons/checkout_finance.svg +1 -0
  53. data/app/assets/images/payment_icons/chinabank.svg +1 -0
  54. data/app/assets/images/payment_icons/clearpay.svg +1 -0
  55. data/app/assets/images/payment_icons/collector_bank.svg +1 -0
  56. data/app/assets/images/payment_icons/dai.svg +1 -0
  57. data/app/assets/images/payment_icons/dana.svg +1 -0
  58. data/app/assets/images/payment_icons/dankort.svg +1 -0
  59. data/app/assets/images/payment_icons/danske_bank.svg +1 -0
  60. data/app/assets/images/payment_icons/deutschebank.svg +1 -0
  61. data/app/assets/images/payment_icons/diners_club.svg +1 -0
  62. data/app/assets/images/payment_icons/directbanktransferlatinamerica.svg +1 -0
  63. data/app/assets/images/payment_icons/discover.svg +1 -0
  64. data/app/assets/images/payment_icons/dogecoin.svg +1 -0
  65. data/app/assets/images/payment_icons/duitnow.svg +1 -0
  66. data/app/assets/images/payment_icons/eft_secure.svg +1 -0
  67. data/app/assets/images/payment_icons/eftpos_au.svg +1 -0
  68. data/app/assets/images/payment_icons/eftposau.svg +1 -0
  69. data/app/assets/images/payment_icons/elo.svg +1 -0
  70. data/app/assets/images/payment_icons/eos.svg +1 -0
  71. data/app/assets/images/payment_icons/eps.svg +1 -0
  72. data/app/assets/images/payment_icons/ethereum.svg +1 -0
  73. data/app/assets/images/payment_icons/eurobonuscheckout.svg +1 -0
  74. data/app/assets/images/payment_icons/ezcash.svg +1 -0
  75. data/app/assets/images/payment_icons/fantom.svg +1 -0
  76. data/app/assets/images/payment_icons/fpx.svg +1 -0
  77. data/app/assets/images/payment_icons/gcash.svg +1 -0
  78. data/app/assets/images/payment_icons/generic.svg +1 -0
  79. data/app/assets/images/payment_icons/genericbank.svg +1 -0
  80. data/app/assets/images/payment_icons/gift-card.svg +1 -0
  81. data/app/assets/images/payment_icons/giropay.svg +1 -0
  82. data/app/assets/images/payment_icons/glbepaypal.svg +1 -0
  83. data/app/assets/images/payment_icons/gmobanktransfer.svg +1 -0
  84. data/app/assets/images/payment_icons/gnosis.svg +1 -0
  85. data/app/assets/images/payment_icons/google_pay.svg +1 -0
  86. data/app/assets/images/payment_icons/google_wallet.svg +1 -0
  87. data/app/assets/images/payment_icons/gopay.svg +1 -0
  88. data/app/assets/images/payment_icons/grabpay.svg +1 -0
  89. data/app/assets/images/payment_icons/gusd.svg +1 -0
  90. data/app/assets/images/payment_icons/handelsbanken.svg +1 -0
  91. data/app/assets/images/payment_icons/hongleongbank.svg +1 -0
  92. data/app/assets/images/payment_icons/hypercard.svg +1 -0
  93. data/app/assets/images/payment_icons/hypercash.svg +1 -0
  94. data/app/assets/images/payment_icons/ideal.svg +1 -0
  95. data/app/assets/images/payment_icons/in3viaideal.svg +1 -0
  96. data/app/assets/images/payment_icons/inbank.svg +1 -0
  97. data/app/assets/images/payment_icons/jcb.svg +1 -0
  98. data/app/assets/images/payment_icons/kakao_pay.svg +1 -0
  99. data/app/assets/images/payment_icons/kasikornbank.svg +1 -0
  100. data/app/assets/images/payment_icons/klarna-pay-later.svg +1 -0
  101. data/app/assets/images/payment_icons/klarna-pay-now.svg +1 -0
  102. data/app/assets/images/payment_icons/klarna-slice-it.svg +1 -0
  103. data/app/assets/images/payment_icons/klarna.svg +1 -0
  104. data/app/assets/images/payment_icons/knet.svg +1 -0
  105. data/app/assets/images/payment_icons/krungthaibank.svg +1 -0
  106. data/app/assets/images/payment_icons/landbank.svg +1 -0
  107. data/app/assets/images/payment_icons/laybuybyklarna.svg +1 -0
  108. data/app/assets/images/payment_icons/litecoin.svg +1 -0
  109. data/app/assets/images/payment_icons/lunchcheck.svg +1 -0
  110. data/app/assets/images/payment_icons/mach.svg +1 -0
  111. data/app/assets/images/payment_icons/mada.svg +1 -0
  112. data/app/assets/images/payment_icons/maestro.svg +1 -0
  113. data/app/assets/images/payment_icons/master.svg +1 -0
  114. data/app/assets/images/payment_icons/mayabank.svg +1 -0
  115. data/app/assets/images/payment_icons/maybank.svg +1 -0
  116. data/app/assets/images/payment_icons/maybankm2u.svg +1 -0
  117. data/app/assets/images/payment_icons/maybankqrpay.svg +1 -0
  118. data/app/assets/images/payment_icons/mbway.svg +1 -0
  119. data/app/assets/images/payment_icons/mcash.svg +1 -0
  120. data/app/assets/images/payment_icons/medicinosbankas.svg +1 -0
  121. data/app/assets/images/payment_icons/mercadopago.svg +1 -0
  122. data/app/assets/images/payment_icons/metrobank.svg +1 -0
  123. data/app/assets/images/payment_icons/mobilepay.svg +1 -0
  124. data/app/assets/images/payment_icons/momopay.svg +1 -0
  125. data/app/assets/images/payment_icons/monero.svg +1 -0
  126. data/app/assets/images/payment_icons/mtn_mobile_money.svg +1 -0
  127. data/app/assets/images/payment_icons/mybank.svg +1 -0
  128. data/app/assets/images/payment_icons/naver_pay.svg +1 -0
  129. data/app/assets/images/payment_icons/nelo.svg +1 -0
  130. data/app/assets/images/payment_icons/netbanking.svg +1 -0
  131. data/app/assets/images/payment_icons/novalnetdirectdebitach.svg +1 -0
  132. data/app/assets/images/payment_icons/nubank.svg +1 -0
  133. data/app/assets/images/payment_icons/ocbcbank.svg +1 -0
  134. data/app/assets/images/payment_icons/offlinebanktransferlatinamerica.svg +1 -0
  135. data/app/assets/images/payment_icons/ola_money.svg +1 -0
  136. data/app/assets/images/payment_icons/oney.svg +1 -0
  137. data/app/assets/images/payment_icons/onlinebanking.svg +1 -0
  138. data/app/assets/images/payment_icons/onlinebanktransfer.svg +1 -0
  139. data/app/assets/images/payment_icons/orangemobilemoney.svg +1 -0
  140. data/app/assets/images/payment_icons/otpbank.svg +1 -0
  141. data/app/assets/images/payment_icons/oxxo.svg +1 -0
  142. data/app/assets/images/payment_icons/paybybank.svg +1 -0
  143. data/app/assets/images/payment_icons/paybybankmollie.svg +1 -0
  144. data/app/assets/images/payment_icons/paybybankus.svg +1 -0
  145. data/app/assets/images/payment_icons/paycash.svg +1 -0
  146. data/app/assets/images/payment_icons/payco.svg +1 -0
  147. data/app/assets/images/payment_icons/payeverpaybybank.svg +1 -0
  148. data/app/assets/images/payment_icons/payme.svg +1 -0
  149. data/app/assets/images/payment_icons/paynow.svg +1 -0
  150. data/app/assets/images/payment_icons/paynowmbank.svg +1 -0
  151. data/app/assets/images/payment_icons/paypal.svg +1 -0
  152. data/app/assets/images/payment_icons/paypo.svg +1 -0
  153. data/app/assets/images/payment_icons/payrexxbanktransfer.svg +1 -0
  154. data/app/assets/images/payment_icons/payrexxpaybybank.svg +1 -0
  155. data/app/assets/images/payment_icons/paysafecash.svg +1 -0
  156. data/app/assets/images/payment_icons/pix.svg +1 -0
  157. data/app/assets/images/payment_icons/polygon.svg +1 -0
  158. data/app/assets/images/payment_icons/przelewy24.svg +1 -0
  159. data/app/assets/images/payment_icons/publicbank.svg +1 -0
  160. data/app/assets/images/payment_icons/publicbank_pbe.svg +1 -0
  161. data/app/assets/images/payment_icons/qr_promptpay.svg +1 -0
  162. data/app/assets/images/payment_icons/rabobank.svg +1 -0
  163. data/app/assets/images/payment_icons/revolut.svg +1 -0
  164. data/app/assets/images/payment_icons/rhbbank.svg +1 -0
  165. data/app/assets/images/payment_icons/riverty.svg +1 -0
  166. data/app/assets/images/payment_icons/safetypaybanktransfer.svg +1 -0
  167. data/app/assets/images/payment_icons/samsung_pay.svg +1 -0
  168. data/app/assets/images/payment_icons/satispay.svg +1 -0
  169. data/app/assets/images/payment_icons/scalapay.svg +1 -0
  170. data/app/assets/images/payment_icons/scotiabank.svg +1 -0
  171. data/app/assets/images/payment_icons/seabankid.svg +1 -0
  172. data/app/assets/images/payment_icons/seabankph.svg +1 -0
  173. data/app/assets/images/payment_icons/sepa_bank_transfer.svg +1 -0
  174. data/app/assets/images/payment_icons/sepadirectdebit.svg +1 -0
  175. data/app/assets/images/payment_icons/seveneleven.svg +1 -0
  176. data/app/assets/images/payment_icons/shib.svg +1 -0
  177. data/app/assets/images/payment_icons/shopcash.svg +1 -0
  178. data/app/assets/images/payment_icons/siauliubankas.svg +1 -0
  179. data/app/assets/images/payment_icons/skeps.svg +1 -0
  180. data/app/assets/images/payment_icons/snap_checkout.svg +1 -0
  181. data/app/assets/images/payment_icons/sofort.svg +1 -0
  182. data/app/assets/images/payment_icons/softbank.svg +1 -0
  183. data/app/assets/images/payment_icons/spei.svg +1 -0
  184. data/app/assets/images/payment_icons/storecredit.svg +1 -0
  185. data/app/assets/images/payment_icons/stripe.svg +1 -0
  186. data/app/assets/images/payment_icons/sveacheckout.svg +1 -0
  187. data/app/assets/images/payment_icons/swedbank.svg +1 -0
  188. data/app/assets/images/payment_icons/swish.svg +1 -0
  189. data/app/assets/images/payment_icons/tbibank.svg +1 -0
  190. data/app/assets/images/payment_icons/thanachartbank.svg +1 -0
  191. data/app/assets/images/payment_icons/tnmmoney.svg +1 -0
  192. data/app/assets/images/payment_icons/touchngo.svg +1 -0
  193. data/app/assets/images/payment_icons/trustly.svg +1 -0
  194. data/app/assets/images/payment_icons/twint.svg +1 -0
  195. data/app/assets/images/payment_icons/uaevisa.svg +1 -0
  196. data/app/assets/images/payment_icons/unionpay.svg +1 -0
  197. data/app/assets/images/payment_icons/unzerbanktransfer.svg +1 -0
  198. data/app/assets/images/payment_icons/upi.svg +1 -0
  199. data/app/assets/images/payment_icons/usbank.svg +1 -0
  200. data/app/assets/images/payment_icons/usdc.svg +1 -0
  201. data/app/assets/images/payment_icons/usdt.svg +1 -0
  202. data/app/assets/images/payment_icons/venmo.svg +1 -0
  203. data/app/assets/images/payment_icons/vipps.svg +1 -0
  204. data/app/assets/images/payment_icons/visa.svg +1 -0
  205. data/app/assets/images/payment_icons/volksbank.svg +1 -0
  206. data/app/assets/images/payment_icons/waavepaybybank.svg +1 -0
  207. data/app/assets/images/payment_icons/walley.svg +1 -0
  208. data/app/assets/images/payment_icons/wbtc.svg +1 -0
  209. data/app/assets/images/payment_icons/wechatpay.svg +1 -0
  210. data/app/assets/images/payment_icons/whishcheckout.svg +1 -0
  211. data/app/assets/images/payment_icons/wingbank.svg +1 -0
  212. data/app/assets/images/payment_icons/xrp.svg +1 -0
  213. data/app/assets/images/payment_icons/zip.svg +1 -0
  214. data/app/finders/spree/posts/find.rb +137 -0
  215. data/app/helpers/spree/addresses_helper.rb +9 -3
  216. data/app/helpers/spree/base_helper.rb +8 -4
  217. data/app/jobs/spree/images/save_from_url_job.rb +70 -0
  218. data/app/jobs/spree/imports/create_rows_job.rb +72 -0
  219. data/app/jobs/spree/imports/process_rows_job.rb +19 -0
  220. data/app/models/concerns/spree/adjustment_source.rb +3 -3
  221. data/app/models/concerns/spree/admin_user_methods.rb +52 -0
  222. data/app/models/concerns/spree/display_on.rb +7 -1
  223. data/app/models/concerns/spree/has_image_alt_text.rb +18 -0
  224. data/app/models/concerns/spree/has_one_link.rb +1 -1
  225. data/app/models/concerns/spree/has_page_links.rb +1 -1
  226. data/app/models/concerns/spree/linkable.rb +9 -0
  227. data/app/models/concerns/spree/metadata.rb +2 -0
  228. data/app/models/concerns/spree/metafields.rb +94 -0
  229. data/app/models/concerns/spree/named_type.rb +1 -1
  230. data/app/models/concerns/spree/user_methods.rb +20 -7
  231. data/app/models/concerns/spree/user_payment_source.rb +1 -1
  232. data/app/models/concerns/spree/user_roles.rb +3 -2
  233. data/app/models/spree/ability.rb +4 -0
  234. data/app/models/spree/address.rb +3 -1
  235. data/app/models/spree/adjustable/adjuster/tax.rb +2 -2
  236. data/app/models/spree/adjustable/adjustments_updater.rb +26 -3
  237. data/app/models/spree/adjustment.rb +4 -1
  238. data/app/models/spree/asset.rb +16 -0
  239. data/app/models/spree/base.rb +1 -1
  240. data/app/models/spree/credit_card.rb +1 -0
  241. data/app/models/spree/current.rb +5 -1
  242. data/app/models/spree/custom_domain.rb +5 -0
  243. data/app/models/spree/customer_return.rb +1 -0
  244. data/app/models/spree/export.rb +39 -2
  245. data/app/models/spree/exports/customers.rb +17 -0
  246. data/app/models/spree/exports/gift_cards.rb +13 -0
  247. data/app/models/spree/exports/newsletter_subscribers.rb +13 -0
  248. data/app/models/spree/exports/orders.rb +6 -4
  249. data/app/models/spree/exports/products.rb +9 -3
  250. data/app/models/spree/gift_card.rb +7 -1
  251. data/app/models/spree/gift_card_batch.rb +0 -1
  252. data/app/models/spree/import.rb +279 -0
  253. data/app/models/spree/import_mapping.rb +46 -0
  254. data/app/models/spree/import_row.rb +108 -0
  255. data/app/models/spree/import_schema.rb +25 -0
  256. data/app/models/spree/import_schemas/products.rb +45 -0
  257. data/app/models/spree/imports/products.rb +9 -0
  258. data/app/models/spree/invitation.rb +1 -1
  259. data/app/models/spree/legacy_user.rb +0 -1
  260. data/app/models/spree/line_item.rb +2 -7
  261. data/app/models/spree/metafield.rb +53 -0
  262. data/app/models/spree/metafield_definition.rb +91 -0
  263. data/app/models/spree/metafields/boolean.rb +15 -0
  264. data/app/models/spree/metafields/json.rb +27 -0
  265. data/app/models/spree/metafields/long_text.rb +7 -0
  266. data/app/models/spree/metafields/number.rb +15 -0
  267. data/app/models/spree/metafields/rich_text.rb +18 -0
  268. data/app/models/spree/metafields/short_text.rb +7 -0
  269. data/app/models/spree/newsletter_subscriber/emails.rb +12 -0
  270. data/app/models/spree/newsletter_subscriber.rb +64 -0
  271. data/app/models/spree/option_type.rb +2 -0
  272. data/app/models/spree/option_value.rb +1 -0
  273. data/app/models/spree/order/checkout.rb +7 -0
  274. data/app/models/spree/order.rb +8 -3
  275. data/app/models/spree/order_merger.rb +2 -4
  276. data/app/models/spree/order_promotion.rb +3 -0
  277. data/app/models/spree/order_updater.rb +2 -2
  278. data/app/models/spree/page.rb +5 -1
  279. data/app/models/spree/page_block.rb +4 -0
  280. data/app/models/spree/page_blocks/image.rb +3 -0
  281. data/app/models/spree/page_blocks/metafields.rb +18 -0
  282. data/app/models/spree/page_blocks/products/brand.rb +15 -0
  283. data/app/models/spree/page_blocks/products/buy_buttons.rb +5 -1
  284. data/app/models/spree/page_blocks/products/description.rb +18 -0
  285. data/app/models/spree/page_blocks/products/price.rb +1 -1
  286. data/app/models/spree/page_blocks/products/quantity_selector.rb +6 -2
  287. data/app/models/spree/page_blocks/products/title.rb +5 -1
  288. data/app/models/spree/page_blocks/products/variant_picker.rb +1 -1
  289. data/app/models/spree/page_section.rb +2 -2
  290. data/app/models/spree/page_sections/breadcrumbs.rb +12 -0
  291. data/app/models/spree/page_sections/footer.rb +20 -13
  292. data/app/models/spree/page_sections/image_banner.rb +6 -1
  293. data/app/models/spree/page_sections/image_with_text.rb +5 -0
  294. data/app/models/spree/page_sections/newsletter.rb +7 -0
  295. data/app/models/spree/page_sections/product_details.rb +38 -1
  296. data/app/models/spree/pages/account.rb +4 -0
  297. data/app/models/spree/pages/cart.rb +10 -0
  298. data/app/models/spree/pages/checkout.rb +6 -0
  299. data/app/models/spree/pages/custom.rb +4 -0
  300. data/app/models/spree/pages/homepage.rb +4 -0
  301. data/app/models/spree/pages/login.rb +4 -0
  302. data/app/models/spree/pages/password.rb +2 -0
  303. data/app/models/spree/pages/post_list.rb +4 -0
  304. data/app/models/spree/pages/product_details.rb +1 -0
  305. data/app/models/spree/pages/shop_all.rb +4 -0
  306. data/app/models/spree/pages/wishlist.rb +4 -0
  307. data/app/models/spree/payment.rb +1 -0
  308. data/app/models/spree/payment_method.rb +4 -5
  309. data/app/models/spree/payment_source.rb +1 -0
  310. data/app/models/spree/policy.rb +70 -0
  311. data/app/models/spree/post.rb +4 -2
  312. data/app/models/spree/post_category.rb +1 -0
  313. data/app/models/spree/product.rb +16 -9
  314. data/app/models/spree/promotion/actions/free_shipping.rb +3 -3
  315. data/app/models/spree/promotion.rb +24 -8
  316. data/app/models/spree/promotion_action.rb +1 -1
  317. data/app/models/spree/promotion_handler/cart.rb +1 -1
  318. data/app/models/spree/promotion_handler/coupon.rb +1 -1
  319. data/app/models/spree/promotion_rule.rb +1 -1
  320. data/app/models/spree/property.rb +18 -0
  321. data/app/models/spree/refund.rb +2 -1
  322. data/app/models/spree/role_user.rb +1 -1
  323. data/app/models/spree/shipment.rb +1 -0
  324. data/app/models/spree/shipping_method.rb +1 -0
  325. data/app/models/spree/stock_item.rb +1 -0
  326. data/app/models/spree/stock_transfer.rb +1 -0
  327. data/app/models/spree/store.rb +45 -3
  328. data/app/models/spree/store_credit.rb +2 -1
  329. data/app/models/spree/tax_category.rb +1 -1
  330. data/app/models/spree/tax_rate.rb +18 -3
  331. data/app/models/spree/taxon.rb +12 -2
  332. data/app/models/spree/taxonomy.rb +11 -0
  333. data/app/models/spree/theme.rb +1 -1
  334. data/app/models/spree/variant.rb +17 -0
  335. data/app/presenters/spree/csv/customer_presenter.rb +59 -0
  336. data/app/presenters/spree/csv/gift_card_presenter.rb +49 -0
  337. data/app/presenters/spree/csv/metafields_helper.rb +17 -0
  338. data/app/presenters/spree/csv/newsletter_subscriber_presenter.rb +39 -0
  339. data/app/presenters/spree/csv/order_line_item_presenter.rb +12 -5
  340. data/app/presenters/spree/csv/product_variant_presenter.rb +9 -5
  341. data/app/services/spree/cart/recalculate.rb +6 -4
  342. data/app/services/spree/cart/remove_line_item.rb +1 -2
  343. data/app/services/spree/imports/row_processors/base.rb +19 -0
  344. data/app/services/spree/imports/row_processors/product_variant.rb +228 -0
  345. data/app/services/spree/line_items/destroy.rb +1 -1
  346. data/app/services/spree/newsletter/subscribe.rb +43 -0
  347. data/app/services/spree/newsletter/verify.rb +30 -0
  348. data/app/sorters/spree/posts/sort.rb +40 -0
  349. data/app/views/spree/shared/_payment.html.erb +3 -3
  350. data/config/locales/en.yml +56 -2
  351. data/db/migrate/20250605131334_add_missing_fields_to_users.rb +23 -13
  352. data/db/migrate/20250728095629_create_spree_metafields.rb +27 -0
  353. data/db/migrate/20250730102644_add_missing_unique_indexes_to_spree_option_types_and_values.rb +40 -0
  354. data/db/migrate/20250730154601_add_unique_index_on_spree_properties_name.rb +24 -0
  355. data/db/migrate/20250811112056_create_spree_policies.rb +22 -0
  356. data/db/migrate/20250821211705_fix_unique_index_on_spree_order_promotions.rb +20 -0
  357. data/db/migrate/20250825175217_add_missing_page_builder_indexes.rb +7 -0
  358. data/db/migrate/20250826093602_create_spree_newsletter_subscribers.rb +12 -0
  359. data/db/migrate/20250902143122_fix_policies_store_association.rb +18 -0
  360. data/db/migrate/20250911205659_add_missing_index_on_spree_products_promotionable.rb +5 -0
  361. data/db/migrate/20250913130044_add_page_links_counter_cache_to_spree_stores.rb +10 -0
  362. data/db/migrate/20250914101955_add_missing_indexes_on_spree_adjustments.rb +6 -0
  363. data/db/migrate/20250915093930_add_metadata_to_spree_newsletter_subscribers.rb +20 -0
  364. data/db/migrate/20250923141845_create_spree_imports.rb +47 -0
  365. data/lib/generators/spree/cursor_rules/cursor_rules_generator.rb +19 -0
  366. data/lib/generators/spree/cursor_rules/templates/spree_rules.mdc +387 -0
  367. data/lib/generators/spree/dummy/dummy_generator.rb +0 -1
  368. data/lib/generators/spree/install/templates/config/initializers/spree.rb +5 -0
  369. data/lib/spree/core/configuration.rb +1 -0
  370. data/lib/spree/core/dependencies.rb +3 -0
  371. data/lib/spree/core/engine.rb +57 -2
  372. data/lib/spree/core/version.rb +1 -1
  373. data/lib/spree/core.rb +3 -1
  374. data/lib/spree/permitted_attributes.rb +18 -3
  375. data/lib/spree/testing_support/capybara_config.rb +9 -3
  376. data/lib/spree/testing_support/factories/export_factory.rb +5 -17
  377. data/lib/spree/testing_support/factories/import_factory.rb +20 -0
  378. data/lib/spree/testing_support/factories/import_mapping_factory.rb +7 -0
  379. data/lib/spree/testing_support/factories/import_row_factory.rb +7 -0
  380. data/lib/spree/testing_support/factories/metafield_definition_factory.rb +72 -0
  381. data/lib/spree/testing_support/factories/metafield_factory.rb +68 -0
  382. data/lib/spree/testing_support/factories/newsletter_subscriber_factory.rb +18 -0
  383. data/lib/spree/testing_support/factories/policy_factory.rb +8 -0
  384. data/lib/spree/testing_support/factories/post_factory.rb +8 -0
  385. data/lib/spree/testing_support/store.rb +11 -0
  386. data/lib/tasks/core.rake +128 -0
  387. metadata +279 -6
  388. data/app/models/action_text/rich_text_decorator.rb +0 -11
  389. 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,5 @@
1
+ class AddMissingIndexOnSpreeProductsPromotionable < ActiveRecord::Migration[7.2]
2
+ def change
3
+ add_index :spree_products, :promotionable, if_not_exists: true
4
+ end
5
+ 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,6 @@
1
+ class AddMissingIndexesOnSpreeAdjustments < ActiveRecord::Migration[7.2]
2
+ def change
3
+ add_index :spree_adjustments, :source_type, if_not_exists: true
4
+ add_index :spree_adjustments, :amount, if_not_exists: true
5
+ end
6
+ 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