spree_core 4.5.3 → 4.6.0

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/app/finders/spree/option_values/find_available.rb +1 -1
  3. data/app/finders/spree/product_properties/find_available.rb +1 -1
  4. data/app/finders/spree/products/find.rb +20 -12
  5. data/app/finders/spree/taxons/find.rb +10 -7
  6. data/app/helpers/spree/base_helper.rb +2 -2
  7. data/app/models/concerns/spree/product_scopes.rb +27 -23
  8. data/app/models/concerns/spree/translatable_resource.rb +25 -0
  9. data/app/models/concerns/spree/translatable_resource_scopes.rb +24 -0
  10. data/app/models/concerns/spree/translatable_resource_slug.rb +17 -0
  11. data/app/models/spree/base.rb +1 -0
  12. data/app/models/spree/data_feed/google.rb +15 -0
  13. data/app/models/spree/data_feed.rb +40 -0
  14. data/app/models/spree/option_type.rb +4 -0
  15. data/app/models/spree/option_value.rb +4 -0
  16. data/app/models/spree/product.rb +41 -10
  17. data/app/models/spree/product_property.rb +12 -3
  18. data/app/models/spree/property.rb +7 -1
  19. data/app/models/spree/shipment.rb +2 -2
  20. data/app/models/spree/store.rb +20 -1
  21. data/app/models/spree/taxon.rb +22 -6
  22. data/app/models/spree/taxonomy.rb +4 -0
  23. data/app/models/spree/variant.rb +4 -7
  24. data/app/services/spree/data_feeds/google/optional_attributes.rb +23 -0
  25. data/app/services/spree/data_feeds/google/optional_sub_attributes.rb +21 -0
  26. data/app/services/spree/data_feeds/google/products_list.rb +14 -0
  27. data/app/services/spree/data_feeds/google/required_attributes.rb +67 -0
  28. data/app/services/spree/data_feeds/google/rss.rb +107 -0
  29. data/app/sorters/spree/products/sort.rb +23 -0
  30. data/brakeman.ignore +326 -18
  31. data/config/initializers/friendly_id.rb +2 -0
  32. data/config/initializers/mobility.rb +18 -0
  33. data/config/locales/en.yml +1 -0
  34. data/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb +27 -0
  35. data/db/migrate/20220715083542_create_spree_product_translations_for_mobility.rb +7 -0
  36. data/db/migrate/20220715120222_change_product_name_null_to_true.rb +5 -0
  37. data/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb +27 -0
  38. data/db/migrate/20220718100948_change_taxon_name_null_to_true.rb +5 -0
  39. data/db/migrate/20220802070609_add_locale_to_friendly_id_slugs.rb +11 -0
  40. data/db/migrate/20220802073225_create_spree_product_slug_translations_for_mobility_table_backend.rb +5 -0
  41. data/db/migrate/20220804073928_transfer_data_to_translatable_tables.rb +66 -0
  42. data/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb +8 -0
  43. data/db/migrate/20221219123957_add_deleted_at_to_product_translations.rb +6 -0
  44. data/db/migrate/20221220133432_add_uniqueness_constraint_to_product_translations.rb +5 -0
  45. data/db/migrate/20221229132350_create_spree_data_feed_settings.rb +14 -0
  46. data/db/migrate/20230103144439_create_option_type_translations.rb +26 -0
  47. data/db/migrate/20230103151034_create_option_value_translations.rb +26 -0
  48. data/db/migrate/20230109084253_create_product_property_translations.rb +25 -0
  49. data/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb +58 -0
  50. data/db/migrate/20230109105943_create_property_translations.rb +26 -0
  51. data/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb +59 -0
  52. data/db/migrate/20230110142344_backfill_friendly_id_slug_locale.rb +15 -0
  53. data/db/migrate/20230111121534_add_additional_taxon_translation_fields.rb +8 -0
  54. data/db/migrate/20230111122511_transfer_product_and_taxon_data_to_translatable_tables.rb +82 -0
  55. data/db/migrate/20230117115531_create_taxonomy_translations.rb +24 -0
  56. data/db/migrate/20230117120430_allow_null_taxonomy_name.rb +5 -0
  57. data/db/migrate/20230117121303_transfer_taxonomy_data_to_translatable_tables.rb +11 -0
  58. data/db/migrate/20230210142732_create_store_translations.rb +50 -0
  59. data/db/migrate/20230210142849_transfer_store_data_to_translatable_tables.rb +11 -0
  60. data/db/migrate/20230210230434_add_deleted_at_to_store_translations.rb +6 -0
  61. data/db/migrate/20230415155958_rename_data_feed_settings_table.rb +5 -0
  62. data/db/migrate/20230415160828_rename_data_feed_table_columns.rb +7 -0
  63. data/db/migrate/20230415161226_add_indexes_to_data_feeds_table.rb +5 -0
  64. data/db/migrate/20230512094803_rename_data_feeds_column_provider_to_type.rb +5 -0
  65. data/db/migrate/20230514162157_add_index_on_locale_and_permalink_to_spree_taxons.rb +5 -0
  66. data/lib/spree/core/configuration.rb +1 -0
  67. data/lib/spree/core/controller_helpers/locale.rb +26 -2
  68. data/lib/spree/core/dependencies.rb +70 -94
  69. data/lib/spree/core/dependencies_helper.rb +19 -0
  70. data/lib/spree/core/engine.rb +6 -1
  71. data/lib/spree/core/product_duplicator.rb +1 -1
  72. data/lib/spree/core/product_filters.rb +7 -4
  73. data/lib/spree/core/search/base.rb +1 -1
  74. data/lib/spree/core/version.rb +1 -1
  75. data/lib/spree/core.rb +2 -0
  76. data/lib/spree/permitted_attributes.rb +1 -1
  77. data/lib/spree/testing_support/factories/google_data_feed_factory.rb +8 -0
  78. data/lib/spree/testing_support/factories/product_factory.rb +6 -0
  79. data/lib/spree/testing_support/factories/product_translation_factory.rb +6 -0
  80. data/lib/spree/testing_support/factories/store_factory.rb +1 -0
  81. data/lib/spree/testing_support/factories/variant_factory.rb +4 -0
  82. data/lib/spree/translation_migrations.rb +40 -0
  83. data/spree_core.gemspec +3 -0
  84. metadata +92 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65c07dcb2b230c430ef669d130077a6b37328bad3d4a684358f3c462611cb2d4
4
- data.tar.gz: 1f2fe362ca2d556585c55d69c6a339c87cc34cd3566097a1f2b66a494eab8262
3
+ metadata.gz: 619f5416fb099c1cccaeb2432fd6002b53aa605aff90c9b4078f2a51ba73e89b
4
+ data.tar.gz: e53a5c780f9c4e79c50cc38d2fdd055dc20b04bef93c89e33e88b7a4fd9facce
5
5
  SHA512:
6
- metadata.gz: 1019730fe49fd1809896d281545897eeb14c3795a56ebcf13238299292516eefe925ca5dae258de512b36eab8df475c4dc45b8119b8c1f91471e0e694a15ff2b
7
- data.tar.gz: bd6af18b359d118d2392b1a36cfd997b8521b0444cd803e97a2b72b24b035920acdcee35c8ac25df35a5adf108b592706706e8aa9cba48a8b4ff99314dde8f01
6
+ metadata.gz: 7e8cc84789a7c8564b45576c2aa9f8974f62da3b2c752869a9950497b01382b073b50a4d7746d9f2142bee4248115b58a527637b104f3750504499f2a6608459
7
+ data.tar.gz: fab84d0b2eb02221f96209f11c118feb77928d69269b009abb4c9a773089f1a011a96bbb3b731abce9cd8af45208eaabdef464c27244b7fd954554de1203c15b
@@ -9,7 +9,7 @@ module Spree
9
9
  end
10
10
 
11
11
  def execute
12
- find_available(scope, products_scope).select(select_args).order(order_args)
12
+ find_available(scope, products_scope).includes(:translations).select(select_args).order(order_args)
13
13
  end
14
14
 
15
15
  private
@@ -9,7 +9,7 @@ module Spree
9
9
  end
10
10
 
11
11
  def execute
12
- find_available(scope, products_scope)
12
+ find_available(scope, products_scope).includes(:translations)
13
13
  end
14
14
 
15
15
  private
@@ -146,7 +146,10 @@ module Spree
146
146
  def by_name(products)
147
147
  return products unless name?
148
148
 
149
- products.like_any([:name], [name])
149
+ product_name = name
150
+
151
+ # i18n mobility scope doesn't automatically get set for query blocks (Mobility issue #599) - set it explicitly
152
+ products.i18n { name.matches("%#{product_name}%") }
150
153
  end
151
154
 
152
155
  def by_options(products)
@@ -183,7 +186,7 @@ module Spree
183
186
 
184
187
  next if values.empty?
185
188
 
186
- ids = products.with_property_values(property_filter_param, values).ids
189
+ ids = scope.unscope(:order, :includes).with_property_values(property_filter_param, values).ids
187
190
  product_ids = index == 0 ? ids : product_ids & ids
188
191
  index += 1
189
192
  end
@@ -209,21 +212,19 @@ module Spree
209
212
  products
210
213
  end
211
214
  when 'name-a-z'
212
- products.order(name: :asc)
215
+ # workaround for Mobility issue #596 - explicitly select fields to avoid error when selecting distinct
216
+ products.i18n.
217
+ select("#{Product.table_name}.*").select(:name).order(name: :asc)
213
218
  when 'name-z-a'
214
- products.order(name: :desc)
219
+ # workaround for Mobility issue #596
220
+ products.i18n.
221
+ select("#{Product.table_name}.*").select(:name).order(name: :desc)
215
222
  when 'newest-first'
216
223
  products.order(available_on: :desc)
217
224
  when 'price-high-to-low'
218
- products.
219
- select("#{Product.table_name}.*, #{Spree::Price.table_name}.amount").
220
- reorder('').
221
- send(:descend_by_master_price)
225
+ order_by_price(products, :descend_by_master_price)
222
226
  when 'price-low-to-high'
223
- products.
224
- select("#{Product.table_name}.*, #{Spree::Price.table_name}.amount").
225
- reorder('').
226
- send(:ascend_by_master_price)
227
+ order_by_price(products, :ascend_by_master_price)
227
228
  end
228
229
  end
229
230
 
@@ -265,6 +266,13 @@ module Spree
265
266
  taxons = store.taxons.where(id: taxons_ids.to_s.split(','))
266
267
  taxons.map(&:cached_self_and_descendants_ids).flatten.compact.uniq.map(&:to_s)
267
268
  end
269
+
270
+ def order_by_price(scope, order_type)
271
+ scope.
272
+ select("#{Product.table_name}.*, #{Spree::Price.table_name}.amount").
273
+ reorder('').
274
+ send(order_type)
275
+ end
268
276
  end
269
277
  end
270
278
  end
@@ -50,10 +50,6 @@ module Spree
50
50
  name.present?
51
51
  end
52
52
 
53
- def name_matcher
54
- Spree::Taxon.arel_table[:name].matches("%#{name}%")
55
- end
56
-
57
53
  def by_ids(taxons)
58
54
  return taxons unless ids?
59
55
 
@@ -70,9 +66,13 @@ module Spree
70
66
  return taxons unless parent_permalink?
71
67
 
72
68
  if Rails::VERSION::STRING >= '6.1'
73
- taxons.joins(:parent).where(parent: { permalink: parent_permalink })
69
+ taxons.joins(:parent).
70
+ join_translation_table(Taxon, 'parents_spree_taxons').
71
+ where(["#{Taxon.translation_table_alias}.permalink = ?", parent_permalink])
74
72
  else
75
- taxons.joins("INNER JOIN #{Spree::Taxon.table_name} AS parent_taxon ON parent_taxon.id = #{Spree::Taxon.table_name}.parent_id").where(["parent_taxon.permalink = ?", parent_permalink])
73
+ taxons.joins("INNER JOIN #{Spree::Taxon.table_name} AS parent_taxon ON parent_taxon.id = #{Spree::Taxon.table_name}.parent_id").
74
+ join_translation_table(Taxon, 'parent_taxon').
75
+ where(["#{Taxon.translation_table_alias}.permalink = ?", parent_permalink])
76
76
  end
77
77
  end
78
78
 
@@ -91,7 +91,10 @@ module Spree
91
91
  def by_name(taxons)
92
92
  return taxons unless name?
93
93
 
94
- taxons.where(name_matcher)
94
+ taxon_name = name
95
+
96
+ # i18n mobility scope doesn't automatically get set for query blocks (Mobility issue #599) - set it explicitly
97
+ taxons.i18n { name.matches("%#{taxon_name}%") }
95
98
  end
96
99
  end
97
100
  end
@@ -113,8 +113,8 @@ module Spree
113
113
  meta = {}
114
114
 
115
115
  if object.is_a? ApplicationRecord
116
- meta[:keywords] = object.meta_keywords if object[:meta_keywords].present?
117
- meta[:description] = object.meta_description if object[:meta_description].present?
116
+ meta[:keywords] = object.meta_keywords if object.try(:meta_keywords).present?
117
+ meta[:description] = object.meta_description if object.try(:meta_description).present?
118
118
  end
119
119
 
120
120
  if meta[:description].blank? && object.is_a?(Spree::Product)
@@ -32,15 +32,16 @@ module Spree
32
32
  end
33
33
 
34
34
  def self.property_conditions(property)
35
- properties = Property.table_name
35
+ properties_table = Property.table_name
36
+ property_translations_table = Property.translation_table_alias
36
37
  case property
37
- when Property then { "#{properties}.id" => property.id }
38
- when Integer then { "#{properties}.id" => property }
38
+ when Property then { "#{properties_table}.id" => property.id }
39
+ when Integer then { "#{properties_table}.id" => property }
39
40
  else
40
41
  if Property.column_for_attribute('id').type == :uuid
41
- ["#{properties.name} = ? OR #{properties.id} = ?", property, property]
42
+ ["#{property_translations_table.name} = ? OR #{properties_table.id} = ?", property, property]
42
43
  else
43
- { "#{properties}.name" => property }
44
+ { "#{property_translations_table}.name" => property }
44
45
  end
45
46
  end
46
47
  end
@@ -126,21 +127,25 @@ module Spree
126
127
 
127
128
  # a scope that finds all products having property specified by name, object or id
128
129
  add_search_scope :with_property do |property|
129
- joins(:properties).where(property_conditions(property))
130
+ joins(:properties).join_translation_table(Property).where(property_conditions(property))
130
131
  end
131
132
 
132
133
  # a simple test for product with a certain property-value pairing
133
134
  # note that it can test for properties with NULL values, but not for absent values
134
135
  add_search_scope :with_property_value do |property, value|
135
136
  joins(:properties).
136
- where("#{ProductProperty.table_name}.value = ?", value).
137
+ join_translation_table(Property).
138
+ join_translation_table(ProductProperty).
139
+ where("#{ProductProperty.translation_table_alias}.value = ?", value).
137
140
  where(property_conditions(property))
138
141
  end
139
142
 
140
143
  add_search_scope :with_property_values do |property_filter_param, property_values|
141
144
  joins(product_properties: :property).
142
- where(Property.table_name => { filter_param: property_filter_param }).
143
- where(ProductProperty.table_name => { filter_param: property_values.map(&:parameterize) })
145
+ join_translation_table(Property).
146
+ join_translation_table(ProductProperty).
147
+ where(Property.translation_table_alias => { filter_param: property_filter_param }).
148
+ where(ProductProperty.translation_table_alias => { filter_param: property_values.map(&:parameterize) })
144
149
  end
145
150
 
146
151
  add_search_scope :with_option do |option|
@@ -151,7 +156,9 @@ module Spree
151
156
  elsif OptionType.column_for_attribute('id').type == :uuid
152
157
  joins(:option_types).where(spree_option_types: { name: option }).or(Product.joins(:option_types).where(spree_option_types: { id: option }))
153
158
  else
154
- joins(:option_types).where(spree_option_types: { name: option })
159
+ joins(:option_types).
160
+ join_translation_table(OptionType).
161
+ where(OptionType.translation_table_alias => { name: option })
155
162
  end
156
163
  end
157
164
 
@@ -164,6 +171,7 @@ module Spree
164
171
  OptionType.where(id: option).or(OptionType.where(name: option))&.first&.id
165
172
  else
166
173
  OptionType.where(name: option)&.first&.id
174
+ OptionType.where(name: option)&.first&.id
167
175
  end
168
176
  end
169
177
 
@@ -171,7 +179,9 @@ module Spree
171
179
 
172
180
  group("#{Spree::Product.table_name}.id").
173
181
  joins(variants_including_master: :option_values).
174
- where(Spree::OptionValue.table_name => { name: value, option_type_id: option_type_id })
182
+ join_translation_table(Spree::OptionValue).
183
+ where(Spree::OptionValue.translation_table_alias => { name: value },
184
+ Spree::OptionValue.table_name => { option_type_id: option_type_id })
175
185
  end
176
186
 
177
187
  # Finds all products which have either:
@@ -295,19 +305,10 @@ module Spree
295
305
  # .search_by_name
296
306
  if defined?(PgSearch)
297
307
  include PgSearch::Model
298
-
299
- if defined?(SpreeGlobalize)
300
- pg_search_scope :search_by_name, associated_against: { translations: :name }, using: { tsearch: { any_word: true, prefix: true } }
301
- else
302
- pg_search_scope :search_by_name, against: :name, using: { tsearch: { any_word: true, prefix: true } }
303
- end
308
+ pg_search_scope :search_by_name, against: :name, using: { tsearch: { any_word: true, prefix: true } }
304
309
  else
305
310
  def self.search_by_name(query)
306
- if defined?(SpreeGlobalize)
307
- joins(:translations).order(:name).where("LOWER(#{Product::Translation.table_name}.name) LIKE LOWER(:query)", query: "%#{query}%").distinct
308
- else
309
- where("LOWER(#{Product.table_name}.name) LIKE LOWER(:query)", query: "%#{query}%")
310
- end
311
+ i18n { name.lower.matches("%#{query.downcase}%") }
311
312
  end
312
313
  end
313
314
  search_scopes << :search_by_name
@@ -339,7 +340,10 @@ module Spree
339
340
  case t
340
341
  when ApplicationRecord then t
341
342
  else
342
- Taxon.where(Taxon.arel_table[:name].eq(t)).or(Taxon.where(Taxon.arel_table[:id].eq(t))).or(Taxon.where(Taxon.arel_table[:permalink].matches("%/#{t}/"))).or(Taxon.where(Taxon.arel_table[:permalink].matches("#{t}/"))).first
343
+ Taxon.where(name: t).
344
+ or(Taxon.where(Taxon.arel_table[:id].eq(t))).
345
+ or(Taxon.where(Taxon.arel_table[:permalink].matches("%/#{t}/"))).
346
+ or(Taxon.where(Taxon.arel_table[:permalink].matches("#{t}/"))).first
343
347
  end
344
348
  end.compact.flatten.uniq
345
349
  end
@@ -0,0 +1,25 @@
1
+ module Spree
2
+ module TranslatableResource
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ extend Mobility
7
+ default_scope { i18n }
8
+
9
+ def get_field_with_locale(locale, field_name, fallback: false)
10
+ # method will return nil if no translation is present due to fallback: false setting
11
+ public_send(field_name, locale: locale, fallback: fallback)
12
+ end
13
+ end
14
+
15
+ class_methods do
16
+ def translatable_fields
17
+ const_get(:TRANSLATABLE_FIELDS)
18
+ end
19
+
20
+ def translation_table_alias
21
+ "#{self::Translation.table_name}_#{Mobility.locale}"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ module Spree
2
+ module TranslatableResourceScopes
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ # To be used when joining on the resource itself does not automatically join on its translations table
7
+ # This method is to be used when you've already joined on the translatable table itself
8
+ #
9
+ # If the resource table is aliased, pass the alias to `join_on_table_alias`, otherwise omit the param
10
+ def join_translation_table(translatable_class, join_on_table_alias = nil)
11
+ join_on_table_name = if join_on_table_alias.nil?
12
+ translatable_class.table_name
13
+ else
14
+ join_on_table_alias
15
+ end
16
+ translatable_class_foreign_key = "#{translatable_class.table_name.singularize}_id"
17
+
18
+ joins("LEFT OUTER JOIN #{translatable_class::Translation.table_name} #{translatable_class.translation_table_alias}
19
+ ON #{translatable_class.translation_table_alias}.#{translatable_class_foreign_key} = #{join_on_table_name}.id
20
+ AND #{translatable_class.translation_table_alias}.locale = '#{Mobility.locale}'")
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ module TranslatableResourceSlug
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ def localized_slugs_for_store(store)
7
+ localized_slugs = Hash[translations.pluck(:locale, :slug)]
8
+ default_locale = store.default_locale
9
+ supported_locales = store.supported_locales_list
10
+
11
+ supported_locales.each_with_object({}) do |locale, hash|
12
+ hash[locale] = localized_slugs[locale] || localized_slugs[default_locale]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -3,6 +3,7 @@ class Spree::Base < ApplicationRecord
3
3
  serialize :preferences, Hash
4
4
 
5
5
  include Spree::RansackableAttributes
6
+ include Spree::TranslatableResourceScopes
6
7
 
7
8
  after_initialize do
8
9
  if has_attribute?(:preferences) && !preferences.nil?
@@ -0,0 +1,15 @@
1
+ require_dependency 'spree/data_feed'
2
+
3
+ module Spree
4
+ class DataFeed::Google < DataFeed
5
+ class << self
6
+ def label
7
+ 'Google Merchant Center Feed'
8
+ end
9
+
10
+ def provider_name
11
+ 'google'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,40 @@
1
+ module Spree
2
+ class DataFeed < Base
3
+ belongs_to :store, class_name: 'Spree::Store', foreign_key: 'store_id'
4
+
5
+ scope :for_store, ->(store) { where(store: store) }
6
+
7
+ before_validation :generate_slug
8
+
9
+ with_options presence: true do
10
+ validates :store
11
+ validates :name, uniqueness: true
12
+ validates :slug, uniqueness: { scope: :store_id }
13
+ end
14
+
15
+ def formatted_url
16
+ "#{store.formatted_url}/api/v2/data_feeds/#{self.class.provider_name}/#{slug}.rss"
17
+ end
18
+
19
+ private
20
+
21
+ def generate_slug
22
+ new_slug = slug.blank? ? SecureRandom.uuid : slug.parameterize
23
+ write_attribute(:slug, new_slug)
24
+ end
25
+
26
+ class << self
27
+ def label
28
+ raise NotImplementedError
29
+ end
30
+
31
+ def provider_name
32
+ raise NotImplementedError
33
+ end
34
+
35
+ def available_types
36
+ Rails.application.config.spree.data_feed_types
37
+ end
38
+ end
39
+ end
40
+ end
@@ -2,10 +2,14 @@ module Spree
2
2
  class OptionType < Spree::Base
3
3
  include UniqueName
4
4
  include Metadata
5
+ include TranslatableResource
5
6
  if defined?(Spree::Webhooks)
6
7
  include Spree::Webhooks::HasWebhooks
7
8
  end
8
9
 
10
+ TRANSLATABLE_FIELDS = %i[name presentation].freeze
11
+ translates(*TRANSLATABLE_FIELDS)
12
+
9
13
  acts_as_list
10
14
  auto_strip_attributes :name, :presentation
11
15
 
@@ -1,10 +1,14 @@
1
1
  module Spree
2
2
  class OptionValue < Spree::Base
3
3
  include Metadata
4
+ include TranslatableResource
4
5
  if defined?(Spree::Webhooks)
5
6
  include Spree::Webhooks::HasWebhooks
6
7
  end
7
8
 
9
+ TRANSLATABLE_FIELDS = %i[name presentation].freeze
10
+ translates(*TRANSLATABLE_FIELDS)
11
+
8
12
  belongs_to :option_type, class_name: 'Spree::OptionType', touch: true, inverse_of: :option_values
9
13
 
10
14
  acts_as_list scope: :option_type
@@ -23,6 +23,8 @@ module Spree
23
23
  extend FriendlyId
24
24
  include ProductScopes
25
25
  include MultiStoreResource
26
+ include TranslatableResource
27
+ include TranslatableResourceSlug
26
28
  include MemoizedData
27
29
  include Metadata
28
30
  if defined?(Spree::Webhooks)
@@ -32,12 +34,20 @@ module Spree
32
34
  include Spree::VendorConcern
33
35
  end
34
36
 
35
- MEMOIZED_METHODS = %w(total_on_hand taxonomy_ids taxon_and_ancestors category
37
+ MEMOIZED_METHODS = %w[total_on_hand taxonomy_ids taxon_and_ancestors category
36
38
  default_variant_id tax_category default_variant
37
- purchasable? in_stock? backorderable?)
39
+ purchasable? in_stock? backorderable?]
38
40
 
39
- friendly_id :slug_candidates, use: :history
41
+ TRANSLATABLE_FIELDS = %i[name description slug meta_description meta_keywords meta_title].freeze
42
+ translates(*TRANSLATABLE_FIELDS)
40
43
 
44
+ self::Translation.class_eval do
45
+ acts_as_paranoid
46
+ # deleted translation values also need to be accessible for index views listing deleted resources
47
+ default_scope { unscope(where: :deleted_at) }
48
+ end
49
+
50
+ friendly_id :slug_candidates, use: [:history, :mobility]
41
51
  acts_as_paranoid
42
52
  auto_strip_attributes :name
43
53
 
@@ -306,14 +316,26 @@ module Spree
306
316
  end
307
317
 
308
318
  def property(property_name)
309
- product_properties.joins(:property).find_by(spree_properties: { name: property_name }).try(:value)
319
+ product_properties.joins(:property).
320
+ join_translation_table(Property).
321
+ find_by(Property.translation_table_alias => { name: property_name }).try(:value)
310
322
  end
311
323
 
312
324
  def set_property(property_name, property_value, property_presentation = property_name)
313
325
  ApplicationRecord.transaction do
314
- # Works around spree_i18n #301
315
- property = Property.create_with(presentation: property_presentation).find_or_create_by(name: property_name)
316
- product_property = ProductProperty.where(product: self, property: property).first_or_initialize
326
+ # Manual first_or_create to work around Mobility bug
327
+ property = if Property.where(name: property_name).exists?
328
+ Property.where(name: property_name).first
329
+ else
330
+ Property.create(name: property_name, presentation: property_presentation)
331
+ end
332
+
333
+ product_property = if ProductProperty.where(product: self, property: property).exists?
334
+ ProductProperty.where(product: self, property: property).first
335
+ else
336
+ ProductProperty.create(product: self, property: property)
337
+ end
338
+
317
339
  product_property.value = property_value
318
340
  product_property.save!
319
341
  end
@@ -337,11 +359,16 @@ module Spree
337
359
  end
338
360
 
339
361
  def brand
340
- @brand ||= taxons.joins(:taxonomy).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_brands_name) })
362
+ @brand ||= taxons.joins(:taxonomy).
363
+ join_translation_table(Taxonomy).
364
+ find_by(Taxonomy.translation_table_alias => { name: Spree.t(:taxonomy_brands_name) })
341
365
  end
342
366
 
343
367
  def category
344
- @category ||= taxons.joins(:taxonomy).order(depth: :desc).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_categories_name) })
368
+ @category ||= taxons.joins(:taxonomy).
369
+ join_translation_table(Taxonomy).
370
+ order(depth: :desc).
371
+ find_by(Taxonomy.translation_table_alias => { name: Spree.t(:taxonomy_categories_name) })
345
372
  end
346
373
 
347
374
  def taxons_for_store(store)
@@ -415,7 +442,11 @@ module Spree
415
442
 
416
443
  def punch_slug
417
444
  # punch slug with date prefix to allow reuse of original
418
- update_column :slug, "#{Time.current.to_i}_#{slug}"[0..254] unless frozen?
445
+ return if frozen?
446
+
447
+ translations.with_deleted.each do |t|
448
+ t.update_column :slug, "#{Time.current.to_i}_#{t.slug}"[0..254]
449
+ end
419
450
  end
420
451
 
421
452
  def update_slug_history
@@ -1,8 +1,14 @@
1
1
  module Spree
2
2
  class ProductProperty < Spree::Base
3
3
  include Spree::FilterParam
4
+ include TranslatableResource
4
5
 
5
- auto_strip_attributes :value
6
+ TRANSLATABLE_FIELDS = %i[value filter_param].freeze
7
+ translates(*TRANSLATABLE_FIELDS)
8
+
9
+ self::Translation.class_eval do
10
+ auto_strip_attributes :value
11
+ end
6
12
 
7
13
  acts_as_list scope: :product
8
14
 
@@ -31,8 +37,11 @@ module Spree
31
37
  `ProductProperty#property_name=` is deprecated and will be removed in Spree 5.0.
32
38
  DEPRECATION
33
39
  if name.present?
34
- # don't use `find_by :name` to workaround globalize/globalize#423 bug
35
- self.property = Property.where(name: name).first_or_create(presentation: name)
40
+ self.property = if Property.where(name: name).exists?
41
+ Property.where(name: name).first
42
+ else
43
+ Property.create(name: name, presentation: name)
44
+ end
36
45
  end
37
46
  end
38
47
 
@@ -2,11 +2,17 @@ module Spree
2
2
  class Property < Spree::Base
3
3
  include Spree::FilterParam
4
4
  include Metadata
5
+ include TranslatableResource
5
6
  if defined?(Spree::Webhooks)
6
7
  include Spree::Webhooks::HasWebhooks
7
8
  end
8
9
 
9
- auto_strip_attributes :name, :presentation
10
+ TRANSLATABLE_FIELDS = %i[name presentation filter_param].freeze
11
+ translates(*TRANSLATABLE_FIELDS)
12
+
13
+ self::Translation.class_eval do
14
+ auto_strip_attributes :name, :presentation
15
+ end
10
16
 
11
17
  has_many :property_prototypes, class_name: 'Spree::PropertyPrototype'
12
18
  has_many :prototypes, through: :property_prototypes, class_name: 'Spree::Prototype'
@@ -249,8 +249,8 @@ module Spree
249
249
  end
250
250
 
251
251
  def selected_shipping_rate_id=(id)
252
- shipping_rates.update_all(selected: false)
253
- shipping_rates.touch_all # Bust cache dependent on "updated_at" timestamp
252
+ # Explicitly updates the timestamp in order to bust cache dependent on "updated_at"
253
+ shipping_rates.update_all(selected: false, updated_at: Time.current)
254
254
  shipping_rates.update(id, selected: true)
255
255
  save!
256
256
  end
@@ -1,5 +1,6 @@
1
1
  module Spree
2
2
  class Store < Spree::Base
3
+ include TranslatableResource
3
4
  if defined?(Spree::Webhooks)
4
5
  include Spree::Webhooks::HasWebhooks
5
6
  end
@@ -7,6 +8,17 @@ module Spree
7
8
  include Spree::Security::Stores
8
9
  end
9
10
 
11
+ TRANSLATABLE_FIELDS = %i[name meta_description meta_keywords seo_title facebook
12
+ twitter instagram customer_support_email description
13
+ address contact_phone new_order_notifications_email].freeze
14
+ translates(*TRANSLATABLE_FIELDS)
15
+
16
+ self::Translation.class_eval do
17
+ acts_as_paranoid
18
+ # deleted translation values still need to be accessible - remove deleted_at scope
19
+ default_scope { unscope(where: :deleted_at) }
20
+ end
21
+
10
22
  typed_store :settings, coder: ActiveRecord::TypedStore::IdentityCoder do |s|
11
23
  # Spree Digital Asset Configurations
12
24
  s.boolean :limit_digital_download_count, default: true, null: false
@@ -55,6 +67,8 @@ module Spree
55
67
 
56
68
  has_many :wishlists, class_name: 'Spree::Wishlist'
57
69
 
70
+ has_many :data_feeds, class_name: 'Spree::DataFeed'
71
+
58
72
  belongs_to :default_country, class_name: 'Spree::Country'
59
73
  belongs_to :checkout_zone, class_name: 'Spree::Zone'
60
74
 
@@ -109,7 +123,12 @@ module Spree
109
123
  # this behaviour is very buggy and unpredictable
110
124
  def self.default
111
125
  Rails.cache.fetch('default_store') do
112
- where(default: true).first_or_initialize
126
+ # workaround for Mobility bug with first_or_initialize
127
+ if where(default: true).any?
128
+ where(default: true).first
129
+ else
130
+ new(default: true)
131
+ end
113
132
  end
114
133
  end
115
134