Learn Ruby on Rails Book

Adding create action

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
2
3  def index
4   @tasks = Task.all
5  end
6
7  def create
8    @task = Task.new(task_params)
9    if @task.save
10      render status: :ok, json: { notice: 'Task was successfully created' }
11    else
12      errors = @task.errors.full_messages
13      render status: :unprocessable_entity, json: { errors: errors  }
14    end
15  rescue ActiveRecord::RecordNotUnique => e
16    render status: :unprocessable_entity, json: { errors: e.message }
17  end
18
19  private
20
21  def task_params
22    params.require(:task).permit(:title)
23  end
24end

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

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

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

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

Now, inside TaskForm.jsx, add the following contents:

1import React from "react";
2
3import Input from "components/Input";
4import Button from "components/Button";
5
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="Docs Revamp"
18        value={title}
19        onChange={e => setTitle(e.target.value)}
20      />
21      <Button
22        type="submit"
23        buttonText={type === "create" ? "Create Task" : "Update Task"}
24        loading={loading}
25      />
26    </form>
27  );
28};
29
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)

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

Inside CreateTask.jsx, add the following contents:

1import React, { useState } from "react";
2import Container from "components/Container";
3import TaskForm from "components/Tasks/Form/TaskForm";
4import tasksApi from "apis/tasks";
5
6const CreateTask = ({ history }) => {
7  const [title, setTitle] = useState("");
8  const [loading, setLoading] = useState(false);
9
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  };
21
22  return (
23    <Container>
24      <TaskForm
25        setTitle={setTitle}
26        loading={loading}
27        handleSubmit={handleSubmit}
28      />
29    </Container>
30  );
31};
32
33export default CreateTask;

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

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

Now, we will create a route to render the CreateTask component inside App.jsx

1import React, { useEffect, useState } from "react";
2import { Route, Switch, BrowserRouter as Router } from "react-router-dom";
3
4import CreateTask from "components/Tasks/CreateTask";
5import Dashboard from "components/Dashboard";
6
7const App = () => {
8  return (
9    <Router>
10      <Switch>
11        <Route exact path="/tasks/create" component={CreateTask} />
12        <Route exact path="/dashboard" component={Dashboard} />
13      </Switch>
14    </Router>
15  );
16};
17
18export default App;

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:

1en:
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
7      render status: :unprocessable_entity, json: { errors: errors  }
8    end
9  rescue ActiveRecord::RecordNotUnique => e
10    render status: :unprocessable_entity, json: { errors: e.message }
11  end

Now, let's commit the changes:

1git add -A
2git commit -m "Implemented create action"