spree_mobility 1.0.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 (102) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.hound.yml +21 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +7 -0
  6. data/.travis.yml +45 -0
  7. data/Appraisals +8 -0
  8. data/CONTRIBUTING.md +81 -0
  9. data/Gemfile +8 -0
  10. data/Guardfile +12 -0
  11. data/LICENSE.md +26 -0
  12. data/README.md +92 -0
  13. data/Rakefile +24 -0
  14. data/app/assets/javascripts/spree/backend/spree_mobility.js +2 -0
  15. data/app/assets/javascripts/spree/backend/taxon_tree_menu.js +49 -0
  16. data/app/assets/javascripts/spree/backend/translations.js +50 -0
  17. data/app/controllers/concerns/spree_mobility/controller_mobility_helper.rb +18 -0
  18. data/app/controllers/spree/admin/option_values_controller_decorator.rb +23 -0
  19. data/app/controllers/spree/admin/product_properties_controller_decorator.rb +22 -0
  20. data/app/controllers/spree/admin/shipping_methods_controller_decorator.rb +18 -0
  21. data/app/controllers/spree/admin/translations_controller.rb +53 -0
  22. data/app/controllers/spree/api/base_controller_decorator.rb +5 -0
  23. data/app/controllers/spree/base_controller_decorator.rb +5 -0
  24. data/app/helpers/spree_mobility/locale_helper.rb +11 -0
  25. data/app/models/concerns/spree_mobility/translatable.rb +63 -0
  26. data/app/overrides/spree/admin/option_types/_option_value_fields/add_translation.rb +10 -0
  27. data/app/overrides/spree/admin/option_types/index/add_translation.rb +8 -0
  28. data/app/overrides/spree/admin/product_properties/_product_property_fields/add_translation.rb +10 -0
  29. data/app/overrides/spree/admin/products/index/search_by_name_or_sku.rb +21 -0
  30. data/app/overrides/spree/admin/promotions/index/add_translation_link.rb +8 -0
  31. data/app/overrides/spree/admin/properties/index/add_translation.rb +8 -0
  32. data/app/overrides/spree/admin/shared/_product_tabs/add_translations.rb +10 -0
  33. data/app/overrides/spree/admin/shared/_translations/translation.rb +8 -0
  34. data/app/overrides/spree/admin/shared/sub_menu/_configuration/store_translations.rb +8 -0
  35. data/app/overrides/spree/admin/shipping_methods/index/add_translation.rb +8 -0
  36. data/app/overrides/spree/admin/taxonomies/_list/add_translations.rb +8 -0
  37. data/app/views/spree/admin/translations/_fields.html.erb +12 -0
  38. data/app/views/spree/admin/translations/_form.html.erb +13 -0
  39. data/app/views/spree/admin/translations/_form_fields.html.erb +38 -0
  40. data/app/views/spree/admin/translations/_settings.html.erb +22 -0
  41. data/app/views/spree/admin/translations/option_type.html.erb +9 -0
  42. data/app/views/spree/admin/translations/option_value.html.erb +25 -0
  43. data/app/views/spree/admin/translations/product.html.erb +7 -0
  44. data/app/views/spree/admin/translations/product_property.html.erb +23 -0
  45. data/app/views/spree/admin/translations/promotion.html.erb +9 -0
  46. data/app/views/spree/admin/translations/property.html.erb +9 -0
  47. data/app/views/spree/admin/translations/shipping_method.html.erb +9 -0
  48. data/app/views/spree/admin/translations/store.html.erb +25 -0
  49. data/app/views/spree/admin/translations/taxon.html.erb +25 -0
  50. data/app/views/spree/admin/translations/taxonomy.html.erb +9 -0
  51. data/bin/rails +7 -0
  52. data/config/initializers/enable_extensions.rb +27 -0
  53. data/config/initializers/form_builder_mobility_patch.rb +53 -0
  54. data/config/initializers/mobility.rb +118 -0
  55. data/config/initializers/spree_ransack.rb +31 -0
  56. data/config/locales/be.yml +6 -0
  57. data/config/locales/bg.yml +6 -0
  58. data/config/locales/en.yml +6 -0
  59. data/config/locales/it.yml +6 -0
  60. data/config/locales/ru.yml +6 -0
  61. data/config/locales/zh-TW.yml +6 -0
  62. data/config/routes.rb +7 -0
  63. data/db/migrate/20220411041407_add_translations_to_main_models.rb +83 -0
  64. data/db/migrate/20220412095648_remove_null_constraints_from_spree_tables.rb +13 -0
  65. data/db/migrate/20220413095648_migrate_translation_data.rb +53 -0
  66. data/gemfiles/spree_4_2.gemfile +8 -0
  67. data/gemfiles/spree_master.gemfile +10 -0
  68. data/lib/generators/spree_mobility/install/install_generator.rb +23 -0
  69. data/lib/spree_mobility/configuration.rb +12 -0
  70. data/lib/spree_mobility/core_ext/mobility/backends/active_record/table/mobility_acts_as_paranoid_decorator.rb +31 -0
  71. data/lib/spree_mobility/core_ext/spree/option_type_decorator.rb +11 -0
  72. data/lib/spree_mobility/core_ext/spree/option_value_decorator.rb +30 -0
  73. data/lib/spree_mobility/core_ext/spree/product_decorator.rb +87 -0
  74. data/lib/spree_mobility/core_ext/spree/product_property_decorator.rb +6 -0
  75. data/lib/spree_mobility/core_ext/spree/product_scopes_with_mobility_decorator.rb +16 -0
  76. data/lib/spree_mobility/core_ext/spree/products/find_with_mobility_decorator.rb +12 -0
  77. data/lib/spree_mobility/core_ext/spree/promotion_decorator.rb +11 -0
  78. data/lib/spree_mobility/core_ext/spree/property_decorator.rb +26 -0
  79. data/lib/spree_mobility/core_ext/spree/shipping_method_decorator.rb +10 -0
  80. data/lib/spree_mobility/core_ext/spree/store_decorator.rb +12 -0
  81. data/lib/spree_mobility/core_ext/spree/taxon_decorator.rb +54 -0
  82. data/lib/spree_mobility/core_ext/spree/taxonomy_decorator.rb +29 -0
  83. data/lib/spree_mobility/core_ext/spree/variant_decorator.rb +19 -0
  84. data/lib/spree_mobility/engine.rb +33 -0
  85. data/lib/spree_mobility/fallbacks.rb +45 -0
  86. data/lib/spree_mobility/translation_query.rb +36 -0
  87. data/lib/spree_mobility/version.rb +18 -0
  88. data/lib/spree_mobility.rb +47 -0
  89. data/spec/features/admin/products_spec.rb +15 -0
  90. data/spec/features/admin/translations_spec.rb +259 -0
  91. data/spec/features/translations_spec.rb +35 -0
  92. data/spec/models/product_spec.rb +77 -0
  93. data/spec/models/taxon_spec.rb +16 -0
  94. data/spec/models/translated_models_spec.rb +33 -0
  95. data/spec/models/variant_spec.rb +33 -0
  96. data/spec/spec_helper.rb +10 -0
  97. data/spec/support/i18n.rb +5 -0
  98. data/spec/support/matchers/be_a_thorough_translation_of_matcher.rb +21 -0
  99. data/spec/support/matchers/have_text_like.rb +3 -0
  100. data/spec/support/shared_contexts/translatable_context.rb +63 -0
  101. data/spree_mobility.gemspec +37 -0
  102. metadata +316 -0
@@ -0,0 +1,118 @@
1
+ require 'mobility'
2
+
3
+ Mobility.configure do
4
+ # PLUGINS
5
+ plugins do
6
+ # Backend
7
+ #
8
+ # Sets the default backend to use in models. This can be overridden in models
9
+ # by passing +backend: ...+ to +translates+.
10
+ #
11
+ # To default to a different backend globally, replace +:key_value+ by another
12
+ # backend name.
13
+ #
14
+ backend :table
15
+
16
+ # ActiveRecord
17
+ #
18
+ # Defines ActiveRecord as ORM, and enables ActiveRecord-specific plugins.
19
+ active_record
20
+
21
+ # Accessors
22
+ #
23
+ # Define reader and writer methods for translated attributes. Remove either
24
+ # to disable globally, or pass +reader: false+ or +writer: false+ to
25
+ # +translates+ in any translated model.
26
+ #
27
+ reader
28
+ writer
29
+
30
+ # Backend Reader
31
+ #
32
+ # Defines reader to access the backend for any attribute, of the form
33
+ # +<attribute>_backend+.
34
+ #
35
+ backend_reader
36
+ #
37
+ # Or pass an interpolation string to define a different pattern:
38
+ # backend_reader "%s_translations"
39
+
40
+ # Query
41
+ #
42
+ # Defines a scope on the model class which allows querying on
43
+ # translated attributes. The default scope is named +i18n+, pass a different
44
+ # name as default to change the global default, or to +translates+ in any
45
+ # model to change it for that model alone.
46
+ #
47
+ query
48
+
49
+ # Cache
50
+ #
51
+ # Comment out to disable caching reads and writes.
52
+ #
53
+ cache
54
+
55
+ # Dirty
56
+ #
57
+ # Uncomment this line to include and enable globally:
58
+ dirty
59
+ #
60
+ # Or uncomment this line to include but disable by default, and only enable
61
+ # per model by passing +dirty: true+ to +translates+.
62
+ # dirty false
63
+
64
+ # Fallbacks
65
+ #
66
+ # Uncomment line below to enable fallbacks, using +I18n.fallbacks+.
67
+ fallbacks
68
+ #
69
+ # Or uncomment this line to enable fallbacks with a global default.
70
+ # fallbacks { :pt => :en }
71
+
72
+ # Presence
73
+ #
74
+ # Converts blank strings to nil on reads and writes. Comment out to
75
+ # disable.
76
+ #
77
+ presence
78
+
79
+ # Default
80
+ #
81
+ # Set a default translation per attributes. When enabled, passing +default:
82
+ # 'foo'+ sets a default translation string to show in case no translation is
83
+ # present. Can also be passed a proc.
84
+ #
85
+ # default 'foo'
86
+
87
+ # Fallthrough Accessors
88
+ #
89
+ # Uses method_missing to define locale-specific accessor methods like
90
+ # +title_en+, +title_en=+, +title_fr+, +title_fr=+ for each translated
91
+ # attribute. If you know what set of locales you want to support, it's
92
+ # generally better to use Locale Accessors (or both together) since
93
+ # +method_missing+ is very slow. (You can use both fallthrough and locale
94
+ # accessor plugins together without conflict.)
95
+ #
96
+ # fallthrough_accessors
97
+
98
+ # Locale Accessors
99
+ #
100
+ # Uses +def+ to define accessor methods for a set of locales. By default uses
101
+ # +I18n.available_locales+, but you can pass the set of locales with
102
+ # +translates+ and/or set a global default here.
103
+ #
104
+ locale_accessors
105
+ #
106
+ # Or define specific defaults by uncommenting line below
107
+ # locale_accessors [:en, :ja]
108
+
109
+ # Attribute Methods
110
+ #
111
+ # Adds translated attributes to +attributes+ hash, and defines methods
112
+ # +translated_attributes+ and +untranslated_attributes+ which return hashes
113
+ # with translated and untranslated attributes, respectively. Be aware that
114
+ # this plugin can create conflicts with other gems.
115
+ #
116
+ # attribute_methods
117
+ end
118
+ end
@@ -0,0 +1,31 @@
1
+ # Core Spree is biased towards SpreeGlobalize, so we need to prepend our versions
2
+ Rails.application.config.to_prepare do
3
+ module SpreeMobilityAdminTaxonSearch
4
+ private
5
+ def load_taxonomy
6
+ @taxonomy = scope.includes(:translations, taxons: [:translations]).find(params[:taxonomy_id])
7
+ end
8
+ end
9
+ Spree::Admin::TaxonsController.prepend SpreeMobilityAdminTaxonSearch
10
+
11
+ module SpreeMobilityAdminStoreSearch
12
+ def load_stores_by_query
13
+ @stores =
14
+ stores_scope.joins(:translations).where("LOWER(#{Spree::Store::Translation.table_name}.name) LIKE LOWER(:query)",
15
+ query: "%#{params[:q]}%")
16
+ end
17
+ end
18
+ Spree::Admin::StoresController.prepend SpreeMobilityAdminStoreSearch
19
+
20
+ module SpreeMobilityApiV1TaxonSearch
21
+ private
22
+ def taxonomy
23
+ if params[:taxonomy_id].present?
24
+ @taxonomy ||=
25
+ Spree::Taxonomy.includes(:translations, taxons: [:translations]).
26
+ accessible_by(current_ability, :show).find(params[:taxonomy_id])
27
+ end
28
+ end
29
+ end
30
+ Spree::Api::V1::TaxonsController.prepend SpreeMobilityApiV1TaxonSearch
31
+ end
@@ -0,0 +1,6 @@
1
+ be:
2
+ spree:
3
+ mobility:
4
+ locales_displayed_on_translation_forms: "Мовы, адлюстрованныя на формах для перакладу"
5
+ no_translations_for_criteria: Пераклады па зададзенаму крытэру адсутнічаюць
6
+ store_translations: Пераклады
@@ -0,0 +1,6 @@
1
+ bg:
2
+ spree:
3
+ mobility:
4
+ locales_displayed_on_translation_forms: Преводи показани на формите
5
+ no_translations_for_criteria: Няма преводи по зададената критерия
6
+ store_translations: Преводи
@@ -0,0 +1,6 @@
1
+ en:
2
+ spree:
3
+ mobility:
4
+ locales_displayed_on_translation_forms: Locales displayed on translation forms
5
+ no_translations_for_criteria: There are no translations for given criteria
6
+ store_translations: Store Translations
@@ -0,0 +1,6 @@
1
+ it:
2
+ spree:
3
+ mobility:
4
+ locales_displayed_on_translation_forms: Lingue visualizzate nei form di traduzione
5
+ no_translations_for_criteria: Non ci sono traduzioni per i criteri selezionati
6
+ store_translations: Traduzioni per lo Store
@@ -0,0 +1,6 @@
1
+ ru:
2
+ spree:
3
+ mobility:
4
+ locales_displayed_on_translation_forms: "Языки, отображаемые на формах для перевода"
5
+ no_translations_for_criteria: Переводы по заданному критерию отсутствуют
6
+ store_translations: Переводы
@@ -0,0 +1,6 @@
1
+ zh-TW:
2
+ spree:
3
+ mobility:
4
+ locales_displayed_on_translation_forms: 顯示於編輯翻譯欄位的文字
5
+ no_translations_for_criteria: 目前沒有適合的翻譯
6
+ store_translations: 商店翻譯
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ Spree::Core::Engine.add_routes do
2
+ namespace :admin, path: Spree.admin_path do
3
+ get '/:resource/:resource_id/translations' => 'translations#index', as: :translations
4
+ patch '/option_values/:id' => 'option_values#update', as: :option_type_option_value
5
+ patch 'product/:id/product_properties/:id' => "product_properties#translate", as: :translate_product_property
6
+ end
7
+ end
@@ -0,0 +1,83 @@
1
+ class AddTranslationsToMainModels < SpreeExtension::Migration[4.2]
2
+ def up
3
+ unless table_exists?(:spree_product_translations)
4
+ create_helper :spree_product, { name: :string, description: :text, meta_title: :string,
5
+ meta_description: :string, meta_keywords: :string, slug: :string, deleted_at: :datetime }
6
+ add_index :spree_product_translations, :deleted_at
7
+ end
8
+
9
+ unless table_exists?(:spree_promotion_translations)
10
+ create_helper :spree_promotion, { name: :string, description: :string }
11
+ end
12
+
13
+ unless table_exists?(:spree_option_type_translations)
14
+ create_helper :spree_option_type, { name: :string, presentation: :string }
15
+ end
16
+
17
+ unless table_exists?(:spree_property_translations)
18
+ create_helper :spree_property, { name: :string, presentation: :string }
19
+ end
20
+
21
+ unless table_exists?(:spree_taxonomy_translations)
22
+ create_helper :spree_taxonomy, { name: :string }
23
+ end
24
+
25
+ unless table_exists?(:spree_taxon_translations)
26
+ create_helper :spree_taxon, { name: :string, description: :text, meta_title: :string,
27
+ meta_description: :string, meta_keywords: :string,
28
+ permalink: :string }
29
+ end
30
+
31
+ unless table_exists?(:spree_option_value_translations)
32
+ create_helper :spree_option_value, { name: :string, presentation: :string }
33
+ end
34
+
35
+ unless table_exists?(:spree_product_property_translations)
36
+ create_helper :spree_product_property, { value: :string }
37
+ end
38
+
39
+ unless table_exists?(:spree_store_translations)
40
+ create_helper :spree_store, { name: :string, meta_description: :text, meta_keywords: :text, seo_title: :string }
41
+ end
42
+
43
+ unless table_exists?(:spree_shipping_method_translations)
44
+ create_helper :spree_shipping_method, { name: :string }
45
+ end
46
+
47
+ add_column :friendly_id_slugs, :locale, :string
48
+ end
49
+
50
+ def down
51
+ drop_table :spree_product_translations
52
+ drop_table :spree_promotion_translations
53
+ drop_table :spree_option_type_translations
54
+ drop_table :spree_property_translations
55
+ drop_table :spree_taxonomy_translations
56
+ drop_table :spree_taxon_translations
57
+ drop_table :spree_option_value_translations
58
+ drop_table :spree_product_property_translations
59
+ drop_table :spree_store_translations
60
+ drop_table :spree_shipping_method_translations
61
+ remove_column :friendly_id_slugs, :locale
62
+ end
63
+
64
+ protected
65
+
66
+ def create_helper(table, params)
67
+ create_table :"#{table}_translations" do |t|
68
+
69
+ # Translated attribute(s)
70
+ params.each_pair do |attr, attr_type|
71
+ t.send attr_type, attr
72
+ end
73
+
74
+ t.string :locale, null: false
75
+ t.references table, null: false, foreign_key: true, index: false
76
+
77
+ t.timestamps null: false
78
+ end
79
+
80
+ add_index :"#{table}_translations", :locale, name: :"index_#{table}_translations_on_locale"
81
+ add_index :"#{table}_translations", [:"#{table}_id", :locale], name: :"index_#{table}_translations_on_id_and_locale", unique: true
82
+ end
83
+ end
@@ -0,0 +1,13 @@
1
+ class RemoveNullConstraintsFromSpreeTables < SpreeExtension::Migration[4.2]
2
+ def up
3
+ change_column :spree_properties, :presentation, :string, null: true
4
+ change_column :spree_taxonomies, :name, :string, null: true
5
+ change_column :spree_taxons, :name, :string, null: true
6
+ end
7
+
8
+ def down
9
+ change_column :spree_properties, :presentation, :string, null: false
10
+ change_column :spree_taxonomies, :name, :string, null: false
11
+ change_column :spree_taxons, :name, :string, null: false
12
+ end
13
+ end
@@ -0,0 +1,53 @@
1
+ class MigrateTranslationData < SpreeExtension::Migration[4.2]
2
+ def up
3
+ create_helper Spree::Product, { name: :string, description: :text, meta_title: :string,
4
+ meta_description: :string, meta_keywords: :string, slug: :string, deleted_at: :datetime }
5
+
6
+ create_helper Spree::Promotion, { name: :string, description: :string }
7
+
8
+ create_helper Spree::OptionType, { name: :string, presentation: :string }
9
+
10
+ create_helper Spree::Property, { name: :string, presentation: :string }
11
+
12
+ create_helper Spree::Taxonomy, { name: :string }
13
+
14
+ create_helper Spree::Taxon, { name: :string, description: :text, meta_title: :string,
15
+ meta_description: :string, meta_keywords: :string,
16
+ permalink: :string }
17
+
18
+ create_helper Spree::OptionValue, { name: :string, presentation: :string }
19
+
20
+ create_helper Spree::ProductProperty, { value: :string }
21
+
22
+ create_helper Spree::Store, { name: :string, meta_description: :text, meta_keywords: :text, seo_title: :string }
23
+
24
+ create_helper Spree::ShippingMethod, { name: :string }
25
+ end
26
+
27
+ def down
28
+ # no need to do anything
29
+ end
30
+
31
+ protected
32
+
33
+ def create_helper(model_klass, fields)
34
+ store = Spree::Store.unscoped.first
35
+ return unless store
36
+ default_locale = store.default_locale.to_s
37
+ field_names = fields.keys + ['created_at', 'updated_at']
38
+ translation_table = "#{model_klass.table_name.singularize}_translations"
39
+ foreign_key = "#{model_klass.table_name.singularize}_id"
40
+
41
+ # In case we are migrating from Globalize with existing translations, skip this step
42
+ return if ActiveRecord::Base.connection.execute("SELECT id FROM #{translation_table}").any?
43
+ ActiveRecord::Base.connection.execute("SELECT id, #{field_names.join(',')} FROM #{model_klass.table_name}").each do |r|
44
+ field_values =
45
+ field_names.each_with_object([]) do |field_name, a|
46
+ a << r[field_name.to_s]
47
+ end
48
+
49
+ ActiveRecord::Base.connection.execute(ActiveRecord::Base.sanitize_sql_array(["INSERT INTO #{translation_table} (locale, #{foreign_key}, #{field_names.join(',')}) VALUES (?,?,#{(['?'] * field_names.size).join(',')})",
50
+ default_locale.to_s, r['id'], *field_values]))
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem 'spree', '~> 4.2.0.rc4'
6
+ gem 'spree_i18n', github: 'spree-contrib/spree_i18n', branch: 'main'
7
+
8
+ gemspec path: '../'
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem 'spree', github: 'spree/spree', branch: 'main'
6
+ gem 'spree_frontend', github: 'spree/spree', branch: 'main'
7
+ gem 'spree_backend', github: 'spree/spree', branch: 'main'
8
+ gem 'spree_i18n', github: 'spree-contrib/spree_i18n', branch: 'main'
9
+
10
+ gemspec path: '../'
@@ -0,0 +1,23 @@
1
+ module SpreeMobility
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ class_option :migrate, type: :boolean, default: true
5
+
6
+ def add_javascripts
7
+ append_file "vendor/assets/javascripts/spree/backend/all.js", "//= require spree/backend/spree_mobility\n"
8
+ end
9
+
10
+ def add_migrations
11
+ run 'bundle exec rake spree_mobility:install:migrations'
12
+ end
13
+
14
+ def run_migrations
15
+ if options[:migrate]
16
+ run 'bundle exec rake db:migrate'
17
+ else
18
+ puts "Skiping rake db:migrate, don't forget to run it!"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ module SpreeMobility
2
+ class Configuration < Spree::Preferences::Configuration
3
+ # These configs intend to, respectively:
4
+ #
5
+ # Say which mobility inputs are displayed on backend
6
+ # Set locales that should be available for end users
7
+ #
8
+ # e.g. If available_locales are [:en, :es] admin can translate model records
9
+ # to spanish as well. Once it's done :es can be added to supported_locales
10
+ preference :supported_locales, :array, default: [:en]
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ module SpreeMobility::CoreExt::Mobility::Backends::ActiveRecord::Table
2
+ module MobilityActsAsParanoidDecorator
3
+ private
4
+ def using_acts_as_paranoid?
5
+ translation_class = model_class.const_get(subclass_name)
6
+ return false unless translation_class
7
+ translation_class.column_names.include?('deleted_at')
8
+ end
9
+
10
+ # If joining a table with a deleted_at column (acts_as_paranoid), then
11
+ # filter the join with deleted_at IS NULL
12
+ def join_translations(relation, locale, join_type)
13
+ if using_acts_as_paranoid?
14
+ # check if joins changed
15
+ old_joins = relation.joins_values
16
+ relation = super
17
+ new_joins = relation.joins_values - old_joins
18
+ return relation if new_joins.empty?
19
+ fail "join_translations new_joins.size > 1" unless new_joins.size == 1
20
+ # append deleted_at IS NULL to joins ON clause using Arel
21
+ new_join = new_joins.first
22
+ joined_table = model_class.const_get(subclass_name).arel_table.alias(table_alias(locale))
23
+ relation.joins_values -= [new_join]
24
+ new_join.right = new_join.right.and(joined_table[:deleted_at].eq(nil))
25
+ relation.joins(new_join)
26
+ else
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ module SpreeMobility::CoreExt::Spree::OptionTypeDecorator
2
+ def self.prepended(base)
3
+ base.include SpreeMobility::Translatable
4
+ SpreeMobility.translates_for base, :name, :presentation
5
+
6
+ base.translation_class.class_eval do
7
+ validates :name, presence: true, uniqueness: { scope: :locale, case_sensitive: false, allow_blank: true }
8
+ validates :presentation, presence: true
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,30 @@
1
+ module SpreeMobility::CoreExt::Spree::OptionValueDecorator
2
+ module TranslationMethods
3
+ def name_uniqueness_validation
4
+ return unless name.present?
5
+ return unless translated_model
6
+ check_scope =
7
+ ::Spree::OptionValue.
8
+ where.not(id: translated_model.id).
9
+ where(option_type_id: translated_model.option_type_id).
10
+ joins(:translations).
11
+ where(spree_option_value_translations: { locale: locale }).
12
+ where('LOWER(spree_option_value_translations.name) = LOWER(?)', name)
13
+ if check_scope.exists?
14
+ errors.add(:name, :taken, value: name)
15
+ end
16
+ end
17
+ end
18
+
19
+ def self.prepended(base)
20
+ base.include SpreeMobility::Translatable
21
+ SpreeMobility.translates_for base, :name, :presentation
22
+
23
+ base.translation_class.class_eval do
24
+ include TranslationMethods
25
+ validate :name_uniqueness_validation
26
+ validates :name, presence: true
27
+ validates :presentation, presence: true
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,87 @@
1
+ module SpreeMobility::CoreExt::Spree
2
+ module ProductDecorator
3
+
4
+
5
+ module ClassMethods
6
+ def search_by_name(query)
7
+ helper = SpreeMobility::TranslationQuery.new(all.model.mobility_backend_class(:name))
8
+
9
+ helper.add_joins(self.all).
10
+ where("LOWER(#{helper.col_name(:name)}) LIKE LOWER(:query)", query: "%#{query}%").distinct
11
+ end
12
+
13
+ def search_by_name_or_sku(query)
14
+ helper = SpreeMobility::TranslationQuery.new(all.model.mobility_backend_class(:name))
15
+
16
+ helper.add_joins(self.all).
17
+ joins(:variants_including_master).
18
+ where("(LOWER(#{helper.col_name(:name)}) LIKE LOWER(:query)) OR (LOWER(#{::Spree::Variant.table_name}.sku) LIKE LOWER(:query))", query: "%#{query}%").distinct
19
+ end
20
+
21
+ def like_any(fields, values)
22
+ mobility_fields = fields.select { |field| mobility_attributes.include?(field.to_s) }
23
+ other_fields = fields - mobility_fields
24
+
25
+ helper = SpreeMobility::TranslationQuery.new(all.model.mobility_backend_class(:name))
26
+ conditions = mobility_fields.product(values).map do |(field, value)|
27
+ sanitize_sql_array(["LOWER(#{helper.col_name(field)}) LIKE LOWER(?)", "%#{value}%"])
28
+ end
29
+
30
+ scope = other_fields.empty? ? self.all : super(other_fields, values)
31
+
32
+ helper.add_joins(scope).where(conditions.join(' OR '))
33
+ end
34
+ end
35
+
36
+ def self.prepended(base)
37
+ base.include SpreeMobility::Translatable
38
+ SpreeMobility.translates_for base, :name, :description, :meta_title, :meta_description, :meta_keywords, :slug
39
+ base.friendly_id :slug_candidates, use: [:history, :mobility]
40
+ base.whitelisted_ransackable_scopes << 'search_by_name_or_sku'
41
+
42
+ base.translation_class.class_eval do
43
+ acts_as_paranoid
44
+ after_destroy :punch_slug
45
+ default_scopes = []
46
+ validates :slug, presence: true, uniqueness: { scope: :locale, case_sensitive: false }
47
+
48
+ with_options length: { maximum: 255 }, allow_blank: true do
49
+ validates :meta_keywords
50
+ validates :meta_title
51
+ end
52
+ with_options presence: true do
53
+ validates :name
54
+ end
55
+ end
56
+
57
+ if RUBY_VERSION.to_f >= 2.5
58
+ base.translation_class.define_method(:punch_slug) { update(slug: "#{Time.current.to_i}_#{slug}"[0..254]) }
59
+ else
60
+ base.translation_class.send(:define_method, :punch_slug) { update(slug: "#{Time.current.to_i}_#{slug}"[0..254]) }
61
+ end
62
+ end
63
+
64
+ # Don't punch slug on original product as it prevents bulk deletion.
65
+ # Also we don't need it, as it is translated.
66
+ def punch_slug; end
67
+
68
+ def duplicate_extra(old_product)
69
+ duplicate_translations(old_product)
70
+ end
71
+
72
+ def property(property_name)
73
+ product_properties.joins(:property).find_by(spree_properties: { id: ::Spree::Property.find_by(name: property_name) }).try(:value)
74
+ end
75
+
76
+ private
77
+
78
+ def duplicate_translations(old_product)
79
+ self.translations.clear
80
+ old_product.translations.each do |translation|
81
+ translation.slug = nil # slug must be regenerated
82
+ translation.name = "COPY OF #{translation.name}" unless translation.name&.start_with?('COPY OF ')
83
+ self.translations << translation.dup
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,6 @@
1
+ module SpreeMobility::CoreExt::Spree::ProductPropertyDecorator
2
+ def self.prepended(base)
3
+ base.include SpreeMobility::Translatable
4
+ SpreeMobility.translates_for base, :value
5
+ end
6
+ end
@@ -0,0 +1,16 @@
1
+ module SpreeMobility::CoreExt::Spree::ProductScopesWithMobilityDecorator
2
+ def ascend_by_taxons_min_position(taxon_ids)
3
+ # order() must not refer to select(), because select could be removed
4
+ # in Spree::Products::Find
5
+ joins(:classifications).
6
+ where(::Spree::Classification.table_name => { taxon_id: taxon_ids }).
7
+ select(
8
+ [
9
+ "#{::Spree::Product.table_name}.*",
10
+ "MIN(#{::Spree::Classification.table_name}.position) AS min_position"
11
+ ].join(', ')
12
+ ).
13
+ group(:id).
14
+ order(Arel.sql("MIN(#{::Spree::Classification.table_name}.position) ASC"))
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ module SpreeMobility::CoreExt::Spree::Products::FindWithMobilityDecorator
2
+ # The issue here is that ordering by translated attr (e.g. name) will add
3
+ # an ORDER BY translations_table.name, but the query has a SELECT DISTINCT,
4
+ # which would require the translations_table.name to be added to the SELECT
5
+ # Instead of using DISTINCT, we select the ID only and use that as a subquery
6
+ # in a simple SELECT ... FROM products WHERE products.id IN (SUBQUERY).
7
+ # Performance-wise should be almost the same, or the same.
8
+ def execute
9
+ ::Spree::Product.where(id: super.except(:select).select(:id).distinct(false))
10
+ end
11
+ end
12
+
@@ -0,0 +1,11 @@
1
+ module SpreeMobility::CoreExt::Spree::PromotionDecorator
2
+ def self.prepended(base)
3
+ base.include SpreeMobility::Translatable
4
+ SpreeMobility.translates_for base, :name, :description
5
+
6
+ base.translation_class.class_eval do
7
+ validates :name, presence: true
8
+ validates :description, length: { maximum: 255 }, allow_blank: true
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ module SpreeMobility::CoreExt::Spree::PropertyDecorator
2
+ def self.prepended(base)
3
+ base.include SpreeMobility::Translatable
4
+ SpreeMobility.translates_for base, :name, :presentation
5
+
6
+ base.translation_class.class_eval do
7
+ validates :name, :presentation, presence: true
8
+ end
9
+ end
10
+
11
+ # Currently, mobilize does not yet support fallbacks in query scopes,
12
+ # so we update this method to fetch property IDs instead of value and then let
13
+ # mobilize logic determine the correct translation.
14
+ # The performance impact in this case should be minimal.
15
+ def uniq_values(product_properties_scope: nil)
16
+ with_uniq_values_cache_key(product_properties_scope) do
17
+ properties = product_properties
18
+ properties = properties.where(id: product_properties_scope) if product_properties_scope.present?
19
+
20
+ filter_params_scope = properties.reorder(nil).group(:filter_param).select(::Arel.sql('MAX(id)'))
21
+ properties.where(id: filter_params_scope).
22
+ includes(:translations).sort_by(&:value).
23
+ map { |property| [property.filter_param, property.value] }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ module SpreeMobility::CoreExt::Spree::ShippingMethodDecorator
2
+ def self.prepended(base)
3
+ base.include SpreeMobility::Translatable
4
+ SpreeMobility.translates_for base, :name
5
+
6
+ base.translation_class.class_eval do
7
+ validates :name, presence: true
8
+ end
9
+ end
10
+ end