Hot questions for Using Spree in routes

Question:

When I add the following to the routes

mount Spree::Core::Engine, :at => '/'

it adds all the spree routes to my application but I want only the api routes to be added to the application. What changes should I do for this?


Answer:

Spree is split into spree_core, spree_backend, spree_frontend, spree_api, spree_sample and spree_cmd.

From the Spree README

spree_api (RESTful API)
spree_frontend (User-facing components)
spree_backend (Admin area)
spree_cmd (Command-line tools)
spree_core (Models & Mailers, the basic components of Spree that it can't run without)
spree_sample (Sample data)

Each component appends its own set of routes to the Spree::Core::Engine with add_routes. There is a bit of overlap, for instance spree_api includes some admin routes. With that said, you would still use the same mounting method.

# config/routes.rb
mount Spree::Core::Engine, :at => '/'

However, you would select the Spree components individually instead of the all-inclusive spree gem that includes all routes from all components.

For example:

# Gemfile
gem 'spree_api'

Question:

I've got a ruby 2.4.0, rails 5.0.2, spree 3.2 app. I tried to create a custom admin report for viewing all my inventory products. Ugly for now but works perfectly on development, unlike production where app crashes. When running heroku run rails c it says You may have defined two routes with the same name using the:asoption, or you may be overriding a route already defined by a resource with the same naming.

Following, everything added after the last successful commit, by expected relevance:

routes.rb
Rails.application.routes.draw do
  mount Spree::Core::Engine, at: '/'
  MyApp::Application.routes.draw do
      Spree::Core::Engine.routes.append do
        get '/admin/reports/stock_per_location' => 'admin/reports#stock_per_location', as: 'stock_per_location_admin_reports'
      end
    end
end
production.rb
Rails.application.configure do
  config.cache_classes = true

  config.eager_load = true

  config.consider_all_requests_local       = false
  config.action_controller.perform_caching = true

  config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?

  config.assets.js_compressor = :uglifier
  config.assets.compile = true

  config.log_level = :debug

  config.log_tags = [ :request_id ]

  config.action_mailer.perform_caching = false

  config.i18n.fallbacks = true

  config.active_support.deprecation = :notify
  config.log_formatter = ::Logger::Formatter.new


  if ENV["RAILS_LOG_TO_STDOUT"].present?
    logger           = ActiveSupport::Logger.new(STDOUT)
    logger.formatter = config.log_formatter
    config.logger = ActiveSupport::TaggedLogging.new(logger)
  end

  config.active_record.dump_schema_after_migration = false


  config.paperclip_defaults = {
    storage: :s3,
    s3_credentials: {
      bucket: ENV.fetch('S3_BUCKET_NAME'),
      access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID'),
      secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY'),
      s3_region: ENV.fetch('AWS_REGION'),
      url: ENV.fetch("BUCKET_URL"),
    }
  }

end
reports_controller_decorator.rb
require_dependency 'spree/admin/reports_controller'

Spree::Admin::ReportsController.class_eval do
  add_available_report! :stock_per_location

  def stock_per_location
    @stock_locations = Spree::StockLocation.all
  end
end

Answer:

Aparently creating the routes directly isn't supported by the latest version of Spree, changed my routes.rb and it worked fine.

MyApp::Application.routes.draw do
      Spree::Core::Engine.routes.append do
        #causing troubles on production: get '/admin/reports/stock_per_location' => 'admin/reports#stock_per_location', as: 'stock_per_location_admin_reports', only: [:index]
        namespace :admin do
        resources :reports, only: [:index] do
          collection do
            get :stock_per_location
            #post :total_sales_of_each_product
          end
        end
      end
    end
end

Question:

I'm in process of learning RoR, and obviously Spree and few things aren't clear to me. I'm not totally familiar with Rails engines neither.

My question is should I copy all controllers and routes from Spree github page and then to overwrite them or they are already "booted" through engine?

Also, I noticed that some people use Deface to overwrite things on their own applications. Isn't it easier to copy/paste from github code and then modify it or there is some trick with it?

Thanks


Answer:

The whole idea of gems is that they are package distribution mechanism that you can use instead of copy-pasting code.

Most modern languages have some sort of package distribution. Ruby's package manager of choice is Bundler.

It should always be used instead of copy-pasting because:

  1. Copy-pasting is error prone and tedious.
  2. You're not bloating your code repository with vendor code which makes it easier to maintain.
  3. Package managers can do dependency tree resolution to ensure that your dependencies are compatible with each other.
  4. Its not 1995 and copy-pasting a library will cast doubts on your competency.

If you need to modify a gem for whatever reason you can fork the repository and tell bundler to use your fork. But in most cases this is a last resort as Ruby is an extremely flexible language.

My question is should I copy all controllers and routes from Spree github page and then to overwrite them or they are already "booted" through engine?

No. Just mount the gem. In all likelihood its very configurable and provides options to customize it to your hearts intent without changing any of the gems code.

Or in many cases you can just use object oriented programming to configure your own subtypes of the controllers provided by the gem.

Deface is used to modify views after they are rendered. Its basically a more refined version of using a regular expression and thus quite hacky if the problem can be solved in the first place by using partials or helpers to make the code more modular. It has nothing to do with routes or controllers.

Question:

Right now I'm looking at the edit product view in the admin interface, but every time I try to find the option types, my api returns a 404. This appears to be because Select2 is hitting the following url: /ecommerce/api/option_types?... (not including the actual query on purpose), even though my api url should be at /store/api/ since I have the following in my routes.rb file: mount Spree::Core::Engine, at: '/store'

I confirmed that when I switch the code to mount Spree::Core::Engine, at: '/ecommerce' temporarily in my routes.rb file, my option types appeared correctly in the select2 select box.

My only guess is that earlier in the project, I had mounted the spree engine at /ecommerce (ie mount Spree::Core::Engine, at: '/ecommerce'). But I have since changed the code to mount it at store, and after that have restarted the rails server in the terminal and made sure to hard refresh the browser (in case there was a caching issue somewhere), but still the select2 form is hitting the wrong route (/ecommerce/api/option_types?...).

I tried digging through the source code, but it gets very dense very quick with select2 and its js and everything, haha.

Any guesses on how to fix this?


Answer:

At first I was not able to reproduce the issue. But later I figured it out. The issue is caused by rails caching the assets... you can fix the issue simply by removing the content of tmp/cache/assets/ directory.

Why is the issue happening? The routes used in the backend for JS API calls are defined and stored in the JS object Spree.routes, youn can inspect its contents in the browser javascript console.

These URLs prefix comes from Spree.pathFor defined in core/app/assets/javascripts/spree.js.erb:

Spree.mountedAt = function() {
  return "<%= Rails.application.routes.url_helpers.spree_path(trailing_slash: true) %>";
};

While Rails.application.routes.url_helpers.spree_path changes when you change the Spree mount path, this JS file, once generated, is not going to change, as its MD5 checksum is still the same. So the cached version in the tmp/cache/assets/ directory is going to be used.

Question:

Invoking named routes helpers in views ends with NameError for following configuration:

routes.rb:

scope :orders, as: :orders do
  scope '/:order_id', as: :order do
    post :returns, :to => 'order_returns#create'
  end
end

$rake routes:

orders_order_returns POST /orders/:order_id/returns(.:format) order_returns#create

When I add <%= orders_order_returns_path %> to template, Railsexit with undefined local variable or method 'orders_order_returns_path' for #<#<Class:0x007faec10fe728>:0x007faec10dc8d0>...

Executing Rails.application.routes.named_routes.helpers.map(&:to_s) in console on exception page returns:

["spree_path", "orders_order_returns_path", "rails_info_properties_path", "rails_info_routes_path", "rails_info_path", "rails_mailers_path", "spree_url", "orders_order_returns_url", "rails_info_properties_url", "rails_info_routes_url", "rails_info_url", "rails_mailers_url"]

My question is: why using named routes path/url helpers in view ends with exception even when they are visible in consoles ?


Answer:

I found answer here:

Because you're calling this inside a spree template you need to prefix it with main_app., like main_app.products_starting_with_path

Here's release note from Spree:

Conversely, to reference routes from the main application within a controller, view or template from Spree, you must use the main_app routing proxy

Using main_app routing proxy solved described problem :)

Question:

I added Alchemy CMS to my Spree project that was previously functioning ok. But now none of the routes work. For all routes I get a page not found error. I've done the install and ran all of the migrations.

error
Alchemy::Page not found "/"
Gemfile
ruby '2.2.4'
source 'https://rubygems.org'

gem 'rails', '4.2.5'
gem 'pg', '~> 0.15'
gem 'sass-rails'
gem 'uglifier'
gem 'coffee-rails'
gem 'jquery-rails'
gem 'turbolinks'
gem 'active_model_serializers'
gem 'sdoc', '~> 0.4.0', group: :doc
gem 'spree', github: 'spree/spree', branch: 'master'
gem 'spree_auth_devise', github: 'spree/spree_auth_devise'
gem 'puma'
gem 'paperclip'
gem 'aws-sdk', '< 2.0'
gem 'delayed_job_active_record'

gem 'alchemy_spree', github: 'magiclabs/alchemy_spree', branch: 'master'
# gem 'alchemy_cms', github: 'AlchemyCMS/alchemy_cms', branch: '3.1-stable'
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

group :development, :test do
  gem 'byebug'
end

group :development do
  gem 'web-console', '~> 2.0'
  gem 'spring'
end
config/routes.rb
Rails.application.routes.draw do
  mount Alchemy::Engine => '/'
  mount Spree::Core::Engine, :at => '/shop'
end
config/initializer/alchemy.rb
# Tell Alchemy to use the Spree::User class
Alchemy.user_class_name = 'Spree::User'
Alchemy.current_user_method = :spree_current_user

# Load the Spree.user_class decorator for Alchemy roles
require 'alchemy/spree/spree_user_decorator'

# Include the Spree controller helpers to render the
# alchemy pages within the default Spree layout
Alchemy::BaseHelper.send :include, Spree::BaseHelper
Alchemy::BaseController.send :include, Spree::Core::ControllerHelpers::Common
Alchemy::BaseController.send :include, Spree::Core::ControllerHelpers::Store
config/application.rb
module DistinctExistence
  class Application < Rails::Application
    config.to_prepare do
      # Load application's model / class decorators
      Dir.glob(File.join(File.dirname(__FILE__), '../app/**/*_decorator*.rb')) do |c|
        Rails.configuration.cache_classes ? require(c) : load(c)
      end

      # Load application's view overrides
      Dir.glob(File.join(File.dirname(__FILE__), '../app/overrides/*.rb')) do |c|
        Rails.configuration.cache_classes ? require(c) : load(c)
      end

      Spree::UserSessionsController.class_eval do
        include Alchemy::ControllerActions
      end
    end
    config.time_zone = 'Central Time (US & Canada)'
    config.active_job.queue_adapter = :delayed_job
    config.active_record.raise_in_transactional_callbacks = true
  end
end
config/initializers/spree.rb
Spree.config do |config|
  # Example:
  # Uncomment to stop tracking inventory levels in the application
  # config.track_inventory_levels = false
end

Spree.user_class = 'Spree::User'

attachment_config = {

  s3_credentials: {
    access_key_id:     ENV['AWS_ACCESS_KEY_ID'],
    secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
    bucket:            ENV['S3_BUCKET_NAME']
  },

  storage:        :s3,
  s3_headers:     { 'Cache-Control' => 'max-age=31557600' },
  s3_protocol:    'https',
  bucket:         ENV['S3_BUCKET_NAME'],
  url:            ':s3_domain_url',

  styles: {
    mini:     '48x48>',
    small:    '100x100>',
    product:  '240x240>',
    large:    '600x600>'
  },

  path:           '/:class/:id/:style/:basename.:extension',
  default_url:    '/:class/:id/:style/:basename.:extension',
  default_style:  'product'
}

attachment_config.each do |key, value|
  Spree::Image.attachment_definitions[:attachment][key.to_sym] = value
end
config/alchemy/element.yml
- name: article
  hint: true
  unique: true
  contents:
  - name: headline
    type: EssenceText
    default: :article_headline
    hint: true
  - name: picture
    type: EssencePicture
    hint: true
  - name: text
    type: EssenceRichtext
    default: :article_text
    hint: true

- name: product
  contents:
  - name: spree_product
    type: EssenceSpreeProduct

- name: product_category
  contents:
  - name: spree_taxon
    type: EssenceSpreeTaxon
config/alchemy.page_layouts.yml
- name: index
  unique: true
  elements: [article]
  autogenerate: [article]

- name: product
  elements: [product]
- name: products
  elements: [product_category]

Answer:

Alchemy has a very strong catch all route. Please mount Alchemy as last Engine in your routes file:

Rails.application.routes.draw do
  mount Spree::Core::Engine, :at => '/shop'
  mount Alchemy::Engine => '/'
end

Then all routes of each specific engine will be accessible under it's route prefix.

So, to access Spree admin you need to use /shop/admin, while Alchemy routes are all accessible directly under /.

NOTE: To make redirecting to login paths work, you need to tell Alchemy about this. Following the example above and if you are using spree_auth_devise:

# config/initializers/alchemy.rb
Alchemy.login_path = '/shop/login'

or, if you use your own custom authentication:

# config/initializers/alchemy.rb
Alchemy.login_path = '/sessions/new'