One place for hosting & domains

      Rails

      Build a RESTful JSON API With Rails 5 – Part Three

      Introduction

      In part two of this tutorial, we added token-based authentication with JWT (JSON Web Tokens) to our todo API.

      In this final part of the series, we’ll wrap with the following:

      • Versioning
      • Serializers
      • Pagination

      When building an API whether public or internal facing, it’s highly recommended that you version it.
      This might seem trivial when you have total control over all clients. However, when the API is public-facing, you want to establish a contract with your clients. Every breaking change should be a new version. Convincing enough? Great, let’s do this!

      In order to version a Rails API, we need to do two things:

      1. Add a route constraint – this will select a version based on the request headers
      2. Namespace the controllers – have different controller namespaces to handle different versions.

      Rails routing supports advanced constraints. Provided an object that responds to matches?, you can control which controller handles a specific route.

      We’ll define a class ApiVersion that checks the API version from the request headers and routes to the appropriate controller module. The class will live in app/lib since it’s non-domain-specific.

      1. touch app/lib/api_version.rb

      Implement ApiVersion

      app/lib/api_version.rb

      class ApiVersion
        attr_reader :version, :default
      
        def initialize(version, default = false)
          @version = version
          @default = default
        end
      
        
        def matches?(request)
          check_headers(request.headers) || default
        end
      
        private
      
        def check_headers(headers)
          
          accept = headers[:accept]
          accept && accept.include?("application/vnd.todos.#{version}+json")
        end
      end
      

      The ApiVersion class accepts a version and a default flag on initialization. In accordance with Rails constraints, we implement an instance method matches?. This method will be called with the request object upon initialization.

      From the request object, we can access the Accept headers and check for the requested version or if the instance is the default version. This process is called content negotiation. Let’s add some more context to this.

      Content Negotiation

      REST is closely tied to the HTTP specification. HTTP defines mechanisms that make it possible to serve different versions (representations) of a resource at the same URI. This is called content negotiation.

      Our ApiVersion class implements server-driven content negotiation where the client (user agent) informs the server what media types it understands by providing an Accept HTTP header.

      According to the Media Type Specification, you can define your own media types using the vendor tree i.e., application/vnd.example.resource+json.

      The vendor tree is used for media types associated with publicly available products. It uses the “vnd” facet.

      Thus, we define a custom vendor media type application/vnd.todos.{version_number}+json giving clients the ability to choose which API version they require.

      Cool, now that we have the constraint class, let’s change our routing to accommodate this.

      Since we don’t want to have the version number as part of the URI (this is argued as an anti-pattern), we’ll make use of the module scope to namespace our controllers.

      Let’s move the existing todos and todo-items resources into a v1 namespace.

      config/routes

      Rails.application.routes.draw do
        
      
        
        scope module: :v1, constraints: ApiVersion.new('v1', true) do
          resources :todos do
            resources :items
          end
        end
      
      
        post 'auth/login', to: 'authentication#authenticate'
        post 'signup', to: 'users#create'
      end
      

      We’ve set the version constraint at the namespace level. Thus, this will be applied to all resources within it. We’ve also defined v1 as the default version; in cases where the version is not provided, the API will default to v1.

      In the event we were to add new versions, they would have to be defined above the default version since Rails will cycle through all routes from top to bottom searching for one that matches(till method matches? resolves to true).

      Next up, let’s move the existing todos and items controllers into the v1 namespace. First, create a module directory in the controllers folder.

      1. mkdir app/controllers/v1

      Move the files into the module folder.

      1. mv app/controllers/{todos_controller.rb,items_controller.rb} app/controllers/v1

      That’s not all, let’s define the controllers in the v1 namespace. Let’s start with the todos controller.

      app/controllers/v1/todos_controller.rb

      module V1
        class TodosController < ApplicationController
        
        end
      end
      

      Do the same for the items controller.

      app/controllers/v1/items_controller.rb

      module V1
        class ItemsController < ApplicationController
        
        end
      end
      

      Let’s fire up the server and run some tests.

      1. http :3000/auth/login email=[email protected] password=foobar
      1. http :3000/todos Accept:'application/vnd.todos.v1+json' Authorization:'ey...AWH3FNTd3T0jMB7HnLw2bYQbK0g'
      1. http :3000/todos Accept:'application/vnd.todos.v2+json' Authorization:'ey...AWH3FNTd3T0jMB7HnLw2bYQbK0g'

      In case we attempt to access a nonexistent version, the API will default to v1 since we set it as the default version. For testing purposes, let’s define v2.

      Generate a v2 todos controller

      1. rails g controller v2/todos

      Define the namespace in the routes.

      config/routes.rb

      Rails.application.routes.draw do
        
      
        
        scope module: :v2, constraints: ApiVersion.new('v2') do
          resources :todos, only: :index
        end
      
        scope module: :v1, constraints: ApiVersion.new('v1', true) do
          
        end
        
      end
      

      Remember, non-default versions have to be defined above the default version.

      Since this is test controller, we’ll define an index controller with a dummy response.

      app/controllers/v2/todos_controller.rb

      class V2::TodosController < ApplicationController
        def index
          json_response({ message: 'Hello there'})
        end
      end
      

      Note the namespace syntax, this is shorthand in Ruby to define a class within a namespace.
      Great, now fire up the server once more and run some tests.

      1. http :3000/todos Accept:'application/vnd.todos.v1+json' Authorization:'eyJ0e...Lw2bYQbK0g'
      1. http :3000/todos Accept:'application/vnd.todos.v2+json' Authorization:'eyJ0e...Lw2bYQbK0g'

      Voila! Our API responds to version 2!

      At this point, if we wanted to get a todo and its items, we’d have to make two API calls.
      Although this works well, it’s not ideal.

      We can achieve this with serializers. Serializers allow for custom representations of JSON responses. Active model serializers make it easy to define which model attributes and relationships need to be serialized. In order to get todos with their respective items, we need to define serializers on the Todo model to include its attributes and relationships.

      First, let’s add active model serializers to the Gemfile:

      Gemfile

      
        gem 'active_model_serializers', '~> 0.10.0'
      
      

      Run bundle to install it:

      1. bundle install

      Generate a serializer from the todo model:

      1. rails g serializer todo

      This creates a new directory app/serializers and adds a new file todo_serializer.rb. Let’s define the todo serializer with the data that we want it to contain.

      app/serializers/todo_serializer.rb

      class TodoSerializer < ActiveModel::Serializer
        
        attributes :id, :title, :created_by, :created_at, :updated_at
        
        has_many :items
      end
      

      We define a whitelist of attributes to be serialized and the model association (only defined attributes will be serialized). We’ve also defined a model association to the item model, this way the payload will include an array of items. Fire up the server, let’s test this.

      1. http POST :3000/todos/1/items name='Listen to Don Giovanni' Accept:'application/vnd.todos.v1+json' Authorization:'ey...HnLw2bYQbK0g'
      1. http :3000/todos Accept:'application/vnd.todos.v1+json' Authorization:'ey...HnLw2bYQbK0g'

      This is great. One request to rule them all!

      Our todos API has suddenly become very popular. All of a sudden everyone has something to do. Our data set has grown substantially. To make sure the requests are still fast and optimized, we’re going to add pagination; we’ll give clients the power to say what portion of data they require.

      To achieve this, we’ll make use of the will_paginate gem.

      Let’s add it to the Gemfile:

      Gemfile

      
        gem 'will_paginate', '~> 3.1.0'
      
      

      Install it:

      1. bundle install

      Let’s modify the todos controller index action to paginate its response.

      app/controllers/v1/todos_controller.rb

      module V1
        class TodosController < ApplicationController
        
        
        def index
          
          @todos = current_user.todos.paginate(page: params[:page], per_page: 20)
          json_response(@todos)
        end
        
      end
      

      The index action checks for the page number in the request params. If provided, it’ll return the page data with each page having twenty records each. As always, let’s fire up the Rails server and run some tests.

      1. http :3000/todos Accept:'application/vnd.todos.v1+json' Authorization:'eyJ0...nLw2bYQbK0g'
      1. http :3000/todos page==1 Accept:'application/vnd.todos.v1+json' Authorization:'eyJ0...nLw2bYQbK0g'
      1. http :3000/todos page==2 Accept:'application/vnd.todos.v1+json' Authorization:'eyJ0...nLw2bYQbK0g'

      The page number is part of the query string. Note that when we request the second page, we get an empty array. This is because we don’t have more than 20 records in the database.

      Let’s seed some test data into the database.

      Add faker and install faker gem. Faker generates data at random.

      Gemfile

      
        gem 'faker'
      
      

      In db/seeds.rb let’s define seed data.

      db/seeds.rb

      
      50.times do
        todo = Todo.create(title: Faker::Lorem.word, created_by: User.first.id)
        todo.items.create(name: Faker::Lorem.word, done: false)
      end
      

      Seed the database by running:

      1. rake db:seed

      Awesome, fire up the server and rerun the HTTP requests. Since we have test data, we’re able to see data from different pages.

      Congratulations for making it this far! We’ve come a long way! We’ve gone through generating an API-only Rails application, setting up a test framework, using TDD to implement the todo API, adding token-based authentication with JWT, versioning our API, serializing with active model serializers, and adding pagination features.

      Having gone through this series, I believe you should be able to build a RESTful API with Rails 5. Feel free to leave any feedback you may have in the comments section below. If you found the tutorial helpful, don’t hesitate to hit that share button. Cheers!

      How To Install Ruby on Rails with rbenv on Ubuntu 20.04


      Introduction

      Ruby on Rails is one of the most popular application stacks for developers looking to create sites and web apps. The Ruby programming language, combined with the Rails development framework, allows you to build and deploy scalable apps quickly.

      You can install Ruby and Rails with the command line tool rbenv. Using rbenv provides you with a solid environment for developing your Ruby on Rails applications and allows you to switch between Ruby versions, keeping your entire team on the same version. rbenv also provides support for specifying application-specific versions of Ruby, allows you to change the global Ruby for each user, and the option to use an environment variable to override the Ruby version.

      In this tutorial, we will guide you through the Ruby and Rails installation processes with rbenv and gem. First, you’ll install the appropriate packages to install rbenv and then Ruby. After, you’ll install the ruby-build plugin so that you can install available versions of Ruby. Last, you’ll use gem to install Rails and can then use Ruby on Rails to begin your web development. We will also provide steps on how to check if your rbenv version is up-to-date, and how to uninstall Ruby versions and rbenv.

      Prerequisites

      To follow this tutorial, you need:

      Step 1 – Install rbenv and Dependencies

      Ruby relies on several packages that you can install through your package manager. Once those are installed, you can install rbenv and use it to install Ruby.

      First, update your package list:

      Next, install the dependencies required to install Ruby:

      • sudo apt install git curl libssl-dev libreadline-dev zlib1g-dev autoconf bison build-essential libyaml-dev libreadline-dev libncurses5-dev libffi-dev libgdbm-dev

      After installing the dependencies, you can install rbenv itself. Use curl to transfer information from the rbenv repository on GitHub into the directory ~/.rbenv:

      • curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash

      Next, add ~/.rbenv/bin to your $PATH so that you can use the rbenv command line utility. Do this by altering your ~/.bashrc file so that it affects future login sessions:

      • echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc

      Then, add the command eval "$(rbenv init -)" to your ~/.bashrc file so rbenv loads automatically:

      • echo 'eval "$(rbenv init -)"' >> ~/.bashrc

      Next, apply the changes you made to your ~/.bashrc file to your current shell session:

      Verify that rbenv is set up properly by running the type command, which will display more information about the rbenv command:

      Your terminal window will display the following:

      Output

      rbenv is a function rbenv () { local command; command="${1:-}"; if [ "$#" -gt 0 ]; then shift; fi; case "$command" in rehash | shell) eval "$(rbenv "sh-$command" "$@")" ;; *) command rbenv "$command" "$@" ;; esac }

      Next, install the ruby-build plugin. This plugin adds the rbenv install command, which makes the installation process of new versions of Ruby less complex. To install ruby-build, first clone the ruby-build GitHub repository:

      • git clone https://github.com/rbenv/ruby-build.git

      After running this command, you’ll have a directory named ruby-build in your working directory. Within the ruby-build directory is a script named install.sh which you’ll use to actually install ruby-build.

      Before running this script, take a moment to review its contents. Rather than opening the file with a text editor, you can print its contents to your terminal’s output with the following command:

      • cat ruby-build/install.sh

      Output

      #!/bin/sh # Usage: PREFIX=/usr/local ./install.sh # # Installs ruby-build under $PREFIX. set -e cd "$(dirname "$0")" if [ -z "${PREFIX}" ]; then PREFIX="/usr/local" fi BIN_PATH="${PREFIX}/bin" SHARE_PATH="${PREFIX}/share/ruby-build" mkdir -p "$BIN_PATH" "$SHARE_PATH" install -p bin/* "$BIN_PATH" install -p -m 0644 share/ruby-build/* "$SHARE_PATH"

      Notice the second line of this file that reads # Usage: PREFIX=/usr/local ./install.sh. This commented-out line explains that in order to execute this script and install ruby-build, you must precede the script with PREFIX=/usr/local. This will create a temporary environment variable that will affect how the script is run. Essentially, this will cause the string $PREFIX to be replaced with /usr/local any time it appears in the script and will ultimately cause all the necessary ruby-build files to be installed within the /usr/local directory. This environment variable is only temporary and will cease to exist once the script terminates.

      Create this temporary environment variable and run the script with the following command. Note that this command includes sudo before calling the script. This is necessary since you must have advanced privileges to install files to the /usr/local directory:

      • PREFIX=/usr/local sudo ./ruby-build/install.sh

      At this point, you have both rbenv and ruby-build installed. Let’s install Ruby next.

      Step 2 – Installing Ruby with ruby-build

      With the ruby-build plugin now installed, you can install whatever versions of Ruby that you may need with a single command. First, list all the available versions of Ruby:

      The output of that command will be a list of versions that you can choose to install:

      Output

      2.6.8 2.7.4 3.0.2 jruby-9.2.19.0 mruby-3.0.0 rbx-5.0 truffleruby-21.2.0.1 truffleruby+graalvm-21.2.0 Only latest stable releases for each Ruby implementation are shown. Use 'rbenv install --list-all / -L' to show all local versions.

      Now let’s install Ruby 3.0.2:

      Installing Ruby can be a lengthy process, so be prepared for the installation to take some time to complete.

      Once it’s done installing, set it as your default version of Ruby with the global sub-command:

      Verify that Ruby was properly installed by checking its version number:

      If you installed version 3.0.2 of Ruby, this command will return output like this:

      Output

      ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux]

      To install and use a different version of Ruby, run the rbenv commands with a different version number, as in rbenv install 2.3.0 followed by rbenv global 2.3.0.

      You now have at least one version of Ruby installed and have set your default Ruby version. Next, you will set up gems and Rails.

      Step 3 – Working with Gems

      Gems are the way Ruby libraries are distributed. You use the gem command to manage these gems, and use this command to install Rails.

      When you install a gem, the installation process generates local documentation. This can add a significant amount of time to each gem’s installation process, so turn off local documentation generation by creating a file called ~/.gemrc which contains a configuration setting to turn off this feature:

      • echo "gem: --no-document" > ~/.gemrc

      Bundler is a tool that manages gem dependencies for projects. Install the Bundler gem next, as Rails depends on it:

      You’ll receive the following output:

      Output

      Fetching bundler-2.2.27.gem Successfully installed bundler-2.2.27 1 gem installed

      You can use the gem env command (the subcommand env is short for environment) to learn more about the environment and configuration of gems. You can confirm where gems are being installed by using the home argument, like this:

      You’ll receive an output similar to this:

      Output

      /home/sammy/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0

      Once you have gems set up, you can install Rails.

      Step 4 – Installing Rails

      To install Rails, use the gem install command along with the -v flag to specify the version. For this tutorial, you’ll use version 6.1.4.1:

      • gem install rails -v 6.1.4.1

      The gem command installs the gem you specify, as well as any of its dependencies. Rails is a complex web development framework and has many dependencies, so the process will take some time to complete. Eventually, you’ll receive a message stating that Rails is installed, along with its dependencies:

      Output

      ... Successfully installed rails-6.1.4.1 37 gems installed

      Note: If you would like to install a different version of Rails, you can list the valid versions of Rails by doing a search, which will output a list of possible versions. You can then install a specific version, such as 4.2.7:

      • gem search '^rails$' --all
      • gem install rails -v 4.2.7

      If you would like to install the latest version of Rails, run the command without a version specified:

      rbenv works by creating a directory of shims, which point to the files used by the Ruby version that’s currently enabled. Through the rehash sub-command, rbenv maintains shims in that directory to match every Ruby command across every installed version of Ruby on your server. Whenever you install a new version of Ruby or a gem that provides commands as Rails does, you should run the following:

      Verify that Rails has been installed properly by printing its version, with the following command:

      If it’s installed properly, this command will return the version of Rails that was installed:

      Output

      Rails 6.1.4.1

      At this point, you can begin testing your Ruby on Rails installation and start to develop web applications. Now let’s review how to keep the rbenv up-to-date.

      Step 5 – Updating rbenv

      Since you installed rbenv manually using Git, you can upgrade your installation to the most recent version at any time by using the git pull command in the ~/.rbenv directory:

      This will ensure that you are using the most up-to-date version of rbenv available.

      Step 6 – Uninstalling Ruby versions

      As you download additional versions of Ruby, you may accumulate more versions than you would like in your ~/.rbenv/versions directory. Use the ruby-build plugin’s uninstall subcommand to remove these previous versions.

      The following command will uninstall Ruby version 3.0.2:

      With the rbenv uninstall command you can clean up old versions of Ruby so that you do not have more installed than you are currently using.

      Step 7 – Uninstalling rbenv

      If you’ve decided you no longer want to use rbenv, you can remove it from your system.

      To do this, first open your ~/.bashrc file in your editor. In this example, we will use nano:

      Find and remove the following two lines from the file:

      ~/.bashrc

      ...
      export PATH="$HOME/.rbenv/bin:$PATH"
      eval "$(rbenv init -)"
      

      After removing these lines, save the file and exit the editor. If you used nano, you can exit by pressing CTRL + X then Y and ENTER.

      Then remove rbenv and all installed Ruby versions with the following command:

      Log out and back in to apply the changes to your shell.

      Conclusion

      In this tutorial, you installed rbenv and gem to install the entire Ruby on Rails framework. From here, you can begin creating your web development application projects. If you want to learn more about making those environments more robust you can check out our series on How To Code In Ruby.



      Source link