Sunday, 13 March 2016

How to make REST API in Rails | Part 2

How to make REST API in Rails

Making a simple API in rails 

We have seen in the part-1 of this post "How to make an end point through ReST to get the user info as Json". If you haven't covered up part-1, please go through it to have a better understanding of this tutorial.


So far, we have created an end point for show action which look like:
http://localhost:3000/api/v1/users/1

Now we will make more endpoints to create, update and delete a user.

Create action in Rails API

Let's code "create" action in app/controllers/api/v1/users_controller.rb


class Api::V1::UsersController < ApplicationController
  respond_to :json
  skip_before_filter  :verify_authenticity_token

  def show
    respond_with User.find(params[:id])
  end

  def create
   user=User.new(user_params) 
        # if the user is saved successfully than respond with json data and status code 201
        if user.save 
    render json: user, status: 201
   else
    render json: { errors: user.errors}, status: 422
   end
  end

  private
  def user_params
   params.require(:user).permit(:email, :password, :password_confirmation) 
        end
end 

Here we have another private method user_params to sanitize the attributes to be assigned through mass-assignment. After successful creation of the user we are responding with the json data and status code 201.

Now, we need to tell our config/routes.rb file that we also have create action. Let's add create action in config/routes.rb file


Rails.application.routes.draw do

  # API routes path
  namespace :api, defaults: { format: :json } do
    namespace :v1 do
      #devise_for :users
      resources :users, :only => [:show, :create]
    end
  end  
end

Hurray!!! We have successfully created the endpoint to create a user  which you can check through by sending a post request through POSTMAN (A tool to check API endpoints).

POST request through postman to create a user

Response of the POST request



Update action in Rails API

Time to update the user being already created. The update action responds to PUT/PATCH request.

Let's add update action to app/controllers/api/v1/users_controller.rb

class Api::V1::UsersController < ApplicationController
  respond_to :json
  skip_before_filter  :verify_authenticity_token

  def show
    respond_with User.find(params[:id])
  end

  # Creating users
  def create
    user=User.new(user_params)

    if user.save 
      render json: user, status: 201 
    else
      render json: { errors: user.errors}, status: 422
    end
  end

  # Updating users
  def update
    user = User.find(params[:id])

    if user.update(user_params)
      render json: user, status: 200
    else
      render json: { errors: user.errors }, status: 422
    end
  end

  private

  def user_params
   params.require(:user).permit(:email, :password, :password_confirmation)
  end
end

You might have guessed the next step, yes, now we need to add the update action in config/routes.rb file too. So let's do it.

Rails.application.routes.draw do

  # API routes path
  namespace :api, defaults: { format: :json } do
    namespace :v1 do
      #devise_for :users
      resources :users, :only => [:show, :create, :update]
    end
  end  
end

Now Let's test our update action.

Update action through POSTMAN 



Update action response




Destroy action in Rails API

Destroy action is used to delete a record and here we are going to delete a user. So let's add the destroy action in app/controllers/api/v1/users_controller.rb

class Api::V1::UsersController < ApplicationController
  respond_to :json
  skip_before_filter  :verify_authenticity_token

  def show
    respond_with User.find(params[:id])
  end

  # Creating users
  def create
    user=User.new(user_params)

    if user.save 
      render json: user, status: 201 
    else
      render json: { errors: user.errors}, status: 422
    end
  end

  # Updating users
  def update
    user = User.find(params[:id])

    if user.update(user_params)
      render json: user, status: 200
    else
      render json: { errors: user.errors }, status: 422
    end
  end

  # Deleting users
  def destroy
    user = User.find(params[:id])
    user.destroy
    head 204
  end

  private

  def user_params
   params.require(:user).permit(:email, :password, :password_confirmation)
  end
end

Here we are deleting the user with a particular ID which is being passed as a parameter and after successful deletion of the user we are giving 204 code which means the server has processed the request and doesn't return any content. We may also respond with 200 status code but this is more suitable.

Remember to update config/routes.rb file


Rails.application.routes.draw do

  # API routes path
  namespace :api, defaults: { format: :json } do
    namespace :v1 do
      #devise_for :users
      resources :users, :only => [:show, :create, :update, :destroy]
    end
  end  
end

Destroy action with POSTMAN



Destroy action response



Congratulations!! you are done with the CRUD part of a ReSTful API in Rails.

Further you can dockerize this Rails app - Follow this post to dockerize this API

25 comments:

  1. where do I download of the project???

    ReplyDelete
    Replies
    1. We suggest you to write code step by step following this tutorial for better learning and understanding but if you directly want to get the app, here is the project - https://github.com/AjeetK/railsapi

      Delete
  2. respond_to :json not working for me

    any other syntax?

    ReplyDelete
    Replies
    1. What is the error you are getting? Please paste your error trace here.

      Delete
  3. It works now after I commented some other code!
    class Api::V1::ServerdetailsController < ApplicationController
    #class ServerdetailsController < ApplicationController
    respond_to :json
    before_action :set_serverdetail, only: [:show, :edit, :update, :destroy]

    # GET /serverdetails
    def index
    @serverdetails = Serverdetail.all
    render json: @serverdetails
    end

    # GET /serverdetails/1
    def show
    # render json: @serverdetail

    end

    # GET /serverdetails/new
    def new
    @serverdetail = Serverdetail.new
    end

    # GET /serverdetails/1/edit
    def edit
    end

    # POST /serverdetails
    def create
    @serverdetail = Serverdetail.new(serverdetail_params)

    if @serverdetail.save
    # render json: @serverdetail, redirect_to @serverdetail, notice: 'Serverdetail was successfully created.'
    else
    # render json: @servdetail.errors, render :new
    end
    end

    # PATCH/PUT /serverdetails/1
    def update
    if @serverdetail.update(serverdetail_params)
    # render json: @serverdetail,redirect_to @serverdetail, notice: 'Serverdetail was successfully updated.'
    else
    render :edit
    end
    end

    # DELETE /serverdetails/1
    def destroy
    @serverdetail.destroy
    redirect_to serverdetails_url, notice: 'Serverdetail was successfully destroyed.'
    end

    private
    # Use callbacks to share common setup or constraints between actions.
    def set_serverdetail
    @serverdetail = Serverdetail.find(params[:id])
    end

    # Only allow a trusted parameter "white list" through.
    def serverdetail_params
    params.require(:serverdetail).permit(:servername, :cpu, :disk, :process)
    end
    end


    ubuntu@rails-dev-box:/vagrant/r$ curl localhost:3000/api/v1/serverdetails/
    [{"id":1,"servername":"wserver","cpu":23,"disk":34,"process":2,"created_at":"2017-10-31T04:21:55.520Z","updated_at":"2017-10-31T04:21:55.520Z"},{"id":2,"servername":"servertaiwan","cpu":34,"disk":344,"process":3,"created_at":"2017-10-31T04:22:24.390Z","updated_at":"2017-10-31T04:22:24.390Z"},{"id":3,"servername":"333yur","cpu":77,"disk":77,"process":67,"created_at":"2017-10-31T04:24:48.682Z","updated_at":"2017-10-31T04:24:48.682Z"},{"id":4,"servername":"vbvvb","cpu":87,"disk":45,"process":54,"created_at":"2017-10-31T06:45:53.295Z","updated_at":"2017-10-31T06:45:53.295Z"}]ubuntu@rails-dev-box:/vagrant/r$
    ubuntu@rails-dev-box:/vagrant/r$

    The above is the output to curl. What is the curl command to update the values of those existing servers . The app(with api) is to receive curl inputs containing server(linux) command outputs for cpu usage, disk usage etc..Do you know how such values means the output of linux commands could be passed using curl? say I will set a cron job in linux server which will send the values of cpu , disk usage etc every 3 hours or so?

    ReplyDelete
    Replies
    1. You can write a bash script which collects these metrics and store it in json format which is compatible with your api post data. Then you can pass this json data with -d option to your curl request with method post.

      Delete
  4. oops sorry the api part still does not work with respond_to :json
    here is the full trace

    Routing Error
    undefined method `respond_to' for Api::V1::ServerdetailsController:Class

    Rails.root: /vagrant/r
    Application Trace | Framework Trace | Full Trace

    app/controllers/api/v1/serverdetails_controller.rb:3:in `'
    app/controllers/api/v1/serverdetails_controller.rb:1:in `'
    activesupport (5.1.4) lib/active_support/dependencies.rb:476:in `load'
    activesupport (5.1.4) lib/active_support/dependencies.rb:476:in `block in load_file'
    activesupport (5.1.4) lib/active_support/dependencies.rb:661:in `new_constants_in'
    activesupport (5.1.4) lib/active_support/dependencies.rb:475:in `load_file'
    activesupport (5.1.4) lib/active_support/dependencies.rb:374:in `block in require_or_load'
    activesupport (5.1.4) lib/active_support/dependencies.rb:36:in `block in load_interlock'
    activesupport (5.1.4) lib/active_support/dependencies/interlock.rb:12:in `block in loading'
    activesupport (5.1.4) lib/active_support/concurrency/share_lock.rb:149:in `exclusive'
    activesupport (5.1.4) lib/active_support/dependencies/interlock.rb:11:in `loading'
    activesupport (5.1.4) lib/active_support/dependencies.rb:36:in `load_interlock'
    activesupport (5.1.4) lib/active_support/dependencies.rb:357:in `require_or_load'
    activesupport (5.1.4) lib/active_support/dependencies.rb:510:in `load_missing_constant'
    activesupport (5.1.4) lib/active_support/dependencies.rb:202:in `const_missing'
    activesupport (5.1.4) lib/active_support/inflector/methods.rb:284:in `const_get'
    activesupport (5.1.4) lib/active_support/inflector/methods.rb:284:in `block in constantize'
    activesupport (5.1.4) lib/active_support/inflector/methods.rb:267:in `each'
    activesupport (5.1.4) lib/active_support/inflector/methods.rb:267:in `inject'
    activesupport (5.1.4) lib/active_support/inflector/methods.rb:267:in `constantize'
    activesupport (5.1.4) lib/active_support/dependencies.rb:582:in `get'
    activesupport (5.1.4) lib/active_support/dependencies.rb:613:in `constantize'
    actionpack (5.1.4) lib/action_dispatch/http/request.rb:85:in `controller_class_for'
    actionpack (5.1.4) lib/action_dispatch/http/request.rb:78:in `controller_class'
    actionpack (5.1.4) lib/action_dispatch/routing/route_set.rb:43:in `controller'
    actionpack (5.1.4) lib/action_dispatch/routing/route_set.rb:29:in `serve'
    actionpack (5.1.4) lib/action_dispatch/journey/router.rb:50:in `block in serve'
    actionpack (5.1.4) lib/action_dispatch/journey/router.rb:33:in `each'
    actionpack (5.1.4) lib/action_dispatch/journey/router.rb:33:in `serve'
    actionpack (5.1.4) lib/action_dispatch/routing/route_set.rb:834:in `call'
    rack (2.0.3) lib/rack/etag.rb:25:in `call'
    rack (2.0.3) lib/rack/conditional_get.rb:25:in `call'
    rack (2.0.3) lib/rack/head.rb:12:in `call'

    ReplyDelete
  5. rack (2.0.3) lib/rack/session/abstract/id.rb:232:in `context'
    rack (2.0.3) lib/rack/session/abstract/id.rb:226:in `call'
    actionpack (5.1.4) lib/action_dispatch/middleware/cookies.rb:613:in `call'
    activerecord (5.1.4) lib/active_record/migration.rb:556:in `call'
    actionpack (5.1.4) lib/action_dispatch/middleware/callbacks.rb:26:in `block in call'
    activesupport (5.1.4) lib/active_support/callbacks.rb:97:in `run_callbacks'
    actionpack (5.1.4) lib/action_dispatch/middleware/callbacks.rb:24:in `call'
    actionpack (5.1.4) lib/action_dispatch/middleware/executor.rb:12:in `call'
    actionpack (5.1.4) lib/action_dispatch/middleware/debug_exceptions.rb:59:in `call'
    web-console (3.5.1) lib/web_console/middleware.rb:135:in `call_app'
    web-console (3.5.1) lib/web_console/middleware.rb:20:in `block in call'
    web-console (3.5.1) lib/web_console/middleware.rb:18:in `catch'
    web-console (3.5.1) lib/web_console/middleware.rb:18:in `call'
    actionpack (5.1.4) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'
    railties (5.1.4) lib/rails/rack/logger.rb:36:in `call_app'
    railties (5.1.4) lib/rails/rack/logger.rb:24:in `block in call'
    activesupport (5.1.4) lib/active_support/tagged_logging.rb:69:in `block in tagged'
    activesupport (5.1.4) lib/active_support/tagged_logging.rb:26:in `tagged'
    activesupport (5.1.4) lib/active_support/tagged_logging.rb:69:in `tagged'
    railties (5.1.4) lib/rails/rack/logger.rb:24:in `call'
    sprockets-rails (3.2.1) lib/sprockets/rails/quiet_assets.rb:13:in `call'
    actionpack (5.1.4) lib/action_dispatch/middleware/remote_ip.rb:79:in `call'
    actionpack (5.1.4) lib/action_dispatch/middleware/request_id.rb:25:in `call'
    rack (2.0.3) lib/rack/method_override.rb:22:in `call'
    rack (2.0.3) lib/rack/runtime.rb:22:in `call'
    activesupport (5.1.4) lib/active_support/cache/strategy/local_cache_middleware.rb:27:in `call'
    actionpack (5.1.4) lib/action_dispatch/middleware/executor.rb:12:in `call'
    actionpack (5.1.4) lib/action_dispatch/middleware/static.rb:125:in `call'
    rack (2.0.3) lib/rack/sendfile.rb:111:in `call'
    railties (5.1.4) lib/rails/engine.rb:522:in `call'
    puma (3.10.0) lib/puma/configuration.rb:225:in `call'
    puma (3.10.0) lib/puma/server.rb:605:in `handle_request'
    puma (3.10.0) lib/puma/server.rb:437:in `process_client'
    puma (3.10.0) lib/puma/server.rb:301:in `block in run'
    puma (3.10.0) lib/puma/thread_pool.rb:120:in `call'
    puma (3.10.0) lib/puma/thread_pool.rb:120:in `block in spawn_thread'

    ReplyDelete
  6. The below file is routes.rb
    Rails.application.routes.draw do
    resources :serverdetails
    root "serverdetails#index"
    namespace :api, defaults: {format: :json} do
    namespace :v1 do

    resources :serverdetails, only: [:index, :create, :show, :update, :destroy]
    # resources :microposts, only: [:index, :create, :show, :update, :destroy]

    end
    end

    # mount API::Base, at: "/"
    # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
    end

    ReplyDelete
  7. I changed the serverdetailscontroller.rb to the below one as in your code in the website..still the same error.
    class Api::V1::ServerdetailsController < ApplicationController
    respond_to :json
    def show
    respond_with Serverdatail.find(params[:id])
    end

    # Creating users
    def create
    serverdetail=Serverdetail.new(serverdetail_params)

    if serverdetail.save
    render json: serverdetail, status: 201
    else
    render json: { errors: serverdetail.errors}, status: 422
    end
    end

    # Updating users
    def update
    serverdetail = Serverdetail.find(params[:id])

    if serverdetail.update(serverdetail_params)
    render json: serverdetail, status: 200
    else
    render json: { errors: serverdetail.errors }, status: 422
    end
    end

    private

    def serverdetail_params
    params.require(:serverdetail).permit(:servername, :cpu, :disk , :process)
    end
    end

    ReplyDelete
  8. How do I update existing values or create new values? The below commands are not causing any changes or creating any new values.

    ubuntu@rails-dev-box:/vagrant/r$ curl -X PUT -H '{serverdetails:{servername: "hexagon", cpu: "22", disk:"23", process:"55"}}' localhost:3000/api/v1/serverdetails/3
    ubuntu@rails-dev-box:/vagrant/r$ curl -X POST -H '{serverdetails:{servername: "hexagon", cpu: "22", disk:"23", process:"55"}}' localhost:3000/api/v1/serverdetails/30
    ubuntu@rails-dev-box:/vagrant/r$

    ReplyDelete
  9. ActiveRecord::Schema.define(version: 20171031041803) do

    create_table "serverdetails", force: :cascade do |t|
    t.string "servername"
    t.integer "cpu"
    t.integer "disk"
    t.integer "process"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    end

    end

    ReplyDelete
  10. This comment has been removed by the author.

    ReplyDelete
  11. irb(main):002:0> Serverdetail.all
    Serverdetail Load (42.9ms) SELECT "serverdetails".* FROM "serverdetails" LIMIT ? [["LIMIT", 11]]
    => #, #, #, #]>
    irb(main):003:0>

    ReplyDelete
  12. Is this correct?
    Rails.application.routes.draw do
    resources :serverdetails
    root "serverdetails#index"
    namespace :api, defaults: {format: :json} do
    namespace :v1 do

    resources :serverdetails, only: [:index, :create, :show, :update, :destroy]
    # resources :microposts, only: [:index, :create, :show, :update, :destroy]
    get 'serverdetails/:id' => 'serverdetails#show'
    patch 'serverdetails/:id' => 'serverdetails#update'

    get 'serverdetails/' => 'serverdetails#index'
    end
    end

    # mount API::Base, at: "/"
    # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
    end

    ReplyDelete
  13. In PostmanI always get the error below:
    There was an error in evaluating the Pre-request Script: SyntaxError: Unexpected token :
    when pasting this content:
    '{"serverdetails":{"servername": "hexagon", "cpu": "22", "disk":"23", "process":"55"}}'

    ReplyDelete
  14. ubuntu@rails-dev-box:/vagrant/r$ rake routes
    RubyDep: WARNING: Your Ruby has security vulnerabilities! (To disable warnings, set RUBY_DEP_GEM_SILENCE_WARNINGS=1)
    RubyDep: WARNING: Your Ruby is: 2.2.3 (insecure). Recommendation: install 2.2.5 or 2.3.1. (Or, at least to 2.2.4 or 2.3.0)
    Prefix Verb URI Pattern Controller#Action
    serverdetails GET /serverdetails(.:format) serverdetails#index
    POST /serverdetails(.:format) serverdetails#create
    new_serverdetail GET /serverdetails/new(.:format) serverdetails#new
    edit_serverdetail GET /serverdetails/:id/edit(.:format) serverdetails#edit
    serverdetail GET /serverdetails/:id(.:format) serverdetails#show
    PATCH /serverdetails/:id(.:format) serverdetails#update
    PUT /serverdetails/:id(.:format) serverdetails#update
    DELETE /serverdetails/:id(.:format) serverdetails#destroy
    root GET / serverdetails#index
    api_v1_serverdetails GET /api/v1/serverdetails(.:format) api/v1/serverdetails#index {:format=>:json}
    POST /api/v1/serverdetails(.:format) api/v1/serverdetails#create {:format=>:json}
    api_v1_serverdetail GET /api/v1/serverdetails/:id(.:format) api/v1/serverdetails#show {:format=>:json}
    PATCH /api/v1/serverdetails/:id(.:format) api/v1/serverdetails#update {:format=>:json}
    PUT /api/v1/serverdetails/:id(.:format) api/v1/serverdetails#update {:format=>:json}
    DELETE /api/v1/serverdetails/:id(.:format) api/v1/serverdetails#destroy {:format=>:json}
    api_v1 GET /api/v1/serverdetails/:id(.:format) api/v1/serverdetails#show {:format=>:json}
    PATCH /api/v1/serverdetails/:id(.:format) api/v1/serverdetails#update {:format=>:json}
    POST /api/v1/serverdetails(.:format) api/v1/serverdetails#create {:format=>:json}
    GET /api/v1/serverdetails(.:format) api/v1/serverdetails#index {:format=>:json}
    ubuntu@rails-dev-box:/vagrant/r$

    ReplyDelete
  15. Please note that only the api show action and api list action work. What changes should I make?

    ReplyDelete
  16. in other words only the REST using GET are working..everything else fails

    ReplyDelete
    Replies
    1. Please paste your code on https://pastebin.com as it is not readable in the comments. Also to get your problem solved you can reach our team at appychip[at]gmail[dot]com

      Delete
  17. I created a new app as api only and then added a view and it works. The remote linux server is now able to send data to the rails app using curl. But I need one more thing. When the cpu load goes above say 90 I need to set an alert. Where can the alert be set. Is it in the view or should I make a new model for that. Can you guide on the required code?

    ReplyDelete
    Replies
    1. Why are you doing so much of hardwork to get metrics. Why don't you use some free open source monitoring tool like sensu, nagios, prometheus etc.

      Delete
  18. I am in a learning process. I set a logic in the model which can send am email when the cpu is above a certain value.

    ReplyDelete
  19. Really nice blog post.provided a helpful information.I hope that you will post more updates like this Ruby on Rails Online Training Bangalore

    ReplyDelete

 

Copyright @ 2013 Appychip.

Designed by Appychip & YouTube Channel