diff --git a/.circleci/config.yml b/.circleci/config.yml index f7fc48736e4..bfacdd2c074 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,10 +13,10 @@ defaults: &defaults - image: &ruby_2_7_image circleci/ruby:2.7-node-browsers - image: &redis_image circleci/redis:6.2-alpine -defaults_3_0: &defaults_3_0 +defaults_3_2: &defaults_3_2 <<: *defaults docker: - - image: &ruby_3_0_image circleci/ruby:3.0-node-browsers + - image: &ruby_3_2_image cimg/ruby:3.2.0-browsers - image: *redis_image run_tests_2_7: &run_tests_2_7 @@ -28,9 +28,12 @@ run_tests_2_7: &run_tests_2_7 keys: - spree-bundle-v10-ruby-2-7-{{ .Branch }} - spree-bundle-v10-ruby-2-7 + - run: + name: Add keyserver + command: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4EB27DB2A3B88B8B - run: name: Install libvips - command: sudo apt-get install libvips + command: sudo apt-get update && sudo apt-get install libvips - run: name: Ensure Bundle Install command: | @@ -48,18 +51,18 @@ run_tests_2_7: &run_tests_2_7 - store_test_results: path: /tmp/test-results -run_tests_3_0: &run_tests_3_0 - <<: *defaults_3_0 +run_tests_3_2: &run_tests_3_2 + <<: *defaults_3_2 parallelism: 3 steps: - checkout - restore_cache: keys: - - spree-bundle-v10-ruby-3-0-{{ .Branch }} - - spree-bundle-v10-ruby-3-0 + - spree-bundle-v10-ruby-3-2-{{ .Branch }} + - spree-bundle-v10-ruby-3-2 - run: name: Install libvips - command: sudo apt-get install libvips + command: sudo apt-get update && sudo apt-get install libvips42 - run: name: Ensure Bundle Install command: | @@ -86,9 +89,12 @@ jobs: keys: - spree-bundle-v10-ruby-2-7-{{ .Branch }} - spree-bundle-v10-ruby-2-7 + - run: + name: Add keyserver + command: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4EB27DB2A3B88B8B - run: name: Install libvips - command: sudo apt-get install libvips + command: sudo apt-get update && sudo apt-get install libvips - run: name: Bundle Install command: | @@ -99,29 +105,29 @@ jobs: paths: - ~/spree/vendor/bundle - bundle_ruby_3_0: - <<: *defaults_3_0 + bundle_ruby_3_2: + <<: *defaults_3_2 steps: - checkout - restore_cache: keys: - - spree-bundle-v10-ruby-3-0-{{ .Branch }} - - spree-bundle-v10-ruby-3-0 + - spree-bundle-v10-ruby-3-2-{{ .Branch }} + - spree-bundle-v10-ruby-3-2 - run: name: Install libvips - command: sudo apt-get install libvips + command: sudo apt-get update && sudo apt-get install libvips42 - run: name: Bundle Install command: | bundle check || bundle install ./bin/build-ci.rb install - save_cache: - key: spree-bundle-v10-ruby-3-0-{{ .Branch }}-{{ checksum "Gemfile.lock" }} + key: spree-bundle-v10-ruby-3-1-{{ .Branch }}-{{ checksum "Gemfile.lock" }} paths: - ~/spree/vendor/bundle brakeman: - <<: *defaults_3_0 + <<: *defaults_3_2 steps: - checkout - restore_cache: @@ -136,7 +142,7 @@ jobs: - run: name: Run Brakeman spree/api command: | - bundle exec brakeman -p api/ --skip-files app/controllers/spree/api/v1/ --exit-on-warn --exit-on-error + bundle exec brakeman -p api/ --ignore-config api/brakeman.ignore --skip-files app/controllers/spree/api/v1/ --exit-on-warn --exit-on-error - run: name: Run Brakeman spree/core command: | @@ -160,12 +166,12 @@ jobs: environment: POSTGRES_USER: postgres - tests_ruby_3_0_rails_7_0_postgres: - <<: *run_tests_3_0 + tests_ruby_3_2_rails_7_0_postgres: + <<: *run_tests_3_2 environment: <<: *postgres_environment docker: - - image: *ruby_3_0_image + - image: *ruby_3_2_image - image: *postgres_image - image: *redis_image @@ -175,8 +181,8 @@ jobs: <<: *postgres_environment RAILS_VERSION: '~> 6.1.0' - tests_ruby_3_0_rails_7_0_mysql: - <<: *run_tests_3_0 + tests_ruby_3_2_rails_7_0_mysql: + <<: *run_tests_3_2 environment: <<: *environment DB: mysql @@ -185,7 +191,7 @@ jobs: COVERAGE: true COVERAGE_DIR: /tmp/workspace/simplecov docker: - - image: *ruby_3_0_image + - image: *ruby_3_2_image - image: *redis_image - image: circleci/mysql:8-ram command: [--default-authentication-plugin=mysql_native_password] @@ -193,11 +199,14 @@ jobs: - checkout - restore_cache: keys: - - spree-bundle-v10-ruby-3-0-{{ .Branch }} - - spree-bundle-v10-ruby-3-0 + - spree-bundle-v10-ruby-3-2-{{ .Branch }} + - spree-bundle-v10-ruby-3-2 + - run: + name: Add keyserver + command: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4EB27DB2A3B88B8B - run: name: Install libvips - command: sudo apt-get install libvips + command: sudo apt-get update && sudo apt-get install libvips - run: name: Ensure Bundle Install command: | @@ -254,22 +263,22 @@ workflows: main: jobs: - bundle_ruby_2_7 - - bundle_ruby_3_0 + - bundle_ruby_3_2 - brakeman: requires: - - bundle_ruby_3_0 - - tests_ruby_3_0_rails_7_0_postgres: + - bundle_ruby_3_2 + - tests_ruby_3_2_rails_7_0_postgres: requires: - - bundle_ruby_3_0 + - bundle_ruby_3_2 - tests_ruby_2_7_rails_7_0_postgres: requires: - bundle_ruby_2_7 - tests_ruby_2_7_rails_6_1_postgres: requires: - bundle_ruby_2_7 - - tests_ruby_3_0_rails_7_0_mysql: + - tests_ruby_3_2_rails_7_0_mysql: requires: - - bundle_ruby_3_0 + - bundle_ruby_3_2 - send_test_coverage: requires: - - tests_ruby_3_0_rails_7_0_mysql + - tests_ruby_3_2_rails_7_0_mysql diff --git a/api/app/controllers/spree/api/v2/base_controller.rb b/api/app/controllers/spree/api/v2/base_controller.rb index 21cafcae516..02760585d36 100644 --- a/api/app/controllers/spree/api/v2/base_controller.rb +++ b/api/app/controllers/spree/api/v2/base_controller.rb @@ -82,6 +82,8 @@ def spree_current_user @spree_current_user ||= doorkeeper_token.resource_owner end + alias try_spree_current_user spree_current_user # for compatibility with spree_legacy_frontend + def spree_authorize!(action, subject, *args) authorize!(action, subject, *args) end diff --git a/api/app/controllers/spree/api/v2/data_feeds/google_controller.rb b/api/app/controllers/spree/api/v2/data_feeds/google_controller.rb new file mode 100644 index 00000000000..c14713c0468 --- /dev/null +++ b/api/app/controllers/spree/api/v2/data_feeds/google_controller.rb @@ -0,0 +1,24 @@ +module Spree + module Api + module V2 + module DataFeeds + class GoogleController < ::Spree::Api::V2::BaseController + def rss_feed + send_data data_feeds_google_rss_service.value[:file], filename: 'products.rss', type: 'text/xml' + end + + private + + def settings + @settings ||= Spree::DataFeedSetting.find_by!(spree_store_id: current_store, uuid: params[:unique_url], provider: 'google', enabled: true) + end + + def data_feeds_google_rss_service + Spree::Dependencies.data_feeds_google_rss_service.constantize.new.call(settings) + end + end + end + end + end +end + diff --git a/api/app/controllers/spree/api/v2/platform/google_feed_settings_controller.rb b/api/app/controllers/spree/api/v2/platform/google_feed_settings_controller.rb new file mode 100644 index 00000000000..0460ae59d83 --- /dev/null +++ b/api/app/controllers/spree/api/v2/platform/google_feed_settings_controller.rb @@ -0,0 +1,15 @@ +module Spree + module Api + module V2 + module Platform + class GoogleFeedSettingsController < ResourceController + private + + def model_class + Spree::GoogleFeedSetting + end + end + end + end + end +end diff --git a/api/app/controllers/spree/api/v2/platform/taxons_controller.rb b/api/app/controllers/spree/api/v2/platform/taxons_controller.rb index 6f9d39d1da9..9ac17f4b5dd 100644 --- a/api/app/controllers/spree/api/v2/platform/taxons_controller.rb +++ b/api/app/controllers/spree/api/v2/platform/taxons_controller.rb @@ -2,7 +2,7 @@ module Spree module Api module V2 module Platform - class TaxonsController < TranslatableResourceController + class TaxonsController < ResourceController include ::Spree::Api::V2::Platform::NestedSetRepositionConcern private diff --git a/api/app/controllers/spree/api/v2/platform/translatable_resource_controller.rb b/api/app/controllers/spree/api/v2/platform/translatable_resource_controller.rb deleted file mode 100644 index 24d75a06e89..00000000000 --- a/api/app/controllers/spree/api/v2/platform/translatable_resource_controller.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Spree - module Api - module V2 - module Platform - class TranslatableResourceController < ResourceController - def scope - super.joins(:translations) - end - end - end - end - end -end - - diff --git a/api/app/controllers/spree/api/v2/platform/variants_controller.rb b/api/app/controllers/spree/api/v2/platform/variants_controller.rb index 740eee88902..303752deed1 100644 --- a/api/app/controllers/spree/api/v2/platform/variants_controller.rb +++ b/api/app/controllers/spree/api/v2/platform/variants_controller.rb @@ -16,11 +16,8 @@ def spree_permitted_attributes def collection # if filtering on products, manually join on product translation to workaround mobility-ransack issue if params.key?(:filter) && params[:filter].keys.any? { |k| k.include? 'product' } - @collection ||= scope.joins(:product). - joins("LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias} - ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id - AND #{Product.translation_table_alias}.locale = '#{Mobility.locale.to_s}'"). - ransack(params[:filter]).result + @collection ||= scope.joins(:product).join_translation_table(Product). + ransack(params[:filter]).result else super end diff --git a/api/app/controllers/spree/api/v2/resource_controller.rb b/api/app/controllers/spree/api/v2/resource_controller.rb index 9992b2ee6a5..916d12d47e9 100644 --- a/api/app/controllers/spree/api/v2/resource_controller.rb +++ b/api/app/controllers/spree/api/v2/resource_controller.rb @@ -35,7 +35,7 @@ def scope(skip_cancancan: false) base_scope = model_class.for_store(current_store) base_scope = base_scope.accessible_by(current_ability, :show) unless skip_cancancan base_scope = base_scope.includes(scope_includes) if scope_includes.any? && action_name == 'index' - base_scope + model_class.include?(TranslatableResource) ? base_scope.i18n : base_scope end def scope_includes diff --git a/api/app/models/concerns/spree/webhooks/has_webhooks.rb b/api/app/models/concerns/spree/webhooks/has_webhooks.rb index 850a0e233c4..42b5ec23266 100644 --- a/api/app/models/concerns/spree/webhooks/has_webhooks.rb +++ b/api/app/models/concerns/spree/webhooks/has_webhooks.rb @@ -58,13 +58,7 @@ def resource_serializer end def updating_only_ignored_attributes? - if defined?(translations) && - translations.any?(&:saved_changes) && - (translations.first(&:saved_changes).saved_changes.keys - ignored_attributes).any? - false - else - (saved_changes.keys - ignored_attributes).empty? - end + (saved_changes.keys - ignored_attributes).empty? end def ignored_attributes diff --git a/api/app/serializers/spree/api/v2/platform/google_feed_setting_serializer.rb b/api/app/serializers/spree/api/v2/platform/google_feed_setting_serializer.rb new file mode 100644 index 00000000000..8e4e418bcc6 --- /dev/null +++ b/api/app/serializers/spree/api/v2/platform/google_feed_setting_serializer.rb @@ -0,0 +1,11 @@ +module Spree + module Api + module V2 + module Platform + class GoogleFeedSettingSerializer < BaseSerializer + include ResourceSerializerConcern + end + end + end + end +end diff --git a/api/app/serializers/spree/api/v2/platform/user_serializer.rb b/api/app/serializers/spree/api/v2/platform/user_serializer.rb index ec3f116c500..25a4865dbe1 100644 --- a/api/app/serializers/spree/api/v2/platform/user_serializer.rb +++ b/api/app/serializers/spree/api/v2/platform/user_serializer.rb @@ -5,7 +5,7 @@ module Platform class UserSerializer < BaseSerializer set_type :user - attributes :email, :first_name, :last_name, :created_at, :updated_at, :public_metadata, :private_metadata + attributes :email, :first_name, :last_name, :created_at, :updated_at, :public_metadata, :private_metadata, :selected_locale attribute :average_order_value do |user, params| price_stats(user.report_values_for(:average_order_value, params[:store])) diff --git a/api/app/serializers/spree/v2/storefront/user_serializer.rb b/api/app/serializers/spree/v2/storefront/user_serializer.rb index 67cdd3d6a3c..c3f88d400a3 100644 --- a/api/app/serializers/spree/v2/storefront/user_serializer.rb +++ b/api/app/serializers/spree/v2/storefront/user_serializer.rb @@ -4,7 +4,7 @@ module Storefront class UserSerializer < BaseSerializer set_type :user - attributes :email, :first_name, :last_name, :public_metadata + attributes :email, :first_name, :selected_locale, :last_name, :public_metadata attribute :store_credits do |user| user.total_available_store_credit diff --git a/api/brakeman.ignore b/api/brakeman.ignore new file mode 100644 index 00000000000..e253a812ea0 --- /dev/null +++ b/api/brakeman.ignore @@ -0,0 +1,7 @@ +{ + "ignored_warnings": [ + + ], + "updated": "2022-12-29 09:29:46 +0100", + "brakeman_version": "5.4.0" +} diff --git a/api/config/routes.rb b/api/config/routes.rb index e702fb4279a..dc8e070ec21 100644 --- a/api/config/routes.rb +++ b/api/config/routes.rb @@ -196,6 +196,9 @@ # Store API resources :stores + # Google Feed Setting API + resources :google_feed_settings + # Configurations API resources :payment_methods resources :shipping_categories @@ -207,6 +210,11 @@ resources :subscribers end end + + namespace :data_feeds do + # google data feed API + get '/google/:unique_url', to: 'google#rss_feed' + end end end end diff --git a/api/docs/oauth/index.yml b/api/docs/oauth/index.yml index b558874078e..36a9ef3dece 100644 --- a/api/docs/oauth/index.yml +++ b/api/docs/oauth/index.yml @@ -2,7 +2,7 @@ openapi: 3.0.3 servers: - url: 'https://demo.spreecommerce.org' description: demo - - url: 'http://localhost:3000' + - url: 'http://localhost:4000' description: localhost info: version: 1.0.0 @@ -139,7 +139,7 @@ components: - expires_in - refresh_token - created_at - x-internal: true + x-internal: false CreateTokenBody: type: object x-examples: @@ -152,7 +152,7 @@ components: username: spree@example.com password: spree123 scope: admin - x-internal: true + x-internal: false title: 'Create a new token (grant_type: password)' description: '' properties: @@ -186,7 +186,7 @@ components: example-1: grant_type: refresh_token refresh_token: SqJDIwX00fehqHxS6xmb-kzqAlrYe_0EHgekMexVT8k - x-internal: true + x-internal: false title: 'Create a new token (grant_type: client_credentials)' description: '' properties: @@ -213,7 +213,7 @@ components: example-1: grant_type: refresh_token refresh_token: SqJDIwX00fehqHxS6xmb-kzqAlrYe_0EHgekMexVT8k - x-internal: true + x-internal: false title: 'Refresh an existing token (grant_type: refresh_token)' description: '' properties: diff --git a/api/docs/v2/platform/index.yaml b/api/docs/v2/platform/index.yaml index ef253bbcc31..81092a35fb9 100644 --- a/api/docs/v2/platform/index.yaml +++ b/api/docs/v2/platform/index.yaml @@ -15490,6 +15490,7 @@ paths: email: ivonne.braun@smith.biz first_name: Liberty last_name: Becker + selected_locale: nil created_at: '2022-11-08T19:35:53.726Z' updated_at: '2022-11-08T19:35:53.726Z' public_metadata: {} @@ -15508,6 +15509,7 @@ paths: email: lenita.mayer@kulas.us first_name: Chasidy last_name: Strosin + selected_locale: 'fr' created_at: '2022-11-08T19:35:53.730Z' updated_at: '2022-11-08T19:35:53.730Z' public_metadata: {} @@ -15526,6 +15528,7 @@ paths: email: dewayne@terrybarton.info first_name: Ruben last_name: Schmidt + selected_locale: 'de' created_at: '2022-11-08T19:35:53.732Z' updated_at: '2022-11-08T19:35:53.732Z' public_metadata: {} @@ -15591,6 +15594,7 @@ paths: email: rex_champlin@breitenberg.com first_name: Zenia last_name: King + selected_locale: 'pl' created_at: '2022-11-08T19:35:54.351Z' updated_at: '2022-11-08T19:35:54.351Z' public_metadata: {} @@ -15660,6 +15664,7 @@ paths: email: gaynell@parisian.biz first_name: Irwin last_name: DuBuque + selected_locale: 'en' created_at: '2022-11-08T19:35:54.635Z' updated_at: '2022-11-08T19:35:54.635Z' public_metadata: {} @@ -15730,6 +15735,7 @@ paths: email: john@example.com first_name: Astrid last_name: Kohler + selected_locale: 'fr' created_at: '2022-11-08T19:35:55.180Z' updated_at: '2022-11-08T19:35:55.414Z' public_metadata: {} @@ -17669,7 +17675,7 @@ servers: - url: http://{defaultHost} variables: defaultHost: - default: localhost:3000 + default: localhost:4000 tags: - name: Addresses - name: Adjustments @@ -21338,6 +21344,8 @@ components: type: string password_confirmation: type: string + selected_locale: + type: string ship_address_id: type: string bill_address_id: @@ -21365,6 +21373,8 @@ components: type: string password_confirmation: type: string + selected_locale: + type: string ship_address_id: type: string bill_address_id: diff --git a/api/docs/v2/storefront/index.yaml b/api/docs/v2/storefront/index.yaml index 8c8b04cb547..4db0ed4f6e1 100644 --- a/api/docs/v2/storefront/index.yaml +++ b/api/docs/v2/storefront/index.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 servers: - url: 'https://demo.spreecommerce.org' description: demo - - url: 'http://localhost:3000' + - url: 'http://localhost:4000' description: localhost info: version: 2.0.0 @@ -63,6 +63,9 @@ paths: last_name: type: string example: Snow + selected_locale: + type: string + example: 'en' password: type: string example: spree123 @@ -110,6 +113,9 @@ paths: last_name: type: string example: Snow + selected_locale: + type: string + example: 'fr' bill_address_id: type: string example: '1' @@ -1913,7 +1919,7 @@ components: - id - type - attributes - x-internal: true + x-internal: false AddressPayload: example: firstname: John @@ -1926,7 +1932,7 @@ components: state_name: MD country_iso: US type: object - x-internal: true + x-internal: false title: '' x-examples: {} description: '' @@ -1973,7 +1979,7 @@ components: description: 'The Cart provides a central place to collect information about an order, including line items, adjustments, payments, addresses, and shipments. [Read more in Spree docs](https://dev-docs.spreecommerce.org/internals/orders)' type: object title: Cart - x-internal: true + x-internal: false properties: id: type: string @@ -2181,7 +2187,7 @@ components: - attributes - relationships CartIncludes: - x-internal: true + x-internal: false title: Cart Includes anyOf: - $ref: '#/components/schemas/LineItem' @@ -2197,7 +2203,7 @@ components: type: object title: CMS Page description: 'The CMS Page model contains page data for Standard pages, Feature Pages and Homepages.' - x-internal: true + x-internal: false properties: id: type: string @@ -2250,7 +2256,7 @@ components: - attributes - relationships CmsPageIncludes: - x-internal: true + x-internal: false title: CMS Page Includes allOf: - $ref: '#/components/schemas/CmsSection' @@ -2259,7 +2265,7 @@ components: type: object title: CMS Section description: The CMS Section model represents a single section belonging to a CMS Page. CMS Sections can link to other resources through the `linked_resource`. - x-internal: true + x-internal: false properties: id: type: string @@ -2361,7 +2367,7 @@ components: title: Country description: 'Countries within Spree are used as a container for states. Countries can be zone members, and also link to an address. The difference between one country and another on an address record can determine which tax rates and shipping methods are used for the order.[Read more about Countries in Spree](https://dev-docs.spreecommerce.org/internals/addresses#countries)' type: object - x-internal: true + x-internal: false properties: id: type: string @@ -2407,14 +2413,14 @@ components: - attributes - relationships CountryIncludes: - x-internal: true + x-internal: false title: Country Includes oneOf: - $ref: '#/components/schemas/State' CreditCard: title: Credit Card type: object - x-internal: true + x-internal: false properties: id: type: string @@ -2467,12 +2473,12 @@ components: - attributes - relationships CreditCardIncludes: - x-internal: true + x-internal: false title: Credit Card Includes allOf: - $ref: '#/components/schemas/PaymentMethod' Error: - x-internal: true + x-internal: false title: Error type: object properties: @@ -2482,7 +2488,7 @@ components: type: object description: 'The Icon object contains a url attribute pointing to an Active Storage asset. ' title: Icon - x-internal: true + x-internal: false properties: id: type: string @@ -2526,15 +2532,15 @@ components: - type - attributes title: Image - x-internal: true + x-internal: false ImageStyle: - x-internal: true + x-internal: false title: Image Style type: object properties: url: type: string - example: 'http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJbWQyWVhKcFlXNTBjeTltWm1sMmRURlNORFpZWjJaSFpYUkdZMjk2WWsxM1RHWXZNVGs1T1RCak5XVmlNamN4TlRnd1pqVTBabUpqTWpCbFkyVXhZMlZpTTJFd05ERTJZemMzT0dKaE5tSTFNREkyT0dKaFpqa3paV1JtWTJWaE16aGxaQVk2QmtWVSIsImV4cCI6IjIwMTgtMDYtMjRUMTM6NTk6NTguOTY5WiIsInB1ciI6ImJsb2Jfa2V5In19--5e9ff358dc747f73754e332678c5762114ac6f3f/ror_jr_spaghetti.jpeg?content_type=image%2Fjpeg&disposition=inline%3B+filename%3D%22ror_jr_spaghetti.jpeg%22%3B+filename%2A%3DUTF-8%27%27ror_jr_spaghetti.jpeg' + example: 'http://localhost:4000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJbWQyWVhKcFlXNTBjeTltWm1sMmRURlNORFpZWjJaSFpYUkdZMjk2WWsxM1RHWXZNVGs1T1RCak5XVmlNamN4TlRnd1pqVTBabUpqTWpCbFkyVXhZMlZpTTJFd05ERTJZemMzT0dKaE5tSTFNREkyT0dKaFpqa3paV1JtWTJWaE16aGxaQVk2QmtWVSIsImV4cCI6IjIwMTgtMDYtMjRUMTM6NTk6NTguOTY5WiIsInB1ciI6ImJsb2Jfa2V5In19--5e9ff358dc747f73754e332678c5762114ac6f3f/ror_jr_spaghetti.jpeg?content_type=image%2Fjpeg&disposition=inline%3B+filename%3D%22ror_jr_spaghetti.jpeg%22%3B+filename%2A%3DUTF-8%27%27ror_jr_spaghetti.jpeg' description: Absolute URL of the uploaded image in selected style (width/height) width: type: integer @@ -2545,7 +2551,7 @@ components: example: 1080 description: Actual height of image ListLinks: - x-internal: true + x-internal: false type: object title: Pagination Links properties: @@ -2566,7 +2572,7 @@ components: description: URL to the first page of the listing ListMeta: type: object - x-internal: true + x-internal: false title: Pagination Meta properties: count: @@ -2584,7 +2590,7 @@ components: LineItem: title: Line Item type: object - x-internal: true + x-internal: false properties: id: type: string @@ -2693,7 +2699,7 @@ components: Menu: type: object title: Menu - x-internal: true + x-internal: false properties: id: type: string @@ -2734,7 +2740,7 @@ components: MenuItem: title: Menu Item type: object - x-internal: true + x-internal: false properties: id: type: string @@ -2823,7 +2829,7 @@ components: - attributes - relationships MenuIncludes: - x-internal: true + x-internal: false title: Menu Includes anyOf: - $ref: '#/components/schemas/MenuItem' @@ -2834,7 +2840,7 @@ components: OptionType: title: Option Type type: object - x-internal: true + x-internal: false properties: id: type: string @@ -2862,7 +2868,7 @@ components: title: Payment type: object description: '' - x-internal: true + x-internal: false properties: id: type: string @@ -2918,7 +2924,7 @@ components: title: Payment Method description: '' type: object - x-internal: true + x-internal: false properties: id: type: string @@ -2948,7 +2954,7 @@ components: Product: type: object title: Product - x-internal: true + x-internal: false properties: id: type: string @@ -3077,7 +3083,7 @@ components: - attributes - relationships ProductIncludes: - x-internal: true + x-internal: false title: Product Includes anyOf: - $ref: '#/components/schemas/OptionType' @@ -3086,7 +3092,7 @@ components: - $ref: '#/components/schemas/Image' - $ref: '#/components/schemas/Taxon' StoreIncludes: - x-internal: true + x-internal: false title: Store Includes anyOf: - $ref: '#/components/schemas/Country' @@ -3121,7 +3127,7 @@ components: - id - type - attributes - x-internal: true + x-internal: false Promotion: type: object title: Promotion @@ -3154,7 +3160,7 @@ components: - id - type - attributes - x-internal: true + x-internal: false Relation: type: object nullable: true @@ -3166,7 +3172,7 @@ components: required: - id - type - x-internal: true + x-internal: false description: '' State: type: object @@ -3181,12 +3187,12 @@ components: type: string example: New York description: State name - x-internal: true + x-internal: false Store: type: object description: Stores are the center of the Spree ecosystem. Each Spree installation can have multiple Stores. Each Store operates on a different domain or subdomain. title: Store - x-internal: true + x-internal: false properties: id: type: string @@ -3301,7 +3307,7 @@ components: Shipment: type: object title: Shipment - x-internal: true + x-internal: false properties: id: type: string @@ -3371,7 +3377,7 @@ components: - attributes - relationships ShipmentIncludes: - x-internal: true + x-internal: false title: Shipment Includes allOf: - $ref: '#/components/schemas/ShippingRate' @@ -3380,7 +3386,7 @@ components: type: object title: Shipping Rate description: '' - x-internal: true + x-internal: false properties: id: type: string @@ -3434,7 +3440,7 @@ components: StockLocation: title: Stock Location type: object - x-internal: true + x-internal: false properties: id: type: string @@ -3455,7 +3461,7 @@ components: Taxon: title: Taxon type: object - x-internal: true + x-internal: false properties: id: type: string @@ -3586,9 +3592,9 @@ components: - type - attributes title: Taxon Image - x-internal: true + x-internal: false TaxonIncludes: - x-internal: true + x-internal: false title: Taxon Includes anyOf: - $ref: '#/components/schemas/Product' @@ -3597,7 +3603,7 @@ components: Taxonomy: type: object title: Taxonomy - x-internal: true + x-internal: false properties: id: type: string @@ -3622,7 +3628,7 @@ components: type: string format: date-time example: '2020-02-16T07:14:54.617Z' - x-internal: true + x-internal: false title: Time Stamp x-examples: example-1: '2020-02-16T07:14:54.617Z' @@ -3630,7 +3636,7 @@ components: type: object title: User description: ' ' - x-internal: true + x-internal: false properties: id: type: string @@ -3650,6 +3656,9 @@ components: last_name: type: string example: Doe + selected_locale: + type: string + example: 'fr' store_credits: type: number example: 150.75 @@ -3687,7 +3696,7 @@ components: description: 'Variant records track the individual variants of a Product. Variants are of two types: master variants and normal variants.' x-examples: {} type: object - x-internal: true + x-internal: false properties: id: type: string @@ -3769,7 +3778,7 @@ components: - relationships WishedItem: type: object - x-internal: true + x-internal: false properties: id: type: string @@ -3803,7 +3812,7 @@ components: - attributes - relationships WishedItemIncludes: - x-internal: true + x-internal: false title: Wished Item Includes allOf: - $ref: '#/components/schemas/Variant' @@ -3811,7 +3820,7 @@ components: description: '' type: object title: Wishlist - x-internal: true + x-internal: false properties: id: type: string @@ -3849,14 +3858,14 @@ components: - attributes - relationships WishlistIncludes: - x-internal: true + x-internal: false title: Wishlist Includes allOf: - $ref: '#/components/schemas/WishedItem' DigitalLink: title: Digital Link type: object - x-internal: true + x-internal: false properties: id: type: string @@ -4803,11 +4812,11 @@ components: total_count: 1 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/account/orders' - next: 'http://localhost:3000/api/v2/storefront/account/orders?page=1' - prev: 'http://localhost:3000/api/v2/storefront/account/orders?page=1' - last: 'http://localhost:3000/api/v2/storefront/account/orders?page=1' - first: 'http://localhost:3000/api/v2/storefront/account/orders?page=1' + self: 'http://localhost:4000/api/v2/storefront/account/orders' + next: 'http://localhost:4000/api/v2/storefront/account/orders?page=1' + prev: 'http://localhost:4000/api/v2/storefront/account/orders?page=1' + last: 'http://localhost:4000/api/v2/storefront/account/orders?page=1' + first: 'http://localhost:4000/api/v2/storefront/account/orders?page=1' Including all related objects: value: data: @@ -5078,11 +5087,11 @@ components: total_count: 1 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/account/orders?include=line_items,variants,variants.images,billing_address,shipping_address,user,payments,shipments,promotions' - next: 'http://localhost:3000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' - prev: 'http://localhost:3000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' - last: 'http://localhost:3000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' - first: 'http://localhost:3000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' + self: 'http://localhost:4000/api/v2/storefront/account/orders?include=line_items,variants,variants.images,billing_address,shipping_address,user,payments,shipments,promotions' + next: 'http://localhost:4000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' + prev: 'http://localhost:4000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' + last: 'http://localhost:4000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' + first: 'http://localhost:4000/api/v2/storefront/account/orders?include=line_items%2Cvariants%2Cvariants.images%2Cbilling_address%2Cshipping_address%2Cuser%2Cpayments%2Cshipments%2Cpromotions&page=1' User: description: 200 Success - Returns the `user` object. content: @@ -5109,6 +5118,7 @@ components: email: spree@example.com first_name: John last_name: Snow + selected_locale: 'en' store_credits: 0 completed_orders: 0 public_metadata: @@ -5252,11 +5262,11 @@ components: total_count: 3 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/account/addresses' - next: 'http://localhost:3000/api/v2/storefront/account/addresses?page=1' - prev: 'http://localhost:3000/api/v2/storefront/account/addresses?page=1' - last: 'http://localhost:3000/api/v2/storefront/account/addresses?page=1' - first: 'http://localhost:3000/api/v2/storefront/account/addresses?page=1' + self: 'http://localhost:4000/api/v2/storefront/account/addresses' + next: 'http://localhost:4000/api/v2/storefront/account/addresses?page=1' + prev: 'http://localhost:4000/api/v2/storefront/account/addresses?page=1' + last: 'http://localhost:4000/api/v2/storefront/account/addresses?page=1' + first: 'http://localhost:4000/api/v2/storefront/account/addresses?page=1' Address: description: 200 Success - Returns the `address` object. content: @@ -5700,11 +5710,11 @@ components: total_count: 6 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/cms_pages' - next: 'http://localhost:3000/api/v2/storefront/cms_pages?page=1' - prev: 'http://localhost:3000/api/v2/storefront/cms_pages?page=1' - last: 'http://localhost:3000/api/v2/storefront/cms_pages?page=1' - first: 'http://localhost:3000/api/v2/storefront/cms_pages?page=1' + self: 'http://localhost:4000/api/v2/storefront/cms_pages' + next: 'http://localhost:4000/api/v2/storefront/cms_pages?page=1' + prev: 'http://localhost:4000/api/v2/storefront/cms_pages?page=1' + last: 'http://localhost:4000/api/v2/storefront/cms_pages?page=1' + first: 'http://localhost:4000/api/v2/storefront/cms_pages?page=1' CMS Pages with Included Sections: value: data: @@ -6199,11 +6209,11 @@ components: total_count: 6 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/cms_pages?include=cms_sections' - next: 'http://localhost:3000/api/v2/storefront/cms_pages?include=cms_sections&page=1' - prev: 'http://localhost:3000/api/v2/storefront/cms_pages?include=cms_sections&page=1' - last: 'http://localhost:3000/api/v2/storefront/cms_pages?include=cms_sections&page=1' - first: 'http://localhost:3000/api/v2/storefront/cms_pages?include=cms_sections&page=1' + self: 'http://localhost:4000/api/v2/storefront/cms_pages?include=cms_sections' + next: 'http://localhost:4000/api/v2/storefront/cms_pages?include=cms_sections&page=1' + prev: 'http://localhost:4000/api/v2/storefront/cms_pages?include=cms_sections&page=1' + last: 'http://localhost:4000/api/v2/storefront/cms_pages?include=cms_sections&page=1' + first: 'http://localhost:4000/api/v2/storefront/cms_pages?include=cms_sections&page=1' CreditCard: description: 200 Success - Returns the `credit_card` object. content: @@ -6313,11 +6323,11 @@ components: total_count: 2 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/account/credit_cards' - next: 'http://localhost:3000/api/v2/storefront/account/credit_cards?page=0' - prev: 'http://localhost:3000/api/v2/storefront/account/credit_cards?page=1' - last: 'http://localhost:3000/api/v2/storefront/account/credit_cards?page=0' - first: 'http://localhost:3000/api/v2/storefront/account/credit_cards?page=1' + self: 'http://localhost:4000/api/v2/storefront/account/credit_cards' + next: 'http://localhost:4000/api/v2/storefront/account/credit_cards?page=0' + prev: 'http://localhost:4000/api/v2/storefront/account/credit_cards?page=1' + last: 'http://localhost:4000/api/v2/storefront/account/credit_cards?page=0' + first: 'http://localhost:4000/api/v2/storefront/account/credit_cards?page=1' Product: description: 200 Success - Returns the `product` object. content: @@ -8546,11 +8556,11 @@ components: - value: Zeta filter_param: zeta links: - self: 'http://localhost:3000/api/v2/storefront/products' - next: 'http://localhost:3000/api/v2/storefront/products?page=2' - prev: 'http://localhost:3000/api/v2/storefront/products?page=1' - last: 'http://localhost:3000/api/v2/storefront/products?page=5' - first: 'http://localhost:3000/api/v2/storefront/products?page=1' + self: 'http://localhost:4000/api/v2/storefront/products' + next: 'http://localhost:4000/api/v2/storefront/products?page=2' + prev: 'http://localhost:4000/api/v2/storefront/products?page=1' + last: 'http://localhost:4000/api/v2/storefront/products?page=5' + first: 'http://localhost:4000/api/v2/storefront/products?page=1' Store: description: 200 Success - Returns the `store` object. content: @@ -11906,11 +11916,11 @@ components: total_count: 26 total_pages: 2 links: - self: 'http://localhost:3000/api/v2/storefront/taxons' - next: 'http://localhost:3000/api/v2/storefront/taxons?page=2' - prev: 'http://localhost:3000/api/v2/storefront/taxons?page=1' - last: 'http://localhost:3000/api/v2/storefront/taxons?page=2' - first: 'http://localhost:3000/api/v2/storefront/taxons?page=1' + self: 'http://localhost:4000/api/v2/storefront/taxons' + next: 'http://localhost:4000/api/v2/storefront/taxons?page=2' + prev: 'http://localhost:4000/api/v2/storefront/taxons?page=1' + last: 'http://localhost:4000/api/v2/storefront/taxons?page=2' + first: 'http://localhost:4000/api/v2/storefront/taxons?page=1' Country: description: 200 Success - Returns the `country` object. content: @@ -15212,11 +15222,11 @@ components: total_count: 240 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/countries' - next: 'http://localhost:3000/api/v2/storefront/countries?page=1&per_page=240' - prev: 'http://localhost:3000/api/v2/storefront/countries?page=1&per_page=240' - last: 'http://localhost:3000/api/v2/storefront/countries?page=1&per_page=240' - first: 'http://localhost:3000/api/v2/storefront/countries?page=1&per_page=240' + self: 'http://localhost:4000/api/v2/storefront/countries' + next: 'http://localhost:4000/api/v2/storefront/countries?page=1&per_page=240' + prev: 'http://localhost:4000/api/v2/storefront/countries?page=1&per_page=240' + last: 'http://localhost:4000/api/v2/storefront/countries?page=1&per_page=240' + first: 'http://localhost:4000/api/v2/storefront/countries?page=1&per_page=240' Shipment: description: '200 Success - Returns an array containing several `shipment` objects, along with the included array containing all available `shipping_rate` and `stock_location` objects. ' content: @@ -16219,11 +16229,11 @@ components: total_count: 2 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/menus' - next: 'http://localhost:3000/api/v2/storefront/menus?page=1' - prev: 'http://localhost:3000/api/v2/storefront/menus?page=1' - last: 'http://localhost:3000/api/v2/storefront/menus?page=1' - first: 'http://localhost:3000/api/v2/storefront/menus?page=1' + self: 'http://localhost:4000/api/v2/storefront/menus' + next: 'http://localhost:4000/api/v2/storefront/menus?page=1' + prev: 'http://localhost:4000/api/v2/storefront/menus?page=1' + last: 'http://localhost:4000/api/v2/storefront/menus?page=1' + first: 'http://localhost:4000/api/v2/storefront/menus?page=1' Wishlist: description: 200 Success - Returns the `wishlist` object. content: @@ -16361,11 +16371,11 @@ components: total_count: 3 total_pages: 1 links: - self: 'http://localhost:3000/api/v2/storefront/wishlists?is_variant_included=150' - next: 'http://localhost:3000/api/v2/storefront/wishlists?page=1' - prev: 'http://localhost:3000/api/v2/storefront/wishlists?page=1' - last: 'http://localhost:3000/api/v2/storefront/wishlists?page=1' - first: 'http://localhost:3000/api/v2/storefront/wishlists?page=1' + self: 'http://localhost:4000/api/v2/storefront/wishlists?is_variant_included=150' + next: 'http://localhost:4000/api/v2/storefront/wishlists?page=1' + prev: 'http://localhost:4000/api/v2/storefront/wishlists?page=1' + last: 'http://localhost:4000/api/v2/storefront/wishlists?page=1' + first: 'http://localhost:4000/api/v2/storefront/wishlists?page=1' WishedItem: description: 200 Success - Returns an array of `wished_item` objects. content: diff --git a/api/spec/controllers/spree/api/v2/base_controller_spec.rb b/api/spec/controllers/spree/api/v2/base_controller_spec.rb index 94c14869769..a82e19a8d45 100644 --- a/api/spec/controllers/spree/api/v2/base_controller_spec.rb +++ b/api/spec/controllers/spree/api/v2/base_controller_spec.rb @@ -109,7 +109,7 @@ def index shared_examples 'rescues from error' do it do expect(subject).to receive(:index).and_raise(exception) - expect(subject).to receive(:spree_current_user).and_return(user) + expect(subject).to receive(:spree_current_user).at_least(:once).and_return(user) expect_next_instance_of(::Spree::Api::ErrorHandler) do |instance| expect(instance).to receive(:call).with( exception: exception, diff --git a/api/spec/integration/api/v2/platform/menu_items_spec.rb b/api/spec/integration/api/v2/platform/menu_items_spec.rb index 28f247a4bd1..cadb3c9aae2 100644 --- a/api/spec/integration/api/v2/platform/menu_items_spec.rb +++ b/api/spec/integration/api/v2/platform/menu_items_spec.rb @@ -16,7 +16,14 @@ let!(:menu_item_three) { create(:menu_item, menu: menu) } let(:records_list) { create_list(:menu_item, 4, menu: menu) } - let(:valid_create_param_value) { build(:menu_item, menu: menu).attributes } + let(:valid_create_param_value) do + { + name: 'Special Menu Item', + item_type: 'Link', + linked_resource_type: 'Spree::Linkable::Uri', + menu_id: menu.id + } + end let(:valid_update_param_value) do { menu_item: { diff --git a/api/spec/integration/api/v2/platform/menus_spec.rb b/api/spec/integration/api/v2/platform/menus_spec.rb index 700c699d6dd..eb2db184f5a 100644 --- a/api/spec/integration/api/v2/platform/menus_spec.rb +++ b/api/spec/integration/api/v2/platform/menus_spec.rb @@ -33,7 +33,14 @@ create(:menu_item, menu: menu_2) create(:menu_item, menu: menu_2) end - let(:valid_create_param_value) { build(:menu, name: 'Main Menu').attributes } + let(:valid_create_param_value) do + { + name: 'Special Menu', + locale: 'en', + location: 'Header', + store: Spree::Store.default + } + end let(:valid_update_param_value) do { name: 'Main Menu', diff --git a/api/spec/integration/api/v2/platform/option_types_spec.rb b/api/spec/integration/api/v2/platform/option_types_spec.rb index b76c9fe7f31..f98173bad25 100644 --- a/api/spec/integration/api/v2/platform/option_types_spec.rb +++ b/api/spec/integration/api/v2/platform/option_types_spec.rb @@ -12,7 +12,13 @@ let(:id) { create(:option_type).id } let(:option_type) { create(:option_type) } let(:records_list) { create_list(:option_type, 2) } - let(:valid_create_param_value) { build(:option_type).attributes } + let(:valid_create_param_value) do + { + name: 'option type name', + presentation: 'Option Type', + position: 10 + } + end let(:valid_update_param_value) do { name: 'Size-X' diff --git a/api/spec/integration/api/v2/platform/option_values_spec.rb b/api/spec/integration/api/v2/platform/option_values_spec.rb index 72dec896bff..55be093fc08 100644 --- a/api/spec/integration/api/v2/platform/option_values_spec.rb +++ b/api/spec/integration/api/v2/platform/option_values_spec.rb @@ -13,7 +13,14 @@ let(:id) { create(:option_value).id } let(:option_type) { create(:option_value) } let(:records_list) { create_list(:option_value, 2) } - let(:valid_create_param_value) { build(:option_value).attributes } + let(:valid_create_param_value) do + { + name: 'option value', + presentation: 'Option Value', + position: 10, + option_type_id: option_type.id + } + end let(:valid_update_param_value) do { name: 'M' diff --git a/api/spec/integration/api/v2/platform/taxonomies_spec.rb b/api/spec/integration/api/v2/platform/taxonomies_spec.rb index 6ec35dfb59b..728ff598fbe 100644 --- a/api/spec/integration/api/v2/platform/taxonomies_spec.rb +++ b/api/spec/integration/api/v2/platform/taxonomies_spec.rb @@ -11,7 +11,12 @@ let(:id) { create(:taxonomy, store: store).id } let(:records_list) { create_list(:taxonomy, 2) } - let(:valid_create_param_value) { build(:taxonomy).attributes } + let(:valid_create_param_value) do + { + name: 'First Taxonomy', + store: Spree::Store.default + } + end let(:valid_update_param_value) do { name: 'Categories', diff --git a/api/spec/requests/spree/api/v2/platform/resources_spec.rb b/api/spec/requests/spree/api/v2/platform/resources_spec.rb index 933ed100f3d..cf67cc2b55c 100644 --- a/api/spec/requests/spree/api/v2/platform/resources_spec.rb +++ b/api/spec/requests/spree/api/v2/platform/resources_spec.rb @@ -327,7 +327,15 @@ context '#ensure_current_store' do context 'single store resource' do let(:execute) { post '/api/v2/platform/menus', params: menu_resource_params, headers: bearer_token } - let(:menu_resource_params) { { menu: build(:menu, name: 'Ensure-MenuTest', location: 'Header', locale: 'en').attributes.symbolize_keys } } + let(:menu_attribute_hash) do + { + :id => 77, + :name => 'Ensure-MenuTest', + :location => 'Header', + :locale => 'en' + } + end + let(:menu_resource_params) { { menu: menu_attribute_hash } } before { execute } diff --git a/api/spec/requests/spree/api/v2/storefront/products_spec.rb b/api/spec/requests/spree/api/v2/storefront/products_spec.rb index feb1d1c5b26..99b821b5ad1 100644 --- a/api/spec/requests/spree/api/v2/storefront/products_spec.rb +++ b/api/spec/requests/spree/api/v2/storefront/products_spec.rb @@ -8,9 +8,9 @@ let(:product_with_taxon) { create(:product, taxons: [taxon], stores: [store]) } let(:product_with_name) { create(:product, name: 'Test Product', stores: [store]) } let(:product_with_price) { create(:product, price: 13.44, stores: [store]) } - let!(:option_type) { create(:option_type) } + let!(:option_type) { create(:option_type, name: 'test option type') } let!(:option_value) { create(:option_value, option_type: option_type) } - let(:product_with_option) { create(:product, option_types: [option_type], stores: [store]) } + let(:product_with_option) { create(:product, name: 'Product with Option', option_types: [option_type], stores: [store]) } let!(:variant) { create(:variant, product: product_with_option, option_values: [option_value]) } let(:product) { create(:product, stores: [store]) } let!(:deleted_product) { create(:product, deleted_at: Time.current - 1.day, stores: [store]) } @@ -24,6 +24,7 @@ let!(:product_property) { create(:product_property, property: new_property, product: product_with_property, value: 'Some Value') } let!(:product_property2) { create(:product_property, property: property, product: product_with_property, value: 'Some Value 2') } + before { Spree::Api::Config[:api_v2_per_page_limit] = 4 } describe 'products#index' do @@ -151,14 +152,37 @@ end context 'with specified options' do - before { get "/api/v2/storefront/products?filter[options][#{option_type.name}]=#{option_value.name}&include=option_types,variants.option_values" } + context 'with no locale set' do + before { + get "/api/v2/storefront/products?filter[options][#{option_type.name}]=#{option_value.name}&include=option_types,variants.option_values" + } - it_behaves_like 'returns 200 HTTP status' + it_behaves_like 'returns 200 HTTP status' + + it 'returns products with specified options' do + expect(json_response['data'].first).to have_id(product_with_option.id.to_s) + expect(json_response['included']).to include(have_type('option_type').and(have_attribute(:name).with_value(option_type.name))) + expect(json_response['included']).to include(have_type('option_value').and(have_attribute(:name).with_value(option_value.name))) + end + end - it 'returns products with specified options' do - expect(json_response['data'].first).to have_id(product_with_option.id.to_s) - expect(json_response['included']).to include(have_type('option_type').and(have_attribute(:name).with_value(option_type.name))) - expect(json_response['included']).to include(have_type('option_value').and(have_attribute(:name).with_value(option_value.name))) + context 'with locale set to polish' do + # create translated resources + let!(:option_type_pl_locale) { Mobility.with_locale(:pl) { create(:option_type) } } + let!(:option_value_pl_locale) { Mobility.with_locale(:pl) { create(:option_value, option_type: option_type_pl_locale) } } + let!(:product_pl_locale) { Mobility.with_locale(:pl) { create(:product, name: 'Produkt Superowy', option_types: [option_type_pl_locale], stores: [store]) } } + let!(:variant_pl_locale) { Mobility.with_locale(:pl) { create(:variant, product: product_pl_locale, option_values: [option_value_pl_locale]) } } + + before do + store.update_column(:supported_locales, 'en,pl') + get "/api/v2/storefront/products?filter[options][#{option_type_pl_locale.name(locale: :pl)}]=#{option_value_pl_locale.name(locale: :pl)}&include=option_types,variants.option_values&locale=pl" + end + + it 'returns products with specified options in polish' do + expect(json_response['data'].first).to have_id(product_pl_locale.id.to_s) + expect(json_response['included']).to include(have_type('option_type').and(have_attribute(:name).with_value(option_type_pl_locale.name(locale: :pl)))) + expect(json_response['included']).to include(have_type('option_value').and(have_attribute(:name).with_value(option_value_pl_locale.name(locale: :pl)))) + end end end diff --git a/api/spec/serializers/spree/api/v2/platform/user_serializer_spec.rb b/api/spec/serializers/spree/api/v2/platform/user_serializer_spec.rb index 3e6cfec75a1..a78ab0fc341 100644 --- a/api/spec/serializers/spree/api/v2/platform/user_serializer_spec.rb +++ b/api/spec/serializers/spree/api/v2/platform/user_serializer_spec.rb @@ -1,10 +1,10 @@ require 'spec_helper' describe Spree::Api::V2::Platform::UserSerializer do - include_context 'API v2 serializers params' - subject { described_class.new(user, params: serializer_params) } + include_context 'API v2 serializers params' + let(:user) { create(:user_with_addresses) } it { expect(subject.serializable_hash).to be_kind_of(Hash) } @@ -21,6 +21,7 @@ last_name: user.last_name, average_order_value: [], lifetime_value: [], + selected_locale: nil, store_credits: [], created_at: user.created_at, updated_at: user.updated_at, @@ -61,6 +62,7 @@ last_name: user.last_name, average_order_value: [{ amount: '110.00', currency: 'USD' }, { amount: '110.00', currency: 'EUR' }], lifetime_value: [{ amount: '110.00', currency: 'USD' }, { amount: '110.00', currency: 'EUR' }], + selected_locale: nil, store_credits: [{ amount: '100.00', currency: 'USD' }, { amount: '90.00', currency: 'EUR' }], created_at: user.created_at, updated_at: user.updated_at, @@ -69,4 +71,12 @@ }) end end + + context 'when user has selected non default locale' do + let(:user) { create(:user_with_addresses, selected_locale: 'fr') } + + it 'returns the selected locale in the serialized hash' do + expect(subject.serializable_hash[:data][:attributes][:selected_locale]).to eq('fr') + end + end end diff --git a/api/spec/serializers/spree/v2/storefront/user_serializer_spec.rb b/api/spec/serializers/spree/v2/storefront/user_serializer_spec.rb new file mode 100644 index 00000000000..6d471cbfa8a --- /dev/null +++ b/api/spec/serializers/spree/v2/storefront/user_serializer_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' + +describe Spree::V2::Storefront::UserSerializer do + subject { described_class.new(user, params: serializer_params) } + + include_context 'API v2 serializers params' + + let(:user) { create(:user_with_addresses) } + + it { expect(subject.serializable_hash).to be_kind_of(Hash) } + + it do + expect(subject.serializable_hash).to eq( + { + data: { + id: user.id.to_s, + type: :user, + attributes: { + completed_orders: 0, + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + public_metadata: {}, + selected_locale: nil, + store_credits: 0 + }, + relationships: { + default_billing_address: { + data: { + id: user.bill_address.id.to_s, + type: :address + } + }, + default_shipping_address: { + data: { + id: user.ship_address.id.to_s, + type: :address + } + } + } + } + } + ) + end + + context 'when user has orders' do + before do + create(:completed_order_with_totals, user: user, currency: 'USD') + create(:completed_order_with_totals, user: user, currency: 'EUR') + create(:store_credit, amount: '100', store: store, user: user, currency: 'USD') + create(:store_credit, amount: '90', store: store, user: user, currency: 'EUR') + end + + it do + expect(subject.serializable_hash[:data][:attributes]).to eq({ + completed_orders: 2, + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + public_metadata: {}, + selected_locale: nil, + store_credits: 0.1e3 + }) + end + end + + context 'when user has selected non default locale' do + let(:user) { create(:user_with_addresses, selected_locale: 'fr') } + + it 'returns the selected locale in the serialized hash' do + expect(subject.serializable_hash[:data][:attributes][:selected_locale]).to eq('fr') + end + end +end diff --git a/cli/lib/spree_cli/templates/extension/lib/%file_name%.rb.tt b/cli/lib/spree_cli/templates/extension/lib/%file_name%.rb.tt index 144500999d4..f279f52529a 100644 --- a/cli/lib/spree_cli/templates/extension/lib/%file_name%.rb.tt +++ b/cli/lib/spree_cli/templates/extension/lib/%file_name%.rb.tt @@ -2,3 +2,4 @@ require 'spree_core' require 'spree_extension' require '<%=file_name%>/engine' require '<%=file_name%>/version' +require '<%=file_name%>/configuration' diff --git a/cli/lib/spree_cli/templates/extension/app/models/%file_name%/configuration.rb.tt b/cli/lib/spree_cli/templates/extension/lib/%file_name%/configuration.rb.tt similarity index 100% rename from cli/lib/spree_cli/templates/extension/app/models/%file_name%/configuration.rb.tt rename to cli/lib/spree_cli/templates/extension/lib/%file_name%/configuration.rb.tt diff --git a/core/app/finders/spree/products/find.rb b/core/app/finders/spree/products/find.rb index 22ea785c044..116d44e4304 100644 --- a/core/app/finders/spree/products/find.rb +++ b/core/app/finders/spree/products/find.rb @@ -145,12 +145,11 @@ def by_concat_taxons(products) def by_name(products) return products unless name? + product_name = name - # i18n scope doesn't automatically get set here (mobility gem bug?) set it explicitly - products.i18n do - name.matches("%#{product_name}%") - end + # i18n mobility scope doesn't automatically get set for query blocks (Mobility issue #599) - set it explicitly + products.i18n { name.matches("%#{product_name}%") } end def by_options(products) @@ -213,25 +212,19 @@ def ordered(products) products end when 'name-a-z' + # workaround for Mobility issue #596 - explicitly select fields to avoid error when selecting distinct products.i18n. - select("#{Product.table_name}.*, #{Product.translation_table_alias}.name"). - order(name: :asc) + select("#{Product.table_name}.*").select(:name).order(name: :asc) when 'name-z-a' + # workaround for Mobility issue #596 products.i18n. - select("#{Product.table_name}.*, #{Product.translation_table_alias}.name"). - order(name: :desc) + select("#{Product.table_name}.*").select(:name).order(name: :desc) when 'newest-first' products.order(available_on: :desc) when 'price-high-to-low' - products. - select("#{Product.table_name}.*, #{Spree::Price.table_name}.amount"). - reorder(''). - send(:descend_by_master_price) + order_by_price(products, :descend_by_master_price) when 'price-low-to-high' - products. - select("#{Product.table_name}.*, #{Spree::Price.table_name}.amount"). - reorder(''). - send(:ascend_by_master_price) + order_by_price(products, :ascend_by_master_price) end end @@ -273,6 +266,13 @@ def taxon_ids(taxons_ids) taxons = store.taxons.where(id: taxons_ids.to_s.split(',')) taxons.map(&:cached_self_and_descendants_ids).flatten.compact.uniq.map(&:to_s) end + + def order_by_price(scope, order_type) + scope. + select("#{Product.table_name}.*, #{Spree::Price.table_name}.amount"). + reorder(''). + send(order_type) + end end end end diff --git a/core/app/finders/spree/taxons/find.rb b/core/app/finders/spree/taxons/find.rb index 125d68d34e8..6858455c8b2 100644 --- a/core/app/finders/spree/taxons/find.rb +++ b/core/app/finders/spree/taxons/find.rb @@ -86,12 +86,11 @@ def by_roots(taxons) def by_name(taxons) return taxons unless name? + taxon_name = name - # i18n scope doesn't automatically get set here (mobility gem bug?) set it explicitly - taxons.i18n do - name.matches("%#{taxon_name}%") - end + # i18n mobility scope doesn't automatically get set for query blocks (Mobility issue #599) - set it explicitly + taxons.i18n { name.matches("%#{taxon_name}%") } end end end diff --git a/core/app/models/concerns/spree/product_scopes.rb b/core/app/models/concerns/spree/product_scopes.rb index dc2dab186f9..effe62f8caa 100644 --- a/core/app/models/concerns/spree/product_scopes.rb +++ b/core/app/models/concerns/spree/product_scopes.rb @@ -32,15 +32,16 @@ def self.add_simple_scopes(scopes) end def self.property_conditions(property) - properties = Property.table_name + properties_table = Property.table_name + property_translations_table = Property.translation_table_alias case property - when Property then { "#{properties}.id" => property.id } - when Integer then { "#{properties}.id" => property } + when Property then { "#{properties_table}.id" => property.id } + when Integer then { "#{properties_table}.id" => property } else if Property.column_for_attribute('id').type == :uuid - ["#{properties.name} = ? OR #{properties.id} = ?", property, property] + ["#{property_translations_table.name} = ? OR #{properties_table.id} = ?", property, property] else - { "#{properties}.name" => property } + { "#{property_translations_table}.name" => property } end end end @@ -126,21 +127,25 @@ def self.property_conditions(property) # a scope that finds all products having property specified by name, object or id add_search_scope :with_property do |property| - joins(:properties).where(property_conditions(property)) + joins(:properties).join_translation_table(Property).where(property_conditions(property)) end # a simple test for product with a certain property-value pairing # note that it can test for properties with NULL values, but not for absent values add_search_scope :with_property_value do |property, value| joins(:properties). - where("#{ProductProperty.table_name}.value = ?", value). + join_translation_table(Property). + join_translation_table(ProductProperty). + where("#{ProductProperty.translation_table_alias}.value = ?", value). where(property_conditions(property)) end add_search_scope :with_property_values do |property_filter_param, property_values| joins(product_properties: :property). - where(Property.table_name => { filter_param: property_filter_param }). - where(ProductProperty.table_name => { filter_param: property_values.map(&:parameterize) }) + join_translation_table(Property). + join_translation_table(ProductProperty). + where(Property.translation_table_alias => { filter_param: property_filter_param }). + where(ProductProperty.translation_table_alias => { filter_param: property_values.map(&:parameterize) }) end add_search_scope :with_option do |option| @@ -151,7 +156,9 @@ def self.property_conditions(property) elsif OptionType.column_for_attribute('id').type == :uuid joins(:option_types).where(spree_option_types: { name: option }).or(Product.joins(:option_types).where(spree_option_types: { id: option })) else - joins(:option_types).where(spree_option_types: { name: option }) + joins(:option_types). + join_translation_table(OptionType). + where(OptionType.translation_table_alias => { name: option }) end end @@ -164,6 +171,7 @@ def self.property_conditions(property) OptionType.where(id: option).or(OptionType.where(name: option))&.first&.id else OptionType.where(name: option)&.first&.id + OptionType.where(name: option)&.first&.id end end @@ -171,7 +179,9 @@ def self.property_conditions(property) group("#{Spree::Product.table_name}.id"). joins(variants_including_master: :option_values). - where(Spree::OptionValue.table_name => { name: value, option_type_id: option_type_id }) + join_translation_table(Spree::OptionValue). + where(Spree::OptionValue.translation_table_alias => { name: value }, + Spree::OptionValue.table_name => { option_type_id: option_type_id }) end # Finds all products which have either: @@ -295,21 +305,10 @@ def self.for_user(user = nil) # .search_by_name if defined?(PgSearch) include PgSearch::Model - - if defined?(SpreeGlobalize) - pg_search_scope :search_by_name, associated_against: { translations: :name }, using: { tsearch: { any_word: true, prefix: true } } - else - pg_search_scope :search_by_name, against: :name, using: { tsearch: { any_word: true, prefix: true } } - end + pg_search_scope :search_by_name, against: :name, using: { tsearch: { any_word: true, prefix: true } } else def self.search_by_name(query) - if defined?(SpreeGlobalize) - joins(:translations).order(:name).where("LOWER(#{Product::Translation.table_name}.name) LIKE LOWER(:query)", query: "%#{query}%").distinct - elsif defined?(Mobility) - i18n { name.lower.matches("%#{query.downcase}%") } - else - where("LOWER(#{Product.table_name}.name) LIKE LOWER(:query)", query: "%#{query}%") - end + i18n { name.lower.matches("%#{query.downcase}%") } end end search_scopes << :search_by_name diff --git a/core/app/models/concerns/spree/translatable_resource.rb b/core/app/models/concerns/spree/translatable_resource.rb index 123b054b04d..73c0de27b42 100644 --- a/core/app/models/concerns/spree/translatable_resource.rb +++ b/core/app/models/concerns/spree/translatable_resource.rb @@ -14,11 +14,11 @@ def get_field_with_locale(locale, field_name, fallback: false) class_methods do def translatable_fields - self.const_get(:TRANSLATABLE_FIELDS) + const_get(:TRANSLATABLE_FIELDS) end def translation_table_alias - "#{self::Translation.table_name}_#{Mobility.locale.to_s}" + "#{self::Translation.table_name}_#{Mobility.locale}" end end end diff --git a/core/app/models/concerns/spree/translatable_resource_scopes.rb b/core/app/models/concerns/spree/translatable_resource_scopes.rb new file mode 100644 index 00000000000..d7f2d75ed17 --- /dev/null +++ b/core/app/models/concerns/spree/translatable_resource_scopes.rb @@ -0,0 +1,16 @@ +module Spree + module TranslatableResourceScopes + extend ActiveSupport::Concern + + class_methods do + # To be used when joining on the resource itself does not automatically join on its translations table + # This method is to be used when you've already joined on the translatable table itself + def join_translation_table(translatable_class) + translatable_class_foreign_key = "#{translatable_class.table_name.singularize}_id" + joins("LEFT OUTER JOIN #{translatable_class::Translation.table_name} #{translatable_class.translation_table_alias} + ON #{translatable_class.translation_table_alias}.#{translatable_class_foreign_key} = #{translatable_class.table_name}.id + AND #{translatable_class.translation_table_alias}.locale = '#{Mobility.locale}'") + end + end + end +end diff --git a/core/app/models/spree/data_feed_setting.rb b/core/app/models/spree/data_feed_setting.rb new file mode 100644 index 00000000000..fabec42b041 --- /dev/null +++ b/core/app/models/spree/data_feed_setting.rb @@ -0,0 +1,24 @@ +module Spree + class DataFeedSetting < Base + belongs_to :store, class_name: 'Spree::Store', foreign_key: 'spree_store_id' + + before_create :generate_uuid + + with_options presence: true do + validates :store + validates :name, uniqueness: true + validates :provider + validates :uuid, uniqueness: true, on: :update + end + + def formatted_url + "#{store.formatted_url}/api/v2/data_feeds/#{provider}/#{uuid}.rss" + end + + private + + def generate_uuid + write_attribute(:uuid, SecureRandom.uuid) + end + end +end diff --git a/core/app/models/spree/log_entry.rb b/core/app/models/spree/log_entry.rb index 20da2fe73a6..e3102553320 100644 --- a/core/app/models/spree/log_entry.rb +++ b/core/app/models/spree/log_entry.rb @@ -15,7 +15,11 @@ def save_anyway end def parsed_details - @details ||= YAML.safe_load(details, [ActiveMerchant::Billing::Response]) + @details ||= if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.1.0') + YAML.safe_load(details, permitted_classes: [ActiveMerchant::Billing::Response]) + else + YAML.safe_load(details, [ActiveMerchant::Billing::Response]) + end end end end diff --git a/core/app/models/spree/menu.rb b/core/app/models/spree/menu.rb index 7705f442dd0..4cd8c1b96d6 100644 --- a/core/app/models/spree/menu.rb +++ b/core/app/models/spree/menu.rb @@ -1,10 +1,14 @@ module Spree class Menu < Spree::Base include SingleStoreResource + include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end + TRANSLATABLE_FIELDS = %i[name] + translates(*TRANSLATABLE_FIELDS) + MENU_LOCATIONS = ['Header', 'Footer'] MENU_LOCATIONS_PARAMETERIZED = [] diff --git a/core/app/models/spree/menu_item.rb b/core/app/models/spree/menu_item.rb index 0d3503a46b2..1372e908b09 100644 --- a/core/app/models/spree/menu_item.rb +++ b/core/app/models/spree/menu_item.rb @@ -1,10 +1,14 @@ module Spree class MenuItem < Spree::Base + include TranslatableResource include Spree::DisplayLink if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end + TRANSLATABLE_FIELDS = %i[name subtitle destination] + translates(*TRANSLATABLE_FIELDS) + acts_as_nested_set dependent: :destroy ITEM_TYPE = %w[Link Container] @@ -38,6 +42,15 @@ def code?(item_code = nil) end end + # Override first_or_create! to work around Mobility issue with the original first_or_create! method + def self.first_or_create!(attrs) + if where(attrs).any? + where(attrs).first + else + create!(attrs) + end + end + private def reset_link_attributes diff --git a/core/app/models/spree/option_type.rb b/core/app/models/spree/option_type.rb index 84366de9ef9..244aaac8c94 100644 --- a/core/app/models/spree/option_type.rb +++ b/core/app/models/spree/option_type.rb @@ -2,10 +2,14 @@ module Spree class OptionType < Spree::Base include UniqueName include Metadata + include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end + TRANSLATABLE_FIELDS = %i[name presentation].freeze + translates(*TRANSLATABLE_FIELDS) + acts_as_list auto_strip_attributes :name, :presentation diff --git a/core/app/models/spree/option_value.rb b/core/app/models/spree/option_value.rb index ccd0623bdfc..e594d72f2f2 100644 --- a/core/app/models/spree/option_value.rb +++ b/core/app/models/spree/option_value.rb @@ -1,10 +1,14 @@ module Spree class OptionValue < Spree::Base include Metadata + include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end + TRANSLATABLE_FIELDS = %i[name presentation].freeze + translates(*TRANSLATABLE_FIELDS) + belongs_to :option_type, class_name: 'Spree::OptionType', touch: true, inverse_of: :option_values acts_as_list scope: :option_type diff --git a/core/app/models/spree/order.rb b/core/app/models/spree/order.rb index d0b81db07a2..05a0edb6f0f 100644 --- a/core/app/models/spree/order.rb +++ b/core/app/models/spree/order.rb @@ -24,7 +24,7 @@ class Order < Spree::Base include NumberAsParam include SingleStoreResource include MemoizedData - include Metadata + include Spree::Metadata if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index 25b0cb92ca7..47dcf3f63a8 100644 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -24,6 +24,7 @@ class Product < Spree::Base include ProductScopes include MultiStoreResource include TranslatableResource + include TranslatableResourceScopes include MemoizedData include Metadata if defined?(Spree::Webhooks) @@ -38,8 +39,13 @@ class Product < Spree::Base purchasable? in_stock? backorderable?] TRANSLATABLE_FIELDS = %i[name description slug meta_description meta_keywords meta_title].freeze - translates *TRANSLATABLE_FIELDS - Product::Translation.class_eval { acts_as_paranoid } + translates(*TRANSLATABLE_FIELDS) + + self::Translation.class_eval do + acts_as_paranoid + # deleted translation values also need to be accessible for index views listing deleted resources + default_scope { unscope(where: :deleted_at) } + end friendly_id :slug_candidates, use: [:history, :mobility] acts_as_paranoid @@ -310,14 +316,26 @@ def empty_option_values? end def property(property_name) - product_properties.joins(:property).find_by(spree_properties: { name: property_name }).try(:value) + product_properties.joins(:property). + join_translation_table(Property). + find_by(Property.translation_table_alias => { name: property_name }).try(:value) end def set_property(property_name, property_value, property_presentation = property_name) ApplicationRecord.transaction do - # Works around spree_i18n #301 - property = Property.create_with(presentation: property_presentation).find_or_create_by(name: property_name) - product_property = ProductProperty.where(product: self, property: property).first_or_initialize + # Manual first_or_create to work around Mobility bug + property = if Property.where(name: property_name).exists? + Property.where(name: property_name).first + else + Property.create(name: property_name, presentation: property_presentation) + end + + product_property = if ProductProperty.where(product: self, property: property).exists? + ProductProperty.where(product: self, property: property).first + else + ProductProperty.create(product: self, property: property) + end + product_property.value = property_value product_property.save! end @@ -341,11 +359,16 @@ def master end def brand - @brand ||= taxons.joins(:taxonomy).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_brands_name) }) + @brand ||= taxons.joins(:taxonomy). + join_translation_table(Taxonomy). + find_by(Taxonomy.translation_table_alias => { name: Spree.t(:taxonomy_brands_name) }) end def category - @category ||= taxons.joins(:taxonomy).order(depth: :desc).find_by(spree_taxonomies: { name: Spree.t(:taxonomy_categories_name) }) + @category ||= taxons.joins(:taxonomy). + join_translation_table(Taxonomy). + order(depth: :desc). + find_by(Taxonomy.translation_table_alias => { name: Spree.t(:taxonomy_categories_name) }) end def taxons_for_store(store) diff --git a/core/app/models/spree/product_property.rb b/core/app/models/spree/product_property.rb index 785cb7bba0f..58dec2bf853 100644 --- a/core/app/models/spree/product_property.rb +++ b/core/app/models/spree/product_property.rb @@ -1,8 +1,15 @@ module Spree class ProductProperty < Spree::Base include Spree::FilterParam + include TranslatableResource + include TranslatableResourceScopes - auto_strip_attributes :value + TRANSLATABLE_FIELDS = %i[value filter_param].freeze + translates(*TRANSLATABLE_FIELDS) + + self::Translation.class_eval do + auto_strip_attributes :value + end acts_as_list scope: :product @@ -31,8 +38,11 @@ def property_name=(name) `ProductProperty#property_name=` is deprecated and will be removed in Spree 5.0. DEPRECATION if name.present? - # don't use `find_by :name` to workaround globalize/globalize#423 bug - self.property = Property.where(name: name).first_or_create(presentation: name) + self.property = if Property.where(name: name).exists? + Property.where(name: name).first + else + Property.create(name: name, presentation: name) + end end end diff --git a/core/app/models/spree/property.rb b/core/app/models/spree/property.rb index 026522ea904..73e2f55a49f 100644 --- a/core/app/models/spree/property.rb +++ b/core/app/models/spree/property.rb @@ -2,11 +2,17 @@ module Spree class Property < Spree::Base include Spree::FilterParam include Metadata + include TranslatableResource if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end - auto_strip_attributes :name, :presentation + TRANSLATABLE_FIELDS = %i[name presentation filter_param].freeze + translates(*TRANSLATABLE_FIELDS) + + self::Translation.class_eval do + auto_strip_attributes :name, :presentation + end has_many :property_prototypes, class_name: 'Spree::PropertyPrototype' has_many :prototypes, through: :property_prototypes, class_name: 'Spree::Prototype' diff --git a/core/app/models/spree/store.rb b/core/app/models/spree/store.rb index 76d13541c3a..0e834e0879d 100644 --- a/core/app/models/spree/store.rb +++ b/core/app/models/spree/store.rb @@ -55,6 +55,9 @@ class Store < Spree::Base has_many :wishlists, class_name: 'Spree::Wishlist' + has_many :data_feed_settings, class_name: 'Spree::DataFeedSetting', foreign_key: 'spree_store_id' + accepts_nested_attributes_for :data_feed_settings, update_only: true + belongs_to :default_country, class_name: 'Spree::Country' belongs_to :checkout_zone, class_name: 'Spree::Zone' diff --git a/core/app/models/spree/taxon.rb b/core/app/models/spree/taxon.rb index 78d72c44f62..a17ce3cb2cc 100644 --- a/core/app/models/spree/taxon.rb +++ b/core/app/models/spree/taxon.rb @@ -4,6 +4,7 @@ module Spree class Taxon < Spree::Base include TranslatableResource + include TranslatableResourceScopes include Metadata if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks @@ -56,7 +57,7 @@ class Taxon < Spree::Base scope :for_stores, ->(stores) { joins(:taxonomy).where(spree_taxonomies: { store_id: stores.ids }) } TRANSLATABLE_FIELDS = %i[name description].freeze - translates *TRANSLATABLE_FIELDS + translates(*TRANSLATABLE_FIELDS) # indicate which filters should be used for a taxon # this method should be customized to your own site diff --git a/core/app/models/spree/taxonomy.rb b/core/app/models/spree/taxonomy.rb index e0b2ee14f11..f41d598f5a6 100644 --- a/core/app/models/spree/taxonomy.rb +++ b/core/app/models/spree/taxonomy.rb @@ -1,13 +1,13 @@ module Spree class Taxonomy < Spree::Base - include Metadata include TranslatableResource + include Metadata if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end - TRANSLATABLE_FIELDS = %i[name].freeze - translates *TRANSLATABLE_FIELDS + TRANSLATABLE_FIELDS = %i[name] + translates(*TRANSLATABLE_FIELDS) acts_as_list diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb index 07e192893e2..0e2f44d8465 100644 --- a/core/app/models/spree/variant.rb +++ b/core/app/models/spree/variant.rb @@ -5,6 +5,7 @@ class Variant < Spree::Base include MemoizedData include Metadata + include TranslatableResourceScopes if defined?(Spree::Webhooks) include Spree::Webhooks::HasWebhooks end @@ -122,19 +123,13 @@ class Variant < Spree::Base self.whitelisted_ransackable_scopes = %i(product_name_or_sku_cont search_by_product_name_or_sku) def self.product_name_or_sku_cont(query) - joins(:product).joins("LEFT OUTER JOIN #{Product::Translation.table_name} #{Product.translation_table_alias} - ON #{Product.translation_table_alias}.spree_product_id = #{Product.table_name}.id - AND #{Product.translation_table_alias}.locale = '#{Mobility.locale.to_s}'"). - where("LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%") + joins(:product).join_translation_table(Product). + where("LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query) + OR LOWER(sku) LIKE LOWER(:query)", query: "%#{query}%") end def self.search_by_product_name_or_sku(query) - if defined?(SpreeGlobalize) - joins(product: :translations).where("LOWER(#{Product::Translation.table_name}.name) LIKE LOWER(:query) OR LOWER(sku) LIKE LOWER(:query)", - query: "%#{query}%") - else - product_name_or_sku_cont(query) - end + product_name_or_sku_cont(query) end def available? diff --git a/core/app/services/spree/data_feeds/google/optional_attributes.rb b/core/app/services/spree/data_feeds/google/optional_attributes.rb new file mode 100644 index 00000000000..09592353527 --- /dev/null +++ b/core/app/services/spree/data_feeds/google/optional_attributes.rb @@ -0,0 +1,23 @@ +module Spree + module DataFeeds + module Google + class OptionalAttributes + prepend Spree::ServiceModule::Base + + def call(input) + information = {} + + input[:product].property_ids.each do |key| + name = Spree::Property.find(key)&.name + value = input[:product].property(name) + unless value.nil? + information[name] = value + end + end + + success(information: information) + end + end + end + end +end diff --git a/core/app/services/spree/data_feeds/google/optional_sub_attributes.rb b/core/app/services/spree/data_feeds/google/optional_sub_attributes.rb new file mode 100644 index 00000000000..0f767a538c9 --- /dev/null +++ b/core/app/services/spree/data_feeds/google/optional_sub_attributes.rb @@ -0,0 +1,21 @@ +module Spree + module DataFeeds + module Google + class OptionalSubAttributes + prepend Spree::ServiceModule::Base + + def call(input) + information = {} + + # This is a place where you can put attributes that have sub-attributes, example for shipping: + # + # information['shipping'] = {} + # information['shipping']['price'] = calculate_shipping(input[:product]) + # information['shipping']['country'] = input[:store].default_country + + success(information: information) + end + end + end + end +end diff --git a/core/app/services/spree/data_feeds/google/products_list.rb b/core/app/services/spree/data_feeds/google/products_list.rb new file mode 100644 index 00000000000..0279c240ce3 --- /dev/null +++ b/core/app/services/spree/data_feeds/google/products_list.rb @@ -0,0 +1,14 @@ +module Spree + module DataFeeds + module Google + class ProductsList + prepend Spree::ServiceModule::Base + + def call(store) + products = store.products.active + success(products: products) + end + end + end + end +end diff --git a/core/app/services/spree/data_feeds/google/required_attributes.rb b/core/app/services/spree/data_feeds/google/required_attributes.rb new file mode 100644 index 00000000000..d5cdfb085e6 --- /dev/null +++ b/core/app/services/spree/data_feeds/google/required_attributes.rb @@ -0,0 +1,67 @@ +module Spree + module DataFeeds + module Google + class RequiredAttributes + prepend Spree::ServiceModule::Base + + def call(input) + information = {} + + return failure(nil, error: 'No image link') if get_image_link(input[:variant], input[:product]).nil? + + information['id'] = input[:variant].id + information['title'] = format_title(input[:product], input[:variant]) + information['description'] = get_description(input[:product], input[:variant]) + information['link'] = "#{input[:store].url}/#{input[:product].slug}" + information['image_link'] = get_image_link(input[:variant], input[:product]) + information['price'] = format_price(input[:variant]) + information['availability'] = get_availability(input[:product]) + information['availability_date'] = input[:product].available_on&.xmlschema unless input[:product].available_on.nil? + + success(information: information) + end + + private + + def format_title(product, variant) + # Title of a variant is created by joining title of a product and variant's option_values, as they are + # what differentiates it from other variants. + title = product.name + variant.option_values.find_each do |option_value| + title << " - #{option_value.name}" + end + title + end + + def get_description(product, variant) + return product.description unless product.description.nil? + + format_title(product, variant) + end + + def get_image_link(variant, product) + # try getting image from variant + img = variant.images.first&.plp_url + + # if no image specified for variant try getting product image + if img.nil? + img = product.images.first&.plp_url + end + + img + end + + def format_price(variant) + "#{variant.price} #{variant.cost_currency}" + end + + def get_availability(product) + return 'in stock' if product.available? && product.available_on&.past? + return 'backorder' if product.backorderable? && product.backordered? && product.available_on&.future? + + 'out of stock' + end + end + end + end +end diff --git a/core/app/services/spree/data_feeds/google/rss.rb b/core/app/services/spree/data_feeds/google/rss.rb new file mode 100644 index 00000000000..059d6415459 --- /dev/null +++ b/core/app/services/spree/data_feeds/google/rss.rb @@ -0,0 +1,107 @@ +require 'nokogiri' + +module Spree + module DataFeeds + module Google + class Rss + prepend Spree::ServiceModule::Base + + def call(settings) + @settings = settings + + return failure(store, error: "Store with id: #{settings.spree_store_id} does not exist.") if store.nil? + + builder = Nokogiri::XML::Builder.new do |xml| + xml.rss('xmlns:g' => 'http://base.google.com/ns/1.0', 'version' => '2.0') do + xml.channel do + add_store_information_to_xml(xml) + result = products_list.call(store) + if result.success? + result.value[:products].find_each do |product| + product.variants.active.find_each do |variant| + add_variant_information_to_xml(xml, product, variant) + end + end + end + end + end + end + + success(file: builder.to_xml) + end + + private + + def store + return @store if defined? @store + + @store ||= Spree::Store.find_by(id: @settings.spree_store_id) + end + + def add_store_information_to_xml(xml) + xml.title store.name + xml.link store.url + xml.description store.meta_description + end + + def add_variant_information_to_xml(xml, product, variant) + input = { product: product, variant: variant, settings: @settings, store: store } + result = required_attributes.call(input) + + if result.success + xml.item do + result.value[:information]&.each do |key, value| + xml['g'].send(key, value) + end + + add_optional_information(xml, product, variant) + add_optional_sub_attributes(xml, product, variant) + end + end + end + + def add_optional_information(xml, product, variant) + input = { product: product, variant: variant, settings: @settings, store: store } + result = optional_attributes.call(input) + if result.success? + information = result.value[:information] + information.each do |key, value| + xml['g'].send(key, value) + end + end + end + + def add_optional_sub_attributes(xml, product, variant) + input = { product: product, variant: variant, settings: @settings, store: store } + result = optional_sub_attributes.call(input) + if result.success? + information = result.value[:information] + information.each do |key, value| + xml['g'].send(key) do + value.each do |subkey, subvalue| + xml['g'].send(subkey, subvalue) + end + end + end + end + end + + def optional_attributes + Spree::Dependencies.data_feeds_google_optional_attributes_service.constantize.new + end + + def required_attributes + Spree::Dependencies.data_feeds_google_required_attributes_service.constantize.new + end + + def optional_sub_attributes + Spree::Dependencies.data_feeds_google_optional_sub_attributes_service.constantize.new + end + + def products_list + Spree::Dependencies.data_feeds_google_products_list.constantize.new + end + end + end + end +end diff --git a/core/app/sorters/spree/products/sort.rb b/core/app/sorters/spree/products/sort.rb index dbc199d28c7..511dabd8692 100644 --- a/core/app/sorters/spree/products/sort.rb +++ b/core/app/sorters/spree/products/sort.rb @@ -49,18 +49,25 @@ def sort_by?(field) sort.detect { |s| s[0] == field } end - # Add translatable fields to SELECT statement to avoid InvalidColumnReference error (Mobility bug workaround) + # Add translatable fields to SELECT statement to avoid InvalidColumnReference error (workaround for Mobility issue #596) def select_translatable_fields(scope) - translatable_sortable_fields = [] - Product.translatable_fields.each do |field| - translatable_sortable_fields << field if sort_by?(field.to_s) - end + translatable_fields = translatable_sortable_fields + return scope if translatable_fields.empty? - return scope unless translatable_sortable_fields.any? + # if sorting by 'sku' or 'price', spree_products.* is already included in SELECT statement + if sort_by?('sku') || sort_by?('price') + scope.i18n.select(*translatable_fields) + else + scope.i18n.select("#{Product.table_name}.*").select(*translatable_fields) + end + end - sql_fields = translatable_sortable_fields.map {|field| "#{Product.translation_table_alias}.#{field}" } - .join(', ') - scope.i18n.select("#{Product.table_name}.*, #{sql_fields}") + def translatable_sortable_fields + fields = [] + Product.translatable_fields.each do |field| + fields << field if sort_by?(field.to_s) + end + fields end end end diff --git a/core/brakeman.ignore b/core/brakeman.ignore index c82e0794edb..146de579c7f 100644 --- a/core/brakeman.ignore +++ b/core/brakeman.ignore @@ -1,20 +1,259 @@ { - "ignored_warnings": [ - { - "fingerprint": "011b2643940ba1112f7a737e403abe3616ad91764703c801cc35a48d36b721da", - "note": "interpolating table name" - }, - { - "fingerprint": "965d3919f811ab63b7b8d62da528559a7f38dc122c57efea7136e7ec5ef1f062", - "note": "interpolating table name" - }, - { - "fingerprint": "abd8e90e7a7dfbcdcd6d44fd3fb550598aee6d7a9ef2bb132ad1a18a3c50be30", - "note": "interpolating table name" - }, - { - "fingerprint": "efcc57e1a5648d7db59d1beaf5e399d2278539a8667b19c520b305a6ca7e15e8", - "note": "interpolating table name" - } - ] + "ignored_warnings": [ + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "011b2643940ba1112f7a737e403abe3616ad91764703c801cc35a48d36b721da", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 64, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "where(\"#{price_table_name}.amount <= ?\", price)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree", + "method": null + }, + "user_input": "price_table_name", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "interpolating table name" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "1f02952550c2f54d044c9577a45e7ba7c7990c8b8a59d1dac83a96790237f507", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 139, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "joins(:properties).join_translation_table(Property).join_translation_table(ProductProperty).where(\"#{ProductProperty.translation_table_alias}.value = ?\", value)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::ProductScopes", + "method": null + }, + "user_input": "ProductProperty.translation_table_alias", + "confidence": "Weak", + "cwe_id": [ + 89 + ], + "note": "" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "7928c0813a0bf084ead091b4554ef6abea9ae9c7167936f5c62da9e328b9f736", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 139, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "joins(:properties).join_translation_table(Property).join_translation_table(ProductProperty).where(\"#{ProductProperty.translation_table_alias}.value = ?\", value)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree", + "method": null + }, + "user_input": "ProductProperty.translation_table_alias", + "confidence": "Weak", + "cwe_id": [ + 89 + ], + "note": "" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "857c335935a00f584137f31dbcb1a4532af5c8bb5cf53a86058b4af98c6597dc", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "lib/spree/translation_migrations.rb", + "line": 21, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "ActiveRecord::Base.connection.execute(\"\\n UPDATE #{resource_class.table_name}\\n SET #{resource_class.translatable_fields.map do\n \"#{f}=null\"\n end.join(\", \")};\\n \")", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::TranslationMigrations", + "method": "transfer_translation_data" + }, + "user_input": "resource_class.translatable_fields.map do\n \"#{f}=null\"\n end.join(\", \")", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "965d3919f811ab63b7b8d62da528559a7f38dc122c57efea7136e7ec5ef1f062", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 68, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "where(\"#{price_table_name}.amount >= ?\", price)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::ProductScopes", + "method": null + }, + "user_input": "price_table_name", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "interpolating table name" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "abd8e90e7a7dfbcdcd6d44fd3fb550598aee6d7a9ef2bb132ad1a18a3c50be30", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 64, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "where(\"#{price_table_name}.amount <= ?\", price)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::ProductScopes", + "method": null + }, + "user_input": "price_table_name", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "interpolating table name" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "c1c97347a2d74ea41d46519e3bfbd94c511a1bd9c285f3f2a1fa0cb7e624d232", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "lib/spree/translation_migrations.rb", + "line": 32, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "ActiveRecord::Base.connection.execute(\"\\n UPDATE #{resource_class.table_name}\\n SET (#{resource_class.translatable_fields.join(\", \")}) = #{(\"ROW\" or \"\")}(#{resource_class.translatable_fields.map do\n \"#{resource_class::Translation.table_name}.#{f}\"\n end.join(\", \")})\\n FROM #{resource_class::Translation.table_name}\\n WHERE #{resource_class::Translation.table_name}.#{\"#{resource_class.table_name.singularize}_id\"} = #{resource_class.table_name}.id\\n \")", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::TranslationMigrations", + "method": "revert_translation_data_transfer" + }, + "user_input": "resource_class.translatable_fields.join(\", \")", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "c2bc48d98076b7c4fc3314c6a85f7bd1132efe5fcc346da4d28df7c25f93633f", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/spree/variant.rb", + "line": 127, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "joins(:product).join_translation_table(Product).where(\"LOWER(#{Product.translation_table_alias}.name) LIKE LOWER(:query)\\n OR LOWER(sku) LIKE LOWER(:query)\", :query => (\"%#{query}%\"))", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::Variant", + "method": "Spree::Variant.product_name_or_sku_cont" + }, + "user_input": "Product.translation_table_alias", + "confidence": "Weak", + "cwe_id": [ + 89 + ], + "note": "" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "e7aa61c87f26af5f63f00e9791954d787a559dab1eadf8926861796237bd431d", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/translatable_resource_scopes.rb", + "line": 10, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "joins(\"LEFT OUTER JOIN #{translatable_class::Translation.table_name} #{translatable_class.translation_table_alias}\\n ON #{translatable_class.translation_table_alias}.#{\"#{translatable_class.table_name.singularize}_id\"} = #{translatable_class.table_name}.id\\n AND #{translatable_class.translation_table_alias}.locale = '#{Mobility.locale}'\")", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::TranslatableResourceScopes", + "method": "join_translation_table" + }, + "user_input": "translatable_class.translation_table_alias", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "efcc57e1a5648d7db59d1beaf5e399d2278539a8667b19c520b305a6ca7e15e8", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/concerns/spree/product_scopes.rb", + "line": 68, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "where(\"#{price_table_name}.amount >= ?\", price)", + "render_path": null, + "location": { + "type": "method", + "class": "Spree", + "method": null + }, + "user_input": "price_table_name", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "interpolating table name" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "f14dd62fac0dd1e9d5532dd5efc770e2eb873a8db80faf366b6295378634754a", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "lib/spree/translation_migrations.rb", + "line": 16, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "ActiveRecord::Base.connection.execute(\"\\n INSERT INTO #{resource_class::Translation.table_name} (#{resource_class.translatable_fields.join(\", \")}, #{\"#{resource_class.table_name.singularize}_id\"}, locale, created_at, updated_at)\\n SELECT #{resource_class.translatable_fields.join(\", \")}, id, '#{default_locale}' as locale, created_at, updated_at FROM #{resource_class.table_name};\\n \")", + "render_path": null, + "location": { + "type": "method", + "class": "Spree::TranslationMigrations", + "method": "transfer_translation_data" + }, + "user_input": "resource_class.translatable_fields.join(\", \")", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "" + } + ], + "updated": "2023-01-18 14:40:41 +0100", + "brakeman_version": "5.4.0" } diff --git a/core/config/initializers/mobility.rb b/core/config/initializers/mobility.rb index dd05f7e0bcc..a554d7dafca 100644 --- a/core/config/initializers/mobility.rb +++ b/core/config/initializers/mobility.rb @@ -11,6 +11,7 @@ fallbacks locale_accessors presence + dirty end config.defaults[:fallbacks] = true diff --git a/core/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb b/core/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb index ad2fee61d80..1e732aeb245 100644 --- a/core/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb +++ b/core/db/migrate/20220706112554_create_product_name_and_description_translations_for_mobility_table_backend.rb @@ -1,19 +1,27 @@ class CreateProductNameAndDescriptionTranslationsForMobilityTableBackend < ActiveRecord::Migration[6.1] def change - create_table :spree_product_translations do |t| + # create translation table only if spree_globalize has not already created it + if ActiveRecord::Base.connection.table_exists? 'spree_product_translations' + # manually check for index since Rails if_exists does not always work correctly + if ActiveRecord::Migration.connection.index_exists?(:spree_product_translations, :spree_product_id) + remove_index :spree_product_translations, name: "index_spree_product_translations_on_spree_product_id", if_exists: true + end + else + create_table :spree_product_translations do |t| - # Translated attribute(s) - t.string :name - t.text :description + # Translated attribute(s) + t.string :name + t.text :description - t.string :locale, null: false - t.references :spree_product, null: false, foreign_key: true, index: false + t.string :locale, null: false + t.references :spree_product, null: false, foreign_key: true, index: false - t.timestamps null: false - end + t.timestamps null: false + end - add_index :spree_product_translations, :locale, name: :index_spree_product_translations_on_locale - add_index :spree_product_translations, [:spree_product_id, :locale], name: :index_89f757462683439a75913375358673bb7f45ebe0, unique: true + add_index :spree_product_translations, :locale, name: :index_spree_product_translations_on_locale + end + add_index :spree_product_translations, [:spree_product_id, :locale], name: :unique_product_id_per_locale, unique: true end end diff --git a/core/db/migrate/20220715083542_create_spree_product_meta_description_and_meta_keywords_and_meta_title_translations_for_mobility_table_backend.rb b/core/db/migrate/20220715083542_create_spree_product_meta_description_and_meta_keywords_and_meta_title_translations_for_mobility_table_backend.rb index 17c346fbd17..5d712d9cb08 100644 --- a/core/db/migrate/20220715083542_create_spree_product_meta_description_and_meta_keywords_and_meta_title_translations_for_mobility_table_backend.rb +++ b/core/db/migrate/20220715083542_create_spree_product_meta_description_and_meta_keywords_and_meta_title_translations_for_mobility_table_backend.rb @@ -1,7 +1,7 @@ class CreateSpreeProductMetaDescriptionAndMetaKeywordsAndMetaTitleTranslationsForMobilityTableBackend < ActiveRecord::Migration[6.1] def change - add_column :spree_product_translations, :meta_description, :text - add_column :spree_product_translations, :meta_keywords, :string - add_column :spree_product_translations, :meta_title, :string + add_column :spree_product_translations, :meta_description, :text, if_not_exists: true + add_column :spree_product_translations, :meta_keywords, :string, if_not_exists: true + add_column :spree_product_translations, :meta_title, :string, if_not_exists: true end end diff --git a/core/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb b/core/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb index 653f59429be..1ffa0f77376 100644 --- a/core/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb +++ b/core/db/migrate/20220718100743_create_spree_taxon_name_and_description_translations_for_mobility_table_backend.rb @@ -1,19 +1,27 @@ class CreateSpreeTaxonNameAndDescriptionTranslationsForMobilityTableBackend < ActiveRecord::Migration[6.1] def change - create_table :spree_taxon_translations do |t| + # create translation table only if spree_globalize has not already created it + if ActiveRecord::Base.connection.table_exists? 'spree_taxon_translations' + # manually check for index since Rails if_exists does not always work correctly + if ActiveRecord::Migration.connection.index_exists?(:spree_taxon_translations, :spree_taxon_id) + # replacing this with index on spree_taxon_id and locale + remove_index :spree_taxon_translations, name: "index_spree_taxon_translations_on_spree_taxon_id", if_exists: true + end + else + create_table :spree_taxon_translations do |t| + # Translated attribute(s) + t.string :name + t.text :description - # Translated attribute(s) - t.string :name - t.text :description + t.string :locale, null: false + t.references :spree_taxon, null: false, foreign_key: true, index: false - t.string :locale, null: false - t.references :spree_taxon, null: false, foreign_key: true, index: false + t.timestamps null: false + end - t.timestamps null: false + add_index :spree_taxon_translations, :locale, name: :index_spree_taxon_translations_on_locale end - add_index :spree_taxon_translations, :locale, name: :index_spree_taxon_translations_on_locale add_index :spree_taxon_translations, [:spree_taxon_id, :locale], name: :index_spree_taxon_translations_on_spree_taxon_id_and_locale, unique: true - end end diff --git a/core/db/migrate/20221213140831_create_taxonomies_translations_table_for_mobility.rb b/core/db/migrate/20221213140831_create_taxonomies_translations_table_for_mobility.rb deleted file mode 100644 index 76b0af886e9..00000000000 --- a/core/db/migrate/20221213140831_create_taxonomies_translations_table_for_mobility.rb +++ /dev/null @@ -1,16 +0,0 @@ -class CreateTaxonomiesTranslationsTableForMobility < ActiveRecord::Migration[6.1] - def change - create_table :spree_taxonomy_translations do |t| - # Translated attribute(s) - t.string :name - - t.string :locale, null: false - t.references :spree_taxonomy, null: false, foreign_key: true, index: false - - t.timestamps null: false - end - add_index :spree_taxonomy_translations, :locale, name: :index_spree_taxonomy_translations_on_locale - add_index :spree_taxonomy_translations, [:spree_taxonomy_id, :locale], name: :index_spree_taxonomy_translations_on_spree_taxonomy_id_locale, unique: true - - end -end diff --git a/core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb b/core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb new file mode 100644 index 00000000000..44667330482 --- /dev/null +++ b/core/db/migrate/20221215151408_add_selected_locale_to_spree_users.rb @@ -0,0 +1,8 @@ +class AddSelectedLocaleToSpreeUsers < ActiveRecord::Migration[6.1] + def change + if Spree.user_class.present? + users_table_name = Spree.user_class.table_name + add_column users_table_name, :selected_locale, :string unless column_exists?(users_table_name, :selected_locale) + end + end +end diff --git a/core/db/migrate/20221219132220_create_menu_translation_table_for_mobility.rb b/core/db/migrate/20221219132220_create_menu_translation_table_for_mobility.rb new file mode 100644 index 00000000000..7a6fb1eba94 --- /dev/null +++ b/core/db/migrate/20221219132220_create_menu_translation_table_for_mobility.rb @@ -0,0 +1,15 @@ +class CreateMenuTranslationTableForMobility < ActiveRecord::Migration[7.0] + def change + create_table :spree_menu_translations do |t| + # Translated attribute(s) + t.string :name + + t.string :locale, null: false + t.references :spree_menu, null: false, foreign_key: true, index: false + + t.timestamps null: false + end + add_index :spree_menu_translations, :locale, name: :index_spree_menu_translations_on_locale + add_index :spree_menu_translations, [:spree_menu_id, :locale], name: :index_spree_menu_translations_on_spree_menu_id_and_locale, unique: true + end +end diff --git a/core/db/migrate/20221219132250_enable_menus_without_name.rb b/core/db/migrate/20221219132250_enable_menus_without_name.rb new file mode 100644 index 00000000000..9fd3c3ddd12 --- /dev/null +++ b/core/db/migrate/20221219132250_enable_menus_without_name.rb @@ -0,0 +1,5 @@ +class EnableMenusWithoutName < ActiveRecord::Migration[7.0] + def change + change_column_null :spree_menus, :name, true + end +end diff --git a/core/db/migrate/20221219145723_create_menu_item_translation_table_for_mobility.rb b/core/db/migrate/20221219145723_create_menu_item_translation_table_for_mobility.rb new file mode 100644 index 00000000000..ba39820bb78 --- /dev/null +++ b/core/db/migrate/20221219145723_create_menu_item_translation_table_for_mobility.rb @@ -0,0 +1,17 @@ +class CreateMenuItemTranslationTableForMobility < ActiveRecord::Migration[7.0] + def change + create_table :spree_menu_item_translations do |t| + # Translated attribute(s) + t.string :name + t.string :subtitle + t.string :destination + + t.string :locale, null: false + t.references :spree_menu_item, null: false, foreign_key: true, index: false + + t.timestamps null: false + end + add_index :spree_menu_item_translations, :locale, name: :index_spree_menu_item_translations_on_locale + add_index :spree_menu_item_translations, [:spree_menu_item_id, :locale], name: :index_2f1dad0e4e37c6c8147f0b351198ff0013d9cc4d, unique: true + end +end diff --git a/core/db/migrate/20221219145736_enable_menu_items_without_name.rb b/core/db/migrate/20221219145736_enable_menu_items_without_name.rb new file mode 100644 index 00000000000..4b3b613fe91 --- /dev/null +++ b/core/db/migrate/20221219145736_enable_menu_items_without_name.rb @@ -0,0 +1,5 @@ +class EnableMenuItemsWithoutName < ActiveRecord::Migration[7.0] + def change + change_column_null :spree_menu_items, :name, true + end +end diff --git a/core/db/migrate/20221229132350_create_spree_data_feed_settings.rb b/core/db/migrate/20221229132350_create_spree_data_feed_settings.rb new file mode 100644 index 00000000000..f5d791da0a9 --- /dev/null +++ b/core/db/migrate/20221229132350_create_spree_data_feed_settings.rb @@ -0,0 +1,14 @@ +class CreateSpreeDataFeedSettings < ActiveRecord::Migration[6.0] + def change + create_table :spree_data_feed_settings do |t| + t.references :spree_store + + t.string :name + t.string :provider + t.string :uuid, unique: true + t.boolean :enabled, default: true + + t.timestamps + end + end +end diff --git a/core/db/migrate/20230103144439_create_option_type_translations.rb b/core/db/migrate/20230103144439_create_option_type_translations.rb new file mode 100644 index 00000000000..5f798dc909a --- /dev/null +++ b/core/db/migrate/20230103144439_create_option_type_translations.rb @@ -0,0 +1,26 @@ +class CreateOptionTypeTranslations < ActiveRecord::Migration[6.1] + def change + if ActiveRecord::Base.connection.table_exists? 'spree_option_type_translations' + # manually check for index since Rails if_exists does not always work correctly + if ActiveRecord::Migration.connection.index_exists?(:spree_option_type_translations, :spree_option_type_id) + remove_index :spree_option_type_translations, name: "index_spree_option_type_translations_on_spree_option_type_id", if_exists: true + end + else + create_table :spree_option_type_translations do |t| + + # Translated attribute(s) + t.string :name + t.string :presentation + + t.string :locale, null: false + t.references :spree_option_type, null: false, foreign_key: true, index: false + + t.timestamps + end + + add_index :spree_option_type_translations, :locale, name: :index_spree_option_type_translations_on_locale + end + + add_index :spree_option_type_translations, [:spree_option_type_id, :locale], name: :unique_option_type_id_per_locale, unique: true + end +end diff --git a/core/db/migrate/20230103151034_create_option_value_translations.rb b/core/db/migrate/20230103151034_create_option_value_translations.rb new file mode 100644 index 00000000000..b04a264de7c --- /dev/null +++ b/core/db/migrate/20230103151034_create_option_value_translations.rb @@ -0,0 +1,26 @@ +class CreateOptionValueTranslations < ActiveRecord::Migration[6.1] + def change + if ActiveRecord::Base.connection.table_exists? 'spree_option_value_translations' + # manually check for index since Rails if_exists does not always work correctly + if ActiveRecord::Migration.connection.index_exists?(:spree_option_value_translations, :spree_option_value_id) + remove_index :spree_option_value_translations, column: :spree_option_value_id, if_exists: true + end + else + create_table :spree_option_value_translations do |t| + + # Translated attribute(s) + t.string :name + t.string :presentation + + t.string :locale, null: false + t.references :spree_option_value, null: false, foreign_key: true, index: false + + t.timestamps + end + + add_index :spree_option_value_translations, :locale, name: :index_spree_option_value_translations_on_locale + end + + add_index :spree_option_value_translations, [:spree_option_value_id, :locale], name: :unique_option_value_id_per_locale, unique: true + end +end diff --git a/core/db/migrate/20230109084253_create_product_property_translations.rb b/core/db/migrate/20230109084253_create_product_property_translations.rb new file mode 100644 index 00000000000..511ee403281 --- /dev/null +++ b/core/db/migrate/20230109084253_create_product_property_translations.rb @@ -0,0 +1,25 @@ +class CreateProductPropertyTranslations < ActiveRecord::Migration[6.1] + def change + if ActiveRecord::Base.connection.table_exists? 'spree_product_property_translations' + # manually check for index since Rails if_exists does not always work correctly + if ActiveRecord::Migration.connection.index_exists?(:spree_product_property_translations, :spree_product_property_id) + remove_index :spree_product_property_translations, column: :spree_product_property_id, if_exists: true + end + else + create_table :spree_product_property_translations do |t| + # Translated attribute(s) + t.string :value + t.string :filter_param + + t.string :locale, null: false + t.references :spree_product_property, null: false, foreign_key: true, index: false + + t.timestamps + end + + add_index :spree_product_property_translations, :locale, name: :index_spree_product_property_translations_on_locale + end + + add_index :spree_product_property_translations, [:spree_product_property_id, :locale], name: :unique_product_property_id_per_locale, unique: true + end +end diff --git a/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb b/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb new file mode 100644 index 00000000000..ff7eb328fea --- /dev/null +++ b/core/db/migrate/20230109094907_transfer_options_data_to_translatable_tables.rb @@ -0,0 +1,58 @@ +class TransferOptionsDataToTranslatableTables < ActiveRecord::Migration[6.1] + DEFAULT_LOCALE = 'en' + + def up + # Only transfer data if translation tables are being newly created / no translations exist + # Otherwise, assume translation data is already in place from spree_globalize + + # Option Types + if not Spree::OptionType::Translation.exists? + ActiveRecord::Base.connection.execute(" + INSERT INTO spree_option_type_translations (name, presentation, locale, spree_option_type_id, created_at, updated_at) + SELECT name, presentation, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_option_types; + ") + ActiveRecord::Base.connection.execute(" + UPDATE spree_option_types + SET name=null, presentation=null; + ") + end + + # Option Values + if not Spree::OptionValue::Translation.exists? + ActiveRecord::Base.connection.execute(" + INSERT INTO spree_option_value_translations (name, presentation, locale, spree_option_value_id, created_at, updated_at) + SELECT name, presentation, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_option_values; + ") + ActiveRecord::Base.connection.execute(" + UPDATE spree_option_values + SET name=null, presentation=null; + ") + end + end + + def down + # Option Types + ActiveRecord::Base.connection.execute(" + UPDATE spree_option_types as option_types + SET (name, presentation) = (t_option_types.name, t_option_types.presentation) + FROM spree_option_type_translations AS t_option_types + WHERE t_option_types.spree_option_type_id = option_types.id; + ") + ActiveRecord::Base.connection.execute(" + TRUNCATE TABLE spree_option_type_translations; + ") + + # Option Values + ActiveRecord::Base.connection.execute(" + UPDATE spree_option_values as option_values + SET (name, presentation) = (t_option_values.name, t_option_values.presentation) + FROM spree_option_value_translations AS t_option_values + WHERE t_option_values.spree_option_value_id = option_values.id; + ") + ActiveRecord::Base.connection.execute(" + TRUNCATE TABLE spree_option_value_translations; + ") + end +end diff --git a/core/db/migrate/20230109105943_create_property_translations.rb b/core/db/migrate/20230109105943_create_property_translations.rb new file mode 100644 index 00000000000..8dde187338e --- /dev/null +++ b/core/db/migrate/20230109105943_create_property_translations.rb @@ -0,0 +1,26 @@ +class CreatePropertyTranslations < ActiveRecord::Migration[6.1] + def change + if ActiveRecord::Base.connection.table_exists?('spree_property_translations') + # manually check for index since Rails if_exists does not always work correctly + if ActiveRecord::Migration.connection.index_exists?(:spree_property_translations, :spree_property_id) + remove_index :spree_property_translations, column: :spree_property_id, if_exists: true + end + else + create_table :spree_property_translations do |t| + # Translated attribute(s) + t.string :name + t.string :presentation + t.string :filter_param + + t.string :locale, null: false + t.references :spree_property, null: false, foreign_key: true, index: false + + t.timestamps + end + + add_index :spree_property_translations, :locale, name: :index_spree_property_translations_on_locale + end + + add_index :spree_property_translations, [:spree_property_id, :locale], name: :unique_property_id_per_locale, unique: true + end +end diff --git a/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb b/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb new file mode 100644 index 00000000000..5fb7814dc00 --- /dev/null +++ b/core/db/migrate/20230109110840_transfer_property_data_to_translatable_tables.rb @@ -0,0 +1,59 @@ +class TransferPropertyDataToTranslatableTables < ActiveRecord::Migration[6.1] + DEFAULT_LOCALE = 'en' + + def up + # Properties + change_column_null :spree_properties, :presentation, true + + if not Spree::Property::Translation.exists? + ActiveRecord::Base.connection.execute(" + INSERT INTO spree_property_translations (name, presentation, filter_param, locale, spree_property_id, created_at, updated_at) + SELECT name, presentation, filter_param, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_properties; + ") + ActiveRecord::Base.connection.execute(" + UPDATE spree_properties + SET name=null, presentation=null, filter_param=null; + ") + end + + # Product Properties + if not Spree::ProductProperty::Translation.exists? + ActiveRecord::Base.connection.execute(" + INSERT INTO spree_product_property_translations (value, filter_param, locale, spree_product_property_id, created_at, updated_at) + SELECT value, filter_param, '#{DEFAULT_LOCALE}', id, created_at, updated_at + FROM spree_product_properties; + ") + ActiveRecord::Base.connection.execute(" + UPDATE spree_product_properties + SET value=null, filter_param=null; + ") + end + end + + def down + # Properties + change_column_null :spree_properties, :presentation, false + + ActiveRecord::Base.connection.execute(" + UPDATE spree_properties AS properties + SET (name, presentation, filter_param) = (t_properties.name, t_properties.presentation, t_properties.filter_param) + FROM spree_property_translations AS t_properties + WHERE t_properties.spree_property_id = properties.id; + ") + ActiveRecord::Base.connection.execute(" + TRUNCATE TABLE spree_property_translations; + ") + + # Product Properties + ActiveRecord::Base.connection.execute(" + UPDATE spree_product_properties AS product_properties + SET (value, filter_param) = (t_product_properties.value, t_product_properties.filter_param) + FROM spree_product_property_translations AS t_product_properties + WHERE t_product_properties.spree_product_property_id = product_properties.id; + ") + ActiveRecord::Base.connection.execute(" + TRUNCATE TABLE spree_product_property_translations; + ") + end +end diff --git a/core/db/migrate/20230110142344_backfill_friendly_id_slug_locale.rb b/core/db/migrate/20230110142344_backfill_friendly_id_slug_locale.rb new file mode 100644 index 00000000000..c2c168d94c1 --- /dev/null +++ b/core/db/migrate/20230110142344_backfill_friendly_id_slug_locale.rb @@ -0,0 +1,15 @@ +class BackfillFriendlyIdSlugLocale < ActiveRecord::Migration[6.1] + DEFAULT_LOCALE = 'en' + + def up + ActiveRecord::Base.connection.execute(" + UPDATE friendly_id_slugs SET locale = '#{DEFAULT_LOCALE}' + ") + end + + def down + ActiveRecord::Base.connection.execute(" + UPDATE friendly_id_slugs SET locale = NULL + ") + end +end diff --git a/core/db/migrate/20230111121534_add_additional_taxon_translation_fields.rb b/core/db/migrate/20230111121534_add_additional_taxon_translation_fields.rb new file mode 100644 index 00000000000..f59a7ac10b2 --- /dev/null +++ b/core/db/migrate/20230111121534_add_additional_taxon_translation_fields.rb @@ -0,0 +1,8 @@ +class AddAdditionalTaxonTranslationFields < ActiveRecord::Migration[6.1] + def change + add_column :spree_taxon_translations, :meta_title, :string, if_not_exists: true + add_column :spree_taxon_translations, :meta_description, :string, if_not_exists: true + add_column :spree_taxon_translations, :meta_keywords, :string, if_not_exists: true + add_column :spree_taxon_translations, :permalink, :string, if_not_exists: true + end +end diff --git a/core/db/migrate/20230111122511_transfer_product_and_taxon_data_to_translatable_tables.rb b/core/db/migrate/20230111122511_transfer_product_and_taxon_data_to_translatable_tables.rb new file mode 100644 index 00000000000..8c5b0247692 --- /dev/null +++ b/core/db/migrate/20230111122511_transfer_product_and_taxon_data_to_translatable_tables.rb @@ -0,0 +1,82 @@ +class TransferProductAndTaxonDataToTranslatableTables < ActiveRecord::Migration[6.1] + DEFAULT_LOCALE = 'en' + PRODUCTS_TABLE = 'spree_products' + PRODUCT_TRANSLATIONS_TABLE = 'spree_product_translations' + TAXONS_TABLE = 'spree_taxons' + TAXON_TRANSLATIONS_TABLE = 'spree_taxon_translations' + + def up + # Only transfer data if translation tables are being newly created / no translations exist + # Otherwise, assume translation data is already in place from spree_globalize + + # Products + if not Spree::Product::Translation.exists? + ActiveRecord::Base.connection.execute(" + INSERT INTO #{PRODUCT_TRANSLATIONS_TABLE} (name, description, locale, spree_product_id, created_at, updated_at, meta_description, meta_keywords, meta_title, slug) + SELECT name, description, '#{DEFAULT_LOCALE}' as locale, id, created_at, updated_at, meta_description, meta_keywords, meta_title, slug FROM #{PRODUCTS_TABLE}; + ") + ActiveRecord::Base.connection.execute(" + UPDATE #{PRODUCTS_TABLE} + SET name=null, description=null, meta_description=null, meta_keywords=null, meta_title=null, slug=null; + ") + end + + # Taxons + if not Spree::Taxon::Translation.exists? + ActiveRecord::Base.connection.execute(" + INSERT INTO #{TAXON_TRANSLATIONS_TABLE} (name, description, meta_title, meta_description, meta_keywords, permalink, locale, spree_taxon_id, created_at, updated_at) + SELECT name, description, meta_title, meta_description, meta_keywords, permalink, '#{DEFAULT_LOCALE}' as locale, id, created_at, updated_at FROM #{TAXONS_TABLE}; + ") + ActiveRecord::Base.connection.execute(" + UPDATE #{TAXONS_TABLE} + SET name=null, description=null, meta_title=null, meta_description=null, meta_keywords=null, permalink=null; + ") + end + end + + def down + ActiveRecord::Base.connection.execute(" + UPDATE #{PRODUCTS_TABLE} as products + SET (name, + description, + meta_description, + meta_keywords, + meta_title, + slug) = + (t_products.name, + t_products.description, + t_products.meta_description, + t_products.meta_keywords, + t_products.meta_title, + t_products.slug) + FROM #{PRODUCT_TRANSLATIONS_TABLE} AS t_products + WHERE t_products.spree_product_id = products.id + ") + + ActiveRecord::Base.connection.execute(" + TRUNCATE TABLE #{PRODUCT_TRANSLATIONS_TABLE} + ") + + ActiveRecord::Base.connection.execute(" + UPDATE #{TAXONS_TABLE} as taxons + SET (name, + description, + meta_title, + meta_description, + meta_keywords, + permalink) = + (t_taxons.name, + t_taxons.description, + t_taxons.meta_title, + t_taxons.meta_description, + t_taxons.meta_keywords, + t_taxons.permalink) + FROM #{TAXON_TRANSLATIONS_TABLE} AS t_taxons + WHERE t_taxons.spree_taxon_id = taxons.id + ") + + ActiveRecord::Base.connection.execute(" + TRUNCATE TABLE #{TAXON_TRANSLATIONS_TABLE} + ") + end +end diff --git a/core/db/migrate/20230117115531_create_taxonomy_translations.rb b/core/db/migrate/20230117115531_create_taxonomy_translations.rb new file mode 100644 index 00000000000..42305b860b8 --- /dev/null +++ b/core/db/migrate/20230117115531_create_taxonomy_translations.rb @@ -0,0 +1,24 @@ +class CreateTaxonomyTranslations < ActiveRecord::Migration[6.1] + def change + if ActiveRecord::Base.connection.table_exists?('spree_taxonomy_translations') + # manually check for index since Rails if_exists does not always work correctly + if ActiveRecord::Migration.connection.index_exists?(:spree_taxonomy_translations, :spree_taxonomy_id) + remove_index :spree_taxonomy_translations, column: :spree_taxonomy_id, if_exists: true + end + else + create_table :spree_taxonomy_translations do |t| + # Translated attribute(s) + t.string :name + + t.string :locale, null: false + t.references :spree_taxonomy, null: false, foreign_key: true, index: false + + t.timestamps null: false + end + + add_index :spree_taxonomy_translations, :locale, name: :index_spree_taxonomy_translations_on_locale + end + + add_index :spree_taxonomy_translations, [:spree_taxonomy_id, :locale], name: :index_spree_taxonomy_translations_on_spree_taxonomy_id_locale, unique: true + end +end diff --git a/core/db/migrate/20221215121651_enable_taxonomies_without_name.rb b/core/db/migrate/20230117120430_allow_null_taxonomy_name.rb similarity index 54% rename from core/db/migrate/20221215121651_enable_taxonomies_without_name.rb rename to core/db/migrate/20230117120430_allow_null_taxonomy_name.rb index bc924cb4762..743d40de420 100644 --- a/core/db/migrate/20221215121651_enable_taxonomies_without_name.rb +++ b/core/db/migrate/20230117120430_allow_null_taxonomy_name.rb @@ -1,4 +1,4 @@ -class EnableTaxonomiesWithoutName < ActiveRecord::Migration[6.1] +class AllowNullTaxonomyName < ActiveRecord::Migration[6.1] def change change_column_null :spree_taxonomies, :name, true end diff --git a/core/db/migrate/20230117121303_transfer_taxonomy_data_to_translatable_tables.rb b/core/db/migrate/20230117121303_transfer_taxonomy_data_to_translatable_tables.rb new file mode 100644 index 00000000000..a3ef2371236 --- /dev/null +++ b/core/db/migrate/20230117121303_transfer_taxonomy_data_to_translatable_tables.rb @@ -0,0 +1,11 @@ +class TransferTaxonomyDataToTranslatableTables < ActiveRecord::Migration[6.1] + TRANSLATION_MIGRATION = Spree::TranslationMigrations.new(Spree::Taxonomy, 'en') + + def up + TRANSLATION_MIGRATION.transfer_translation_data + end + + def down + TRANSLATION_MIGRATION.revert_translation_data_transfer + end +end diff --git a/core/db/migrate/20230118144224_create_menu_translations.rb b/core/db/migrate/20230118144224_create_menu_translations.rb new file mode 100644 index 00000000000..5ec30605bdd --- /dev/null +++ b/core/db/migrate/20230118144224_create_menu_translations.rb @@ -0,0 +1,16 @@ +class CreateMenuTranslations < ActiveRecord::Migration[6.1] + def change + create_table :spree_menu_translations do |t| + # Translated attribute(s) + t.string :name + + t.string :locale, null: false + t.references :spree_menu, null: false, foreign_key: true, index: false + + t.timestamps null: false + end + + add_index :spree_menu_translations, :locale, name: :index_spree_menu_translations_on_locale + add_index :spree_menu_translations, [:spree_menu_id, :locale], name: :index_spree_menu_translations_on_spree_menu_id_and_locale, unique: true + end +end diff --git a/core/db/migrate/20230118144642_allow_null_menu_name.rb b/core/db/migrate/20230118144642_allow_null_menu_name.rb new file mode 100644 index 00000000000..0c5e217504d --- /dev/null +++ b/core/db/migrate/20230118144642_allow_null_menu_name.rb @@ -0,0 +1,5 @@ +class AllowNullMenuName < ActiveRecord::Migration[6.1] + def change + change_column_null :spree_menus, :name, true + end +end diff --git a/core/db/migrate/20230118144800_transfer_menu_data_to_translatable_tables.rb b/core/db/migrate/20230118144800_transfer_menu_data_to_translatable_tables.rb new file mode 100644 index 00000000000..8e31b2bc417 --- /dev/null +++ b/core/db/migrate/20230118144800_transfer_menu_data_to_translatable_tables.rb @@ -0,0 +1,11 @@ +class TransferMenuDataToTranslatableTables < ActiveRecord::Migration[6.1] + TRANSLATION_MIGRATION = Spree::TranslationMigrations.new(Spree::Menu, 'en') + + def up + TRANSLATION_MIGRATION.transfer_translation_data + end + + def down + TRANSLATION_MIGRATION.revert_translation_data_transfer + end +end diff --git a/core/db/migrate/20230206153517_create_menu_item_translations.rb b/core/db/migrate/20230206153517_create_menu_item_translations.rb new file mode 100644 index 00000000000..29ac2dfb9ec --- /dev/null +++ b/core/db/migrate/20230206153517_create_menu_item_translations.rb @@ -0,0 +1,18 @@ +class CreateMenuItemTranslations < ActiveRecord::Migration[6.1] + def change + create_table :spree_menu_item_translations do |t| + # translatable fields + t.string :name + t.string :subtitle + t.string :destination + + t.string :locale, null: false + t.references :spree_menu_item, null: false, foreign_key: true, index: false + + t.timestamps + end + + add_index :spree_menu_item_translations, :locale, name: :index_spree_menu_item_translations_on_locale + add_index :spree_menu_item_translations, [:spree_menu_item_id, :locale], name: :index_spree_menu_item_translations_on_spree_menu_id_and_locale, unique: true + end +end diff --git a/core/db/migrate/20230206153936_allow_null_menu_item_name.rb b/core/db/migrate/20230206153936_allow_null_menu_item_name.rb new file mode 100644 index 00000000000..bf230ed9a67 --- /dev/null +++ b/core/db/migrate/20230206153936_allow_null_menu_item_name.rb @@ -0,0 +1,5 @@ +class AllowNullMenuItemName < ActiveRecord::Migration[6.1] + def change + change_column_null :spree_menu_items, :name, true + end +end diff --git a/core/db/migrate/20230206154053_transfer_menu_item_data_to_translatable_tables.rb b/core/db/migrate/20230206154053_transfer_menu_item_data_to_translatable_tables.rb new file mode 100644 index 00000000000..a30368e235e --- /dev/null +++ b/core/db/migrate/20230206154053_transfer_menu_item_data_to_translatable_tables.rb @@ -0,0 +1,11 @@ +class TransferMenuItemDataToTranslatableTables < ActiveRecord::Migration[6.1] + TRANSLATION_MIGRATION = Spree::TranslationMigrations.new(Spree::MenuItem, 'en') + + def up + TRANSLATION_MIGRATION.transfer_translation_data + end + + def down + TRANSLATION_MIGRATION.revert_translation_data_transfer + end +end diff --git a/core/lib/spree/core.rb b/core/lib/spree/core.rb index 31a04ed58bf..c95b077704a 100644 --- a/core/lib/spree/core.rb +++ b/core/lib/spree/core.rb @@ -117,6 +117,7 @@ class DestroyWithOrdersError < StandardError; end require 'spree/core/number_generator' require 'spree/migrations' +require 'spree/translation_migrations' require 'spree/core/engine' require 'spree/i18n' diff --git a/core/lib/spree/core/configuration.rb b/core/lib/spree/core/configuration.rb index 54278ba2833..ec68e89990c 100644 --- a/core/lib/spree/core/configuration.rb +++ b/core/lib/spree/core/configuration.rb @@ -65,6 +65,7 @@ class Configuration < Preferences::Configuration preference :show_raw_product_description, :boolean, deprecated: true preference :tax_using_ship_address, :boolean, default: true preference :track_inventory_levels, :boolean, default: true # Determines whether to track on_hand values for variants / products. + preference :use_user_locale, :boolean, default: true # Store credits configurations preference :non_expiring_credit_types, :array, default: [] diff --git a/core/lib/spree/core/controller_helpers/locale.rb b/core/lib/spree/core/controller_helpers/locale.rb index 0a5b20a7d23..5273a303a2e 100644 --- a/core/lib/spree/core/controller_helpers/locale.rb +++ b/core/lib/spree/core/controller_helpers/locale.rb @@ -22,15 +22,29 @@ def set_locale end def current_locale - @current_locale ||= if params[:locale].present? && supported_locale?(params[:locale]) + @current_locale ||= if user_locale? + try_spree_current_user.selected_locale + elsif params_locale? params[:locale] - elsif respond_to?(:config_locale, true) && config_locale.present? + elsif config_locale? config_locale else current_store&.default_locale || Rails.application.config.i18n.default_locale || I18n.default_locale end end + def config_locale? + respond_to?(:config_locale, true) && config_locale.present? + end + + def params_locale? + params[:locale].present? && supported_locale?(params[:locale]) + end + + def user_locale? + Spree::Config.use_user_locale && try_spree_current_user && supported_locale?(try_spree_current_user.selected_locale) + end + def supported_locales @supported_locales ||= current_store&.supported_locales_list end diff --git a/core/lib/spree/core/dependencies.rb b/core/lib/spree/core/dependencies.rb index d5ac71e56ae..e3b707c5a49 100644 --- a/core/lib/spree/core/dependencies.rb +++ b/core/lib/spree/core/dependencies.rb @@ -21,7 +21,8 @@ class Dependencies :order_approve_service, :order_cancel_service, :shipment_change_state_service, :shipment_update_service, :shipment_create_service, :shipment_add_item_service, :shipment_remove_item_service, :payment_create_service, :address_create_service, :address_update_service, - :checkout_select_shipping_method_service + :checkout_select_shipping_method_service, :data_feeds_google_rss_service, :data_feeds_google_optional_attributes_service, + :data_feeds_google_required_attributes_service, :data_feeds_google_optional_sub_attributes_service, :data_feeds_google_products_list ].freeze attr_accessor *INJECTION_POINTS @@ -110,6 +111,13 @@ def set_default_services # errors @error_handler = 'Spree::ErrorReporter' + + # data feeds for Google + @data_feeds_google_rss_service = 'Spree::DataFeeds::Google::Rss' + @data_feeds_google_optional_attributes_service = 'Spree::DataFeeds::Google::OptionalAttributes' + @data_feeds_google_required_attributes_service = 'Spree::DataFeeds::Google::RequiredAttributes' + @data_feeds_google_optional_sub_attributes_service = 'Spree::DataFeeds::Google::OptionalSubAttributes' + @data_feeds_google_products_list = 'Spree::DataFeeds::Google::ProductsList' end def set_default_finders diff --git a/core/lib/spree/core/product_filters.rb b/core/lib/spree/core/product_filters.rb index de24f226d8d..2c586c4c854 100644 --- a/core/lib/spree/core/product_filters.rb +++ b/core/lib/spree/core/product_filters.rb @@ -98,18 +98,21 @@ def self.price_filter conds.each do |new_scope| scope = scope.or(new_scope) end - Spree::Product.with_property('brand').where(scope) + Product.with_property('brand').join_translation_table(ProductProperty).where(scope) end def self.brand_filter brand_property = Spree::Property.find_by(name: 'brand') brands = brand_property ? Spree::ProductProperty.where(property_id: brand_property.id).pluck(:value).uniq.map(&:to_s) : [] - pp = Spree::ProductProperty.arel_table - conds = Hash[*brands.map { |b| [b, pp[:value].eq(b)] }.flatten] + + conditions = brands.map do |brand| + [brand, { ProductProperty.translation_table_alias => { value: brand } }] + end.to_h + { name: I18n.t('spree.taxonomy_brands_name'), scope: :brand_any, - conds: conds, + conds: conditions, labels: brands.sort.map { |k| [k, k] } } end diff --git a/core/lib/spree/core/version.rb b/core/lib/spree/core/version.rb index a53337784a0..6f7011004df 100644 --- a/core/lib/spree/core/version.rb +++ b/core/lib/spree/core/version.rb @@ -1,5 +1,5 @@ module Spree - VERSION = '4.5.0'.freeze + VERSION = '4.6.0.alpha'.freeze def self.version VERSION diff --git a/core/lib/spree/permitted_attributes.rb b/core/lib/spree/permitted_attributes.rb index 1dc967504d4..0a3e5a90a0c 100644 --- a/core/lib/spree/permitted_attributes.rb +++ b/core/lib/spree/permitted_attributes.rb @@ -134,7 +134,8 @@ module PermittedAttributes :new_order_notifications_email, :checkout_zone_id, :seo_robots, :digital_asset_authorized_clicks, :digital_asset_authorized_days, :limit_digital_download_count, :limit_digital_download_days, :digital_asset_link_expire_time, - { mailer_logo_attributes: {}, favicon_image_attributes: {}, logo_attributes: {} }] + { mailer_logo_attributes: {}, favicon_image_attributes: {}, logo_attributes: {} }, + { data_feed_settings_attributes: [:enabled, :id] }] @@store_credit_attributes = %i[amount currency category_id memo] @@ -147,7 +148,7 @@ module PermittedAttributes # TODO: Should probably use something like Spree.user_class.attributes @@user_attributes = [:email, :bill_address_id, :ship_address_id, :password, :first_name, :last_name, - :password_confirmation, { public_metadata: {}, private_metadata: {} }] + :password_confirmation, { public_metadata: {}, private_metadata: {} }, :selected_locale] @@variant_attributes = [ :name, :presentation, :cost_price, :discontinue_on, :lock_version, diff --git a/core/lib/spree/testing_support/factories/data_feed_setting_factory.rb b/core/lib/spree/testing_support/factories/data_feed_setting_factory.rb new file mode 100644 index 00000000000..ab965c207c6 --- /dev/null +++ b/core/lib/spree/testing_support/factories/data_feed_setting_factory.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :data_feed_setting, class: Spree::DataFeedSetting do + id { 1 } + enabled { true } + store { create(:store) } + provider { 'google' } + name { 'test' } + end +end diff --git a/core/lib/spree/testing_support/factories/product_factory.rb b/core/lib/spree/testing_support/factories/product_factory.rb index a88dc4d8ff5..a65f6b5dd78 100644 --- a/core/lib/spree/testing_support/factories/product_factory.rb +++ b/core/lib/spree/testing_support/factories/product_factory.rb @@ -52,6 +52,12 @@ factory :product_with_option_types do after(:create) { |product| create(:product_option_type, product: product) } end + factory :product_with_properties do + after(:create) do |product| + create(:property, :brand, id: 10) + create(:product_property, product: product, property_id: 10, value: 'Epsilon') + end + end end end end diff --git a/core/lib/spree/testing_support/factories/store_factory.rb b/core/lib/spree/testing_support/factories/store_factory.rb index 7358afe0262..e19dfb097ee 100644 --- a/core/lib/spree/testing_support/factories/store_factory.rb +++ b/core/lib/spree/testing_support/factories/store_factory.rb @@ -12,6 +12,7 @@ facebook { 'spreecommerce' } twitter { 'spreecommerce' } instagram { 'spreecommerce' } + meta_description { 'Sample store description.' } trait :with_favicon do transient do diff --git a/core/lib/spree/testing_support/factories/variant_factory.rb b/core/lib/spree/testing_support/factories/variant_factory.rb index 3e8d11d63f6..c9aca8a764d 100644 --- a/core/lib/spree/testing_support/factories/variant_factory.rb +++ b/core/lib/spree/testing_support/factories/variant_factory.rb @@ -25,6 +25,10 @@ factory :variant do # on_hand 5 product { |p| p.association(:product, stores: [create(:store)]) } + + factory :with_image_variant do + images { create_list(:image, 1) } + end end factory :master_variant do diff --git a/core/lib/spree/translation_migrations.rb b/core/lib/spree/translation_migrations.rb new file mode 100644 index 00000000000..19380712b29 --- /dev/null +++ b/core/lib/spree/translation_migrations.rb @@ -0,0 +1,40 @@ +module Spree + class TranslationMigrations + def initialize(resource_class, default_locale) + @resource_class = resource_class + @translations_table = resource_class::Translation.table_name + @translatable_fields = resource_class.translatable_fields.join(', ') + @foreign_key = "#{resource_class.table_name.singularize}_id" + @default_locale = default_locale + end + + def transfer_translation_data + nullify_translatable_fields = @resource_class.translatable_fields.map { |f| "#{f}=null" }.join(', ') + + unless @resource_class::Translation.exists? + ActiveRecord::Base.connection.execute(" + INSERT INTO #{@translations_table} (#{@translatable_fields}, #{@foreign_key}, locale, created_at, updated_at) + SELECT #{@translatable_fields}, id, '#{@default_locale}' as locale, created_at, updated_at FROM #{@resource_class.table_name}; + ") + ActiveRecord::Base.connection.execute(" + UPDATE #{@resource_class.table_name} + SET #{nullify_translatable_fields}; + ") + end + end + + def revert_translation_data_transfer + translation_table_fields = @resource_class.translatable_fields.map { |f| "#{@translations_table}.#{f}" }.join(', ') + row_expression = @resource_class.translatable_fields.count == 1 ? 'ROW' : '' + + ActiveRecord::Base.connection.execute(" + UPDATE #{@resource_class.table_name} + SET (#{@translatable_fields}) = #{row_expression}(#{translation_table_fields}) + FROM #{@translations_table} + WHERE #{@translations_table}.#{@foreign_key} = #{@resource_class.table_name}.id + ") + + ActiveRecord::Base.connection.execute("TRUNCATE TABLE #{@translations_table}") + end + end +end diff --git a/core/spec/finders/spree/taxons/find_spec.rb b/core/spec/finders/spree/taxons/find_spec.rb new file mode 100644 index 00000000000..077b04af3d1 --- /dev/null +++ b/core/spec/finders/spree/taxons/find_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +module Spree + describe Taxons::Find do + let!(:taxon_shirts) { create(:taxon, name: 'Shirts') } + let!(:taxon_shorts) { create(:taxon, name: 'Shorts') } + let!(:taxon_shoes) { create(:taxon, name: 'Shoes') } + + describe 'filtering by taxon property' do + subject do + described_class.new( + scope: Spree::Taxon.all, + params: params + ).execute + end + + context 'when filtering by taxon name' do + let(:params) { { filter: { 'name': 'Shirts' } } } + + it 'returns taxon with matching name' do + expect(subject).to contain_exactly(taxon_shirts) + end + end + + end + + end +end diff --git a/core/spec/helpers/base_helper_spec.rb b/core/spec/helpers/base_helper_spec.rb index 93e71ef925f..31aef6bba97 100644 --- a/core/spec/helpers/base_helper_spec.rb +++ b/core/spec/helpers/base_helper_spec.rb @@ -151,7 +151,7 @@ def link_to_tracking_html(options = {}) context 'when try_spree_current_user defined' do before do - I18n.locale = I18n.default_locale + allow(I18n).to receive(:locale).and_return(I18n.default_locale) allow_any_instance_of(described_class).to receive(:try_spree_current_user).and_return(user) end @@ -183,7 +183,9 @@ def link_to_tracking_html(options = {}) context 'when try_spree_current_user is undefined' do let(:current_currency) { 'USD' } - before { I18n.locale = I18n.default_locale } + before do + allow(I18n).to receive(:locale).and_return(I18n.default_locale) + end it 'returns base cache key' do expect(base_cache_key).to eq [:en, 'USD', nil, nil] diff --git a/core/spec/lib/spree/core/controller_helpers/auth_spec.rb b/core/spec/lib/spree/core/controller_helpers/auth_spec.rb index b370a9e0284..ad0655013e4 100644 --- a/core/spec/lib/spree/core/controller_helpers/auth_spec.rb +++ b/core/spec/lib/spree/core/controller_helpers/auth_spec.rb @@ -24,6 +24,7 @@ def index redirect_back_or_default('/') end end + it 'redirects to session url' do session[:spree_user_return_to] = '/redirect' get :index @@ -47,6 +48,7 @@ def index render plain: 'index' end end + it 'sends cookie header' do get :index expect(response.cookies['token']).not_to be_nil @@ -85,9 +87,10 @@ def index redirect_unauthorized_access end end + context 'when logged in' do before do - allow(controller).to receive_messages(try_spree_current_user: double('User', id: 1, last_incomplete_spree_order: nil)) + allow(controller).to receive_messages(try_spree_current_user: double('User', id: 1, last_incomplete_spree_order: nil, selected_locale: nil)) end it 'redirects forbidden path' do diff --git a/core/spec/lib/spree/core/controller_helpers/locale_spec.rb b/core/spec/lib/spree/core/controller_helpers/locale_spec.rb index 181d996a77d..5796d8c7896 100644 --- a/core/spec/lib/spree/core/controller_helpers/locale_spec.rb +++ b/core/spec/lib/spree/core/controller_helpers/locale_spec.rb @@ -16,72 +16,96 @@ def config_locale describe Spree::Core::ControllerHelpers::Locale, type: :controller do controller(FakesController) {} + let(:user) { build(:user, selected_locale: 'pl') } + + before { allow(controller).to receive(:spree_current_user).and_return(user) } + describe '#current_locale' do - context 'store with locale set' do - let!(:store) { create :store, default: true, default_locale: 'fr', supported_locales: 'fr,de' } + context 'taking locale from user and store with locale set' do + before { allow(Spree::Config).to receive(:use_user_locale).and_return(true) } - it 'returns current store default locale' do - expect(controller.current_locale.to_s).to eq('fr') - end + let!(:store) { create :store, default: true, default_locale: 'fr', supported_locales: 'fr,de,pl' } - it 'return supported locale when passed as param' do - controller.params = { locale: 'de' } - expect(controller.current_locale.to_s).to eq('de') + it 'returns locale set within user' do + expect(controller.current_locale.to_s).to eq('pl') end end - context 'config_locale present' do - controller(FakesControllerWithLocale) {} + context 'not taking locale from user' do + before { allow(Spree::Config).to receive(:use_user_locale).and_return(false) } - let!(:store) { create :store, default: true, default_locale: 'fr' } + context 'store with locale set' do + let!(:store) { create :store, default: true, default_locale: 'fr', supported_locales: 'fr,de' } + + it 'returns current store default locale' do + expect(controller.current_locale.to_s).to eq('fr') + end - it 'returns config_locale if present' do - expect(controller.current_locale.to_s).to eq('en') + it 'return supported locale when passed as param' do + controller.params = { locale: 'de' } + expect(controller.current_locale.to_s).to eq('de') + end end - end - context 'store without locale set' do - let!(:store) { create :store, default: true, default_locale: nil } + context 'config_locale present' do + controller(FakesControllerWithLocale) {} + + let!(:store) { create :store, default: true, default_locale: 'fr' } - context 'without I18n.default_locale set' do - it 'fallbacks to english' do + it 'returns config_locale if present' do expect(controller.current_locale.to_s).to eq('en') end end - context 'with I18n.default_locale set' do - before { I18n.default_locale = :de } + context 'store without locale set' do + let!(:store) { create :store, default: true, default_locale: nil } - after { I18n.default_locale = :en } + context 'without I18n.default_locale set' do + it 'fallbacks to english' do + expect(controller.current_locale.to_s).to eq('en') + end + end - it 'fallbacks to the default application locale' do - expect(controller.current_locale.to_s).to eq('de') + context 'with I18n.default_locale set' do + before do + allow(I18n).to receive(:default_locale).and_return(:de) + end + + it 'fallbacks to the default application locale' do + expect(controller.current_locale.to_s).to eq('de') + end end end end end describe '#supported_locales' do - let!(:store) { create :store, default: true, default_locale: 'de' } + let!(:store) { create :store, default: true, default_locale: 'de', supported_locales: 'de, pl' } it 'returns supported currencies' do expect(controller.supported_locales.to_s).to include('de') end + + it 'returns supported locales' do + expect(controller.supported_locales.to_s).to include('pl') + end end describe '#locale_param' do let!(:store) { create :store, default: true, default_locale: 'en', supported_locales: 'en,de,fr' } context 'same as store default locale' do - before { I18n.locale = :en } + before do + allow(I18n).to receive(:locale).and_return(:en) + end it { expect(controller.locale_param).to eq(nil) } end context 'different than store locale' do - before { I18n.locale = :de } - - after { I18n.locale = :en } + before do + allow(I18n).to receive(:locale).and_return(:de) + end it { expect(controller.locale_param).to eq('de') } end diff --git a/core/spec/lib/spree/localized_number_spec.rb b/core/spec/lib/spree/localized_number_spec.rb index 584132fbe12..8db888d2c9f 100644 --- a/core/spec/lib/spree/localized_number_spec.rb +++ b/core/spec/lib/spree/localized_number_spec.rb @@ -4,12 +4,11 @@ context '.parse' do before do I18n.enforce_available_locales = false - I18n.locale = I18n.default_locale + allow(I18n).to receive(:locale).and_return(I18n.default_locale) I18n.backend.store_translations(:de, number: { currency: { format: { delimiter: '.', separator: ',' } } }) end after do - I18n.locale = I18n.default_locale I18n.enforce_available_locales = true end @@ -21,21 +20,25 @@ context 'with decimal comma' do it 'captures the proper amount for a formatted price' do - I18n.locale = :de + allow(I18n).to receive(:locale).and_return(:de) + allow(I18n.config).to receive(:locale).and_return(:de) + expect(subject.class.parse('1.599,99')).to eq 1599.99 end end context 'with a numeric price' do it 'uses the price as is' do - I18n.locale = :de + allow(I18n).to receive(:locale).and_return(:de) + allow(I18n.config).to receive(:locale).and_return(:de) + expect(subject.class.parse(1599.99)).to eq 1599.99 end end context 'string argument' do it 'is not modified' do - I18n.locale = :de + allow(I18n).to receive(:locale).and_return(:de) number = '1.599,99' number_bak = number.dup subject.class.parse(number) diff --git a/core/spec/models/spree/data_feed_setting_spec.rb b/core/spec/models/spree/data_feed_setting_spec.rb new file mode 100644 index 00000000000..b998555a258 --- /dev/null +++ b/core/spec/models/spree/data_feed_setting_spec.rb @@ -0,0 +1,8 @@ +require 'spec_helper' + +describe Spree::DataFeedSetting, type: :model do + let(:store) { create(:store) } + let(:data_feed_setting) { create(:data_feed_setting, store: store) } + + # TODO +end diff --git a/core/spec/models/spree/log_entry_spec.rb b/core/spec/models/spree/log_entry_spec.rb new file mode 100644 index 00000000000..260dfc7d168 --- /dev/null +++ b/core/spec/models/spree/log_entry_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Spree::LogEntry, type: :model do + let(:log_entry) { create(:log_entry, details: details) } + let(:details) { "--- !ruby/object:ActiveMerchant::Billing::Response\nparams: {}\nmessage: 'Bogus Gateway: Forced success'\nsuccess: true\ntest: true\nauthorization: \nfraud_review: \nerror_code: \nemv_authorization: \nnetwork_transaction_id: \navs_result:\n code: \n message: \n street_match: \n postal_match: \ncvv_result:\n code: \n message: \n" } + + describe '#parsed_details' do + subject { log_entry.parsed_details } + + it 'deserializes log entry with billing response' do + expect(subject).to be_instance_of(ActiveMerchant::Billing::Response) + expect(subject.message).to eq('Bogus Gateway: Forced success') + end + end +end diff --git a/core/spec/models/spree/payment_spec.rb b/core/spec/models/spree/payment_spec.rb index 37dba7872c0..9dcf0e24702 100644 --- a/core/spec/models/spree/payment_spec.rb +++ b/core/spec/models/spree/payment_spec.rb @@ -855,12 +855,10 @@ context 'when the locale uses a coma as a decimal separator' do before do I18n.backend.store_translations(:fr, number: { currency: { format: { delimiter: ' ', separator: ',' } } }) - I18n.locale = :fr - subject.amount = amount - end + allow(I18n).to receive(:locale).and_return(:fr) + allow(I18n.config).to receive(:locale).and_return(:fr) - after do - I18n.locale = I18n.default_locale + subject.amount = amount end context 'amount is a decimal' do diff --git a/core/spec/models/spree/product_spec.rb b/core/spec/models/spree/product_spec.rb index 455354d39de..9081b32d264 100644 --- a/core/spec/models/spree/product_spec.rb +++ b/core/spec/models/spree/product_spec.rb @@ -352,6 +352,12 @@ class Extension < Spree::Base expect(@product.slugs.with_deleted).not_to be_empty end + it 'keeps translations when product is destroyed' do + @product.destroy + + expect(@product.name).not_to be_empty + end + it 'updates the history when the product is restored' do @product.destroy @@ -433,7 +439,7 @@ class Extension < Spree::Base # Regression test for #2455 it "does not overwrite properties' presentation names" do - Spree::Property.where(name: 'foo').first_or_create!(presentation: "Foo's Presentation Name") + Spree::Property.create!(name: 'foo', presentation: "Foo's Presentation Name") product.set_property('foo', 'value1') product.set_property('bar', 'value2') expect(Spree::Property.where(name: 'foo').first.presentation).to eq("Foo's Presentation Name") diff --git a/core/spec/models/spree/property_spec.rb b/core/spec/models/spree/property_spec.rb index 377f21832f1..ed837dc3fb4 100644 --- a/core/spec/models/spree/property_spec.rb +++ b/core/spec/models/spree/property_spec.rb @@ -84,8 +84,10 @@ let(:product_property_2) { create(:product_property, property: property, product: product_2, value: 'Test Test') } before do - product_property.update_column(:filter_param, nil) - product_property.update_column(:value, 'some value') + product_property.translations.each do |t| + t.update_column(:filter_param, nil) + t.update_column(:value, 'some_value') + end end context 'filterable property' do diff --git a/core/spec/services/spree/data_feeds/google/rss_spec.rb b/core/spec/services/spree/data_feeds/google/rss_spec.rb new file mode 100644 index 00000000000..45a172e89d9 --- /dev/null +++ b/core/spec/services/spree/data_feeds/google/rss_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper' + +module Spree + describe DataFeeds::Google::Rss do + subject { described_class.new } + + let(:store) { create(:store) } + let(:setting) { create(:data_feed_setting, store: store) } + let(:product) { create(:product, stores: [store]) } + let!(:variant) { create(:with_image_variant, product: product) } + let(:result) { subject.call(setting) } + + context 'store header is generated correctly' do + before do + allow(subject).to receive(:store).and_return(store) + end + + it 'include store name' do + expect(result.value[:file]).to include("#{store.name}").once + end + + it 'includes store url' do + expect(result.value[:file]).to include("#{store.url}").once + end + + it 'includes store description' do + expect(result.value[:file]).to include("#{store.meta_description}").once + end + end + + context 'required item attributes are generated correctly' do + before do + allow(subject).to receive(:store).and_return(store) + end + + it 'includes id' do + expect(result.value[:file]).to include("#{variant.id}").once + end + + it 'includes title' do + expect(result.value[:file]).to include("#{product.name} - #{variant.option_values.first.name}").once + end + + it 'includes description' do + expect(result.value[:file]).to include("#{product.description}").once + end + + it 'includes link' do + expect(result.value[:file]).to include("#{store.url}/#{product.slug}").once + end + + it 'includes image link' do + expect(result.value[:file]).to include("#{variant.images.first.plp_url}").once + end + + it 'includes price' do + expect(result.value[:file]).to include("#{variant.price} #{variant.cost_currency}").once + end + + context 'product is set to available' do + it 'shows that product is in stock' do + expect(result.value[:file]).to include('in stock') + end + + it 'shows that product availability date is the same' do + expect(result.value[:file]).to include("#{product.available_on.xmlschema}") + end + end + + context 'product is set to backorderable' do + let(:product) { create(:product, stores: [store], available_on: 1.year.from_now) } + + it 'shows that product is on backorder' do + expect(result.value[:file]).to include('backorder') + end + end + + context 'availability date is nil' do + let(:product) { create(:product, stores: [store], available_on: nil) } + + it 'shows that product is out of stock' do + expect(result.value[:file]).to include('out of stock') + end + + it 'shows that product availability date is nil' do + expect(result.value[:file]).not_to include('') + end + end + end + + context 'optional item attributes are generated correctly' do + let(:product) { create(:product_with_properties, stores: [store]) } + + before do + allow(subject).to receive(:store).and_return(store) + end + + it 'adds brand to item attributes' do + expect(result.value[:file]).to include("#{product.property('brand')}") + end + end + end +end diff --git a/emails/spec/mailers/spree/order_mailer_spec.rb b/emails/spec/mailers/spree/order_mailer_spec.rb index 17e1eb21d48..0c84696fa80 100644 --- a/emails/spec/mailers/spree/order_mailer_spec.rb +++ b/emails/spec/mailers/spree/order_mailer_spec.rb @@ -196,7 +196,9 @@ end context 'via I18n' do - before { I18n.locale = :'pt-BR' } + before do + allow(I18n).to receive(:locale).and_return(:'pt-BR') + end it_behaves_like 'translates emails' end diff --git a/sample/db/samples/data_feed_settings.rb b/sample/db/samples/data_feed_settings.rb new file mode 100644 index 00000000000..1e0c1ab1b6f --- /dev/null +++ b/sample/db/samples/data_feed_settings.rb @@ -0,0 +1,7 @@ +# settings for default store +Spree::DataFeedSetting.create!( + store: Spree::Store.default, + name: 'Default Google Data Feed', + provider: 'google' +) + diff --git a/sample/db/samples/menu_items.rb b/sample/db/samples/menu_items.rb index 4d8020a1d1d..d2848d4b80a 100644 --- a/sample/db/samples/menu_items.rb +++ b/sample/db/samples/menu_items.rb @@ -114,35 +114,35 @@ # Root Items # ############## woman_taxon = Spree::Taxon.find_by!(permalink: 'categories/women') - menu_root_women = Spree::MenuItem.where( - name: root_name_a, - item_type: 'Link', - linked_resource_type: 'Spree::Taxon', - menu_id: menu, - parent_id: menu.root.id - ).first_or_create! + menu_root_women = Spree::MenuItem.first_or_create!({ + name: root_name_a, + item_type: 'Link', + linked_resource_type: 'Spree::Taxon', + menu_id: menu.id, + parent_id: menu.root.id + }) menu_root_women.linked_resource_id = woman_taxon.id menu_root_women.save! men_taxon = Spree::Taxon.find_by!(permalink: 'categories/men') - menu_root_men = Spree::MenuItem.where( - name: root_name_b, - item_type: 'Link', - linked_resource_type: 'Spree::Taxon', - menu_id: menu, - parent_id: menu.root.id - ).first_or_create! + menu_root_men = Spree::MenuItem.first_or_create!({ + name: root_name_b, + item_type: 'Link', + linked_resource_type: 'Spree::Taxon', + menu_id: menu.id, + parent_id: menu.root.id + }) menu_root_men.linked_resource_id = men_taxon.id menu_root_men.save! sw_taxon = Spree::Taxon.find_by!(permalink: 'categories/sportswear') - menu_root_sw = Spree::MenuItem.where( - name: root_name_c, - item_type: 'Link', - linked_resource_type: 'Spree::Taxon', - menu_id: menu, - parent_id: menu.root.id - ).first_or_create! + menu_root_sw = Spree::MenuItem.first_or_create!({ + name: root_name_c, + item_type: 'Link', + linked_resource_type: 'Spree::Taxon', + menu_id: menu.id, + parent_id: menu.root.id + }) menu_root_sw.linked_resource_id = sw_taxon.id menu_root_sw.save! @@ -151,57 +151,52 @@ ############## # Categories # ############## - menu_cat_women = Spree::MenuItem.where( - name: categories, - item_type: 'Container', - code: 'category', - menu_id: menu, - parent_id: menu_root_women - ).first_or_create! - - menu_cat_men = Spree::MenuItem.where( - name: categories, - item_type: 'Container', - code: 'category', - menu_id: menu, - parent_id: menu_root_men - ).first_or_create! - - menu_cat_sw = Spree::MenuItem.where( - name: categories, - item_type: 'Container', - code: 'category', - menu_id: menu, - parent_id: menu_root_sw - ).first_or_create! + menu_cat_women = Spree::MenuItem.first_or_create!({ + name: categories, + item_type: 'Container', + code: 'category', + menu_id: menu.id, + parent_id: menu_root_women + }) + menu_cat_men = Spree::MenuItem.first_or_create!({ + name: categories, + item_type: 'Container', + code: 'category', + menu_id: menu.id, + parent_id: menu_root_men + }) + menu_cat_sw = Spree::MenuItem.first_or_create!({ + name: categories, + item_type: 'Container', + code: 'category', + menu_id: menu.id, + parent_id: menu_root_sw + }) ############## # Promotions # ############## - menu_promo_women = Spree::MenuItem.where( - name: 'Promos', - item_type: 'Container', - code: 'promo', - menu_id: menu, - parent_id: menu_root_women - ).first_or_create! - - menu_promo_men = Spree::MenuItem.where( - name: 'Promos', - item_type: 'Container', - code: 'promo', - menu_id: menu, - parent_id: menu_root_men - ).first_or_create! - - menu_promo_sw = Spree::MenuItem.where( - name: 'Promos', - item_type: 'Container', - code: 'promo', - menu_id: menu, - parent_id: menu_root_sw - ).first_or_create! - + menu_promo_women = Spree::MenuItem.first_or_create!({ + name: 'Promos', + item_type: 'Container', + code: 'promo', + menu_id: menu.id, + parent_id: menu_root_women + }) + menu_promo_men = Spree::MenuItem.first_or_create!({ + name: 'Promos', + item_type: 'Container', + code: 'promo', + menu_id: menu.id, + parent_id: menu_root_men + }) + menu_promo_sw = Spree::MenuItem.first_or_create!({ + name: 'Promos', + item_type: 'Container', + code: 'promo', + menu_id: menu.id, + parent_id: menu_root_sw + }) promos = [menu_promo_women, menu_promo_men, menu_promo_sw] else promos = [] @@ -215,25 +210,25 @@ ##################### promos.each do |promo| - summer_promo = Spree::MenuItem.where( - name: promo_a_name, - subtitle: promo_a_subtitle, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: promo - ).first_or_create! + summer_promo = Spree::MenuItem.first_or_create!({ + name: promo_a_name, + subtitle: promo_a_subtitle, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: promo + }) summer_promo.linked_resource_id = summer.id summer_promo.save! - special_offer = Spree::MenuItem.where( - name: promo_b_name, - subtitle: promo_b_subtitle, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: promo - ).first_or_create! + special_offer = Spree::MenuItem.first_or_create!({ + name: promo_b_name, + subtitle: promo_b_subtitle, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: promo + }) special_offer.linked_resource_id = offers.id special_offer.save! end @@ -249,68 +244,65 @@ end women_skirts_t = Spree::Taxon.find_by!(permalink: 'categories/women/skirts') - women_skirts = Spree::MenuItem.where( - name: skirts, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: women_link_parent_id - ).first_or_create! + women_skirts = Spree::MenuItem.first_or_create!({ + name: skirts, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: women_link_parent_id + }) women_skirts.linked_resource_id = women_skirts_t.id women_skirts.save! women_dresses_t = Spree::Taxon.find_by!(permalink: 'categories/women/dresses') - women_dresses = Spree::MenuItem.where( - name: dresses, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: women_link_parent_id - ).first_or_create! + women_dresses = Spree::MenuItem.first_or_create!({ + name: dresses, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: women_link_parent_id + }) women_dresses.linked_resource_id = women_dresses_t.id women_dresses.save! women_s_b_t = Spree::Taxon.find_by!(permalink: 'categories/women/shirts-and-blouses') - women_s_b = Spree::MenuItem.where( - name: shirts_and_blouses, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: women_link_parent_id - ).first_or_create! - women_s_b.linked_resource_id = women_s_b_t.id - women_s_b.save! - + women_s_b = Spree::MenuItem.first_or_create!({ + name: shirts_and_blouses, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: women_link_parent_id + }) women_sweaters_t = Spree::Taxon.find_by!(permalink: 'categories/women/sweaters') - women_sweaters = Spree::MenuItem.where( - name: sweaters, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: women_link_parent_id - ).first_or_create! + women_sweaters = Spree::MenuItem.first_or_create!({ + name: sweaters, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: women_link_parent_id + }) women_sweaters.linked_resource_id = women_sweaters_t.id women_sweaters.save! women_tops_tees_t = Spree::Taxon.find_by!(permalink: 'categories/women/tops-and-t-shirts') - women_tops_tees = Spree::MenuItem.where( - name: tops_and_t_shirts, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: women_link_parent_id - ).first_or_create! + women_tops_tees = Spree::MenuItem.first_or_create!({ + name: tops_and_t_shirts, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: women_link_parent_id + }) women_tops_tees.linked_resource_id = women_tops_tees_t.id women_tops_tees.save! women_j_c_t = Spree::Taxon.find_by!(permalink: 'categories/women/jackets-and-coats') - women_j_c = Spree::MenuItem.where( - name: jackets_and_coats, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: women_link_parent_id - ).first_or_create! + women_j_c = Spree::MenuItem.first_or_create!({ + name: jackets_and_coats, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: women_link_parent_id + }) women_j_c.linked_resource_id = women_j_c_t.id women_j_c.save! @@ -325,46 +317,46 @@ end men_shirts_t = Spree::Taxon.find_by!(permalink: 'categories/men/shirts') - men_shirts = Spree::MenuItem.where( - name: shirts, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: men_link_parent_id - ).first_or_create! + men_shirts = Spree::MenuItem.first_or_create!({ + name: shirts, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: men_link_parent_id + }) men_shirts.linked_resource_id = men_shirts_t.id men_shirts.save! men_t_shirts_t = Spree::Taxon.find_by!(permalink: 'categories/men/t-shirts') - men_t_shirts = Spree::MenuItem.where( - name: t_shirts, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: men_link_parent_id - ).first_or_create! + men_t_shirts = Spree::MenuItem.first_or_create!({ + name: t_shirts, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: men_link_parent_id + }) men_t_shirts.linked_resource_id = men_t_shirts_t.id men_t_shirts.save! men_sweaters_t = Spree::Taxon.find_by!(permalink: 'categories/men/sweaters') - men_sweaters = Spree::MenuItem.where( - name: sweaters, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: men_link_parent_id - ).first_or_create! + men_sweaters = Spree::MenuItem.first_or_create!({ + name: sweaters, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: men_link_parent_id + }) men_sweaters.linked_resource_id = men_sweaters_t.id men_sweaters.save! men_j_c_t = Spree::Taxon.find_by!(permalink: 'categories/men/jackets-and-coats') - men_j_c = Spree::MenuItem.where( - name: jackets_and_coats, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: men_link_parent_id - ).first_or_create! + men_j_c = Spree::MenuItem.first_or_create!({ + name: jackets_and_coats, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: men_link_parent_id + }) men_j_c.linked_resource_id = men_j_c_t.id men_j_c.save! @@ -379,35 +371,35 @@ end sw_tops_t = Spree::Taxon.find_by!(permalink: 'categories/sportswear/tops') - sw_tops = Spree::MenuItem.where( - name: tops, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: sw_link_parent_id - ).first_or_create! + sw_tops = Spree::MenuItem.first_or_create!({ + name: tops, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: sw_link_parent_id + }) sw_tops.linked_resource_id = sw_tops_t.id sw_tops.save! sw_sweatshirts_t = Spree::Taxon.find_by!(permalink: 'categories/sportswear/sweatshirts') - sw_sweatshirts = Spree::MenuItem.where( - name: sweat_shirts, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: sw_link_parent_id - ).first_or_create! + sw_sweatshirts = Spree::MenuItem.first_or_create!({ + name: sweat_shirts, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: sw_link_parent_id + }) sw_sweatshirts.linked_resource_id = sw_sweatshirts_t.id sw_sweatshirts.save! sw_pants_t = Spree::Taxon.find_by!(permalink: 'categories/sportswear/pants') - sw_pants = Spree::MenuItem.where( - name: pants, - linked_resource_type: 'Spree::Taxon', - item_type: 'Link', - menu_id: menu, - parent_id: sw_link_parent_id - ).first_or_create! + sw_pants = Spree::MenuItem.first_or_create!({ + name: pants, + linked_resource_type: 'Spree::Taxon', + item_type: 'Link', + menu_id: menu.id, + parent_id: sw_link_parent_id + }) sw_pants.linked_resource_id = sw_pants_t.id sw_pants.save! end diff --git a/sample/db/samples/menus.rb b/sample/db/samples/menus.rb index b1346a7b07a..4131ce474ba 100644 --- a/sample/db/samples/menus.rb +++ b/sample/db/samples/menus.rb @@ -1,17 +1,28 @@ Spree::Store.all.each do |store| store.supported_locales_list.each do |locale| - Spree::Menu.where( + main_menu_attrs = { name: 'Main Menu', location: 'header', - store_id: store, + store: store, locale: locale - ).first_or_create! - - Spree::Menu.where( + } + footer_menu_attrs = { name: 'Footer Menu', location: 'footer', - store_id: store, + store: store, locale: locale - ).first_or_create! + } + + if Spree::Menu.where(main_menu_attrs).any? + Spree::Menu.where(main_menu_attrs).first + else + Spree::Menu.create!(main_menu_attrs) + end + + if Spree::Menu.where(footer_menu_attrs).any? + Spree::Menu.where(footer_menu_attrs).first + else + Spree::Menu.create!(footer_menu_attrs) + end end end diff --git a/sample/db/samples/option_types.rb b/sample/db/samples/option_types.rb index 919d55e8e00..33f14de05e8 100644 --- a/sample/db/samples/option_types.rb +++ b/sample/db/samples/option_types.rb @@ -17,5 +17,7 @@ ] option_types_attributes.each do |attrs| - Spree::OptionType.where(attrs).first_or_create! + unless Spree::OptionType.where(attrs).exists? + Spree::OptionType.create!(attrs) + end end diff --git a/sample/db/samples/properties.rb b/sample/db/samples/properties.rb index cd4fd5156b6..1c503d4bf54 100644 --- a/sample/db/samples/properties.rb +++ b/sample/db/samples/properties.rb @@ -14,7 +14,9 @@ } properties.each do |name, presentation| - Spree::Property.where(name: name, presentation: presentation).first_or_create! + unless Spree::Property.where(name: name, presentation: presentation).exists? + Spree::Property.create!(name: name, presentation: presentation) + end end Spree::Property.where(name: %w[brand manufacturer]).update(filterable: true) diff --git a/sample/db/samples/stores.rb b/sample/db/samples/stores.rb index 533e7dd343d..e459689a654 100644 --- a/sample/db/samples/stores.rb +++ b/sample/db/samples/stores.rb @@ -3,12 +3,12 @@ default_store.default_country = Spree::Country.find_by(iso: 'US') default_store.supported_currencies = 'CAD,USD' default_store.supported_locales = 'en,fr' -default_store.url = Rails.env.development? ? 'localhost:3000' : 'demo.spreecommerce.org' +default_store.url = Rails.env.development? ? 'localhost:4000' : 'demo.spreecommerce.org' default_store.save! eu_store = Spree::Store.find_or_initialize_by(code: 'eustore') eu_store.name = 'EU Store' -eu_store.url = Rails.env.development? ? 'eu.lvh.me:3000' : 'demo-eu.spreecommerce.org' +eu_store.url = Rails.env.development? ? 'eu.lvh.me:4000' : 'demo-eu.spreecommerce.org' eu_store.mail_from_address = 'eustore@example.com' eu_store.default_currency = 'EUR' eu_store.default_locale = 'de' @@ -19,7 +19,7 @@ uk_store = Spree::Store.find_or_initialize_by(code: 'ukstore') uk_store.name = 'UK Store' -uk_store.url = Rails.env.development? ? 'uk.lvh.me:3000' : 'demo-uk.spreecommerce.org' +uk_store.url = Rails.env.development? ? 'uk.lvh.me:4000' : 'demo-uk.spreecommerce.org' uk_store.mail_from_address = 'ukstore@example.com' uk_store.default_currency = 'GBP' uk_store.default_locale = 'en' diff --git a/sample/db/samples/taxonomies.rb b/sample/db/samples/taxonomies.rb index 56e245c0c7f..06d29cceb4d 100644 --- a/sample/db/samples/taxonomies.rb +++ b/sample/db/samples/taxonomies.rb @@ -8,5 +8,9 @@ ] taxonomies.each do |taxonomy_attrs| - Spree::Taxonomy.where(taxonomy_attrs).first_or_create! + if Spree::Taxonomy.where(taxonomy_attrs).any? + Spree::Taxonomy.where(taxonomy_attrs).first + else + Spree::Taxonomy.create!(taxonomy_attrs) + end end diff --git a/sample/lib/spree_sample.rb b/sample/lib/spree_sample.rb index ff014991b53..c9f87743c4a 100644 --- a/sample/lib/spree_sample.rb +++ b/sample/lib/spree_sample.rb @@ -32,6 +32,7 @@ def self.load_samples Spree::Sample.load_sample('reimbursements') Spree::Sample.load_sample('return_authorization_reasons') Spree::Sample.load_sample('stores') + Spree::Sample.load_sample('data_feed_settings') Spree::Sample.load_sample('cms_standard_pages') Spree::Sample.load_sample('cms_feature_pages')