How to Build an eCommerce API With Ruby on Rails Part 1

Have you ever bought anything online? Millions of people do it around the globe every day. e-commerce is an essential part of business for many retailers and it continues to gain more and more supporters.

It is not only the way of buying things but of selling them as well. One cannot even count how many online-shops there are on the Internet. Some of them are very poorly made, but you can also see eye-catching ones. Guess which ones are more likely to attract a new customer?

E-commerсe sites have no geographical limits so the number of people who would like to do this kind of business is constantly growing. 

Our developers work with Ruby on Rails to provide back-end solutions for projects. It is not easy to “cook” up a project like this, especially for those without much experience. But don’t worry, we have prepared a detailed “recipe” for beginners on how to create an API for an e-commerce website. Just follow our detailed instructions and you will succeed.

Today we will present the first part of our “e-commerce API recipe” that deals with creating products. It is going to be very informative so get ready and start the “development cooking”.

1. First of all, you need to install Ruby 2.2.3 and Ruby on Rails 4.2.4. You can read about the installation process here

2. Create a new app and name it “Shop” for example. Write the command in the console to do that:

rails new Shop --skip-sprockets --skip-spring --skip-javascript --skip-turbolinks --skip-test-unit --database=postgresql

To learn more about the keys used in the command:

rails new --help

3. Do a preliminary application setup. Set up Ruby on Rails generators first by placing this code into the file config/initializers/generators.rb.

Rails.application.configure do 
  config.generators do |g|
    g.orm :active_record, migration: true
    g.test_framework :rspec, fixtures: false
    g.helper false
    g.decorator false
    g.controller assets: false
  end
end

The next step is testing environment setup. We will use RSpec for the app testing. Not a big deal, just add the following to the Gemfile:

...

group :development, :test do
  gem 'rspec-rails'
end

group :test do
  gem 'shoulda-matchers'
  gem 'rspec-activemodel-mocks'
  gem 'rspec-its'
end

To install new gems write the command in the console:

bundle install # (or just bundle)

Then set up RSpec for the project by running this command in the terminal:

rails generate rspec:install # (or use a shortcut: rails g rspec:install)

A newly generated spec/ directory will be the result of all previous instructions completed successfully. This is where we are going to write tests.

4. As we create only API for an e-commerce website we need to write the following code in the file config/routes.rb:

namespace :api do
    end

config/routes.rb is the file where we will set up the routes to the resources.

For the the next step let’s plan the API's architecture. Since all controllers are inherited from ApplicationController we will arrange there such methods as new, create, update, destroy:

class ApplicationController < ActionController::Base
   ...

     def new
       initialize_resource
     end

     def create
      build_resource

      resource.save!
     end

     def update
       resource.update! resource_params
     end

     def destroy
       resource.destroy!
     end
   end
  

Thus, when we create controllers for our online-shop API we will be able to redefine private methods initialize_resource, build_resource, resource. This will help us to keep the controllers lightweight.

5. It is the right time to add something every shop needs - the products. Create the model “Product”, that will contain such fields for example: name, price, description. Let’s type the command in the console:

rails generate model Product name:string price:integer description:text

This command will generate the model product.rb in the folder app/models/ with the fields name(type string), price(type integer), description(type text) and the migration to the database db/migrate/(timestamp)_create_products.rb. The file where we will write tests for the model spec/models/product_spec.rb. will be generated too.
Then write the command in the console to create a database:

rake db:create

After that write the command to conduct the created migration to the database:

rake db:migrate

The next step is to create an API controller ProductsController where we will process requests to API of the products. Let’s start with writing tests for the controller. Create the file spec/controllers/api/products_controller_spec.rb and add the following code:

require 'rails_helper'

   describe Api::ProductsController do

   end

We will write tests for routes first:
 

require 'rails_helper'

   describe Api::ProductsController do
     it { should route(:get, '/api/products').to(action: :index) }

     it { should route(:get, '/api/products/1').to(action: :show, id: 1) }
   end

Go to the console and type the command rake, you will see that tests do not work and there are two errors on the screen.

Ruby eCommerce API errors

What's next? Has everything been done in vain? Don’t panic, we just need to fix that. Go to config/routes.rb and write:
 

namespace :api do
     resources :products, only: [:index, :show]
   end

Let’s check if the tests run this time. Drumroll… Voila, there are two green dots!

Ruby eCommerce API success

We also need to write tests for actions. Let’s add them to other tests.

RSpec.describe Api::ProductsController, type: :controller do

...

  describe '#index.json' do
    before { get :index, format: :json }

    it { should render_template :index }
  end

  describe '#show.json' do
    before { get :show, id: 1, format: :json }

    it { should render_template :show }
  end

  describe '#collection' do
    before { expect(Product).to receive(:all) }

    it { expect { subject.send :collection }.to_not raise_error }
  end

  describe '#resource' do
    before { expect(subject).to receive(:params).and_return({ id: 1 }) }

    before { expect(Product).to receive(:find).with(1) }

    it { expect { subject.send :resource }.to_not raise_error }
  end
end

Not a surprise they don’t run now. Create a file app/controllers/api/products_controller.rb. and write a code there:

class Api::ProductsController < ApplicationController
     private
     def collection
       @products ||= Product.all
     end

     def resource
       @product ||= Product.find params[:id]
     end
   end

Add one line of code to ApplicationController from which our new controller is inherited.

class ApplicationController < ActionController::Base
    ...

     helper_method :resource, :collection

    ...
   end

Having done this we can call methods resource and collection directly from views. No need to write action methods index and show. They are kept in the ActionController::Base from which all other controllers are inherited. They render templates with their names by default and this is exactly what we need.

Now we have to create templates and this is where the most interesting part begins. All the templates (views) will be the same, even when we add a shopping cart and orders. They will look like those for the products. That is why we need to learn about the inheritance of views in Ruby on Rails.

All of them are inherited from the application, which means in our case that Ruby on Rails  will look for templates in the directory app/views/application if it doesn’t find them in the directory app/views/api/products. If it doesn’t find them in the latter one as well, you will see an error.

So let’s place the templates in the views/application directory. Create two files index.json.erb and show.json.erb. They should be in the json format because the API of the shop will transfer data in it. Add this code to index.json.erb:

<%= sanitize collection.to_json %>

And to show.json.erb:

<%= sanitize resource.to_json %>

Done. Let’s check the performance of the newly written API for the products. Launch the tests first and make sure they have run smoothly, if not - you have done something wrong.
 
Now we have to check the API performance with the help of the curl command in the console. Let’s add a few products to the database. Run the console command rails console and write, for example:

Product.create!([{ name: 'apple', price: 5, description: 'green' },
                    { name: 'beer', price: 10, description: 'cold' }])

The create! method will show an error if you do something wrong.

This code will enter two products in the database. Let’s make a request to the API with the curl command (don’t forget to launch a local server in the console with the rails server command). Write the following command in the console:

curl -H "Accept: application/json" "http://localhost:3000/api/products"

This should show the data of the products we have worked on. Let’s check the show action.

curl -H "Accept: application/json" "http://localhost:3000/api/products/1"

It shows information about the product with id = 1.

Great! The products API works fine but it displays unnecessary information such as time when the product was created (created_at) or when it was updated (updated_at).

Let’s think how we can change it without transferring the parameter (only: [:id, :name, :price, :description]) to the to_json method. We should keep in mind that all templates are the same for the whole app and there will be no such fields in another model. So what should we do? Connect the gem 'draper'. You can read about it here: https://github.com/drapergem/draper. Let’s create a new decorator app/decorators/product_decorator.rb and write the code there:

class ProductDecorator < Draper::Decorator
     delegate_all

     def as_json *args
       {
        id: id,
        name: name,
        price: price,
        description: description
       }
     end
   end

You could probably ask why the method is named as_json if we call to_json in a template. Everything is very simple if you take a closer look at how the to_json method works in the original code: as_json is called within it. If you redefine this method with the necessary hash you will get the data needed. Don’t forget to call the decorate method in templates:

In index.json.erb:

<%= sanitize collection.decorate.to_json %>

In show.json.erb:

<%= sanitize resource.decorate.to_json %>

You should also remember to write tests for the decorator in the file spec/decorators/product_decorator.rb.

require 'rails_helper'

   describe ProductDecorator do
     describe '#as_json' do
       let(:product) { stub_model Product, id: 1, name: 'apple', price: 10.0, description: 'green' }

       subject { product.decorate.as_json }

       its([:id]) { should eq 1 }

       its([:name]) { should eq 'apple' }

       its([:price]) { should eq 10.0 }

       its([:description]) { should eq 'green' }
     end
   end

Check if the tests have run properly and make requests again with the curl command (don’t forget to restart the server as we have just added new files to the app). You should see the data you need on the screen. What if a request to show action will be with an id that does not exist in the database? Then type the following in the code line in the app/controllers/api/products_controller.rb file:

@product ||= Product.find params[:id]

The find method will raise ActiveRecord::RecordNotFound exception and it will render the 404 error. To display this error in the json format add it to the ApplicationController that will handle this exception and render the exception.json.erb. template.

class ApplicationController < ActionController::Base
     ...

     rescue_from ActiveRecord::RecordNotFound do |exception|
       @exception = exception

       render :exception
     end

     ...
   end

Let’s create views/application/exception.json.erb and write the code there:

<%= sanitize({ errors: { @exception.class.name => [@exception.to_s] } }.to_json) %>

Try to use the curl command to make a request to API for a product with an id that does not exist in the database. Let it be 4 for example:

curl -H "Content-Type: application/json" "http://localhost:3000/api/products/4"

You will see the following error description on the screen:

{
     "errors": {
       "ActiveRecord::RecordNotFound": [
         "Couldn't find Product with 'id'=4"
       ]
     }
   }

API for the e-commerce product is successfully completed. Well done! Not bad as for a beginner but this is all for today only. Next time we will unveil the second part of the ‘recipe’ that will deal with searching products using Full Text Search in PostgreSQL. Don’t miss it! 
 
Links:
Ruby on Rails e-commerce API on GitHub

This part of the series has been republished with permission from MLSDev. The original article can be seen here.

Yuriy Blokhin I am a Ruby on Rails Developer at MLSDev Inc. - one of the Top European IT companies specializing in mobile and web apps development, UI/UX and consulting.

Comments

Comments(1)

Tobiwon

Can you help me. I got this problem when run rake

/home/tobi/Rails/Shop/spec/controllers/api/products_controller_spec.rb:3:in `<top (required)>': uninitialized constant API (NameError)