Search
⌘K
    to navigateEnterto select Escto close

    Adding Tasks controller

    Controller and actions

    Rails Controllers are Ruby classes, that store a series of actions. Let's generate a controller to carry out tasks related operations:

    1$ bundle exec rails generate controller Tasks
    2Running via Spring preloader in process 28301
    3      create  app/controllers/tasks_controller.rb
    4      invoke  erb
    5      create    app/views/tasks
    6      invoke  test_unit
    7      create    test/controllers/tasks_controller_test.rb
    8      invoke  helper
    9      create    app/helpers/tasks_helper.rb
    10      invoke    test_unit
    11      invoke  assets
    12      invoke    coffee
    13      create      app/assets/javascripts/tasks.coffee
    14      invoke    scss
    15      create      app/assets/stylesheets/tasks.scss
    

    Notice that the above command created multiple files. Let's open the controller file app/controllers/tasks_controller.rb and add action index to TasksController:

    1class TasksController < ApplicationController
    2  def index
    3    render html: "This is index action of Tasks controller"
    4  end
    5end

    Now go to the browser and hit http://localhost:3000/tasks.

    We will get a routing error:

    1Routing Error
    2No route matches [GET] "/tasks"

    Defining routes to map requests to controller actions

    Whenever we hit URL on the browser, Rails looks up for that path in the routes file. If a match is found, then the mapped action in the controller is invoked.

    We want to route all requests with URL /tasks to index action of TasksController.

    Open config/routes.rb and add following lines:

    1Rails.application.routes.draw do
    2  resources :tasks, only: :index
    3end

    Now visit http://localhost:3000/tasks and this time we should get message This is index action of Tasks controller.

    Adding view file

    In the above case the content was rendered by the controller itself. We want views to deliver the final html content.

    So let's create file app/views/tasks/index.html.erb:

    1touch app/views/tasks/index.html.erb

    Open app/views/tasks/index.html.erb file and add the following line:

    1<H1>List of tasks will appear here</H1>

    Open app/controllers/tasks_controller.rb then change the action index:

    1class TasksController < ApplicationController
    2  def index
    3    render
    4  end
    5end

    Now visit http://localhost:3000/tasks and this time we should get message List of tasks will appear here.

    A gentle introduction to Application controller

    All our controllers inherit from ApplicationController. This class is contained in the file app/controllers/application_controller.rb, which gets created when we execute the rails new command.

    This is what application_controller.rb looks like as of now.

    1class ApplicationController < ActionController::Base
    2end

    As we can see ApplicationController in turn inherits from ActionController::Base, which defines a number of helpful methods. We can take a look at the API documentation to understand in detail.

    Since ApplicationController is the base controller from which all other controllers inherit, this controller also act as a gatekeeper. This controller can check for authentication, authorization, logging etc. We will see all this in action soon.

    Using Rails console to add tasks

    Let's open "rails console":

    1$ bundle exec rails console
    2Running via Spring preloader in process 10511
    3Loading development environment (Rails 5.2.2)
    4irb(main):001:0>

    Let's create a Task record and save it in the table:

    1irb(main):005:0> task = Task.new(title:"Write a blog")
    2=> #<Task id: nil, title: "Write a blog", created_at: nil, updated_at: nil>
    3
    4irb(main):006:0> task.save
    5   (0.3ms)  begin transaction
    6  Task Create (0.7ms)  INSERT INTO "tasks" ("title", "created_at", "updated_at") VALUES (?, ?, ?)  [["title", "Write a blog"], ["created_at", "2019-01-30 07:35:50.117114"], ["updated_at", "2019-01-30 07:35:50.117114"]]
    7   (196.3ms)  commit transaction
    

    Let's create one more task and try to save this record:

    1irb(main):007:0> task = Task.new(title:"Learn Ruby on Rails")
    2=> #<Task id: nil, title: "Learn Ruby on Rails", created_at: nil, updated_at: nil>
    3
    4irb(main):008:0> task.save
    5   (0.1ms)  begin transaction
    6  Task Create (0.6ms)  INSERT INTO "tasks" ("title", "created_at", "updated_at") VALUES (?, ?, ?)  [["title", "Learn Ruby on Rails"], ["created_at", "2019-01-30 07:36:01.248102"], ["updated_at", "2019-01-30 07:36:01.248102"]]
    7   (181.4ms)  commit transaction
    

    Calling task.save saves the object in the database. A row is added in tasks table.

    Now we can see all the tasks stored in the database using Task.all:

    1irb(main):009:0> Task.all
    2  Task Load (0.3ms)  SELECT  "tasks".* FROM "tasks" LIMIT ?  [["LIMIT", 11]]
    3=> #<ActiveRecord::Relation [#<Task id: 3, title: "This is my first task", created_at: "2019-01-30 07:35:50", updated_at: "2019-01-30 07:35:50">, #<Task id: 4, title: "This is my second task", created_at: "2019-01-30 07:36:01", updated_at: "2019-01-30 07:36:01">]>
    

    We will be learning how to display all these tasks in the coming chapters.

    Displaying a list of tasks

    Now that we have our tasks created, we can pass the list of tasks to our view file in order to display it.

    First we need to query the list of tasks in our tasks controller and make them available for our view file like so:

    1class TasksController < ApplicationController
    2  def index
    3    @tasks = Task.all
    4  end
    5end
    

    Now we can use the instance variable @tasks to display our list of tasks. Instance variables from within a controller is directly accessible in the corresponding view files.

    Replace the content of app/views/tasks/index.html.erb file with the following:

    1<h1>ToDo</h1>
    2<ul>
    3<% @tasks.each do |task|>
    4  <li><%= task.title ></li>
    5<% end>
    6</ul>

    Now when we visit http://localhost:3000/tasks we will see a list of the tasks that we have created so far.

    This approach works if we want Rails to respond only with a view file. But what if we want a JSON response or an XML response instead? In that case, we can use Rails MimeResponds, as you will see in the next section.

    Rails MimeResponds

    In this section, we will be seeing the different ways of responding with data, by either going through the views or without it.

    respond_to is a Rails helper method and it's a way to tell Rails all the formats the controller action knows about.

    If different actions support different formats, then this method helps in responding differently based on the request.

    Let's take an example. No need to type this since we won't be committing this code:

    1class TasksController < ApplicationController
    2  def index
    3    @tasks = Task.all
    4    respond_to do |format|
    5      format.html
    6      format.json { render json: @tasks }
    7      format.xml { render xml: @tasks }
    8    end
    9  end
    10end

    In the respond_to block, we are specifying what the action needs to reply with based on the request formats like html, xml or json.

    In case of html we are telling rails to go ahead with the default or implicit route of rendering the view. Whereas for json and xml we are rendering @tasks in the respective formats as response.

    When the format responder is passed with 'json', we are telling it to respond to requests with the .json extension by serializing to JSON.

    We need to pass :html with no arguments in cases where we need to handle .html requests in the default way by using conventions and views.

    If the request is for /tasks.xml then the controller will look for a /tasks/index.xml.erb view file to render.

    If such a view file doesn’t exist then Rails will try to directly render the resource in the xml format by invoking to_xml.

    There is another helper method called respond_with.

    In respond_with method, we don't need to explicitly tell what needs to be sent for different mime-types. This makes our work much easier and also this is the benefit abiding to the DRY principle.

    Let's take an example:

    1class TasksController < ApplicationController
    2  respond_to :html, :xml, :json
    3
    4  def index
    5    @tasks = Task.all
    6    respond_with(@tasks)
    7  end
    8end

    Now let's commit changes:

    1git add -A
    2git commit -m "Added tasks controller and index action"
    Previous
    Next