spree_mobility 1.0.0

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