Blog

Spree: Adding Custom Admin Tabs For New Resource

Suman Awal
November 17, 2022

Spree is one of the most popular open-source eCommerce platforms build with Ruby on Rails. Spree is popular for its modularity and flexibility in building and managing the store.

Github: https://github.com/spree/spree

Spree provides a robust and clean admin interface to manage the commerce and different resource although it might need to manage the new resource by admin. In order to manage the new resource by admin user, there needs to create a Admin Tabs/Menu. Here we will learn how to create a Admin Tabs/Menu for the new resources.

Deface for adding Tab

Deface is a library that allow to customize HTML (Erb, Haml, Slim) views in Rails application without editing the underlying view.

Adding New Resource

Let’s add a new resource called Subscription and allow an admin user to perform CRUD operations on a subscription.

Create a model and migration file.

rails generate migration CreateSubscriptions


# above command will create a file with this content in your db/migrate folder
class CreateSubscriptions < ActiveRecord::Migration[6.1]
  def change
   create_table :spree_subscriptions do |t|
     t.string :name
    t.integer :duration_in_month
   t.integer :status, null: false, default: 0
     t.timestamps
   end
 end
end

Execute rake db:migrate to create the table in database and updates the schema.rb.

Create a model.


# app/models/subscription.rb
module Spree
class Subscription < ApplicationRecord
scope :active, -> { where(status: :active) }
enum status: { inactive: 0, active: 1 }
end
end

Create an admin tab using deface.


# app/overrides/admin_subscription.rb
Deface::Override.new(
name: 'subscription_tab',
virtual_path: 'spree/admin/shared/_main_menu',
 insert_after: '#sidebarProduct',
partial: 'spree/admin/shared/subscription_menu'
)

Subscription Menu Partial


# app/views/spree/admin/shared/subscription_menu.html.erb

<% if can? :manage, Spree::Subscription %> 
<ul class="nav nav-sidebar border-bottom" id="sidebarSubscriptions">
<%= tab 'Subscriptions', icon: "icon.svg", sub_menu: "services", url: "admin_subscriptions_path" %>
</ul>
<% end %>

Routes and Controllers


# config/routes.rb
Spree::Core::Engine.routes.draw do
namespace :admin do
resources :subscriptions
end
end

# app/controllers/spree/admin/subscriptions_controller.rb
module Spree
module Admin
# Aside from index, using the inherited actions as they are sufficient.
class SubscriptionsController < Spree::Admin::ResourceController</pre
def index
@subscriptions = Spree::Subscriptions.active
end

end</pre> end</pre> end</pre>

Views


# app/views/spree/admin/subscriptions/index.html.erb
<% content_for :page_title do %>
Subscriptions
<% end %>
<% if can? :manage, Spree::Subscriptions %>
<% content_for :page_actions do %>
<%= button_link_to 'New Subscription', new_admin_subscription_path, class: 'btn-success', icon: 'add.svg', id: 'new-admin-subscription' %>
<% end %>
<% end %>
<table class="table sortable" data-hook="subscriptions_table" data-sortable-link="<%= update_positions_admin_subscriptions_url %>">
<thead>
<tr data-hook="subscriptions_header">
<th>Name</th>
<th>Duration</th>
 <th>Status</th>
<th class="actions"></th>
</tr>
</thead>
<tbody>
<% @subscriptions.each do |subscription| %>
 <tr id="<%= spree_dom_id subscription %>" data-hook="subscriptions_row">
     <td><%= subscription.name %></td>
     <td><%= subscription.duration_in_month %> month</td>
     <td><%= subscription.status %></td>
   <td class="actions actions-2 text-right">
    <%= link_to_edit subscription, no_text: true, class: 'edit' if can?(:edit, subscription) %>
   <!-- Add translation icon support -->
   <%= link_to_with_icon 'translate',
                        nil,
                        spree.admin_translations_path('subscription', subscription.id),
title: Spree.t(:'i18n.translations'),
class: 'btn btn-sm btn-primary' if defined? (SpreeGlobalize)%>
</td>
</tr>
<% end %>
</tbody>
</table>


# app/views/spree/admin/subscriptions/_form.html.erb
<%= render partial: 'spree/admin/shared/error_messages', locals: { target: @subscription } %>
<div class="form-group">
<%= form_for [:admin, @object] do |form| %>
<%= f.field_container :name do %>
<%= f.label :name, 'Name' %>
<%= f.text_field :name, required: true %>
<% end %>
<%= f.field_container :duration_in_month do %>
 <%= f.label :duration_in_month, 'Duration In Month' %>
<%= f.text_field, :duration_in_month, required: true %>
<% end %>
<%= f.field_container :status do %>
<%= f.label :status %>
<%= f.radio_button :status, :active %>
<%= f.label :status, 'Active', :value => :active %>
<%= f.radio_button :status, :inactive %>
<%= f.label :status, 'Inactive', :value => :inactive %>
<% end %>


<div class="form-actions" data-hook="buttons">
<%= button (@object.persisted? ? Spree.t('actions.update') : 
Spree.t('actions.create')), 'save.svg' %>
<span class="or"><%= Spree.t(:or) %></span>
<%= button_link_to Spree.t('actions.cancel'), admin_subscriptions_path, icon: "cancel.svg" %>
</div>
<% end %>
</div>


# app/views/spree/admin/new.html.erb
<% content_for :page_title do %>
<%= link_to plural_resource_name(Spree::Subscription), 
spree.admin_subscriptions_url %> /
 New Subscription
<% end %>
<%= form_for [:admin, @subscription] do |f| %>
<fieldset>
<%= render partial: 'form', locals: { f: f } %>
<%= render partial: 'spree/admin/shared/new_resource_links' %>
</fieldset>
<% end %>


# app/views/spree/admin/edit.html.erb
<% content_for :page_title do %>
<%= link_to plural_resource_name(Spree::Subscription), 
spree.admin_subscriptions_url %> /
<%= @subscription.name %>
<% end %>


<%= form_for [:admin, @subscription] do |f| %>
<fieldset>
<%= render partial: 'form', locals: { f: f } %>
<%= render partial: 'spree/admin/shared/edit_resource_links' %>
</fieldset>
<% end %>


That’s it! Once the above is in place, restart your app for good measure and you will see a “Subscriptions” menu in your admin area. From there, you’ll have control over your Subscription records.