spree_core 4.5.5 → 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 (109) 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/address.rb +1 -1
  12. data/app/models/spree/asset.rb +1 -1
  13. data/app/models/spree/base.rb +1 -0
  14. data/app/models/spree/calculator/default_tax.rb +1 -1
  15. data/app/models/spree/cms_page.rb +1 -1
  16. data/app/models/spree/cms_section.rb +1 -1
  17. data/app/models/spree/credit_card.rb +1 -1
  18. data/app/models/spree/customer_return.rb +2 -2
  19. data/app/models/spree/data_feed/google.rb +15 -0
  20. data/app/models/spree/data_feed.rb +40 -0
  21. data/app/models/spree/line_item.rb +1 -1
  22. data/app/models/spree/option_type.rb +5 -1
  23. data/app/models/spree/option_value.rb +5 -1
  24. data/app/models/spree/order/store_credit.rb +0 -8
  25. data/app/models/spree/order.rb +3 -3
  26. data/app/models/spree/payment.rb +3 -3
  27. data/app/models/spree/payment_method.rb +1 -1
  28. data/app/models/spree/payment_source.rb +1 -1
  29. data/app/models/spree/price.rb +1 -1
  30. data/app/models/spree/product.rb +44 -13
  31. data/app/models/spree/product_property.rb +12 -3
  32. data/app/models/spree/promotion.rb +2 -2
  33. data/app/models/spree/property.rb +8 -2
  34. data/app/models/spree/prototype.rb +1 -1
  35. data/app/models/spree/refund.rb +1 -1
  36. data/app/models/spree/reimbursement.rb +1 -1
  37. data/app/models/spree/return_authorization.rb +1 -1
  38. data/app/models/spree/shipment.rb +5 -5
  39. data/app/models/spree/shipping_method.rb +1 -1
  40. data/app/models/spree/stock/estimator.rb +1 -1
  41. data/app/models/spree/stock_item.rb +1 -1
  42. data/app/models/spree/stock_transfer.rb +3 -3
  43. data/app/models/spree/store.rb +20 -1
  44. data/app/models/spree/store_credit.rb +1 -1
  45. data/app/models/spree/taxon.rb +23 -7
  46. data/app/models/spree/taxonomy.rb +5 -1
  47. data/app/models/spree/variant.rb +6 -9
  48. data/app/services/spree/data_feeds/google/optional_attributes.rb +23 -0
  49. data/app/services/spree/data_feeds/google/optional_sub_attributes.rb +21 -0
  50. data/app/services/spree/data_feeds/google/products_list.rb +14 -0
  51. data/app/services/spree/data_feeds/google/required_attributes.rb +67 -0
  52. data/app/services/spree/data_feeds/google/rss.rb +107 -0
  53. data/app/sorters/spree/products/sort.rb +23 -0
  54. data/brakeman.ignore +326 -18
  55. data/config/initializers/friendly_id.rb +2 -0
  56. data/config/initializers/mobility.rb +18 -0
  57. data/config/locales/en.yml +1 -0
  58. data/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb +27 -0
  59. data/db/migrate/20220715083542_create_spree_product_translations_for_mobility.rb +7 -0
  60. data/db/migrate/20220715120222_change_product_name_null_to_true.rb +5 -0
  61. data/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb +27 -0
  62. data/db/migrate/20220718100948_change_taxon_name_null_to_true.rb +5 -0
  63. data/db/migrate/20220802070609_add_locale_to_friendly_id_slugs.rb +11 -0
  64. data/db/migrate/20220802073225_create_spree_product_slug_translations_for_mobility_table_backend.rb +5 -0
  65. data/db/migrate/20220804073928_transfer_data_to_translatable_tables.rb +66 -0
  66. data/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb +8 -0
  67. data/db/migrate/20221219123957_add_deleted_at_to_product_translations.rb +6 -0
  68. data/db/migrate/20221220133432_add_uniqueness_constraint_to_product_translations.rb +5 -0
  69. data/db/migrate/20221229132350_create_spree_data_feed_settings.rb +14 -0
  70. data/db/migrate/20230103144439_create_option_type_translations.rb +26 -0
  71. data/db/migrate/20230103151034_create_option_value_translations.rb +26 -0
  72. data/db/migrate/20230109084253_create_product_property_translations.rb +25 -0
  73. data/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb +58 -0
  74. data/db/migrate/20230109105943_create_property_translations.rb +26 -0
  75. data/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb +59 -0
  76. data/db/migrate/20230110142344_backfill_friendly_id_slug_locale.rb +15 -0
  77. data/db/migrate/20230111121534_add_additional_taxon_translation_fields.rb +8 -0
  78. data/db/migrate/20230111122511_transfer_product_and_taxon_data_to_translatable_tables.rb +82 -0
  79. data/db/migrate/20230117115531_create_taxonomy_translations.rb +24 -0
  80. data/db/migrate/20230117120430_allow_null_taxonomy_name.rb +5 -0
  81. data/db/migrate/20230117121303_transfer_taxonomy_data_to_translatable_tables.rb +11 -0
  82. data/db/migrate/20230210142732_create_store_translations.rb +50 -0
  83. data/db/migrate/20230210142849_transfer_store_data_to_translatable_tables.rb +11 -0
  84. data/db/migrate/20230210230434_add_deleted_at_to_store_translations.rb +6 -0
  85. data/db/migrate/20230415155958_rename_data_feed_settings_table.rb +5 -0
  86. data/db/migrate/20230415160828_rename_data_feed_table_columns.rb +7 -0
  87. data/db/migrate/20230415161226_add_indexes_to_data_feeds_table.rb +5 -0
  88. data/db/migrate/20230512094803_rename_data_feeds_column_provider_to_type.rb +5 -0
  89. data/db/migrate/20230514162157_add_index_on_locale_and_permalink_to_spree_taxons.rb +5 -0
  90. data/lib/spree/core/configuration.rb +1 -0
  91. data/lib/spree/core/controller_helpers/locale.rb +26 -2
  92. data/lib/spree/core/dependencies.rb +70 -94
  93. data/lib/spree/core/dependencies_helper.rb +19 -0
  94. data/lib/spree/core/engine.rb +6 -1
  95. data/lib/spree/core/product_duplicator.rb +1 -1
  96. data/lib/spree/core/product_filters.rb +7 -4
  97. data/lib/spree/core/search/base.rb +1 -1
  98. data/lib/spree/core/version.rb +1 -1
  99. data/lib/spree/core.rb +2 -0
  100. data/lib/spree/permitted_attributes.rb +1 -1
  101. data/lib/spree/testing_support/factories/google_data_feed_factory.rb +8 -0
  102. data/lib/spree/testing_support/factories/product_factory.rb +6 -0
  103. data/lib/spree/testing_support/factories/product_translation_factory.rb +6 -0
  104. data/lib/spree/testing_support/factories/store_factory.rb +1 -0
  105. data/lib/spree/testing_support/factories/variant_factory.rb +4 -0
  106. data/lib/spree/translation_migrations.rb +40 -0
  107. data/spree_core.gemspec +3 -0
  108. metadata +93 -6
  109. data/app/models/spree/order_contents.rb +0 -31
@@ -22,9 +22,11 @@ module Spree
22
22
  class Product < Spree::Base
23
23
  extend FriendlyId
24
24
  include ProductScopes
25
- include Spree::MultiStoreResource
26
- include Spree::MemoizedData
27
- include Spree::Metadata
25
+ include MultiStoreResource
26
+ include TranslatableResource
27
+ include TranslatableResourceSlug
28
+ include MemoizedData
29
+ include Metadata
28
30
  if defined?(Spree::Webhooks)
29
31
  include Spree::Webhooks::HasWebhooks
30
32
  end
@@ -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
 
@@ -1,7 +1,7 @@
1
1
  module Spree
2
2
  class Promotion < Spree::Base
3
- include Spree::MultiStoreResource
4
- include Spree::Metadata
3
+ include MultiStoreResource
4
+ include Metadata
5
5
  if defined?(Spree::Webhooks)
6
6
  include Spree::Webhooks::HasWebhooks
7
7
  end
@@ -1,12 +1,18 @@
1
1
  module Spree
2
2
  class Property < Spree::Base
3
3
  include Spree::FilterParam
4
- include Spree::Metadata
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'
@@ -1,6 +1,6 @@
1
1
  module Spree
2
2
  class Prototype < Spree::Base
3
- include Spree::Metadata
3
+ include Metadata
4
4
  if defined?(Spree::Webhooks)
5
5
  include Spree::Webhooks::HasWebhooks
6
6
  end
@@ -1,6 +1,6 @@
1
1
  module Spree
2
2
  class Refund < Spree::Base
3
- include Spree::Metadata
3
+ include Metadata
4
4
  if defined?(Spree::Webhooks)
5
5
  include Spree::Webhooks::HasWebhooks
6
6
  end
@@ -1,7 +1,7 @@
1
1
  module Spree
2
2
  class Reimbursement < Spree::Base
3
3
  include Spree::Core::NumberGenerator.new(prefix: 'RI', length: 9)
4
- include Spree::NumberIdentifier
4
+ include NumberIdentifier
5
5
  if defined?(Spree::Webhooks)
6
6
  include Spree::Webhooks::HasWebhooks
7
7
  end
@@ -1,7 +1,7 @@
1
1
  module Spree
2
2
  class ReturnAuthorization < Spree::Base
3
3
  include Spree::Core::NumberGenerator.new(prefix: 'RA', length: 9)
4
- include Spree::NumberIdentifier
4
+ include NumberIdentifier
5
5
  if defined?(Spree::Webhooks)
6
6
  include Spree::Webhooks::HasWebhooks
7
7
  end
@@ -3,9 +3,9 @@ require 'ostruct'
3
3
  module Spree
4
4
  class Shipment < Spree::Base
5
5
  include Spree::Core::NumberGenerator.new(prefix: 'H', length: 11)
6
- include Spree::NumberIdentifier
7
- include Spree::NumberAsParam
8
- include Spree::Metadata
6
+ include NumberIdentifier
7
+ include NumberAsParam
8
+ include Metadata
9
9
  if defined?(Spree::Webhooks)
10
10
  include Spree::Webhooks::HasWebhooks
11
11
  end
@@ -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
@@ -2,7 +2,7 @@ module Spree
2
2
  class ShippingMethod < Spree::Base
3
3
  acts_as_paranoid
4
4
  include Spree::CalculatedAdjustments
5
- include Spree::Metadata
5
+ include Metadata
6
6
  if defined?(Spree::Webhooks)
7
7
  include Spree::Webhooks::HasWebhooks
8
8
  end
@@ -1,7 +1,7 @@
1
1
  module Spree
2
2
  module Stock
3
3
  class Estimator
4
- include Spree::VatPriceCalculation
4
+ include VatPriceCalculation
5
5
 
6
6
  attr_reader :order, :currency
7
7
 
@@ -2,7 +2,7 @@ module Spree
2
2
  class StockItem < Spree::Base
3
3
  acts_as_paranoid
4
4
 
5
- include Spree::Metadata
5
+ include Metadata
6
6
  if defined?(Spree::Webhooks)
7
7
  include Spree::Webhooks::HasWebhooks
8
8
  end
@@ -1,9 +1,9 @@
1
1
  module Spree
2
2
  class StockTransfer < Spree::Base
3
3
  include Spree::Core::NumberGenerator.new(prefix: 'T')
4
- include Spree::NumberIdentifier
5
- include Spree::NumberAsParam
6
- include Spree::Metadata
4
+ include NumberIdentifier
5
+ include NumberAsParam
6
+ include Metadata
7
7
  if defined?(Spree::Webhooks)
8
8
  include Spree::Webhooks::HasWebhooks
9
9
  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
 
@@ -1,7 +1,7 @@
1
1
  module Spree
2
2
  class StoreCredit < Spree::Base
3
3
  include SingleStoreResource
4
- include Spree::Metadata
4
+ include Metadata
5
5
  if defined?(Spree::Webhooks)
6
6
  include Spree::Webhooks::HasWebhooks
7
7
  end
@@ -3,7 +3,9 @@ require 'stringex'
3
3
 
4
4
  module Spree
5
5
  class Taxon < Spree::Base
6
- include Spree::Metadata
6
+ include TranslatableResource
7
+ include TranslatableResourceSlug
8
+ include Metadata
7
9
  if defined?(Spree::Webhooks)
8
10
  include Spree::Webhooks::HasWebhooks
9
11
  end
@@ -27,7 +29,7 @@ module Spree
27
29
  has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', dependent: :destroy
28
30
  has_many :promotion_rules, through: :promotion_rule_taxons, class_name: 'Spree::PromotionRule'
29
31
 
30
- validates :name, presence: true, uniqueness: { scope: [:parent_id, :taxonomy_id], allow_blank: true, case_sensitive: false }
32
+ validates :name, presence: true, uniqueness: { scope: [:parent_id, :taxonomy_id], case_sensitive: false }
31
33
  validates :taxonomy, presence: true
32
34
  validates :permalink, uniqueness: { case_sensitive: false, scope: [:parent_id, :taxonomy_id] }
33
35
  validates :hide_from_nav, inclusion: { in: [true, false] }
@@ -54,6 +56,24 @@ module Spree
54
56
 
55
57
  scope :for_stores, ->(stores) { joins(:taxonomy).where(spree_taxonomies: { store_id: stores.ids }) }
56
58
 
59
+ TRANSLATABLE_FIELDS = %i[name description permalink].freeze
60
+ translates(*TRANSLATABLE_FIELDS)
61
+
62
+ self::Translation.class_eval do
63
+ alias_attribute :slug, :permalink
64
+
65
+ before_create :set_permalink
66
+
67
+ def set_permalink
68
+ parent = translated_model.parent
69
+ if parent.present?
70
+ self.permalink = [parent.permalink, (permalink.blank? ? name.to_url : permalink.split('/').last)].join('/')
71
+ else
72
+ self.permalink = name.to_url if permalink.blank?
73
+ end
74
+ end
75
+ end
76
+
57
77
  # indicate which filters should be used for a taxon
58
78
  # this method should be customized to your own site
59
79
  def applicable_filters
@@ -73,11 +93,7 @@ module Spree
73
93
 
74
94
  # Creates permalink base for friendly_id
75
95
  def set_permalink
76
- if parent.present?
77
- self.permalink = [parent.permalink, (permalink.blank? ? name.to_url : permalink.split('/').last)].join('/')
78
- else
79
- self.permalink = name.to_url if permalink.blank?
80
- end
96
+ translations.each(&:set_permalink)
81
97
  end
82
98
 
83
99
  def active_products
@@ -1,10 +1,14 @@
1
1
  module Spree
2
2
  class Taxonomy < Spree::Base
3
- include Spree::Metadata
3
+ include TranslatableResource
4
+ include Metadata
4
5
  if defined?(Spree::Webhooks)
5
6
  include Spree::Webhooks::HasWebhooks
6
7
  end
7
8
 
9
+ TRANSLATABLE_FIELDS = %i[name].freeze
10
+ translates(*TRANSLATABLE_FIELDS)
11
+
8
12
  acts_as_list
9
13
 
10
14
  validates :name, presence: true, uniqueness: { case_sensitive: false, allow_blank: true, scope: :store_id }
@@ -3,8 +3,8 @@ module Spree
3
3
  acts_as_paranoid
4
4
  acts_as_list scope: :product
5
5
 
6
- include Spree::MemoizedData
7
- include Spree::Metadata
6
+ include MemoizedData
7
+ include Metadata
8
8
  if defined?(Spree::Webhooks)
9
9
  include Spree::Webhooks::HasWebhooks
10
10
  end
@@ -122,16 +122,13 @@ module Spree
122
122
  self.whitelisted_ransackable_scopes = %i(product_name_or_sku_cont search_by_product_name_or_sku)
123
123
 
124
124
  def self.product_name_or_sku_cont(query)
125
- joins(:product).where("LOWER(#{Product.table_name}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%")
125
+ joins(:product).join_translation_table(Product).
126
+ where("LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query)
127
+ OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%")
126
128
  end
127
129
 
128
130
  def self.search_by_product_name_or_sku(query)
129
- if defined?(SpreeGlobalize)
130
- joins(product: :translations).where("LOWER(#{Product::Translation.table_name}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)",
131
- query: "%#{query}%")
132
- else
133
- product_name_or_sku_cont(query)
134
- end
131
+ product_name_or_sku_cont(query)
135
132
  end
136
133
 
137
134
  def available?
@@ -0,0 +1,23 @@
1
+ module Spree
2
+ module DataFeeds
3
+ module Google
4
+ class OptionalAttributes
5
+ prepend Spree::ServiceModule::Base
6
+
7
+ def call(input)
8
+ information = {}
9
+
10
+ input[:product].property_ids.each do |key|
11
+ name = Spree::Property.find(key)&.name
12
+ value = input[:product].property(name)
13
+ unless value.nil?
14
+ information[name] = value
15
+ end
16
+ end
17
+
18
+ success(information: information)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ module Spree
2
+ module DataFeeds
3
+ module Google
4
+ class OptionalSubAttributes
5
+ prepend Spree::ServiceModule::Base
6
+
7
+ def call(input)
8
+ information = {}
9
+
10
+ # This is a place where you can put attributes that have sub-attributes, example for shipping:
11
+ #
12
+ # information['shipping'] = {}
13
+ # information['shipping']['price'] = calculate_shipping(input[:product])
14
+ # information['shipping']['country'] = input[:store].default_country
15
+
16
+ success(information: information)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module Spree
2
+ module DataFeeds
3
+ module Google
4
+ class ProductsList
5
+ prepend Spree::ServiceModule::Base
6
+
7
+ def call(store)
8
+ products = store.products.active
9
+ success(products: products)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,67 @@
1
+ module Spree
2
+ module DataFeeds
3
+ module Google
4
+ class RequiredAttributes
5
+ prepend Spree::ServiceModule::Base
6
+
7
+ def call(input)
8
+ information = {}
9
+
10
+ return failure(nil, error: 'No image link') if get_image_link(input[:variant], input[:product]).nil?
11
+
12
+ information['id'] = input[:variant].id
13
+ information['title'] = format_title(input[:product], input[:variant])
14
+ information['description'] = get_description(input[:product], input[:variant])
15
+ information['link'] = "#{input[:store].url}/#{input[:product].slug}"
16
+ information['image_link'] = get_image_link(input[:variant], input[:product])
17
+ information['price'] = format_price(input[:variant])
18
+ information['availability'] = get_availability(input[:product])
19
+ information['availability_date'] = input[:product].available_on&.xmlschema unless input[:product].available_on.nil?
20
+
21
+ success(information: information)
22
+ end
23
+
24
+ private
25
+
26
+ def format_title(product, variant)
27
+ # Title of a variant is created by joining title of a product and variant's option_values, as they are
28
+ # what differentiates it from other variants.
29
+ title = product.name
30
+ variant.option_values.find_each do |option_value|
31
+ title << " - #{option_value.name}"
32
+ end
33
+ title
34
+ end
35
+
36
+ def get_description(product, variant)
37
+ return product.description unless product.description.nil?
38
+
39
+ format_title(product, variant)
40
+ end
41
+
42
+ def get_image_link(variant, product)
43
+ # try getting image from variant
44
+ img = variant.images.first&.plp_url
45
+
46
+ # if no image specified for variant try getting product image
47
+ if img.nil?
48
+ img = product.images.first&.plp_url
49
+ end
50
+
51
+ img
52
+ end
53
+
54
+ def format_price(variant)
55
+ "#{variant.price} #{variant.cost_currency}"
56
+ end
57
+
58
+ def get_availability(product)
59
+ return 'in stock' if product.available? && product.available_on&.past?
60
+ return 'backorder' if product.backorderable? && product.backordered? && product.available_on&.future?
61
+
62
+ 'out of stock'
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,107 @@
1
+ require 'nokogiri'
2
+
3
+ module Spree
4
+ module DataFeeds
5
+ module Google
6
+ class Rss
7
+ prepend Spree::ServiceModule::Base
8
+
9
+ def call(settings)
10
+ @settings = settings
11
+
12
+ return failure(store, error: "Store with id: #{settings.store_id} does not exist.") if store.nil?
13
+
14
+ builder = Nokogiri::XML::Builder.new do |xml|
15
+ xml.rss('xmlns:g' => 'http://base.google.com/ns/1.0', 'version' => '2.0') do
16
+ xml.channel do
17
+ add_store_information_to_xml(xml)
18
+ result = products_list.call(store)
19
+ if result.success?
20
+ result.value[:products].find_each do |product|
21
+ product.variants.active.find_each do |variant|
22
+ add_variant_information_to_xml(xml, product, variant)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ success(file: builder.to_xml)
31
+ end
32
+
33
+ private
34
+
35
+ def store
36
+ return @store if defined? @store
37
+
38
+ @store ||= Spree::Store.find_by(id: @settings.store_id)
39
+ end
40
+
41
+ def add_store_information_to_xml(xml)
42
+ xml.title store.name
43
+ xml.link store.url
44
+ xml.description store.meta_description
45
+ end
46
+
47
+ def add_variant_information_to_xml(xml, product, variant)
48
+ input = { product: product, variant: variant, settings: @settings, store: store }
49
+ result = required_attributes.call(input)
50
+
51
+ if result.success
52
+ xml.item do
53
+ result.value[:information]&.each do |key, value|
54
+ xml['g'].send(key, value)
55
+ end
56
+
57
+ add_optional_information(xml, product, variant)
58
+ add_optional_sub_attributes(xml, product, variant)
59
+ end
60
+ end
61
+ end
62
+
63
+ def add_optional_information(xml, product, variant)
64
+ input = { product: product, variant: variant, settings: @settings, store: store }
65
+ result = optional_attributes.call(input)
66
+ if result.success?
67
+ information = result.value[:information]
68
+ information.each do |key, value|
69
+ xml['g'].send(key, value)
70
+ end
71
+ end
72
+ end
73
+
74
+ def add_optional_sub_attributes(xml, product, variant)
75
+ input = { product: product, variant: variant, settings: @settings, store: store }
76
+ result = optional_sub_attributes.call(input)
77
+ if result.success?
78
+ information = result.value[:information]
79
+ information.each do |key, value|
80
+ xml['g'].send(key) do
81
+ value.each do |subkey, subvalue|
82
+ xml['g'].send(subkey, subvalue)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ def optional_attributes
90
+ Spree::Dependencies.data_feeds_google_optional_attributes_service.constantize.new
91
+ end
92
+
93
+ def required_attributes
94
+ Spree::Dependencies.data_feeds_google_required_attributes_service.constantize.new
95
+ end
96
+
97
+ def optional_sub_attributes
98
+ Spree::Dependencies.data_feeds_google_optional_sub_attributes_service.constantize.new
99
+ end
100
+
101
+ def products_list
102
+ Spree::Dependencies.data_feeds_google_products_list.constantize.new
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end