One place for hosting & domains

      Build a RESTful JSON API With Rails 5 – Part One


      Rails is popularly known for building web applications. Chances are if you’re reading this you’ve built a traditional server-rendered web application with Rails before. If not, I’d highly recommend going through the Getting Started with Rails page to familiarize yourself with the Rails framework before proceeding with this tutorial.

      As of version 5, Rails core now supports API only applications! In previous versions, we relied on an external gem: rails-api which has since been merged to core rails.

      API only applications are slimmed down compared to traditional Rails web applications. According to Rails 5 release notes, generating an API only application will:

      • Start the application with a limited set of middleware
      • Make the ApplicationController inherit from ActionController::API instead of ActionController::Base
      • Skip generation of view files

      This works to generate an API-centric framework excluding functionality that would otherwise be unused and unnecessary.

      In this three-part tutorial, we’ll build a todo list API where users can manage their to-do lists and todo items.

      Prerequisites

      Before we begin, make sure you have ruby version >=2.2.2 and rails version 5.

      $ ruby -v # ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin16]
      $ rails -v # Rails 5.0.1
      

      If your ruby version is not up to date, you can update it with a ruby version manager like rvm or rbenv.

      # when using rbenv
      $ rbenv install 2.3.1
      # set 2.3.1 as the global version
      $ rbenv global 2.3.1
      
      # when using rvm
      $ rvm install 2.3.1
      # set 2.3.1 as the global version
      $ rvm use 2.3.1
      

      If your rails version is not up to date, update to the latest version by running:

      $ gem update rails
      

      All good? Let’s get started!

      API Endpoints

      Our API will expose the following RESTful endpoints.

      Endpoint Functionality
      POST /signup Signup
      POST /auth/login Login
      GET /auth/logout Logout
      GET /todos List all todos
      POST /todos Create a new todo
      GET /todos/:id Get a todo
      PUT /todos/:id Update a todo
      DELETE /todos/:id Delete a todo and its items
      GET /todos/:id/items Get a todo item
      PUT /todos/:id/items Update a todo item
      DELETE /todos/:id/items Delete a todo item

      Part One will Cover:

      • Project setup
      • Todos API
      • TodoItems API

      Project Setup

      Generate a new project todos-api by running:

      $ rails new todos-api --api -T
      

      Note that we’re using the --api argument to tell Rails that we want an API application and -T to exclude Minitest the default
      testing framework. Don’t freak out, we’re going to write tests. We’ll be using RSpec instead to test our API. I find RSpec to be more expressive
      and easier to start with as compared to Minitest.

      Dependencies

      Let’s take a moment to review the gems that we’ll be using.

      • rspec-rails – Testing framework.
      • factorybotrails – A fixtures replacement with a more straightforward syntax. You’ll see.
      • shoulda_matchers – Provides RSpec with additional matchers.
      • database_cleaner – You guessed it! It literally cleans our test database to ensure
        a clean state in each test suite.
      • faker – A library for generating fake data. We’ll use this to generate test data.

      All good? Great! Let’s set them up. In your Gemfile:

      Add rspec-rails to both the :development and :test groups.

      # Gemfile
      group :development, :test do
        gem 'rspec-rails', '~> 3.5'
      end
      

      This is a handy shorthand to include a gem in multiple environments.

      Add factory_bot_rails, shoulda_matchers, faker and database_cleaner to the :test group.

      # Gemfile
      group :test do
        gem 'factory_bot_rails', '~> 4.0'
        gem 'shoulda-matchers', '~> 3.1'
        gem 'faker'
        gem 'database_cleaner'
      end
      

      Install the gems by running:

      $ bundle install
      

      Initialize the spec directory (where our tests will reside).

      $ rails generate rspec:install
      

      This adds the following files which are used for configuration:

      • .rspec
      • spec/spec_helper.rb
      • spec/rails_helper.rb

      Create a factories directory (factory bot uses this as the default directory). This is where we’ll define the model factories.

      $ mkdir spec/factories
      

      Configuration

      In spec/rails_helper.rb

      # require database cleaner at the top level
      require 'database_cleaner'
      
      # [...]
      # configure shoulda matchers to use rspec as the test framework and full matcher libraries for rails
      Shoulda::Matchers.configure do |config|
        config.integrate do |with|
          with.test_framework :rspec
          with.library :rails
        end
      end
      
      # [...]
      RSpec.configure do |config|
        # [...]
        # add `FactoryBot` methods
        config.include FactoryBot::Syntax::Methods
      
        # start by truncating all the tables but then use the faster transaction strategy the rest of the time.
        config.before(:suite) do
          DatabaseCleaner.clean_with(:truncation)
          DatabaseCleaner.strategy = :transaction
        end
      
        # start the transaction strategy as examples are run
        config.around(:each) do |example|
          DatabaseCleaner.cleaning do
            example.run
          end
        end
        # [...]
      end
      

      Phew! That was a rather long. Good thing is, it’s a smooth ride from here on out.


      Models

      Let’s start by generating the Todo model

      $ rails g model Todo title:string created_by:string
      

      Notice that we’ve included the model attributes in the model generation command. This way we don’t have to edit the migration file.
      The generator invokes active record and rspec to generate the migration, model, and spec respectively.

      # db/migrate/[timestamp]_create_todos.rb
      class CreateTodos < ActiveRecord::Migration[5.0]
        def change
          create_table :todos do |t|
            t.string :title
            t.string :created_by
      
            t.timestamps
          end
        end
      end
      

      And now the Item model

      $ rails g model Item name:string done:boolean todo:references
      

      By adding todo:references we’re telling the generator to set up an association with the Todo model.
      This will do the following:

      • Add a foreign key column todo_id to the items table
      • Setup a belongs_to association in the Item model
      # db/migrate/[timestamp]_create_items.rb
      class CreateItems < ActiveRecord::Migration[5.0]
        def change
          create_table :items do |t|
            t.string :name
            t.boolean :done
            t.references :todo, foreign_key: true
      
            t.timestamps
          end
        end
      end
      

      Looks good? Let’s run the migrations.

      $ rails db:migrate
      

      We’re Test Driven, let’s write the model specs first.

      # spec/models/todo_spec.rb
      require 'rails_helper'
      
      # Test suite for the Todo model
      RSpec.describe Todo, type: :model do
        # Association test
        # ensure Todo model has a 1:m relationship with the Item model
        it { should have_many(:items).dependent(:destroy) }
        # Validation tests
        # ensure columns title and created_by are present before saving
        it { should validate_presence_of(:title) }
        it { should validate_presence_of(:created_by) }
      end
      

      RSpec has a very expressive DSL (Domain Specific Language). You can almost read the tests like a paragraph.
      Remember our shoulda matchers gem? It provides RSpec with the nifty association and validation matchers above.

      # spec/models/item_spec.rb
      require 'rails_helper'
      
      # Test suite for the Item model
      RSpec.describe Item, type: :model do
        # Association test
        # ensure an item record belongs to a single todo record
        it { should belong_to(:todo) }
        # Validation test
        # ensure column name is present before saving
        it { should validate_presence_of(:name) }
      end
      

      Let’s execute the specs by running:

      $ bundle exec rspec
      

      And to no surprise, we have only one test passing and four failures. Let’s go ahead and fix the failures.

      # app/models/todo.rb
      class Todo < ApplicationRecord
        # model association
        has_many :items, dependent: :destroy
      
        # validations
        validates_presence_of :title, :created_by
      end
      
      # app/models/item.rb
      class Item < ApplicationRecord
        # model association
        belongs_to :todo
      
        # validation
        validates_presence_of :name
      end
      

      At this point run the tests again and…

      voila! All green.


      Controllers

      Now that our models are all setup, let’s generate the controllers.

      $ rails g controller Todos
      $ rails g controller Items
      

      You guessed it! Tests first… with a slight twist. Generating controllers by default generates controller specs.
      However, we won’t be writing any controller specs. We’re going to write request specs instead.

      Request specs are designed to drive behavior through the full stack, including routing. This means they can hit the applications’
      HTTP endpoints as opposed to controller specs which call methods directly. Since we’re building an API application, this is exactly the kind of behavior we want from our tests.

      According to RSpec, the official recommendation of the Rails team and the RSpec core team is to write request specs instead.

      Add a requests folder to the spec directory with the corresponding spec files.

      $ mkdir spec/requests && touch spec/requests/{todos_spec.rb,items_spec.rb} 
      

      Before we define the request specs, Let’s add the model factories which will provide the test data.

      Add the factory files:

      $ touch spec/factories/{todos.rb,items.rb}
      

      Define the factories.

      # spec/factories/todos.rb
      FactoryBot.define do
        factory :todo do
          title { Faker::Lorem.word }
          created_by { Faker::Number.number(10) }
        end
      end
      

      By wrapping faker methods in a block, we ensure that faker generates dynamic data every time the factory is invoked.
      This way, we always have unique data.

      # spec/factories/items.rb
      FactoryBot.define do
        factory :item do
          name { Faker::StarWars.character }
          done false
          todo_id nil
        end
      end
      

      Todo API

      # spec/requests/todos_spec.rb
      require 'rails_helper'
      
      RSpec.describe 'Todos API', type: :request do
        # initialize test data 
        let!(:todos) { create_list(:todo, 10) }
        let(:todo_id) { todos.first.id }
      
        # Test suite for GET /todos
        describe 'GET /todos' do
          # make HTTP get request before each example
          before { get '/todos' }
      
          it 'returns todos' do
            # Note `json` is a custom helper to parse JSON responses
            expect(json).not_to be_empty
            expect(json.size).to eq(10)
          end
      
          it 'returns status code 200' do
            expect(response).to have_http_status(200)
          end
        end
      
        # Test suite for GET /todos/:id
        describe 'GET /todos/:id' do
          before { get "/todos/#{todo_id}" }
      
          context 'when the record exists' do
            it 'returns the todo' do
              expect(json).not_to be_empty
              expect(json['id']).to eq(todo_id)
            end
      
            it 'returns status code 200' do
              expect(response).to have_http_status(200)
            end
          end
      
          context 'when the record does not exist' do
            let(:todo_id) { 100 }
      
            it 'returns status code 404' do
              expect(response).to have_http_status(404)
            end
      
            it 'returns a not found message' do
              expect(response.body).to match(/Couldn't find Todo/)
            end
          end
        end
      
        # Test suite for POST /todos
        describe 'POST /todos' do
          # valid payload
          let(:valid_attributes) { { title: 'Learn Elm', created_by: '1' } }
      
          context 'when the request is valid' do
            before { post '/todos', params: valid_attributes }
      
            it 'creates a todo' do
              expect(json['title']).to eq('Learn Elm')
            end
      
            it 'returns status code 201' do
              expect(response).to have_http_status(201)
            end
          end
      
          context 'when the request is invalid' do
            before { post '/todos', params: { title: 'Foobar' } }
      
            it 'returns status code 422' do
              expect(response).to have_http_status(422)
            end
      
            it 'returns a validation failure message' do
              expect(response.body)
                .to match(/Validation failed: Created by can't be blank/)
            end
          end
        end
      
        # Test suite for PUT /todos/:id
        describe 'PUT /todos/:id' do
          let(:valid_attributes) { { title: 'Shopping' } }
      
          context 'when the record exists' do
            before { put "/todos/#{todo_id}", params: valid_attributes }
      
            it 'updates the record' do
              expect(response.body).to be_empty
            end
      
            it 'returns status code 204' do
              expect(response).to have_http_status(204)
            end
          end
        end
      
        # Test suite for DELETE /todos/:id
        describe 'DELETE /todos/:id' do
          before { delete "/todos/#{todo_id}" }
      
          it 'returns status code 204' do
            expect(response).to have_http_status(204)
          end
        end
      end
      

      We start by populating the database with a list of 10 todo records (thanks to factory bot).
      We also have a custom helper method json which parses the JSON response to a Ruby Hash which is easier to work with in our tests.
      Let’s define it in spec/support/request_spec_helper.

      Add the directory and file:

      $ mkdir spec/support && touch spec/support/request_spec_helper.rb
      
      # spec/support/request_spec_helper
      module RequestSpecHelper
        # Parse JSON response to ruby hash
        def json
          JSON.parse(response.body)
        end
      end
      

      The support directory is not autoloaded by default. To enable this, open the rails helper and comment out the support directory auto-loading and then
      include it as shared module for all request specs in the RSpec configuration block.

      # spec/rails_helper.rb
      # [...]
      Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
      # [...]
      RSpec.configuration do |config|
        # [...]
        config.include RequestSpecHelper, type: :request
        # [...]
      end
      

      Run the tests.

      We get failing routing errors. This is because we haven’t defined the routes yet. Go ahead and define them in config/routes.rb.

      # config/routes.rb
      Rails.application.routes.draw do
        resources :todos do
          resources :items
        end
      end
      

      In our route definition, we’re creating todo resource with a nested items resource. This enforces the 1:m (one to many) associations at the routing level.
      To view the routes, you can run:

      $ rails routes
      

      When we run the tests we see that the routing error is gone. As expected we have controller failures. Let’s go ahead and define the controller methods.

      # app/controllers/todos_controller.rb
      class TodosController < ApplicationController
        before_action :set_todo, only: [:show, :update, :destroy]
      
        # GET /todos
        def index
          @todos = Todo.all
          json_response(@todos)
        end
      
        # POST /todos
        def create
          @todo = Todo.create!(todo_params)
          json_response(@todo, :created)
        end
      
        # GET /todos/:id
        def show
          json_response(@todo)
        end
      
        # PUT /todos/:id
        def update
          @todo.update(todo_params)
          head :no_content
        end
      
        # DELETE /todos/:id
        def destroy
          @todo.destroy
          head :no_content
        end
      
        private
      
        def todo_params
          # whitelist params
          params.permit(:title, :created_by)
        end
      
        def set_todo
          @todo = Todo.find(params[:id])
        end
      end
      

      More helpers. Yay! This time we have:

      • json_response which does… yes, responds with JSON and an HTTP status code (200 by default).
        We can define this method in concerns folder.
      # app/controllers/concerns/response.rb
      module Response
        def json_response(object, status = :ok)
          render json: object, status: status
        end
      end
      
      • set_todo – callback method to find a todo by id. In the case where the record does not exist, ActiveRecord
        will throw an exception ActiveRecord::RecordNotFound. We’ll rescue from this exception and return a 404 message.
      # app/controllers/concerns/exception_handler.rb
      module ExceptionHandler
        # provides the more graceful `included` method
        extend ActiveSupport::Concern
      
        included do
          rescue_from ActiveRecord::RecordNotFound do |e|
            json_response({ message: e.message }, :not_found)
          end
      
          rescue_from ActiveRecord::RecordInvalid do |e|
            json_response({ message: e.message }, :unprocessable_entity)
          end
        end
      end
      

      In our create method in the TodosController, note that we’re using create! instead of create. This way, the model will raise
      an exception ActiveRecord::RecordInvalid. This way, we can avoid deep nested if statements in the controller. Thus, we rescue from this exception
      in the ExceptionHandler module.

      However, our controller classes don’t know about these helpers yet. Let’s fix that by including these modules in the
      application controller.

      # app/controllers/application_controller.rb
      class ApplicationController < ActionController::API
        include Response
        include ExceptionHandler
      end
      

      Run the tests and everything’s all green!

      Let’s fire up the server for some good old manual testing.

      $ rails s
      

      Now let’s go ahead and make requests to the API. I’ll be using httpie as my HTTP client.

      # GET /todos
      $ http :3000/todos
      # POST /todos
      $ http POST :3000/todos title=Mozart created_by=1
      # PUT /todos/:id
      $ http PUT :3000/todos/1 title=Beethoven
      # DELETE /todos/:id
      $ http DELETE :3000/todos/1
      

      You should see similar output.


      TodoItems API

      # spec/requests/items_spec.rb
      require 'rails_helper'
      
      RSpec.describe 'Items API' do
        # Initialize the test data
        let!(:todo) { create(:todo) }
        let!(:items) { create_list(:item, 20, todo_id: todo.id) }
        let(:todo_id) { todo.id }
        let(:id) { items.first.id }
      
        # Test suite for GET /todos/:todo_id/items
        describe 'GET /todos/:todo_id/items' do
          before { get "/todos/#{todo_id}/items" }
      
          context 'when todo exists' do
            it 'returns status code 200' do
              expect(response).to have_http_status(200)
            end
      
            it 'returns all todo items' do
              expect(json.size).to eq(20)
            end
          end
      
          context 'when todo does not exist' do
            let(:todo_id) { 0 }
      
            it 'returns status code 404' do
              expect(response).to have_http_status(404)
            end
      
            it 'returns a not found message' do
              expect(response.body).to match(/Couldn't find Todo/)
            end
          end
        end
      
        # Test suite for GET /todos/:todo_id/items/:id
        describe 'GET /todos/:todo_id/items/:id' do
          before { get "/todos/#{todo_id}/items/#{id}" }
      
          context 'when todo item exists' do
            it 'returns status code 200' do
              expect(response).to have_http_status(200)
            end
      
            it 'returns the item' do
              expect(json['id']).to eq(id)
            end
          end
      
          context 'when todo item does not exist' do
            let(:id) { 0 }
      
            it 'returns status code 404' do
              expect(response).to have_http_status(404)
            end
      
            it 'returns a not found message' do
              expect(response.body).to match(/Couldn't find Item/)
            end
          end
        end
      
        # Test suite for PUT /todos/:todo_id/items
        describe 'POST /todos/:todo_id/items' do
          let(:valid_attributes) { { name: 'Visit Narnia', done: false } }
      
          context 'when request attributes are valid' do
            before { post "/todos/#{todo_id}/items", params: valid_attributes }
      
            it 'returns status code 201' do
              expect(response).to have_http_status(201)
            end
          end
      
          context 'when an invalid request' do
            before { post "/todos/#{todo_id}/items", params: {} }
      
            it 'returns status code 422' do
              expect(response).to have_http_status(422)
            end
      
            it 'returns a failure message' do
              expect(response.body).to match(/Validation failed: Name can't be blank/)
            end
          end
        end
      
        # Test suite for PUT /todos/:todo_id/items/:id
        describe 'PUT /todos/:todo_id/items/:id' do
          let(:valid_attributes) { { name: 'Mozart' } }
      
          before { put "/todos/#{todo_id}/items/#{id}", params: valid_attributes }
      
          context 'when item exists' do
            it 'returns status code 204' do
              expect(response).to have_http_status(204)
            end
      
            it 'updates the item' do
              updated_item = Item.find(id)
              expect(updated_item.name).to match(/Mozart/)
            end
          end
      
          context 'when the item does not exist' do
            let(:id) { 0 }
      
            it 'returns status code 404' do
              expect(response).to have_http_status(404)
            end
      
            it 'returns a not found message' do
              expect(response.body).to match(/Couldn't find Item/)
            end
          end
        end
      
        # Test suite for DELETE /todos/:id
        describe 'DELETE /todos/:id' do
          before { delete "/todos/#{todo_id}/items/#{id}" }
      
          it 'returns status code 204' do
            expect(response).to have_http_status(204)
          end
        end
      end
      

      As expected, running the tests at this point should output failing todo item tests. Let’s define the todo items controller.

      # app/controllers/items_controller.rb
      class ItemsController < ApplicationController
        before_action :set_todo
        before_action :set_todo_item, only: [:show, :update, :destroy]
      
        # GET /todos/:todo_id/items
        def index
          json_response(@todo.items)
        end
      
        # GET /todos/:todo_id/items/:id
        def show
          json_response(@item)
        end
      
        # POST /todos/:todo_id/items
        def create
          @todo.items.create!(item_params)
          json_response(@todo, :created)
        end
      
        # PUT /todos/:todo_id/items/:id
        def update
          @item.update(item_params)
          head :no_content
        end
      
        # DELETE /todos/:todo_id/items/:id
        def destroy
          @item.destroy
          head :no_content
        end
      
        private
      
        def item_params
          params.permit(:name, :done)
        end
      
        def set_todo
          @todo = Todo.find(params[:todo_id])
        end
      
        def set_todo_item
          @item = @todo.items.find_by!(id: params[:id]) if @todo
        end
      end
      

      Run the tests.

      Run some manual tests for the todo items API:

      # GET /todos/:todo_id/items
      $ http :3000/todos/2/items
      # POST /todos/:todo_id/items
      $ http POST :3000/todos/2/items name="Listen to 5th Symphony" done=false
      # PUT /todos/:todo_id/items/:id
      $ http PUT :3000/todos/2/items/1 done=true
      # DELETE /todos/:todo_id/items/1
      $ http DELETE :3000/todos/2/items/1
      


      Conclusion

      That’s it for part one! At this point you should have learned how to:

      • Generate an API application with Rails 5
      • Setup RSpec testing framework with Factory Bot, Database Cleaner, Shoulda Matchers and Faker.
      • Build models and controllers with TDD (Test Driven Development).
      • Make HTTP requests to an API with httpie.

      In the next part, we’ll cover authentication with JWT, pagination, and API versioning. Hope to see you there. Cheers!



      Source link

      How To Set Up a Ruby on Rails GraphQL API


      The author selected Free Press to receive a donation as part of the Write for DOnations program.

      Introduction

      GraphQL is a strongly typed query language for APIs and a server-side runtime for executing those queries with your existing data. GraphQL allows clients to fetch multiple resources from the server in a single request by giving clients the ability to specify the exact data needed in the query. This removes the need for multiple API calls. GraphQL is language and database independent, and thus can be implemented in almost every programming language alongside any database of choice.

      In this tutorial, you will build a GraphQL-powered Ruby on Rails API for taking notes. When you are finished, you will be able to create and view notes from the API using GraphQL.

      GraphiQL IDE

      If you would like to take a look at the code for this tutorial, check out the companion repository for this tutorial on the DigitalOcean Community GitHub.

      Prerequisites

      To follow this tutorial, you’ll need:

      • The Ruby programming language and the Ruby on Rails framework installed on your development machine. This tutorial was tested on version 2.6.3 of Ruby and version 6.0.2.1 of Rails, so make sure to specify these versions during the installation process. Follow one of these tutorials to install Ruby and Rails:
      • PostgreSQL installed. To follow this tutorial, use PostgreSQL version 11.2. Install PostgreSQL by following Steps 1 and 2 of one of the following tutorials:

      Step 1 — Setting Up a New Rails API Application

      In this step, you will set up a new Rails API application and connect it to a PostgreSQL database. This will serve as the foundation for the note-taking API.

      Rails provides commands that make building modern web applications faster for developers. These commands can perform actions that range from creating a new Rails application to generating files required for app development. For a full list of these commands and what they do, run the following command in your terminal window:

      This command yields an extensive list of options you can use to set the parameters of your application. One of the commands listed is the new command, which accepts an APP_PATH and creates a new Rails application at the specified path.

      Create a new Rails application using the new generator. Run the following command in your terminal window:

      • rails new rails_graphql -d=postgresql -T --api

      This creates a new Rails application in a directory named rails_graphql and installs the required dependencies. Let’s go over the flags associated with the new command:

      • The -d flag pre-configures the application with the specified database.
      • The -T flag instructs Rails to not generate test files since you won’t be writing tests in this tutorial. You can also use this flag if you plan to use a different testing framework other than the one provided by Rails.
      • The --api flag configures a Rails application with only the files required for building an API with Rails. It skips configuring settings needed for browser applications.

      Once the command is done running, switch to the newly created rails_graphql directory, which is the application’s root directory:

      Now that you have successfully set up a new Rails API application, you have to connect it to a database before you can run the app. Rails provides a database.yml file found in config/database.yml, which contains configurations for connecting your app to a different database for different development environments. Rails specifies a database name for different development environments by appending an underscore (_) followed by the environment name to your app’s name. You can always change any environment database name to whatever you choose.

      Note: You can alter config/database.yml to choose the PostgreSQL role you would like Rails to use to create your database. If you created a role that is secured by a password, follow the instructions in Step 4 of How To Use PostgreSQL with Your Ruby on Rails Application on Ubuntu 18.04 or How To Use PostgreSQL with Your Ruby on Rails Application on macOS to configure your role.

      Rails includes commands for creating and working with databases. With your database credentials in place, run the following command in your terminal window to create your databases:

      The db:create command creates a development and test database based on the information provided in the config/database.yml file. Running the command yields the following output:

      Output

      Created database 'rails_graphql_development' Created database 'rails_graphql_test'

      With your application now successfully connected to a database, you can test the application to ensure it works. Start your server with the following command if you are working locally:

      If you are working on a development server, you can start your application by specifying the IP address the server should bind to:

      • bundle exec rails server --binding=your_server_ip

      Note: The server listens on port 3000. If you’re working on a development server, ensure that you have opened port 3000 in your firewall to allow connections.

      The rails server command launches Puma, a web server for Ruby distributed with Rails. The --binding=your_server_ip command binds the server to any IP you provide.

      Once you run this command, your command prompt will be replaced with the following output:

      Output

      => Booting Puma => Rails 6.0.2.1 application starting in development => Run `rails server --help` for more startup options Puma starting in single mode... * Version 4.3.1 (ruby 2.6.3-p62), codename: Mysterious Traveller * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://127.0.0.1:3000 * Listening on tcp://[::1]:3000 Use Ctrl-C to stop

      To run your application, navigate to localhost:3000 or http://your_server_ip:3000 in your browser. You’ll see the Rails default welcome page:

      Rails welcome page

      The welcome page means you have properly set up your Rails application.

      To stop the server, press CTRL+C in the terminal window where the server is running.

      You have successfully set up a Rails API application for a note-taking API. In the next step, you will set up your Rails API application to receive and execute GraphQL queries.

      Step 2 — Setting Up GraphQL for Rails

      In this step, you will configure your Rails API application to work with GraphQL. You will install and set up the necessary gems required for GraphQL development in Rails.

      As previously mentioned, GraphQL is language agnostic and is implemented in many programming languages. The graphql-ruby gem is the Ruby implementation for GraphQL. GraphQL also provides an interactive in-browser IDE known as GraphiQL for running GraphQL queries. The graphiql-rails gem helps you add GraphiQL to your development environment.

      To install these dependencies, open the project’s Gemfile for editing, using nano or your favorite text editor:

      Add the graphql and graphiql-rails gems to your Gemfile. You can add the graphiql gem anywhere, but the graphiql-rails gem should be added under the development dependencies:

      ~/rails_graphql/Gemfile

      ...
      group :development do
        gem 'listen', '>= 3.0.5', '< 3.2'
        # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
        gem 'spring'
        gem 'spring-watcher-listen', '~> 2.0.0'
        gem 'graphiql-rails'
      end
      
      gem 'graphql', '1.9.18'
      ...
      

      Save and close the file when you are done adding the gems.

      In your terminal window, use the following command to install the gems:

      The output shows that the gems are installed.

      The graphql gem provides generators to create various files. To view the available generators, run the following command in your terminal window:

      The generators prefixed with graphql: are the ones associated with the graphql gem.

      You will use the graphql:install command to add graphql-ruby boilerplate code to the application and mount GraphiQL in your development environment. The boilerplate code will include all the files and directory needed for the graphql-ruby gem to work with Rails.

      In your terminal window, run the following commands:

      This command generates several files, including a graphql_controller.rb file located at app/controllers/graphql_controller.rb and a graphql directory at app/graphql which contains files required to get started with GraphQL in Rails. It also adds a /graphql HTTP POST route in the routes file located at config/routes.rb. This route is mapped to the app/controllers/graphql_controller.rb#execute method which handles all queries to the GraphQL server.

      Before you can test the GraphQL endpoint, you need to mount the GraphiQL engine to the routes file so you can access the GraphiQL in-browser IDE. To do this open the routes file located at config/routes.rb:

      • nano ~/rails_graphql/config/routes.rb

      Add the following code to the file to mount the GraphiQL engine in the development environment:

      ~/rails_graphql/config/routes.rb

      Rails.application.routes.draw do
        if Rails.env.development?
          mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "graphql#execute"
        end
        post "/graphql", to: "graphql#execute"
        # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
      end
      

      This mounts the GraphiQL engine to the /graphiql path and directs all queries to the graphql#execute method.

      Since this is an API application created with the --api flag, it does not expect to render any page in the browser. To make the GraphiQL editor show up in the browser, you need to make a couple of small changes to your application’s configuration.

      First, open the application.rb file located at config/application.rb:

      • nano ~/rails_graphql/config/application.rb

      Next, uncomment the require "sprockets/railtie" line:

      ~/rails_graphql/config/application.rb

      require_relative 'boot'
      
      require "rails"
      # Pick the frameworks you want:
      require "active_model/railtie"
      require "active_job/railtie"
      require "active_record/railtie"
      require "active_storage/engine"
      require "action_controller/railtie"
      require "action_mailer/railtie"
      require "action_mailbox/engine"
      require "action_text/engine"
      require "action_view/railtie"
      require "action_cable/engine"
      require "sprockets/railtie"
      # require "rails/test_unit/railtie"
      
      ...
      

      Save and close the file after uncommenting the line.

      Now create a config directory at app/assets:

      • mkdir -p app/assets/config

      Next, create a manifest.js file in the newly created config directory. The manifest.js file specifies additional assets to be compiled and made available to the browser:

      • nano app/assets/config/manifest.js

      Add the following code to the file which tells Rails to precompile the graphiql/rails/application.css and graphiql/rails/application.js files so Rails can serve them to your browser:

      ~/rails_graphql/app/assets/config/manifest.js

      //= link graphiql/rails/application.css
      //= link graphiql/rails/application.js
      

      Save and close the file.

      With that done, you can test your GraphQL endpoint. Restart your development server, and in your browser, navigate to localhost:3000/graphiql or http://your_server_ip:3000/graphiql. The GraphiQL query editor displays in your browser:

      GraphiQL IDE

      The left side of the GraphiQL IDE accepts GraphQL queries and the right side displays results of the run query. The GraphiQL query editor also has a syntax highlighter and a typeahead hinter powered by your GraphQL Schema. Together, these help you make a valid query.

      To try a Hello World example, clear out the default text in the editor’s left pane and type in the following query:

      query {
          testField
      }
      

      Click the Play icon button in the header and you’ll recieve a successful response on the screen, as shown in the following figure:

      GraphiQL IDE Response successful response

      You have successfully set up your Rails API application to work with GraphQL and tested your GraphQL endpoint to confirm it works. In the next step, you will create GraphQL types for your application.

      Step 3 — Creating Types for the Application

      GraphQL depends on its Types and Schema to validate and respond to queries. In this step, you will create a Note model and the GraphQL types required in your note-taking API.

      A GraphQL type consists of fields and arguments which, in turn, define the fields and arguments that can appear in any GraphQL query that operates on that type. These types make up a GraphQL Schema. GraphQL defines the following types:

      • The Query and Mutation types: These are special types that define the entry point of every GraphQL query. Every GraphQL service has a query type and may or may not have a mutation type.
      • Object types: These are the basic components of a GraphQL schema. These represent the objects you can fetch from a GraphQL service and the fields each object holds.
      • Scalar types: These are default types that come with GraphQL out of the box. They include Int, Float, String, Boolean, and ID.
      • Enumeration types: These are types that define a particular set of allowed values.
      • Input types: These are similar to object types, with the only difference being that they define objects that you can pass to queries as arguments.

      There are other types, including Union, List, Non-Null, and Interface. You can find a list of available GraphQL types in the official GraphQL documentation.

      For this application, you will create a Note model and a Note object and input type. The Note model will represent the database table that will store your notes while the Note object and input type will define the fields and arguments that exists on a Note object.

      First, create a Note model using the generate model subcommand provided by Rails and specify the name of the model along with its columns and data types. Run the following command in your terminal window:

      • rails generate model note title:string:index body:text

      This command creates a Note model with two fields: title, with the type string, and body, with the type text. The command also adds a database index on the title column. It generates these two files:

      • A note.rb file located at app/models/note.rb. This file will hold all model-related logic.
      • A 20200617173228_create_notes.rb file (the number at the beginning of the file will differ, depending on the date you run the command) located at db/migrate/20200617173228_create_notes.rb. This is a migration file that holds the instruction for creating a corresponding notes table in the database.

      To execute the instructions in the migration file, you’ll use the db:migrate subcommand which executes the instruction in your migration files. Run the following command in your terminal window:

      Once the command runs successfully, you will see output similar to the following:

      Output

      == 20200617173228 CreateNotes: migrating ====================================== -- create_table(:notes) -> 0.0134s -- add_index(:notes, :title) -> 0.0073s == 20200617173228 CreateNotes: migrated (0.0208s) =============================

      With the note model in place, next you’ll create a NoteType. A valid note object is expected to have an id, a title, and text. Run the following command in your terminal window to create a NoteType:

      • rails generate graphql:object Note id:ID! title:String! body:String!

      The command instructs Rails to create a GraphQL object type called Note with three fields: an id field with a type of ID, and the title and body fields, each with a String type. The exclamation point (!) appended to the field type indicates that the field should be non-nullable, meaning that the field should never return a null value. Non-nullable fields are important, as they serve as a form of validation that guarantees which fields must be present whenever GraphQL objects are queried.

      Running the preceding command creates a note_type.rb file located at app/graphql/types/note_type.rb containing a Types::NoteType class with three non-nullable fields.

      Lastly, you will create a NoteInput type to define the arguments required to create a note. Start by creating an input directory under app/graphql/types. The input directory will house input types:

      • mkdir ~/rails_graphql/app/graphql/types/input

      Note: It’s not a requirement to create input types in the input directory; it is merely a common convention. You can decide to keep all your types under the types directory and exclude nesting the class under an Input module whenever you’re accessing it.

      In the ~/rails_graphql/app/graphql/types/input directory, create a note_input_type.rb file:

      • nano ~/rails_graphql/app/graphql/types/input/note_input_type.rb

      Add the following code to the file to define the fields for the Input type:

      ~/rails_graphql/app/graphql/types/input/note_input_type.rb

      module Types
        module Input
          class NoteInputType < Types::BaseInputObject
            argument :title, String, required: true
            argument :body, String, required: true
          end
        end
      end
      

      In the note_input_type.rb file, you added a Types::Input::NoteInputType class that inherits from the Types::BaseInputObject class and accepts two required arguments; title and body, both of a string type.

      You’ve created a model and two GraphQL types for your note-taking app. In the next step, you will create queries to fetch existing notes.

      Step 4 — Creating Queries for the Application

      Your GraphQL-powered API is gradually coming together. In this step you’ll create two queries; one to fetch a single note by id and another to fetch all notes. The GraphQL query type handles the fetching of data and can be likened to a GET request in REST.

      First, you’ll create a query to fetch all notes. To start, create a queries directory to house all queries:

      • mkdir ~/rails_graphql/app/graphql/queries

      In the app/graphql/queries directory, create a base_query.rb file from which all other query classes will inherit:

      • nano ~/rails_graphql/app/graphql/queries/base_query.rb

      Add the following code to the base_query.rb file to create a BaseQuery class that other query classes will inherit from:

      ~/rails_graphql/app/graphql/queries/base_query.rb

      module Queries
        class BaseQuery < GraphQL::Schema::Resolver
        end
      end
      

      In the base_query.rb file, you added a Queries::BaseQuery class that inherits from the GraphQL::Schema::Resolver class. The GraphQL::Schema::Resolver class is a container that can hold logic belonging to a field. It can be attached to a field with the resolver: keyword.

      The Queries::BaseQuery class can also contain any code you intend to reuse across multiple query classes.

      Next, create a fetch_notes.rb file in the queries directory. This file will hold the logic for fetching all existing notes, and will be attached to a field in the query type file:

      • nano ~/rails_graphql/app/graphql/queries/fetch_notes.rb

      Add the following code to the file to define the return object type and resolve the requested notes:

      ~/rails_graphql/app/graphql/queries/fetch_notes.rb

      module Queries
        class FetchNotes < Queries::BaseQuery
      
          type [Types::NoteType], null: false
      
          def resolve
            Note.all.order(created_at: :desc)
          end
        end
      end
      

      In the fetch_notes.rb file, you created a Queries::FetchNotes class that inherits the Queries::BaseQuery previously created. The class has a return type declaration that declares that the data returned by this query should be an array of the already created NoteType.

      The Queries::FetchNotes also contains a resolve method that returns an array of all existing notes sorted by their created date in descending order.

      The FetchNotes query is ready to receive and return requests for notes, but GraphQL is still unaware of its existence, to fix that, open the GraphQL query type file located at app/graphql/types/query_type.rb:

      • nano ~/rails_graphql/app/graphql/types/query_type.rb

      The query_type.rb file is the entry point for all GraphQL query types. It holds the query fields, and their respective resolver methods. Replace the sample code in the file with the following:

      ~/rails_graphql/app/graphql/types/query_type.rb

      module Types
        class QueryType < Types::BaseObject
          # Add root-level fields here.
          # They will be entry points for queries on your schema.
      
          field :fetch_notes, resolver: Queries::FetchNotes
        end
      end
      

      In the query_type.rb file, you added a fetch_notes field and attached it to the Queries::FetchNotes class using a resolver:. This way whenever the fetch_notes query is called, it executes the logic in the resolve method of the Queries::FetchNotes class.

      In order to test your query, you need some data to fetch, but you currently don’t have any notes in your database. You can fix that by adding some seed data to your database. Open the seeds.rb file located at db/seeds.rb:

      • nano ~/rails_graphql/db/seeds.rb

      Add the following code to the file to create five notes:

      ~/rails_graphql/db/seeds.rb

      5.times do |i|
        Note.create(title: "Note #{i + 1}", body: 'Lorem ipsum saves lives')
      end
      

      Save and close the file after adding the code.

      Open your project’s root directory in another terminal window and run the following command to run the code in the seed.rb file:

      This creates 5 notes in the database.

      With data in your database, and your development server running, navigate to localhost:3000/graphiql or http://your_server_ip:3000/graphiql in your browser to open your GraphiQL IDE. In the left side of the editor, type in the following query:

      query {
        fetchNotes {
          id
          title
          body
        }
      }
      

      This GraphQL query declares a query operation, indicating you want to make a query request. In the query operation, you called a fetchNotes field that matches the fetch_notes query field declared in the API, and included the fields on a note that you want to be returned in your response.

      Click the Play icon button in the header. You’ll see a response similar to the following in the output pane:

      {
        "data": {
          "fetchNotes": [
            {
              "id": "5",
              "title": "Note 5",
              "body": "Lorem ipsum saves lives"
            },
            {
              "id": "4",
              "title": "Note 4",
              "body": "Lorem ipsum saves lives"
            },
            {
              "id": "3",
              "title": "Note 3",
              "body": "Lorem ipsum saves lives"
            },
            {
              "id": "2",
              "title": "Note 2",
              "body": "Lorem ipsum saves lives"
            },
            {
              "id": "1",
              "title": "Note 1",
              "body": "Lorem ipsum saves lives"
            }
          ]
        }
      }
      

      The response contains an array of 5 notes that match the fields declared in the query on the left. If you remove some fields in the query on the left side of the editor and re-run the query, you get a response with only the fields you requested. That’s the power of GraphQL.

      Next, you’ll create another query to fetch notes by id. This query will be similar to the fetch_notes query, only that it’ll accept an id argument. Go ahead and create a fetch_note.rb file in the queries directory:

      • nano ~/rails_graphql/app/graphql/queries/fetch_note.rb

      Add the following code to the file to find and return a note with the provided id:

      ~/rails_graphql/app/graphql/queries/fetch_note.rb

      module Queries
        class FetchNote < Queries::BaseQuery
          type Types::NoteType, null: false
          argument :id, ID, required: true
      
          def resolve(id:)
            Note.find(id)
          rescue ActiveRecord::RecordNotFound => _e
            GraphQL::ExecutionError.new('Note does not exist.')
          rescue ActiveRecord::RecordInvalid => e
            GraphQL::ExecutionError.new("Invalid attributes for #{e.record.class}:"
              " #{e.record.errors.full_messages.join(', ')}")
          end
        end
      end
      

      This defines a Queries::FetchNote class that inherits from the Queries::BaseQuery class. This class not only returns a single item that must be of a NoteType, it also accepts an id argument with an ID type. The resolve method receives the provided id argument, then finds and returns a note with the provided id. If no note exists or an error occurs, it is rescued and returned as a GraphQL::ExecutionError.

      Next, you will attach the Queries::FetchNote class to a query field in the query type file. Open the query_type.rb file in your editor:

      • nano ~/rails_graphql/app/graphql/types/query_type.rb

      Add the following code to the file which defines a resolver for fetch_notes:

      ~/rails_graphql/app/graphql/types/query_type.rb

      module Types
        class QueryType < Types::BaseObject
          # Add root-level fields here.
          # They will be entry points for queries on your schema.
      
          field :fetch_notes, resolver: Queries::FetchNotes
          field :fetch_note, resolver: Queries::FetchNote
        end
      end
      

      To test your new query, ensure your server is running and navigate to localhost:3000/graphiql or http://your_server_ip:3000/graphiql in your browser to open your GraphiQL IDE. In the left side of the editor, type in the following query:

      query {
        fetchNote(id: 1) {
          id
          title
          body
        }
      }
      

      This query operation requests a fetchNote field, which corresponds to the fetch_note query field, and is passed an id argument. It specifies that we want three fields to be returned in the response.

      Run the query by clicking the Play icon button in the header. You will get a response like the following in the output pane:

      {
        "data": {
          "fetchNote": {
            "id": "1",
            "title": "Note 1",
            "body": "Lorem ipsum saves lives"
          }
        }
      }
      

      The response contains a single note that matches the requested id with fields matching the ones in the request.

      In this step, you created GraphQL queries to fetch notes from your API. Next you’ll write mutations to create notes.

      Step 5 — Creating GraphQL Mutations to Modify Notes

      In addition to queries, GraphQL also defines a mutation type for operations that modify server-side data. Just as REST provides POST, PUT, PATCH, and DELETE requests for creating, updating and deleting resources, GraphQL’s mutation type defines a convention for operations that cause writes on the server-side. In this step, you’ll create a mutation for adding new notes.

      graphQL-ruby includes two classes for writing mutations. They are:

      • GraphQL::Schema::Mutation: This is the generic base class for writing mutations. If you don’t want an input argument required in your mutations, you should use this class.
      • GraphQL::Schema::RelayClassicMutation: This is a base class with some conventions; an argument called clientMutationId that is always inserted to the response, and mutations that accepts one argument called input. This class is used by default when you use the install generator to add boilerplate GraphQL files to your project.

      Create an add_note.rb file in the mutations directory located at app/graphql/mutations:

      • nano ~/rails_graphql/app/graphql/mutations/add_note.rb

      Add the following code to the file to define the mutation for adding new notes:

      ~/rails_graphql/app/graphql/mutations/add_note.rb

      module Mutations
        class AddNote < Mutations::BaseMutation
          argument :params, Types::Input::NoteInputType, required: true
      
          field :note, Types::NoteType, null: false
      
          def resolve(params:)
            note_params = Hash params
      
            begin
              note = Note.create!(note_params)
      
              { note: note }
            rescue ActiveRecord::RecordInvalid => e
              GraphQL::ExecutionError.new("Invalid attributes for #{e.record.class}:"
                " #{e.record.errors.full_messages.join(', ')}")
            end
          end
        end
      end
      

      This defines a Mutations::AddNote class that inherits from the Mutations::BaseMutation class, which is one of the classes created when you ran the install generator while installing the GraphQL-Ruby gem. The Mutations::AddNote class receives an argument with the name params and a type of NoteInputType, which you created in Step 3. It also returns a field called note that must be a non-null NoteType type.

      The resolve method of the class receives the params and converts it to a hash which it uses to create and return a new hash containing the new note. If there’s an error while creating the note, the error is rescued and returned as a GraphQL::ExecutionError.

      Note: The resolve method in a mutation must return a hash whose symbol matches the field names.

      Like with queries, the Mutations::AddNote mutation has to be attached to a mutation field using the mutation: keyword.

      Open the mutation type file located at app/graphql/types/mutation_type.rb in your editor:

      • nano ~/rails_graphql/app/graphql/types/mutation_type.rb

      Replace the code in the file with the following code, which adds a field for the add_note with its corresponding mutation class:

      ~/rails_graphql/app/graphql/types/mutation_type.rb

      module Types
        class MutationType < Types::BaseObject
          field :add_note, mutation: Mutations::AddNote
        end
      end
      

      In this code, you added an add_note field to the mutation type file and attached it to the Mutations::AddNote class using the mutation: keyword. When the add_note mutation is called, it runs the code in the resolve method of the Mutations::AddNote class.

      To test your new mutation, navigate to localhost:3000/graphiql or http://your_server_ip:3000/graphiql in your browser to open your GraphiQL IDE. In the left side of the editor, type in the following query:

      mutation {
        addNote(input: { params: { title: "GraphQL notes", body: "A long body of text about GraphQL"  }}) {
          note {
            id
            title
            body
          }
        }
      }
      

      This declares a mutation operation with an addNote field that accepts a single input argument, which in turn accepts a param object with keys that match the NoteInputType. The mutation operation also includes a note field that matches the note field returned by the Mutations::AddNote class.

      Run the mutation in GraphiQL and you’ll see the following results in the output pane:

      {
        "data": {
          "addNote": {
            "note": {
              "id": "6",
              "title": "GraphQL notes",
              "body": "A long body of text about GraphQL"
            }
          }
        }
      }
      

      The response returned is the newly created note with the fields requested in the mutation request.

      With your add_note mutation now working, your API can fetch and create notes using GraphQL queries and mutations.

      Conclusion

      In this tutorial, you created a note-taking API application with Ruby on Rails using PostgreSQL as your database and GraphQL as your API query language. You can learn more about GraphQL on its official website. The GraphQL-Ruby gem website also contains some guides to help you work with GraphQL in Rails.



      Source link

      Cómo agregar Sidekiq y Redis a una aplicación de Ruby on Rails


      Introducción

      Al desarrollar una aplicación de Ruby on Rails, es posible que encuentre tareas de aplicación que se deben realizar de forma asíncrona. El procesamiento de datos, el envío de correos electrónicos por lotes o la interacción con API externas son ejemplos de trabajo que se pueden realizar de forma asincrónica con tareas en segundo plano. Con tareas en segundo plano, puede mejorar el rendimiento de su aplicación descargando tareas que pueden demandar gran cantidad de tiempo a una cola de procesamiento en segundo plano y, así, liberar el ciclo de solicitud o respuesta original

      Sidekiq es uno de los marcos de tareas en segundo plano más utilizados que se pueden implementar en una aplicación de Rails. Cuenta con el respaldo de Redis, un sistema de almacenamiento de clave-valor en memoria, conocido por su flexibilidad y rendimiento. Sidekiq utiliza Redis como almacén de administración de tareas para procesar miles de tareas por segundo.

      En este tutorial, agregaremos Redis y Sidekiq a una aplicación de Rails existente. Crearemos un conjunto de clases y métodos de trabajadores de Sidekiq para gestionar lo siguiente:

      • Una carga en lote de información sobre tiburones en peligro a la base de datos de aplicación desde un archivo CSV en el repositorio del proyecto.
      • La eliminación de estos datos.

      Cuando haya terminado, tendrá una aplicación de muestra que utilizará trabajadores y tareas para procesar tareas de forma asíncrona. Esta será una buena base para añadir trabajadores y trabajos a su aplicación utilizando este tutorial como punto de partida.

      Requisitos previos

      Para este tutorial, necesitará lo siguiente:

      Paso 1: Clonar el proyecto e instalar dependencias

      Nuestro primer paso será clonar el repositorio de rails-bootstrap de la cuenta de GitHub comunitaria de DigitalOcean. En este repositorio se incluye el código de la configuración descrita en el artículo Cómo agregar Bootstrap a una aplicación de Ruby on Rails, en el que se explica la forma de añadir Bootstrap a un proyecto de Rails 5 existente.

      Clone el repositorio en un directorio llamado rails-sidekiq:

      • git clone https://github.com/do-community/rails-bootstrap.git rails-sidekiq

      Diríjase al directorio rails-sidekiq:

      Para trabajar con el código, primero deberá instalar las dependencias del proyecto que se enumeran en el Gemfile de este. También deberá añadir la gema sidekiq al proyecto para trabajar con Sidekiq y Redis.

      Abra el archivo de Gemfile del proyecto para editarlo con nano o su editor favorito:

      Añada la gema en cualquier punto de las dependencias del proyecto principal (encima de las dependencias de desarrollo):

      ~/rails-sidekiq/Gemfile

      . . .
      # Reduces boot times through caching; required in config/boot.rb
      gem 'bootsnap', '>= 1.1.0', require: false
      gem 'sidekiq', '~>6.0.0'
      
      group :development, :test do
      . . .
      

      Guarde y cierre el archivo cuando termine de añadir la gema.

      Utilice el siguiente comando para instalar las gemas:

      Veremos en el resultado que la gema de redis también se instala como requisito para sidekiq.

      Luego, instalará sus dependencias de Yarn. Debido a que este proyecto de Rails 5 se modificó para proporcionar recursos con webpack, Yarn ahora gestiona sus dependencias de JavaScript. Esto significa que es necesario instalar y verificar las dependencias que se enumeran en el archivo package.json del proyecto.

      Ejecute yarn install para instalar estas dependencias:

      Luego, ejecute las migraciones de su base de datos:

      Una vez que hayan terminado sus migraciones, puede probar la aplicación para asegurarse de que funcione como se espera. Inicie su servidor en el contexto de su paquete local con el siguiente comando si trabaja a nivel local:

      Si trabaja en un servidor de desarrollo, puede iniciar la aplicación con lo siguiente:

      • bundle exec rails s --binding=your_server_ip

      Diríjase a localhost:3000 o http://your_server_ip:3000. Visualizará la siguiente página de destino:

      Página de inicio de la aplicación

      Para crear un nuevo tiburón, haga clic en el botón Get Shark Info, que lo dirigirá a la ruta sharks/index:

      Ruta del índice de tiburones

      Para verificar que la aplicación funcione, podemos añadirle información de prueba. Haga clic en New Shark. Gracias a la configuración de autenticación del proyecto, se le solicitará un nombre de usuario (sammy) y una contraseña (tiburón).

      En la página New Shark, ingrese “Gran tiburón blanco” en el campo Name y “Terrorífico” en el campo Facts.

      Creación de un tiburón

      Haga clic en el botón Create Shark para crear el tiburón. Una vez que haya creado el tiburón, podrá detener el servidor con CTRL+C.

      Con esto, habrá instalado las dependencias necesarias para su proyecto y comprobado que funciona. Luego, puede realizar algunos cambios en la aplicación de Rails para trabajar con sus recursos relacionados con los tiburones en peligro.

      Paso 2: Generar un controlador para recursos relacionados con tiburones en peligro

      Para trabajar con nuestros recursos relacionados con tiburones en peligro, añadiremos un nuevo modelo a la aplicación y un controlador que regulará la manera en que se presente a los usuarios la información sobre tiburones en peligro. Nuestro objetivo final es permitir que los usuarios carguen un gran lote de información sobre tiburones en peligro, sin bloquear la función general de nuestra aplicación, y eliminen dicha información cuando ya no la necesiten.

      Primero, crearemos un modelo Endangered para nuestros tiburones en peligro. Incluiremos un campo de cadena a la tabla de nuestra base de datos para el nombre del tiburón y otro campo de cadena para las categorías de la Unión Internacional para la Conservación de la Naturaleza (UICN) que determinan el grado de riesgo en el que se encuentra cada tiburón.

      En última instancia, la estructura de nuestro modelo coincidirá con las columnas del archivo CSV que usaremos para crear nuestra carga en lote. Este archivo se encuentra en el directorio db y puede verificar su contenido con el siguiente comando:

      El archivo contiene una lista de 73 tiburones en peligro y sus situaciones según la IUCN: vu significa vulnerable, en significa en peligro y cr significa en peligro crítico.

      Nuestro modelo Endangered se correlacionará con estos datos, lo que nos permitirá crear nuevas instancias de Endangered desde este archivo CSV. Cree el modelo con el siguiente comando:

      • rails generate model Endangered name:string iucn:string

      Luego, genere un controlador Endangered con una acción index:

      • rails generate controller endangered index

      Esto nos proporcionará un punto de partida para crear la función de nuestra aplicación, aunque también tendremos que añadir métodos personalizados al archivo del controlador que Rails generó para nosotros.

      Abra el archivo ahora:

      • nano app/controllers/endangered_controller.rb

      Rails nos proporciona un esquema de borrador que podemos comenzar a completar.

      Primero, debemos determinar las rutas que necesitamos para trabajar con nuestros datos. Gracias al comando generate controller​​​​​​, contamos con un mé todo index para comenzar. Esto se correlacionará con una vista de index, en la que presentaremos a los usuarios la opción para cargar tiburones en peligro.

      Sin embargo, también nos convendrá tratar los casos en los que los usuarios ya hayan cargado tiburones; no necesitarán una opción de carga en este caso. De alguna forma, tendremos que evaluar la cantidad existente de casos de la clase Endangered, puesto que más de una indica que ya se produjo la carga del lote.

      Empecemos creando un método set_endangered private​​​ que tomará cada instancia de nuestra clase Endangered de la base de datos. Añada el siguiente código al archivo:

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      class EndangeredController < ApplicationController
        before_action :set_endangered, only: [:index, :data]
      
        def index
        end
      
        private
      
          def set_endangered
            @endangered = Endangered.all
          end
      
      end
      

      Tenga en cuenta que el filtro before_action garantizará que el valor de @endangered solo esté configurado para las rutas index y data, donde gestionaremos los datos de tiburones en peligro.

      Después, añada el siguiente código al método index a fin de determinar la ruta correcta para los usuarios que visitan esta parte de la aplicación:

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      class EndangeredController < ApplicationController
        before_action :set_endangered, only: [:index, :data]
      
        def index          
          if @endangered.length > 0
            redirect_to endangered_data_path
          else
            render 'index'
          end
        end
      . . .
      

      Si hay más de 0 instancias de nuestra clase Endangered, redirigiremos a los usuarios a la ruta data, donde podrán ver información sobre los tiburones que crearon. De lo contrario, verán la vista de index.

      Luego, debajo del método index, añada un método data, que se correlacionará con una vista de data:

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      . . .
        def index          
          if @endangered.length > 0
            redirect_to endangered_data_path
          else
            render 'index'
          end
        end
      
        def data
        end
      . . .
      

      A continuación, añadiremos un método para gestionar la carga de datos. Llamaremos a este método upload e invocará una clase y método de trabajador Sidekiq para realizar la carga de datos desde el archivo CSV. En el siguiente paso, crearemos la definición de esta clase de trabajador: AddEndangeredWorker.

      Por ahora, añada el siguiente código al archivo para invocar al trabajador Sidekiq y realizar la carga:

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      . . .
        def data
        end
      
        def upload
          csv_file = File.join Rails.root, 'db', 'sharks.csv'   
          AddEndangeredWorker.perform_async(csv_file)
          redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
        end
      . . .
      

      Al invocar el método perform_async en la clase AddEndangeredWorker usando el archivo CSV como argumento, este código garantiza que los datos de tiburones y la tarea de carga se transfieran a Redis. Los trabajadores Sidekiq que configuraremos monitorean la cola de trabajo y responderán cuando surjan nuevos trabajo.

      Después de invocar perform_async, nuestro método upload​​​ aplica redireccionamiento a la ruta data, en la que los usuarios podrán ver los tiburones cargados.

      Luego, añadiremos un método destroy para destruir los datos. Añada el siguiente código que está debajo del método upload:

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      . . .
        def upload
          csv_file = File.join Rails.root, 'db', 'sharks.csv'   
          AddEndangeredWorker.perform_async(csv_file)
          redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
        end
      
        def destroy
          RemoveEndangeredWorker.perform_async
          redirect_to root_path
        end
      . . .
      

      Al igual que nuestro método upload, nuestro método destroy incluye la invocación de perform_async en una clase RemoveEndangeredWorker: el otro trabajador Sidekiq que crearemos. Después de invocar este método, redirecciona a los usuarios a la ruta de la aplicación root.

      El archivo terminado tendrá este aspecto:

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      class EndangeredController < ApplicationController
        before_action :set_endangered, only: [:index, :data]
      
        def index          
          if @endangered.length > 0
            redirect_to endangered_data_path
          else
            render 'index'
          end
        end
      
        def data
        end
      
        def upload
          csv_file = File.join Rails.root, 'db', 'sharks.csv'   
          AddEndangeredWorker.perform_async(csv_file)
          redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
        end
      
        def destroy
          RemoveEndangeredWorker.perform_async
          redirect_to root_path
        end
      
        private
      
          def set_endangered
            @endangered = Endangered.all
          end
      
      end
      

      Guarde y cierre el archivo cuando concluya la edición.

      Como paso final para afianzar las rutas de nuestra aplicación, modificaremos el código de config/routes.rb, el archivo en el que residen nuestras instrucciones de ruta.

      Abra el archivo ahora:

      El archivo actualmente tiene este aspecto:

      ~/rails-sidekiq/config/routes.rb

      Rails.application.routes.draw do
        get 'endangered/index'
        get 'home/index'
        resources :sharks do
                resources :posts
        end
        root 'home#index'
        # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
      end
      

      Será necesario actualizar el archivo para incluir las rutas que definimos en nuestro controlador: data, upload y destroy. Nuestra ruta data coincidirá con una solicitud GET para obtener los datos de tiburones, mientras que nuestras rutas upload y destroy se asignarán a las solicitudes POST que cargan y destruyen esos datos.

      Añada el siguiente código al archivo para definir estas rutas:

      ~/rails-sidekiq/config/routes.rb

      Rails.application.routes.draw do
        get 'endangered/index'
        get 'endangered/data', to: 'endangered#data'
        post 'endangered/upload', to: 'endangered#upload'
        post 'endangered/destroy', to: 'endangered#destroy'
        get 'home/index'
        resources :sharks do
                resources :posts
        end
        root 'home#index'
        # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
      end
      

      Guarde y cierre el archivo cuando concluya la edición.

      Con su modelo y controlador Endangered, ahora puede proceder a definir sus clases de trabajadores Sidekiq.

      Paso 3: Definir trabajadores Sidekiq

      Invocamos los métodos perform_async en nuestros trabajadores Sidekiq de nuestro controlador, pero aún debemos crear los trabajadores.

      Primero, cree un directorio workers para los trabajadores:

      Abra un archivo para el trabajador AddEndangeredWorker:

      • nano app/workers/add_endangered_worker.rb

      En este archivo, añadiremos el código que nos permitirá trabajar con los datos de nuestro archivo CSV. Primero, añada el código al archivo que creará la clase, incluya la biblioteca CSV Ruby y asegúrese que esta clase funcione como trabajador Sidekiq:

      ~/rails-sidekiq/app/workers/add_endangered_worker.rb

      class AddEndangeredWorker
        require 'csv'
        include Sidekiq::Worker
        sidekiq_options retry: false
      
      end
      

      También incluiremos la opción retry:false para garantizar que Sidekiq no vuelva a intentar realizar la carga ante una falla.

      Luego, añada el código para la función perform:

      ~/rails-sidekiq/app/workers/add_endangered_worker.rb

      class AddEndangeredWorker
        require 'csv'
        include Sidekiq::Worker
        sidekiq_options retry: false
      
        def perform(csv_file)
          CSV.foreach(csv_file, headers: true) do |shark|
          Endangered.create(name: shark[0], iucn: shark[1])
        end
       end
      
      end
      

      El método perform recibe los argumentos del método perform_async definido en el controlador, por lo que es importante que los valores de argumento estén nivelados. Aquí, pasamos en csv_file, la variable que definimos en el controlador y usamos el método foreach de la biblioteca CSV para leer los valores del archivo. Configurar headers:true para este bucle garantiza que se trate a la primera fila del archivo como a una fila de encabezados.

      Luego, el bloque luego lee los valores del archivo en las columnas que configuramos para nuestro modelo Endangered: name e iucn. Ejecutar este bucle creará instancias de Endangered para cada una de las entradas de nuestro archivo CSV.

      Una vez que finalice la edición, guarde y cierre el archivo.

      Luego, crearemos un trabajador para que elimine estos datos. Abra un archivo para la clase RemoveEndangeredWorker:

      • nano app/workers/remove_endangered_worker.rb

      Añada el código para definir la clase y para garantizar que utilice la biblioteca CSV y las funciones como un trabajador Sidekiq:

      ~/rails-sidekiq/app/workers/remove_endangered_worker.rb

      class RemoveEndangeredWorker
        include Sidekiq::Worker
        sidekiq_options retry: false
      
      end
      

      Luego, añada un método perform para gestionar la destrucción de los datos de tiburones en peligro:

      ~/rails-sidekiq/app/workers/remove_endangered_worker.rb

      class RemoveEndangeredWorker
        include Sidekiq::Worker
        sidekiq_options retry: false
      
        def perform
          Endangered.destroy_all
        end
      
      end
      

      El método perform invoca a destroy_all en la clase Endangered, que eliminará todas las instancias de esa clase de la base de datos.

      Guarde y cierre el archivo cuando concluya la edición.

      Una vez que sus trabajadores estén activos, podrá proceder a crear un diseño para sus vistas de endangered, así como plantillas para sus vistas de index y data, para que los usuarios puedan cargar y ver tiburones en peligro.

      Paso 4: Agregar diseños y plantillas de vistas

      Para que los usuarios disfruten de su propia información de tiburones en peligro, tendremos que abordar dos aspectos: el diseño de las vistas definidas en nuestro controlador endangered y las plantillas de vista de las vistas de index y data.

      Actualmente, nuestra aplicación utiliza un diseño integral, ubicado en app/views/layouts/application.html.erb, uno parcial de navegación y uno para las vistas de sharks. El diseño de la aplicación busca un bloque de contenido, que nos permite cargar diferentes diseños basados en la parte de la aplicación con la que nuestro usuario interactúa: para la página home​​​ index, este verá un diseño y para cualquier vista que se relacione con tiburones individuales verá otro.

      Podemos reutilizar el diseño de sharks para nuestras vistas de endangered, ya que este formato también funcionará para presentar datos de tiburones de forma masiva.

      Copie el archivo de diseño de sharks para crear un diseño de endangered:

      • cp app/views/layouts/sharks.html.erb app/views/layouts/endangered.html.erb

      Luego, trabajaremos en crear las plantillas de vista para nuestras vistas de index y data.

      Abra la plantilla index primero:

      • nano app/views/endangered/index.html.erb

      Elimine el código estándar y añada en su lugar el siguiente código, que proporcionará a los usuarios información general sobre las categorías en peligro y les dará la opción de cargar información sobre tiburones en peligro:

      ~/rails-sidekiq/app/views/endangered/index.html.erb

      <p id="notice"><%= notice %></p>
      
      <h1>Endangered Sharks</h1>
      
      <p>International Union for Conservation of Nature (ICUN) statuses: <b>vu:</b> Vulnerable, <b>en:</b> Endangered, <b>cr:</b> Critically Endangered </p>
      
      <br>
      
        <%= form_tag endangered_upload_path do %>
        <%= submit_tag "Import Endangered Sharks" %>
        <% end %>
      
        <br>
      
      <%= link_to 'New Shark', new_shark_path, :class => "btn btn-primary btn-sm" %> <%= link_to 'Home', home_index_path, :class => "btn btn-primary btn-sm" %>
      

      Un form_tag hace posible la carga de datos al apuntar una acción posterior a endangered_upload_path, la ruta que definimos para nuestras cargas. El botón de envío, creado con submit_tag, solicita a los usuarios a “Import Endangered Sharks”.

      Además de este código, incluimos información general sobre códigos de ICUN, para que los usuarios puedan interpretar los datos que verán.

      Guarde y cierre el archivo cuando concluya la edición.

      Luego, abra un archivo para la vista de data:

      • nano app/views/endangered/data.html.erb

      Añada el siguiente código, que agregará una tabla con los datos de tiburones en peligro:

      ~/rails-sidekiq/app/views/endangered/data.html.erb

      <p id="notice"><%= notice %></p>
      
      <h1>Endangered Sharks</h1>
      
      <p>International Union for Conservation of Nature (ICUN) statuses: <b>vu:</b> Vulnerable, <b>en:</b> Endangered, <b>cr:</b> Critically Endangered </p>
      
      <div class="table-responsive">
      <table class="table table-striped table-dark">
        <thead>
          <tr>
            <th>Name</th>
            <th>IUCN Status</th>
            <th colspan="3"></th>
          </tr>
        </thead>
      
        <tbody>
          <% @endangered.each do |shark| %>
            <tr>
              <td><%= shark.name %></td>
              <td><%= shark.iucn %></td>
            </tr>
          <% end %>
        </tbody>
      </table>
      </div>
      
      <br>
      
        <%= form_tag endangered_destroy_path do %>
        <%= submit_tag "Delete Endangered Sharks" %>
        <% end %>
      
        <br>
      
      <%= link_to 'New Shark', new_shark_path, :class => "btn btn-primary btn-sm" %> <%= link_to 'Home', home_index_path, :class => "btn btn-primary btn-sm" %>
      

      Este código incluye de nuevo los códigos de estado de la ICUN y una tabla de Bootstrap para los datos emitidos. Al recorrer nuestra variable @endangered, mostramos el nombre y la situación de cada tiburón según la ICUN en la tabla.

      Debajo de la tabla, tenemos otro conjunto de form_tags y submit_tags, que se publican a la ruta destroy ofreciendo a los usuarios la opción de "Delete Endangered Sharks”.

      Guarde y cierre el archivo cuando concluya la edición.

      La última modificación que aplicaremos a nuestras vistas se hará en la vista de index asociada con nuestro controlador home. Como podrá recordar, esta vista se configura como root de la aplicación en config/routes.rb.

      Abra este archivo para editarlo:

      • nano app/views/home/index.html.erb

      Encuentre la columna en la fila Sharks are ancient:

      ~/rails-sidekiq/app/views/home/index.html.erb

      . . .
              <div class="col-lg-6">
                  <h3>Sharks are ancient</h3>
                  <p>There is evidence to suggest that sharks lived up to 400 million years ago.
                  </p>
              </div>
          </div>
      </div>
      

      Añada el siguiente código al archivo:

      ~/rails-sidekiq/app/views/home/index.html.erb

      . . .
              <div class="col-lg-6">
                  <h3>Sharks are ancient and SOME are in danger</h3>
                  <p>There is evidence to suggest that sharks lived up to 400 million years ago. Without our help, some could disappear soon.</p>
                  <p><%= button_to 'Which Sharks Are in Danger?', endangered_index_path, :method => :get,  :class => "btn btn-primary btn-sm"%>
                  </p>
              </div>
          </div>
      </div>
      

      Incluimos un llamado a la acción para que los usuarios aprendan más sobre tiburones en peligro compartiendo, primero, un mensaje fuerte y, luego, añadiendo un asistente button_to que envía una solicitud GET a nuestra ruta index endangered y permite que los usuarios accedan a esa parte de la aplicación. Desde allí, podrán cargar y ver información de tiburones en peligro.

      Guarde y cierre el archivo cuando concluya la edición.

      Una vez que esté listo su código, estará preparado para iniciar la aplicación y cargar algunos tiburones.

      Paso 5: Iniciar Sidekiq y probar la aplicación

      Antes de iniciar la aplicación, necesitaremos ejecutar migraciones en nuestra base de datos e iniciar Sidekiq para habilitar nuestros trabajadores. Redis ya se debería estar ejecutando en el servidor, pero podemos verificarlo para estar seguros. Una vez que hagamos todo esto, estaremos listos para probar la aplicación.

      Primero, verifique que Redis esté en ejecución:

      Debería ver un resultado como el siguiente:

      Output

      ● redis-server.service - Advanced key-value store Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2019-11-12 20:37:13 UTC; 1 weeks 0 days ago

      Luego, ejecute las migraciones de su base de datos:

      Ahora puede iniciar Sidekiq en el contexto de su paquete de proyectos actual usando el comando bundle exec sidekiq:

      Verá un resultado como este, que indica que Sidekiq está listo para procesar tareas:

      Output

      m, `$b .ss, $$: .,d$ `$$P,d$P' .,md$P"' ,$$$$$b/md$$$P^' .d$$$$$$/$$$P' $$^' `"/$$$' ____ _ _ _ _ $: ,$$: / ___|(_) __| | ___| | _(_) __ _ `b :$$ ___ | |/ _` |/ _ |/ / |/ _` | $$: ___) | | (_| | __/ <| | (_| | $$ |____/|_|__,_|___|_|__|__, | .d$$ |_| 2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Running in ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux] 2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: See LICENSE and the LGPL-3.0 for licensing details. 2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org 2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Booting Sidekiq 6.0.3 with redis options {:id=>"Sidekiq-server-PID-17621", :url=>nil} 2019-11-19T21:43:00.543Z pid=17621 tid=gpiqiesdl INFO: Starting processing, hit Ctrl-C to stop

      Abra una segunda ventana de terminal, diríjase al directorio rails-sidekiq e inicie el servidor de aplicaciones.

      Si ejecuta la aplicación a nivel local, utilice el siguiente comando:

      Si trabaja con un servidor de desarrollo, ejecute lo siguiente:

      • bundle exec rails s --binding=your_server_ip

      Diríjase a localhost:3000 o a http://your_server_ip:3000 en el navegador. Visualizará la siguiente página de destino:

      Inicio de la aplicación de Sidekiq

      Haga clic en el botón** ¿Which Sharks Are In Danger?** . Debido a que no cargó ningún tiburón en peligro, esto lo dirigirá a la vista de endangered index:

      Vista del índice de tiburones en peligro

      Haga clic en Import Endangered Sharks para importar los tiburones. Verá un mensaje de estado que le indicará que los tiburones se importaron:

      Inicio de la importación

      También verá el comienzo de la importación. Actualice su página para ver la tabla entera:

      Actualización de tabla

      Gracias a Sidekiq, realizamos la carga del gran lote de tiburones en peligro sin cerrar el navegador ni interferir con otras funciones de la aplicación.

      Haga clic en el botón Home en la parte inferior de la página, que lo llevará de vuelta a la página principal de la aplicación:

      Inicio de la aplicación de Sidekiq

      Aquí, haga clic en Which Sharks Are in Danger? nuevamente. Esto lo llevará directamente a la vista de data, porque ya había cargado los tiburones.

      Para probar la función de eliminación, haga clic en el botón Delete Endangered Sharks que se encuentra debajo de la tabla. Una vez más, debería ser redireccionado a la página de inicio de la aplicación. Si have clic en Which Sharks Are in Danger? una última vez, lo llevará de regreso a la vista de index, en la que tendrá la opción de cargar tiburones de nuevo:

      Vista del índice de tiburones en peligro

      Su aplicación ahora se está ejecutando con los trabajadores de Sidekiq, que están listos para procesar trabajos y garantizar que los usuarios tengan una buena experiencia trabajando con su aplicación.

      Conclusión

      Ahora dispone de una aplicación Rails en funcionamiento con Sidekiq habilitado, que le permitirá descargar operaciones complejas a una cola de trabajo administrada por Sidekiq y respaldada por Redis. Esto le permitirá mejorar la velocidad y la funcionalidad de su sitio a medida que realice el desarrollo.

      Si desea obtener más información sobre Sidekiq, se recomienda comenzar consultando los docs.

      Para obtener más información sobre Redis, consulte nuestra biblioteca de recursos de Redis. También puede obtener más información sobre cómo ejecutar un clúster de Redis administrado en DigitalOcean consultando la documentación del producto.



      Source link