Learn Ruby on Rails Book

Adding a new task using create action

Up until now we have been using the Rails console to create a task. In this chapter we are going to learn how we can achieve the same using our application's UI.


These are the basic requirements of this feature:

  • A form component which contains a title field and a submit button.

The picture below depicts how this feature will appear after it is implemented. Note that in the picture, create form also has a field to select the assigned user. We will only be adding the title field for now because we haven't yet created a user.

We will add the select field once we are through with user creation.

Adding a task using create action feature

Technical design

To implement this feature, we need to introduce the following changes:

On the backend

  • Add a create action in TasksController which will handle the POST request from frontend side with task params.

    Create action will contain the logic to create a task and respond with a valid JSON message.

  • Add a create route for RESTful tasks resources which will route POST request to the create action in the TasksController.

On the frontend

  • Add a POST request API inside the tasks API collection for task creation.

  • Add a TaskForm component which will contain reusable Input and Button components for title field and submit button respectively.

  • Add a CreateTask component which will contain the TaskForm and logic to submit the form once submit button inside the TaskForm is clicked.

  • Create a handleSubmit function inside CreateTask which will be called when the form is submitted. This function will pass the task params as API payload and make a POST request using the POST request API.

  • Add a route in App component to render the CreateTask component.

Implementing create action

We will now add create action in the TasksController.

Here, we need to implement the creation of a task in a way that if the record creation is successful a 200 response code with a relevant notice is returned, and if the record creation fails, a 422 response code with the error message is returned.

Open /app/controllers/tasks_controller.rb and modify the code as shown below.

1class TasksController < ApplicationController
3  def index
4   tasks = Task.all
5   render status: :ok, json: { tasks: tasks }
6  end
8  def create
9    task = Task.new(task_params)
10    if task.save
11      render status: :ok, json: { notice: 'Task was successfully created' }
12    else
13      errors = task.errors.full_messages.to_sentence
14      render status: :unprocessable_entity, json: { errors: errors  }
15    end
16  end
18  private
20  def task_params
21    params.require(:task).permit(:title)
22  end

Open /app/config/routes.rb and make the necessary change.

1Rails.application.routes.draw do
2  resources :tasks, only: [:index, :create], param: :slug

Now we will modify New Task form to send a post request to create new task.

Sending a POST request to create a task

Now, we will handle the react client side logic to create a new task. To do that, we will be abstracting out the API logic and UI Form logic to different components. To do so, run the following commands:

1mkdir -p app/javascript/src/components/Tasks/Form
2touch app/javascript/src/components/Tasks/Form/TaskForm.jsx

In TaskForm.jsx, add the following content:

1import React from "react";
3import Input from "components/Input";
4import Button from "components/Button";
6const TaskForm = ({
7  type = "create",
8  title,
9  setTitle,
10  loading,
11  handleSubmit,
12}) => {
13  return (
14    <form className="max-w-lg mx-auto" onSubmit={handleSubmit}>
15      <Input
16        label="Title"
17        placeholder="Todo Title (Max 50 Characters Allowed)"
18        value={title}
19        onChange={(e) => setTitle(e.target.value.slice(0, 50))}
20      />
21      <Button
22        type="submit"
23        buttonText={type === "create" ? "Create Task" : "Update Task"}
24        loading={loading}
25      />
26    </form>
27  );
30export default TaskForm;

Here, we are using the reusable Input and Button component that we had created before. Also, TaskForm is going to be a reusable form component that we will be using not only while creating a task but also updating a task (which comes in a future chapter).

We have backend validation for title to allow a maximum of 50 characters. However, we have added frontend validation in the onChange method to ensure that from the user's perspective things are very clear.

Now, we will be creating our CreateTask component that handles the API logic to create a task. To do so, run the following command:

1touch app/javascript/src/components/Tasks/CreateTask.jsx

In CreateTask.jsx, add the following content:

1import React, { useState } from "react";
2import Container from "components/Container";
3import TaskForm from "components/Tasks/Form/TaskForm";
4import tasksApi from "apis/tasks";
6const CreateTask = ({ history }) => {
7  const [title, setTitle] = useState("");
8  const [loading, setLoading] = useState(false);
10  const handleSubmit = async (event) => {
11    event.preventDefault();
12    try {
13      await tasksApi.create({ task: { title } });
14      setLoading(false);
15      history.push("/dashboard");
16    } catch (error) {
17      logger.error(error);
18      setLoading(false);
19    }
20  };
22  return (
23    <Container>
24      <TaskForm
25        setTitle={setTitle}
26        loading={loading}
27        handleSubmit={handleSubmit}
28      />
29    </Container>
30  );
33export default CreateTask;

We will now add an API route to create a task using POST request in app/javascript/src/apis/tasks.js. In tasks.js, add the following lines:

1import axios from "axios";
3const list = () => axios.get("/tasks");
5const create = (payload) => axios.post("/tasks/", payload);
7const tasksApi = {
8  list,
9  create,
12export default tasksApi;

Let's create a route to render the CreateTask component in App.jsx.

1// previous imports if any
2import CreateTask from "components/Tasks/CreateTask";
3import Dashboard from "components/Dashboard";
5const App = () => {
6  // previous code if any
7  return (
8    <Router>
9      <Switch>
10        <Route exact path="/tasks/create" component={CreateTask} />
11        <Route exact path="/dashboard" component={Dashboard} />
12      </Switch>
13    </Router>
14  );
16// previous code if any

Visit http://localhost:3000 and click the Create button in NavBar and this time enter a new task and hit "Submit" button. We should see our new task in the tasks list.

Application flow for creating the task

The flow of all the operations are as following:

  1. Click the link Create on NavBar. The react-router will then render the CreateTask form. Once the form is submitted, the data is send using axios POST request to the create action in tasks controller, where the task_params method will get the strong parameters we had passed in.
  2. It will then try to create a new record in database using Task.new(task_params).
  3. If the task creation is successful, it will return a status ok with a notice message, and if the creation fails it will return status unprocessable_entity with the error messages.
  4. If the task record creation is successful, then, the user will be redirected to /dashboard which is the task listing page.

Moving response messages to i18n en.locales

Let's move the response messages to en.yml:

2  successfully_created: "Task was successfully created!"
3  task:
4    slug:
5      immutable: "is immutable!"

Let's use that to show response:

1def create
2  @task = Task.new(task_params)
3  if @task.save
4    render status: :ok, json: { notice:  t('successfully_created') }
5  else
6    errors = task.errors.full_messages.to_sentence
7    render status: :unprocessable_entity, json: { errors: errors  }
8  end

Let's commit the changes:

1git add -A
2git commit -m "Implemented create action"
    to navigateEnterto select Escto close