Ruby on Rails E-commerce API for Beginners. Part 1.

Have you ever bought anything online? Millions of people do it around the globe every day. E-commerce has already become very popular and is still gaining 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 of them 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 company is not behind the progress too: we create great and successful online-service apps for our clients. Check out GoPuff as on of the examples.

If you like the examples and if you are a beginner in developing e-commerce applications then you could find our further information quite useful.
Our developers work with Ruby on Rails to provide back-end solutions for projects. It is not an easy “cooking” to do, especially for those who only try their efforts in it. But don’t worry, we have prepared a detailed “recipe” for beginners on how to create 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”.
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.
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
- 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.
- 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 setup the routes to the resources.
As the next step let’s plan the API 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 initializeresource, buildresource, resource. This will help us to keep the controllers lightweight.
- It is the right time to add something any shop (and online-shop too) is impossible without - 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.

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!

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 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 (createdat) or when it was updated (updatedat).
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.
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 asjson if we call tojson in a template. Everything is very simple if you take a closer look at how the tojson method works in the original code: asjson 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.
And 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 but this is all for today only. Don’t forget to review our example project on API for beginners that is available in open source.
As you can see, using Ruby on Rails for e-commerce solutions may be challenging but practice makes perfect. Next time we will unveil the second part of the ‘recipe’ that will deal with searching products using Full Text Search in PostgreSQL.
We hope this manual will be a good start for you to become a skilled Ruby on Rails developer. Keep up the good work! See you soon!
Looking for a professional team of back-end developers?
At MLSDev, we have talented and skilled back-end developers who are ready to deal with the most challenging tasks and create great products. Contact us to discuss your project.