spree_core 4.5.3 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
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