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.

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 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.