spree_core 4.0.7 → 4.1.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/javascripts/spree.js +0 -1
- data/app/finders/spree/products/find.rb +19 -6
- data/app/finders/spree/variants/option_types_finder.rb +21 -0
- data/app/finders/spree/variants/visible_finder.rb +22 -0
- data/app/helpers/spree/base_helper.rb +48 -18
- data/app/helpers/spree/products_helper.rb +76 -8
- data/app/models/concerns/spree/user_methods.rb +2 -2
- data/app/models/spree/app_dependencies.rb +1 -7
- data/app/models/spree/credit_card.rb +4 -5
- data/app/models/spree/image.rb +52 -3
- data/app/models/spree/image/configuration/active_storage.rb +9 -1
- data/app/models/spree/line_item.rb +1 -2
- data/app/models/spree/option_type.rb +4 -0
- data/app/models/spree/order.rb +12 -12
- data/app/models/spree/order/address_book.rb +20 -7
- data/app/models/spree/payment_method.rb +8 -0
- data/app/models/spree/preferences/preferable.rb +1 -1
- data/app/models/spree/product.rb +7 -6
- data/app/models/spree/promotion/actions/create_item_adjustments.rb +1 -1
- data/app/models/spree/promotion_handler/coupon.rb +2 -1
- data/app/models/spree/return_item/eligibility_validator/base_validator.rb +1 -1
- data/app/models/spree/store.rb +2 -1
- data/app/models/spree/taxon.rb +2 -6
- data/app/models/spree/variant.rb +2 -14
- data/app/models/spree/zone.rb +6 -3
- data/app/presenters/spree/product_summary_presenter.rb +26 -0
- data/app/presenters/spree/variant_presenter.rb +69 -0
- data/app/presenters/spree/variants/option_types_presenter.rb +74 -0
- data/app/presenters/spree/variants/options_presenter.rb +49 -0
- data/app/services/spree/checkout/get_shipping_rates.rb +10 -7
- data/app/services/spree/checkout/update.rb +2 -13
- data/config/locales/en.yml +156 -14
- data/db/default/spree/stores.rb +8 -3
- data/db/migrate/20140309033438_create_store_from_preferences.rb +8 -4
- data/db/migrate/20191005121504_add_store_id_to_payment_methods.rb +7 -0
- data/db/migrate/20191016134113_add_deafult_value_for_store_default_currency.rb +5 -0
- data/db/migrate/20200102141311_add_social_to_spree_stores.rb +7 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +2 -1
- data/lib/generators/spree/dummy/templates/initializers/bullet.rb +5 -0
- data/lib/generators/spree/install/install_generator.rb +13 -5
- data/lib/generators/spree/install/templates/config/initializers/spree.rb +0 -1
- data/lib/generators/spree/install/templates/config/initializers/spree_storefront.rb +1 -0
- data/lib/generators/spree/install/templates/config/spree_storefront.yml +67 -0
- data/lib/spree/core.rb +0 -1
- data/lib/spree/core/controller_helpers/order.rb +12 -6
- data/lib/spree/core/controller_helpers/store.rb +2 -2
- data/lib/spree/core/search/base.rb +59 -22
- data/lib/spree/core/version.rb +1 -3
- data/lib/spree/money.rb +8 -1
- data/lib/spree/permitted_attributes.rb +3 -2
- data/lib/spree/testing_support/capybara_ext.rb +0 -45
- data/lib/spree/testing_support/common_rake.rb +1 -1
- data/lib/spree/testing_support/factories/store_factory.rb +3 -0
- data/lib/spree/testing_support/order_walkthrough.rb +7 -3
- data/spree_core.gemspec +8 -14
- metadata +45 -24
- data/app/finders/spree/addresses/find.rb +0 -17
- data/app/services/spree/account/addresses/base.rb +0 -39
- data/app/services/spree/account/addresses/create.rb +0 -18
- data/app/services/spree/account/addresses/update.rb +0 -18
- data/lib/spree/database_type_utilities.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: db73357d472e46822ad95d6ee2071b01413e2f1ab743177f7cfc8845c6830092
|
4
|
+
data.tar.gz: 83c396103e3809183ac12c21f72e4831cb2c10d1089232bdecd58395459a961b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f57f8d0b1bee1cb0b0d77b7aa82ac8f62d89a783e07dbd1e5f2a8a6d780eaf8de46ccf5e898420d423fe1d133ce2645615cd4611580ca92008d006c059667e1a
|
7
|
+
data.tar.gz: 7e3aee2e5bcc514a78dcc8da3cfb5b35635f691fc2fe9ac68002c9cd208bc96b5df4f784da4612cf1ad409dea467e0a5c29cc13e156e25aa7baeb5d72f367f47
|
@@ -112,17 +112,30 @@ module Spree
|
|
112
112
|
def by_options(products)
|
113
113
|
return products unless options?
|
114
114
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
end.flatten.compact.uniq
|
119
|
-
)
|
115
|
+
options.map do |key, value|
|
116
|
+
products.with_option_value(key, value)
|
117
|
+
end.inject(:&)
|
120
118
|
end
|
121
119
|
|
122
120
|
def by_option_value_ids(products)
|
123
121
|
return products unless option_value_ids?
|
124
122
|
|
125
|
-
|
123
|
+
product_ids = Spree::Product.
|
124
|
+
joins(variants: :option_values).
|
125
|
+
where(spree_option_values: { id: option_value_ids }).
|
126
|
+
group("#{Spree::Product.table_name}.id, #{Spree::Variant.table_name}.id").
|
127
|
+
having('COUNT(spree_option_values.option_type_id) = ?', option_types_count(option_value_ids)).
|
128
|
+
distinct.
|
129
|
+
ids
|
130
|
+
|
131
|
+
products.where(id: product_ids)
|
132
|
+
end
|
133
|
+
|
134
|
+
def option_types_count(option_value_ids)
|
135
|
+
Spree::OptionValue.
|
136
|
+
where(id: option_value_ids).
|
137
|
+
distinct.
|
138
|
+
count(:option_type_id)
|
126
139
|
end
|
127
140
|
|
128
141
|
def ordered(products)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Spree
|
2
|
+
module Variants
|
3
|
+
class OptionTypesFinder
|
4
|
+
COLOR_TYPE = 'color'.freeze
|
5
|
+
|
6
|
+
def initialize(variant_ids:)
|
7
|
+
@variant_ids = variant_ids
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute
|
11
|
+
Spree::OptionType.includes(option_values: :variants).where(spree_variants: { id: variant_ids }).
|
12
|
+
reorder('spree_option_types.position ASC, spree_option_values.position ASC').
|
13
|
+
partition { |option_type| option_type.name.downcase == COLOR_TYPE }.flatten
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :variant_ids
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Spree
|
2
|
+
module Variants
|
3
|
+
class VisibleFinder
|
4
|
+
def initialize(scope:, current_currency:)
|
5
|
+
@scope = scope
|
6
|
+
@current_currency = current_currency
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute
|
10
|
+
Spree::Variant.where(id: active_variants).joins(:option_values).order('spree_option_values.position ASC')
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader :scope, :current_currency
|
16
|
+
|
17
|
+
def active_variants
|
18
|
+
scope.active(current_currency).unscope(:order)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -32,8 +32,12 @@ module Spree
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
def logo(image_path = Spree::Config[:logo])
|
36
|
-
|
35
|
+
def logo(image_path = Spree::Config[:logo], options = {})
|
36
|
+
path = spree.respond_to?(:root_path) ? spree.root_path : main_app.root_path
|
37
|
+
|
38
|
+
link_to path, 'aria-label': current_store.name, method: options[:method] do
|
39
|
+
image_tag image_path, alt: current_store.name, title: current_store.name
|
40
|
+
end
|
37
41
|
end
|
38
42
|
|
39
43
|
def meta_data
|
@@ -50,15 +54,32 @@ module Spree
|
|
50
54
|
end
|
51
55
|
|
52
56
|
if meta[:keywords].blank? || meta[:description].blank?
|
53
|
-
|
54
|
-
|
57
|
+
if object && object[:name].present?
|
58
|
+
meta.reverse_merge!(keywords: [object.name, current_store.meta_keywords].reject(&:blank?).join(', '),
|
59
|
+
description: [object.name, current_store.meta_description].reject(&:blank?).join(', '))
|
60
|
+
else
|
61
|
+
meta.reverse_merge!(keywords: (current_store.meta_keywords || current_store.seo_title),
|
62
|
+
description: (current_store.meta_description || current_store.seo_title))
|
63
|
+
end
|
55
64
|
end
|
56
65
|
meta
|
57
66
|
end
|
58
67
|
|
68
|
+
def meta_image_url_path
|
69
|
+
object = instance_variable_get('@' + controller_name.singularize)
|
70
|
+
return unless object.is_a?(Spree::Product)
|
71
|
+
|
72
|
+
image = default_image_for_product_or_variant(object)
|
73
|
+
image&.attachment.present? ? main_app.url_for(image.attachment) : asset_path(Spree::Config[:logo])
|
74
|
+
end
|
75
|
+
|
76
|
+
def meta_image_data_tag
|
77
|
+
tag('meta', property: 'og:image', content: meta_image_url_path) if meta_image_url_path
|
78
|
+
end
|
79
|
+
|
59
80
|
def meta_data_tags
|
60
81
|
meta_data.map do |name, content|
|
61
|
-
tag('meta', name: name, content: content)
|
82
|
+
tag('meta', name: name, content: content) unless name.nil? || content.nil?
|
62
83
|
end.join("\n")
|
63
84
|
end
|
64
85
|
|
@@ -75,14 +96,30 @@ module Spree
|
|
75
96
|
[I18n.l(time.to_date, format: :long), time.strftime('%l:%M %p')].join(' ')
|
76
97
|
end
|
77
98
|
|
78
|
-
def seo_url(taxon)
|
79
|
-
spree.nested_taxons_path(taxon.permalink)
|
99
|
+
def seo_url(taxon, options = nil)
|
100
|
+
spree.nested_taxons_path(taxon.permalink, options)
|
80
101
|
end
|
81
102
|
|
82
103
|
def frontend_available?
|
83
104
|
Spree::Core::Engine.frontend_available?
|
84
105
|
end
|
85
106
|
|
107
|
+
def default_image_for_product_or_variant(product_or_variant)
|
108
|
+
if product_or_variant.images.empty?
|
109
|
+
if product_or_variant.is_a?(Spree::Product) && product_or_variant.variant_images.any?
|
110
|
+
product_or_variant.variant_images.first
|
111
|
+
elsif product_or_variant.is_a?(Spree::Variant) && product_or_variant.product.variant_images.any?
|
112
|
+
product_or_variant.product.variant_images.first
|
113
|
+
end
|
114
|
+
else
|
115
|
+
product_or_variant.images.first
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def base_cache_key
|
120
|
+
[I18n.locale, current_currency]
|
121
|
+
end
|
122
|
+
|
86
123
|
private
|
87
124
|
|
88
125
|
def create_product_image_tag(image, product, options, style)
|
@@ -94,18 +131,11 @@ module Spree
|
|
94
131
|
self.class.send :define_method, "#{style}_image" do |product, *options|
|
95
132
|
options = options.first || {}
|
96
133
|
options[:alt] ||= product.name
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
else
|
101
|
-
if product.is_a?(Variant) && !product.product.variant_images.empty?
|
102
|
-
create_product_image_tag(product.product.variant_images.first, product, options, style)
|
103
|
-
else
|
104
|
-
image_tag "noimage/#{style}.png", options
|
105
|
-
end
|
106
|
-
end
|
134
|
+
image_path = default_image_for_product_or_variant(product)
|
135
|
+
if image_path.present?
|
136
|
+
create_product_image_tag image_path, product, options, style
|
107
137
|
else
|
108
|
-
|
138
|
+
image_tag "noimage/#{style}.png", options
|
109
139
|
end
|
110
140
|
end
|
111
141
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Spree
|
2
2
|
module ProductsHelper
|
3
|
+
include BaseHelper
|
4
|
+
|
3
5
|
# returns the formatted price for the specified variant as a full price or a difference depending on configuration
|
4
6
|
def variant_price(variant)
|
5
7
|
if Spree::Config[:show_variant_full_price]
|
@@ -29,6 +31,14 @@ module Spree
|
|
29
31
|
end
|
30
32
|
end
|
31
33
|
|
34
|
+
def default_variant(variants)
|
35
|
+
variants_option_types_presenter(variants).default_variant || variants.find(&:is_master)
|
36
|
+
end
|
37
|
+
|
38
|
+
def used_variants_options(variants)
|
39
|
+
variants_option_types_presenter(variants).options
|
40
|
+
end
|
41
|
+
|
32
42
|
# converts line breaks in product description into <p> tags (for html display purposes)
|
33
43
|
def product_description(product)
|
34
44
|
description = if Spree::Config[:show_raw_product_description]
|
@@ -47,15 +57,27 @@ module Spree
|
|
47
57
|
end
|
48
58
|
end
|
49
59
|
|
50
|
-
def cache_key_for_products
|
51
|
-
count =
|
52
|
-
max_updated_at = (
|
53
|
-
products_cache_keys = "spree/products/all-#{params[:page]}-#{max_updated_at}-#{count}"
|
54
|
-
(common_product_cache_keys + [products_cache_keys]).compact.join('/')
|
60
|
+
def cache_key_for_products(products = @products, additional_cache_key = nil)
|
61
|
+
count = products.count
|
62
|
+
max_updated_at = (products.maximum(:updated_at) || Date.today).to_s(:number)
|
63
|
+
products_cache_keys = "spree/products/all-#{params[:page]}-#{params[:sort_by]}-#{max_updated_at}-#{count}-#{@taxon&.id}"
|
64
|
+
(common_product_cache_keys + [products_cache_keys] + [additional_cache_key]).compact.join('/')
|
55
65
|
end
|
56
66
|
|
57
67
|
def cache_key_for_product(product = @product)
|
58
|
-
|
68
|
+
cache_key_elements = common_product_cache_keys
|
69
|
+
cache_key_elements += [
|
70
|
+
product.cache_key_with_version,
|
71
|
+
product.possible_promotions
|
72
|
+
]
|
73
|
+
|
74
|
+
cache_key_elements.compact.join('/')
|
75
|
+
end
|
76
|
+
|
77
|
+
def limit_descritpion(string)
|
78
|
+
return string if string.length <= 450
|
79
|
+
|
80
|
+
string.slice(0..449) + '...'
|
59
81
|
end
|
60
82
|
|
61
83
|
def available_status(product) # will return a human readable string
|
@@ -71,16 +93,62 @@ module Spree
|
|
71
93
|
end
|
72
94
|
end
|
73
95
|
|
74
|
-
|
96
|
+
def product_images(product, variants)
|
97
|
+
variants = if product.variants_and_option_values(current_currency).any?
|
98
|
+
variants.reject(&:is_master)
|
99
|
+
else
|
100
|
+
variants
|
101
|
+
end
|
102
|
+
|
103
|
+
variants.map(&:images).flatten
|
104
|
+
end
|
105
|
+
|
106
|
+
def product_variants_matrix(is_product_available_in_currency)
|
107
|
+
Spree::VariantPresenter.new(
|
108
|
+
variants: @variants,
|
109
|
+
is_product_available_in_currency: is_product_available_in_currency,
|
110
|
+
current_currency: current_currency,
|
111
|
+
current_price_options: current_price_options
|
112
|
+
).call.to_json
|
113
|
+
end
|
114
|
+
|
115
|
+
def related_products
|
116
|
+
return [] unless @product.respond_to?(:has_related_products?) && @product.has_related_products?(:related_products)
|
117
|
+
|
118
|
+
@_related_products ||= @product.
|
119
|
+
related_products.
|
120
|
+
includes(
|
121
|
+
:tax_category,
|
122
|
+
master: [
|
123
|
+
:prices,
|
124
|
+
images: { attachment_attachment: :blob },
|
125
|
+
]
|
126
|
+
).
|
127
|
+
limit(Spree::Config[:products_per_page])
|
128
|
+
end
|
129
|
+
|
130
|
+
def product_available_in_currency?(product)
|
131
|
+
!(product.price_in(current_currency).amount.nil? || product.price_in(current_currency).amount.zero?)
|
132
|
+
end
|
75
133
|
|
76
134
|
def common_product_cache_keys
|
77
|
-
|
135
|
+
base_cache_key + price_options_cache_key
|
78
136
|
end
|
79
137
|
|
138
|
+
private
|
139
|
+
|
80
140
|
def price_options_cache_key
|
81
141
|
current_price_options.sort.map(&:last).map do |value|
|
82
142
|
value.try(:cache_key) || value
|
83
143
|
end
|
84
144
|
end
|
145
|
+
|
146
|
+
def variants_option_types_presenter(variants)
|
147
|
+
@_variants_option_types_presenter ||= begin
|
148
|
+
option_types = Spree::Variants::OptionTypesFinder.new(variant_ids: variants.map(&:id)).execute
|
149
|
+
|
150
|
+
Spree::Variants::OptionTypesPresenter.new(option_types)
|
151
|
+
end
|
152
|
+
end
|
85
153
|
end
|
86
154
|
end
|
@@ -47,9 +47,9 @@ module Spree
|
|
47
47
|
spree_roles.any? { |role| role.name == role_in_question.to_s }
|
48
48
|
end
|
49
49
|
|
50
|
-
def last_incomplete_spree_order(store)
|
50
|
+
def last_incomplete_spree_order(store, options = {})
|
51
51
|
orders.where(store: store).incomplete.
|
52
|
-
includes(
|
52
|
+
includes(options[:includes]).
|
53
53
|
order('created_at DESC').
|
54
54
|
first
|
55
55
|
end
|
@@ -11,8 +11,7 @@ module Spree
|
|
11
11
|
:checkout_remove_store_credit_service, :checkout_get_shipping_rates_service,
|
12
12
|
:coupon_handler, :country_finder, :current_order_finder, :credit_card_finder,
|
13
13
|
:completed_order_finder, :order_sorter, :cart_compare_line_items_service, :collection_paginator, :products_sorter,
|
14
|
-
:products_finder, :taxon_finder, :line_item_by_variant_finder, :cart_estimate_shipping_rates_service
|
15
|
-
:account_create_address_service, :account_update_address_service, :address_finder
|
14
|
+
:products_finder, :taxon_finder, :line_item_by_variant_finder, :cart_estimate_shipping_rates_service
|
16
15
|
].freeze
|
17
16
|
|
18
17
|
attr_accessor *INJECTION_POINTS
|
@@ -60,14 +59,9 @@ module Spree
|
|
60
59
|
# coupons
|
61
60
|
# TODO: we should split this service into 2 seperate - Add and Remove
|
62
61
|
@coupon_handler = 'Spree::PromotionHandler::Coupon'
|
63
|
-
|
64
|
-
# account
|
65
|
-
@account_create_address_service = 'Spree::Account::Addresses::Create'
|
66
|
-
@account_update_address_service = 'Spree::Account::Addresses::Update'
|
67
62
|
end
|
68
63
|
|
69
64
|
def set_default_finders
|
70
|
-
@address_finder = 'Spree::Addresses::Find'
|
71
65
|
@country_finder = 'Spree::Countries::Find'
|
72
66
|
@current_order_finder = 'Spree::Orders::FindCurrent'
|
73
67
|
@completed_order_finder = 'Spree::Orders::FindComplete'
|
@@ -24,9 +24,10 @@ module Spree
|
|
24
24
|
attribute :month, ActiveRecord::Type::Integer.new
|
25
25
|
attribute :year, ActiveRecord::Type::Integer.new
|
26
26
|
|
27
|
-
attr_reader :number
|
27
|
+
attr_reader :number
|
28
28
|
attr_accessor :encrypted_data,
|
29
29
|
:imported,
|
30
|
+
:verification_value,
|
30
31
|
:manual_entry
|
31
32
|
|
32
33
|
with_options if: :require_card_numbers?, on: :create do
|
@@ -100,11 +101,9 @@ module Spree
|
|
100
101
|
end
|
101
102
|
end
|
102
103
|
|
103
|
-
def verification_value=(value)
|
104
|
-
@verification_value = value.to_s.gsub(/\s/, '')
|
105
|
-
end
|
106
|
-
|
107
104
|
def set_last_digits
|
105
|
+
number.to_s.gsub!(/\s/, '')
|
106
|
+
verification_value.to_s.gsub!(/\s/, '')
|
108
107
|
self.last_digits ||= number.to_s.length <= 4 ? number : number.to_s.slice(-4..-1)
|
109
108
|
end
|
110
109
|
|
data/app/models/spree/image.rb
CHANGED
@@ -2,7 +2,7 @@ module Spree
|
|
2
2
|
class Image < Asset
|
3
3
|
include Configuration::ActiveStorage
|
4
4
|
include Rails.application.routes.url_helpers
|
5
|
-
|
5
|
+
|
6
6
|
# In Rails 5.x class constants are being undefined/redefined during the code reloading process
|
7
7
|
# in a rails development environment, after which the actual ruby objects stored in those class constants
|
8
8
|
# are no longer equal (subclass == self) what causes error ActiveRecord::SubclassNotFound
|
@@ -12,14 +12,63 @@ module Spree
|
|
12
12
|
|
13
13
|
def styles
|
14
14
|
self.class.styles.map do |_, size|
|
15
|
-
width, height = size
|
15
|
+
width, height = size.chop.split('x')
|
16
16
|
|
17
17
|
{
|
18
|
-
url: polymorphic_path(attachment.variant(
|
18
|
+
url: polymorphic_path(attachment.variant(combine_options: {
|
19
|
+
gravity: 'center',
|
20
|
+
resize: size,
|
21
|
+
extent: size,
|
22
|
+
background: 'snow2',
|
23
|
+
quality: 80
|
24
|
+
}), only_path: true),
|
19
25
|
width: width,
|
20
26
|
height: height
|
21
27
|
}
|
22
28
|
end
|
23
29
|
end
|
30
|
+
|
31
|
+
def style(name)
|
32
|
+
size = self.class.styles[name]
|
33
|
+
return unless size
|
34
|
+
|
35
|
+
width, height = size.chop.split('x')
|
36
|
+
|
37
|
+
{
|
38
|
+
url: polymorphic_path(attachment.variant(combine_options: {
|
39
|
+
gravity: 'center',
|
40
|
+
resize: size,
|
41
|
+
extent: size,
|
42
|
+
background: 'snow2',
|
43
|
+
quality: 80
|
44
|
+
}), only_path: true),
|
45
|
+
size: size,
|
46
|
+
width: width,
|
47
|
+
height: height
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def style_dimensions(name)
|
52
|
+
size = self.class.styles[name]
|
53
|
+
width, height = size.chop.split('x')
|
54
|
+
|
55
|
+
{
|
56
|
+
width: width,
|
57
|
+
height: height
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def plp_url
|
62
|
+
size = self.class.styles[:plp_and_carousel]
|
63
|
+
variant = attachment.variant(
|
64
|
+
gravity: 'center',
|
65
|
+
resize: size,
|
66
|
+
extent: size,
|
67
|
+
background: 'snow2',
|
68
|
+
quality: 80
|
69
|
+
)
|
70
|
+
|
71
|
+
polymorphic_path(variant, only_path: true)
|
72
|
+
end
|
24
73
|
end
|
25
74
|
end
|