Adding a Custom Model to Spree

  • por Nicolás Kappes

As I started working with Spree and learning about all of its functionalities, I eventually got to the point where a completely new model was needed. As a Spree beginner it took me quite some time to figure out how to create one that could make use of this eCommerce solutions, especially with the little amount of sources I found. As such, I decided it would be a good idea to make a very basic step-by-step guide on how to create, not a Spree extension, but a custom model from scratch.

What do you need to use this guide?

  • Some basic knowledge of how Ruby on Rails works
  • Your Spree App already set up (to do this you can take a look at the official guide
  • Basic knowledge of how Spree's Core, Frontend and Backend work.

What does this guide include?

  • Adding views to the frontend
  • Adding views to the backend
  • Managing admin tabs and sub-menus (directly from Spree's code).

Note: I found working with Deface absolutely annoying, so most of the solutions in this guide will actually be accomplished by directly adding code to the spree app. In case you want to use Deface refer to the official guide

Lets get started!

Namespaces

Spree makes use of namespaces, which means there will have to be some special considerations in regards to the location and contents of the variety of files you will be creating.

Model

The first step is setting up your custom model and corresponding migration. To do this you can use the rails generate command, and adding the Spree namespace to its name. 

rails generate model Spree::CustomModelName name:string 

Now go to your newly created model (app>models>spree>CustomModelName.rb) and change the inheritance to Spree::Base so that it has all of the functionalities of the Spree eCommerce models:

class Spree::CustomModelName < Spree::Base
end

Make sure you have all your corresponding attributes in your migration and run rake db:migrate in your terminal.

Controller

Next, create your controller files with the corresponding rails command (do not forget that when using this command the name of your model has to be plural!!!). Remember you have to do this both for your frontend and backend.

For the backend:

rails generate controller Spree::Admin::CustomModelNames

For the frontend:

rails generate controller Spree::CustomModelNames

This will also create the corresponding views folders.

Now go to your backend controller file (controllers>spree>admin>custom_model_names_controller.rb) and make sure it inherits from ResourceController:

class Spree::Admin::CustomModelNamesController < ResourceController
end

You also have to modify your front end controller file (controllers>spree>custom_model_names_controller.rb) and make sure it inherits from Spree::StoreController:

class Spree::CustomModelNamesController < Spree::StoreController
end

Routes

Now you have to add your models routes to the Spree Core Engine. To do this go to config>routes.rb and add the corresponding code so it should look something like this:

Rails.application.routes.draw do

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

  Spree::Core::Engine.add_routes do
    namespace :admin do
      resources :custom_model_names
    end
    resources :custom_model_names
  end

end

Remember you have to add it in the :admin name space and in the core engine so both your backend and frontend (respectively) views are routed correctly.

Backend Tabs

To add the tab to your backend without Deface you will have to copy the _main_menu.html.erb file from the Spree code into your corresponding folder:

  • spree/spree/backend/app/views/spree/admin/shared/_main_menu.html.erb

Open this file and add your tab in the position you want it. Your _main_menu file should have something like this:

<% if can? :admin, Spree::CustomModelName %>
  <ul class="nav nav-sidebar">
    <%= main_menu_tree Spree.t(:custom_model_names), icon: "gift", sub_menu: "custom_model_sub", url: "#sidebar-custom-model-name" %>
  </ul>
<% end %>

There are three things to note here:

  1. Spree::CustomModelName has to be the name of your model.
  2. Sub_menu:
    1. sub_menu: "custom_model_sub" needs to have the same name as your sub_menu file (this will be explained in the next step).
    2. url: "#sidebar-custom-model-name" is the id of your sidebar in your sub menu file.

This will let you add items to your menu tab creating a sub menu. For this to work you need to create a sub_menu folder in the admin views (views>spree>admin>shared>sub_menu). In this folder create a file named as pointed out in 1., so _custom_model_sub.html.erb. In this file add the following code:

<ul id="sidebar-custom-model-name-sub" class="collapse nav nav-pills nav-stacked" data-hook="admin_custom_model_name_sub_tabs">
  <%= tab :custom_model_names, url: spree.admin_custom_model_names_path %>
</ul>

In this example the tab is routed to the index of your model.

  1. You will need to add the corresponding translation to your locales file for Spree.t(:custom_model_names) to work correctly. So, for example, if you have a spanish translation you can add:
es:
  spree:
    custom_model_names: Custom Model Spanish translation

You will also need to add the translation for your sub-menu tab (tab :custom_model_names):

es:
  spree:
    admin:
      tab:
        custom_model_names: Custom Model Spanish translation

Views

Now add whatever you want to your views. For this I recommend recycling Spree's code from github. The index example shown below is a modified version of the code for the products views from github/spree: 

<% content_for :page_title do %>
  <%= Spree.t(:custom_model_names) %>
<% end %>

<% content_for :page_actions do %>
  <%= button_link_to Spree.t(:new_custom_model_name), new_object_url, { class: "btn-success", icon: 'add', id: 'admin_new_custom_model_name' } %>
<% end if can?(:create, Spree::CustomModelName) %>

<table class="table" id="listing_products">
  <thead>
    <tr data-hook="admin_products_index_headers">
        <th colspan="2"><%= Spree.t(:attribute1) %></th>
        <th><%= Spree.t(:attribute2) %></th>   
        <th><%= Spree.t(:attribute3) %></th>
        <th data-hook="admin_custom_model_names_index_header_actions" class="actions"></th>
      </tr>
  </thead>
  <tbody>
    <% @custom_model_names.each do |custom_model_name| %>
      <tr id="<%= spree_dom_id custom_model_name %>" data-hook="admin_custom_model_names_index_rows" class="<%= cycle('odd', 'even') %>">
        <td colspan="2"><%= custom_model_name.attribute1 %></td>
        <td><%= custom_model_name.attribute2 %></td>
        <td><%= custom_model_name.attribute3 %></td>

        <td class="actions actions-3 text-right" data-hook="admin_custom_model_names_index_row_actions">
          <%= link_to_edit custom_model_name, no_text: true, class: 'edit' if can?(:edit, custom_model_name) %>
          <%= link_to_delete custom_model_name, no_text: true if can?(:delete, custom_model_name) %>
        </td>

      </tr>
    <% end %>
  </tbody>
</table>

Special Considerations

  1. For your javascripts to be automatically included you will need to add them to vendor>assets>javascripts>spree in the folder frontend or backend.
  1. As a general rule, whenever Spree.t('text') is used you will need to add the corresponding translation for 'text' to your locales file.

And that's it!

You should now have a custom model set up to your liking in the frontend and backend of your application. 

 

References

thumbnail