spree_core 5.4.0.beta → 5.4.0.beta3
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/finders/spree/products/find.rb +2 -42
- data/app/helpers/spree/base_helper.rb +1 -54
- data/app/mailers/spree/base_mailer.rb +4 -3
- data/app/models/concerns/spree/adjustment_source.rb +0 -8
- data/app/models/concerns/spree/admin_user_methods.rb +0 -2
- data/app/models/concerns/spree/image_methods.rb +4 -0
- data/app/models/concerns/spree/metadata.rb +10 -0
- data/app/models/concerns/spree/product_scopes.rb +21 -49
- data/app/models/concerns/spree/stores/markets.rb +7 -7
- data/app/models/concerns/spree/vat_price_calculation.rb +2 -2
- data/app/models/spree/ability.rb +5 -5
- data/app/models/spree/address.rb +3 -2
- data/app/models/spree/adjustable/promotion_accumulator.rb +1 -1
- data/app/models/spree/adjustment.rb +1 -20
- data/app/models/spree/api_key.rb +60 -2
- data/app/models/spree/country.rb +7 -3
- data/app/models/spree/credit_card.rb +0 -27
- data/app/models/spree/current.rb +38 -2
- data/app/models/spree/exports/products.rb +0 -6
- data/app/models/spree/gateway/bogus.rb +9 -9
- data/app/models/spree/gateway.rb +0 -3
- data/app/models/spree/gift_card_batch.rb +4 -0
- data/app/models/spree/image/configuration/active_storage.rb +0 -2
- data/app/models/spree/image.rb +7 -31
- data/app/models/spree/log_entry.rb +8 -5
- data/app/models/spree/market.rb +1 -2
- data/app/models/spree/market_country.rb +17 -0
- data/app/models/spree/newsletter_subscriber.rb +0 -3
- data/app/models/spree/order/checkout.rb +0 -18
- data/app/models/spree/order.rb +22 -16
- data/app/models/spree/payment/gateway_options.rb +2 -2
- data/app/models/spree/payment/processing.rb +5 -5
- data/app/models/spree/payment.rb +1 -1
- data/app/models/spree/payment_connection_error.rb +3 -0
- data/app/models/spree/payment_method/check.rb +1 -1
- data/app/models/spree/payment_method/store_credit.rb +5 -5
- data/app/models/spree/payment_response.rb +70 -0
- data/app/models/spree/permission_sets/default_customer.rb +0 -5
- data/app/models/spree/permission_sets/product_display.rb +0 -2
- data/app/models/spree/permission_sets/product_management.rb +0 -2
- data/app/models/spree/product.rb +5 -85
- data/app/models/spree/promotion.rb +2 -14
- data/app/models/spree/promotion_handler/coupon.rb +3 -3
- data/app/models/spree/prototype.rb +0 -3
- data/app/models/spree/refund.rb +6 -2
- data/app/models/spree/reimbursement.rb +0 -2
- data/app/models/spree/shipment.rb +0 -1
- data/app/models/spree/shipment_handler.rb +0 -2
- data/app/models/spree/shipping_category.rb +3 -3
- data/app/models/spree/store.rb +10 -49
- data/app/models/spree/store_credit.rb +4 -0
- data/app/models/spree/tax_category.rb +1 -11
- data/app/models/spree/taxon.rb +0 -16
- data/app/models/spree/variant.rb +2 -40
- data/app/models/spree/zone.rb +1 -4
- data/app/services/spree/cart/add_item.rb +8 -4
- data/app/services/spree/cart/create.rb +13 -2
- data/app/services/spree/newsletter/subscribe.rb +2 -2
- data/app/services/spree/products/duplicator.rb +0 -12
- data/app/services/spree/products/prepare_nested_attributes.rb +0 -12
- data/app/subscribers/spree/invitation_email_subscriber.rb +1 -1
- data/config/locales/en.yml +4 -20
- data/db/migrate/20210914000000_spree_four_three.rb +0 -38
- data/db/migrate/20210915064329_add_metadata_to_spree_multiple_tables.rb +0 -1
- data/db/migrate/20260226000000_add_locale_to_spree_orders.rb +5 -0
- data/db/migrate/20260226100000_add_token_digest_to_spree_api_keys.rb +21 -0
- data/lib/spree/core/configuration.rb +0 -3
- data/lib/spree/core/controller_helpers/strong_parameters.rb +1 -2
- data/lib/spree/core/dependencies.rb +2 -10
- data/lib/spree/core/engine.rb +0 -13
- data/lib/spree/core/pricing/resolver.rb +1 -9
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +10 -26
- data/lib/spree/permitted_attributes.rb +2 -14
- data/lib/spree/testing_support/factories/order_factory.rb +1 -0
- data/lib/spree/testing_support/factories/payment_method_factory.rb +1 -6
- data/lib/spree/testing_support/factories/product_factory.rb +0 -7
- data/lib/spree/testing_support/factories/prototype_factory.rb +0 -2
- data/lib/tasks/cli.rake +50 -0
- data/lib/tasks/core.rake +0 -265
- metadata +9 -111
- data/app/finders/spree/posts/find.rb +0 -137
- data/app/finders/spree/product_properties/find_available.rb +0 -20
- data/app/helpers/spree/mail_helper.rb +0 -27
- data/app/jobs/spree/api_key_touch_job.rb +0 -9
- data/app/mailers/spree/test_mailer.rb +0 -8
- data/app/models/action_text/video_embed.rb +0 -13
- data/app/models/concerns/spree/filter_param.rb +0 -21
- data/app/models/concerns/spree/has_one_link.rb +0 -42
- data/app/models/concerns/spree/linkable.rb +0 -9
- data/app/models/concerns/spree/previewable.rb +0 -17
- data/app/models/spree/data_feed/google.rb +0 -15
- data/app/models/spree/data_feed.rb +0 -42
- data/app/models/spree/gateway/bogus_simple.rb +0 -24
- data/app/models/spree/newsletter_subscriber/emails.rb +0 -12
- data/app/models/spree/order/emails.rb +0 -24
- data/app/models/spree/post.rb +0 -108
- data/app/models/spree/post_category.rb +0 -46
- data/app/models/spree/product_property.rb +0 -51
- data/app/models/spree/property.rb +0 -86
- data/app/models/spree/property_prototype.rb +0 -9
- data/app/models/spree/reimbursement/emails.rb +0 -11
- data/app/models/spree/shipment/emails.rb +0 -11
- data/app/models/spree/store_favicon_image.rb +0 -20
- data/app/models/spree/store_logo.rb +0 -4
- data/app/models/spree/store_mailer_logo.rb +0 -7
- data/app/models/spree/taxon_image/configuration/active_storage.rb +0 -26
- data/app/models/spree/taxon_image.rb +0 -28
- data/app/presenters/spree/filters/properties_presenter.rb +0 -23
- data/app/presenters/spree/filters/property_presenter.rb +0 -42
- data/app/services/spree/data_feeds/google/optional_attributes.rb +0 -23
- data/app/services/spree/data_feeds/google/optional_sub_attributes.rb +0 -21
- data/app/services/spree/data_feeds/google/products_list.rb +0 -14
- data/app/services/spree/data_feeds/google/required_attributes.rb +0 -68
- data/app/services/spree/data_feeds/google/rss.rb +0 -109
- data/app/sorters/spree/posts/sort.rb +0 -40
- data/app/views/action_text/video_embeds/_thumbnail.html.erb +0 -1
- data/app/views/action_text/video_embeds/_video_embed.html.erb +0 -3
- data/app/views/layouts/action_text/contents/_content.html.erb +0 -3
- data/app/views/spree/test_mailer/test_email.html.erb +0 -40
- data/app/views/spree/test_mailer/test_email.text.erb +0 -4
- data/config/initializers/oembed.rb +0 -1
- data/db/migrate/20221229132350_create_spree_data_feed_settings.rb +0 -14
- data/db/migrate/20230109084253_create_product_property_translations.rb +0 -24
- data/db/migrate/20230109105943_create_property_translations.rb +0 -24
- data/db/migrate/20230415155958_rename_data_feed_settings_table.rb +0 -5
- data/db/migrate/20230415160828_rename_data_feed_table_columns.rb +0 -7
- data/db/migrate/20230415161226_add_indexes_to_data_feeds_table.rb +0 -5
- data/db/migrate/20230512094803_rename_data_feeds_column_provider_to_type.rb +0 -5
- data/db/migrate/20240914153106_add_display_on_to_spree_properties.rb +0 -5
- data/db/migrate/20240915144935_add_position_to_spree_properties.rb +0 -6
- data/db/migrate/20250121160028_create_spree_posts_and_spree_post_categories.rb +0 -33
- data/db/migrate/20250127083740_add_kind_to_spree_properties.rb +0 -5
- data/db/migrate/20250217171018_create_action_text_video_embeds.rb +0 -11
- data/db/migrate/20250305121657_remove_spree_posts_indices.rb +0 -7
- data/db/migrate/20250730154601_add_unique_index_on_spree_properties_name.rb +0 -24
- data/db/sample_data/posts.rb +0 -7
- data/lib/generators/spree/cursor_rules/cursor_rules_generator.rb +0 -19
- data/lib/generators/spree/cursor_rules/templates/spree_rules.mdc +0 -387
- data/lib/spree/core/controller_helpers/search.rb +0 -17
- data/lib/spree/core/importer/order.rb +0 -244
- data/lib/spree/core/importer/product.rb +0 -67
- data/lib/spree/core/importer.rb +0 -9
- data/lib/spree/core/product_filters.rb +0 -218
- data/lib/spree/core/query_filters/comparable.rb +0 -50
- data/lib/spree/core/query_filters/date.rb +0 -8
- data/lib/spree/core/query_filters/number.rb +0 -8
- data/lib/spree/core/query_filters/text.rb +0 -36
- data/lib/spree/core/query_filters.rb +0 -11
- data/lib/spree/core/search/base.rb +0 -144
- data/lib/spree/testing_support/factories/favicon_image_factory.rb +0 -9
- data/lib/spree/testing_support/factories/google_data_feed_factory.rb +0 -7
- data/lib/spree/testing_support/factories/post_category_factory.rb +0 -7
- data/lib/spree/testing_support/factories/post_factory.rb +0 -22
- data/lib/spree/testing_support/factories/product_property_factory.rb +0 -7
- data/lib/spree/testing_support/factories/property_factory.rb +0 -28
- data/lib/spree/testing_support/factories/taxon_image_factory.rb +0 -9
- data/lib/spree/testing_support/flash.rb +0 -25
- data/lib/spree/testing_support/flatpickr_capybara.rb +0 -124
- data/lib/tasks/exchanges.rake +0 -66
|
@@ -1,387 +0,0 @@
|
|
|
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.cart_recalculate_service
|
|
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
|
-
Spree.page_builder.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.storefront_product_serializer.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
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
module Spree
|
|
2
|
-
module Core
|
|
3
|
-
module ControllerHelpers
|
|
4
|
-
module Search
|
|
5
|
-
def build_searcher(params)
|
|
6
|
-
Spree::Deprecation.warn("Spree::Core::ControllerHelpers::Search is deprecated and will be removed in Spree 5.5.")
|
|
7
|
-
|
|
8
|
-
Spree.searcher_class.new(params).tap do |searcher|
|
|
9
|
-
searcher.current_user = try_spree_current_user
|
|
10
|
-
searcher.current_currency = current_currency&.upcase
|
|
11
|
-
searcher.current_store = current_store
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
module Spree
|
|
2
|
-
module Core
|
|
3
|
-
module Importer
|
|
4
|
-
class Order
|
|
5
|
-
def self.import(user, params)
|
|
6
|
-
Spree::Deprecation.warn('Spree::Core::Importer::Order is deprecated and will be removed in Spree 5.5. Please use `Spree::Imports::Order` instead.')
|
|
7
|
-
|
|
8
|
-
ensure_country_id_from_params params[:ship_address_attributes]
|
|
9
|
-
ensure_state_id_from_params params[:ship_address_attributes]
|
|
10
|
-
ensure_country_id_from_params params[:bill_address_attributes]
|
|
11
|
-
ensure_state_id_from_params params[:bill_address_attributes]
|
|
12
|
-
|
|
13
|
-
create_params = params.slice :currency
|
|
14
|
-
order = Spree::Order.create! create_params
|
|
15
|
-
order.associate_user!(user)
|
|
16
|
-
|
|
17
|
-
shipments_attrs = params.delete(:shipments_attributes)
|
|
18
|
-
|
|
19
|
-
create_line_items_from_params(params.delete(:line_items_attributes), order)
|
|
20
|
-
create_shipments_from_params(shipments_attrs, order)
|
|
21
|
-
create_adjustments_from_params(params.delete(:adjustments_attributes), order)
|
|
22
|
-
create_payments_from_params(params.delete(:payments_attributes), order)
|
|
23
|
-
|
|
24
|
-
if completed_at = params.delete(:completed_at)
|
|
25
|
-
order.completed_at = completed_at
|
|
26
|
-
order.state = 'complete'
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
params.delete(:user_id) unless user.try(:has_spree_role?, 'admin') && params.key?(:user_id)
|
|
30
|
-
|
|
31
|
-
order.update!(params)
|
|
32
|
-
|
|
33
|
-
order.create_proposed_shipments unless shipments_attrs.present?
|
|
34
|
-
|
|
35
|
-
# Really ensure that the order totals & states are correct
|
|
36
|
-
order.updater.update
|
|
37
|
-
if shipments_attrs.present?
|
|
38
|
-
order.shipments.each_with_index do |shipment, index|
|
|
39
|
-
shipment.update_columns(cost: shipments_attrs[index][:cost].to_f) if shipments_attrs[index][:cost].present?
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
order.reload
|
|
43
|
-
rescue StandardError => e
|
|
44
|
-
order.destroy if order&.persisted?
|
|
45
|
-
raise e.message
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def self.create_shipments_from_params(shipments_hash, order)
|
|
49
|
-
return [] unless shipments_hash
|
|
50
|
-
|
|
51
|
-
shipments_hash.each do |s|
|
|
52
|
-
shipment = order.shipments.build
|
|
53
|
-
shipment.tracking = s[:tracking]
|
|
54
|
-
shipment.stock_location = Spree::StockLocation.find_by(admin_name: s[:stock_location]) ||
|
|
55
|
-
Spree::StockLocation.find_by!(name: s[:stock_location])
|
|
56
|
-
inventory_units = create_inventory_units_from_order_and_params(order, s[:inventory_units])
|
|
57
|
-
|
|
58
|
-
inventory_units.each do |inventory_unit|
|
|
59
|
-
inventory_unit.shipment = shipment
|
|
60
|
-
|
|
61
|
-
if s[:shipped_at].present?
|
|
62
|
-
inventory_unit.pending = false
|
|
63
|
-
inventory_unit.state = 'shipped'
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
inventory_unit.save!
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
if s[:shipped_at].present?
|
|
70
|
-
shipment.shipped_at = s[:shipped_at]
|
|
71
|
-
shipment.state = 'shipped'
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
shipment.save!
|
|
75
|
-
|
|
76
|
-
shipping_method = Spree::ShippingMethod.find_by(name: s[:shipping_method]) ||
|
|
77
|
-
Spree::ShippingMethod.find_by!(admin_name: s[:shipping_method])
|
|
78
|
-
rate = shipment.shipping_rates.create!(shipping_method: shipping_method, cost: s[:cost])
|
|
79
|
-
|
|
80
|
-
shipment.selected_shipping_rate_id = rate.id
|
|
81
|
-
shipment.update_amounts
|
|
82
|
-
|
|
83
|
-
adjustments = s.delete(:adjustments_attributes)
|
|
84
|
-
create_adjustments_from_params(adjustments, order, shipment)
|
|
85
|
-
rescue StandardError => e
|
|
86
|
-
raise "Order import shipments: #{e.message} #{s}"
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def self.create_inventory_units_from_order_and_params(order, inventory_unit_params)
|
|
91
|
-
inventory_unit_params.each_with_object([]) do |inventory_unit_param, inventory_units|
|
|
92
|
-
ensure_variant_id_from_params(inventory_unit_param)
|
|
93
|
-
existing = inventory_units.detect { |unit| unit.variant_id == inventory_unit_param[:variant_id] }
|
|
94
|
-
if existing
|
|
95
|
-
existing.quantity += 1
|
|
96
|
-
else
|
|
97
|
-
line_item = order.line_items.detect { |ln| ln.variant_id == inventory_unit_param[:variant_id] }
|
|
98
|
-
inventory_units << InventoryUnit.new(line_item: line_item, order_id: order.id, variant: line_item.variant, quantity: 1)
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def self.create_line_items_from_params(line_items, order)
|
|
104
|
-
return {} unless line_items
|
|
105
|
-
|
|
106
|
-
line_items.each do |line_item|
|
|
107
|
-
adjustments = line_item.delete(:adjustments_attributes)
|
|
108
|
-
extra_params = line_item.except(:variant_id, :quantity, :sku)
|
|
109
|
-
line_item = ensure_variant_id_from_params(line_item)
|
|
110
|
-
variant = Spree::Variant.find(line_item[:variant_id])
|
|
111
|
-
line_item = Cart::AddItem.call(order: order, variant: variant, quantity: line_item[:quantity]).value
|
|
112
|
-
# Raise any errors with saving to prevent import succeeding with line items
|
|
113
|
-
# failing silently.
|
|
114
|
-
if extra_params.present?
|
|
115
|
-
line_item.update!(extra_params)
|
|
116
|
-
else
|
|
117
|
-
line_item.save!
|
|
118
|
-
end
|
|
119
|
-
create_adjustments_from_params(adjustments, order, line_item)
|
|
120
|
-
rescue StandardError => e
|
|
121
|
-
raise "Order import line items: #{e.message} #{line_item}"
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def self.create_adjustments_from_params(adjustments, order, adjustable = nil)
|
|
126
|
-
return [] unless adjustments
|
|
127
|
-
|
|
128
|
-
adjustments.each do |a|
|
|
129
|
-
adjustment = (adjustable || order).adjustments.build(
|
|
130
|
-
order: order,
|
|
131
|
-
amount: a[:amount].to_f,
|
|
132
|
-
label: a[:label],
|
|
133
|
-
source_type: source_type_from_adjustment(a)
|
|
134
|
-
)
|
|
135
|
-
adjustment.save!
|
|
136
|
-
adjustment.close!
|
|
137
|
-
rescue StandardError => e
|
|
138
|
-
raise "Order import adjustments: #{e.message} #{a}"
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def self.create_payments_from_params(payments_hash, order)
|
|
143
|
-
return [] unless payments_hash
|
|
144
|
-
|
|
145
|
-
payments_hash.each do |p|
|
|
146
|
-
payment = order.payments.build order: order
|
|
147
|
-
payment.amount = p[:amount].to_f
|
|
148
|
-
# Order API should be using state as that's the normal payment field.
|
|
149
|
-
# spree_wombat serializes payment state as status so imported orders should fall back to status field.
|
|
150
|
-
payment.state = p[:state] || p[:status] || 'completed'
|
|
151
|
-
payment.created_at = p[:created_at] if p[:created_at]
|
|
152
|
-
payment.payment_method = Spree::PaymentMethod.find_by!(name: p[:payment_method])
|
|
153
|
-
payment.source = create_source_payment_from_params(p[:source], payment) if p[:source]
|
|
154
|
-
payment.save!
|
|
155
|
-
rescue StandardError => e
|
|
156
|
-
raise "Order import payments: #{e.message} #{p}"
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def self.create_source_payment_from_params(source_hash, payment)
|
|
161
|
-
Spree::CreditCard.create(
|
|
162
|
-
month: source_hash[:month],
|
|
163
|
-
year: source_hash[:year],
|
|
164
|
-
cc_type: source_hash[:cc_type],
|
|
165
|
-
last_digits: source_hash[:last_digits],
|
|
166
|
-
name: source_hash[:name],
|
|
167
|
-
payment_method: payment.payment_method,
|
|
168
|
-
gateway_customer_profile_id: source_hash[:gateway_customer_profile_id],
|
|
169
|
-
gateway_payment_profile_id: source_hash[:gateway_payment_profile_id],
|
|
170
|
-
imported: true
|
|
171
|
-
)
|
|
172
|
-
rescue StandardError => e
|
|
173
|
-
raise "Order import source payments: #{e.message} #{source_hash}"
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def self.ensure_variant_id_from_params(hash)
|
|
177
|
-
sku = hash.delete(:sku)
|
|
178
|
-
unless hash[:variant_id].present?
|
|
179
|
-
hash[:variant_id] = Spree::Variant.active.find_by!(sku: sku).id
|
|
180
|
-
end
|
|
181
|
-
hash
|
|
182
|
-
rescue ActiveRecord::RecordNotFound => e
|
|
183
|
-
raise "Ensure order import variant: Variant w/SKU #{sku} not found."
|
|
184
|
-
rescue StandardError => e
|
|
185
|
-
raise "Ensure order import variant: #{e.message} #{hash}"
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def self.ensure_country_id_from_params(address)
|
|
189
|
-
return if address.nil? || address[:country_id].present? || address[:country].nil?
|
|
190
|
-
|
|
191
|
-
begin
|
|
192
|
-
search = {}
|
|
193
|
-
if name = address[:country]['name']
|
|
194
|
-
search[:name] = name
|
|
195
|
-
elsif iso_name = address[:country]['iso_name']
|
|
196
|
-
search[:iso_name] = iso_name.upcase
|
|
197
|
-
elsif iso = address[:country]['iso']
|
|
198
|
-
search[:iso] = iso.upcase
|
|
199
|
-
elsif iso3 = address[:country]['iso3']
|
|
200
|
-
search[:iso3] = iso3.upcase
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
address.delete(:country)
|
|
204
|
-
address[:country_id] = Spree::Country.where(search).first!.id
|
|
205
|
-
rescue StandardError => e
|
|
206
|
-
raise "Ensure order import address country: #{e.message} #{search}"
|
|
207
|
-
end
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def self.ensure_state_id_from_params(address)
|
|
211
|
-
return if address.nil? || address[:state_id].present? || address[:state].nil?
|
|
212
|
-
|
|
213
|
-
begin
|
|
214
|
-
search = {}
|
|
215
|
-
if name = address[:state]['name']
|
|
216
|
-
search[:name] = name
|
|
217
|
-
elsif abbr = address[:state]['abbr']
|
|
218
|
-
search[:abbr] = abbr.upcase
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
address.delete(:state)
|
|
222
|
-
search[:country_id] = address[:country_id]
|
|
223
|
-
|
|
224
|
-
if state = Spree::State.where(search).first
|
|
225
|
-
address[:state_id] = state.id
|
|
226
|
-
else
|
|
227
|
-
address[:state_name] = search[:name] || search[:abbr]
|
|
228
|
-
end
|
|
229
|
-
rescue StandardError => e
|
|
230
|
-
raise "Ensure order import address state: #{e.message} #{search}"
|
|
231
|
-
end
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
def self.source_type_from_adjustment(adjustment)
|
|
235
|
-
if adjustment[:tax]
|
|
236
|
-
'Spree::TaxRate'
|
|
237
|
-
elsif adjustment[:promotion]
|
|
238
|
-
'Spree::PromotionAction'
|
|
239
|
-
end
|
|
240
|
-
end
|
|
241
|
-
end
|
|
242
|
-
end
|
|
243
|
-
end
|
|
244
|
-
end
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
module Spree
|
|
2
|
-
module Core
|
|
3
|
-
module Importer
|
|
4
|
-
class Product
|
|
5
|
-
attr_reader :product, :product_attrs, :variants_attrs, :options_attrs, :store
|
|
6
|
-
|
|
7
|
-
def initialize(product, product_params, options = {})
|
|
8
|
-
Spree::Deprecation.warn('Spree::Core::Importer::Product is deprecated and will be removed in Spree 5.5. Please use `Spree::Imports::Product` instead.')
|
|
9
|
-
|
|
10
|
-
@store = options[:store] || Spree::Store.default
|
|
11
|
-
@product = product || Spree::Product.new(product_params)
|
|
12
|
-
@product.stores << @store if @product.stores.exclude?(@store)
|
|
13
|
-
|
|
14
|
-
@product_attrs = product_params.to_h
|
|
15
|
-
@variants_attrs = (options[:variants_attrs] || []).map(&:to_h)
|
|
16
|
-
@options_attrs = options[:options_attrs] || []
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def create
|
|
20
|
-
if product.save
|
|
21
|
-
variants_attrs.each do |variant_attribute|
|
|
22
|
-
# make sure the product is assigned before the options=
|
|
23
|
-
product.variants.create({ product: product }.merge(variant_attribute))
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
set_up_options
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
product
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def update
|
|
33
|
-
if product.update(product_attrs)
|
|
34
|
-
variants_attrs.each do |variant_attribute|
|
|
35
|
-
# update the variant if the id is present in the payload
|
|
36
|
-
if variant_attribute['id'].present?
|
|
37
|
-
product.variants.find(variant_attribute['id']).update(variant_attribute)
|
|
38
|
-
else
|
|
39
|
-
# make sure the product is assigned before the options=
|
|
40
|
-
product.variants.create({ product: product }.merge(variant_attribute))
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
set_up_options
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
product
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
private
|
|
51
|
-
|
|
52
|
-
def set_up_options
|
|
53
|
-
options_attrs.each do |name|
|
|
54
|
-
option_type = Spree::OptionType.where(name: name).first_or_initialize do |option_type|
|
|
55
|
-
option_type.presentation = name
|
|
56
|
-
option_type.save!
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
unless product.option_types.include?(option_type)
|
|
60
|
-
product.option_types << option_type
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|