Learn Ruby on Rails Book

Adding new users

Let's implement user signup functionality to add users to our database.

Run following command to generate users controller file.

1bundle exec rails g controller users

Let's open app/controllers/users_controller.rb file and add the following content:

1class UsersController < ApplicationController
2
3  def create
4    @user = User.new(user_params)
5    if @user.save
6      render status: :ok, json: { notice: 'User was successfully created!' }
7    else
8      render status: :unprocessable_entity, json: {
9        errors: @user.errors.full_messages.to_sentence
10      }
11    end
12  end
13
14  private
15
16  def user_params
17    params.require(:user).permit(:name, :email, :password, :password_confirmation)
18  end
19end

Moving response messages to i18n en.locales

Let's move our response message to en locales but this time with even more modularization. Since, the response message for User and Task are same, we can combine them using variable interpolation. To create abstraction, the i18n provides a feature called variable interpolation that allows you to use variables in translation definitions and pass the values for these variables to the translation method.

Let's modify our response message in en.yml to use variable interpolation:

1en:
2  successfully_created: "%{entity} was successfully created!"

Now, successfully_created, in en.yml, will expect a variable named, entity. So let's go through our json responses and pass that variable with appropriate entity name, wherever we are using successfully_created key.

In users_controller.rb:

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

And in tasks_controller.rb:

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

And hence, by doing this we don't have to write two separate messages.

Adding API to create new user

First, we need to add our custom route to apis/auth.js to create a new user. To do so, create a new file app/javascript/src/apis/auth.js and add the following lines to it:

1import axios from "axios";
2
3const login = payload => axios.post("/sessions", payload);
4
5const signup = payload => axios.post("/users", payload);
6
7const authApi = {
8   login,
9   signup
10};
11
12export default authApi;

Creating sign up component

We will need to create a Signup component for letting users to register to our application.

We will be keeping all of our auth based components inside Authentication folder. To do so, run the following command:

1mkdir -p app/javascript/src/components/Authentication

Inside the directory, create a new component by running the command:

1touch app/javascript/src/components/Authentication/Signup.jsx

While creating our Signup component we will be abstracting signup form logic to a different component. To create the component that contains the form logic, run the following command:

1mkdir -p app/javascript/src/components/Authentication/Form
2touch app/javascript/src/components/Authentication/Form/SignupForm.jsx

Inside SignupForm.jsx, paste the following contents:

1import React from "react";
2import { Link } from "react-router-dom";
3
4import Input from "components/Input";
5import Button from "components/Button";
6const SignupForm = ({
7  handleSubmit,
8  setName,
9  setEmail,
10  setPassword,
11  loading,
12  setPasswordConfirmation,
13}) => {
14  return (
15    <div className="flex items-center justify-center min-h-screen px-4
16    py-12 sm:px-6 lg:px-8 bg-gray-50 ">
17      <div className="w-full max-w-md">
18        <h2 className="mt-6 text-3xl font-extrabold leading-9
19        text-center text-bb-gray-700">
20        text-center text-gray-900">
21          Sign Up
22        </h2>
23        <div className="text-center">
24          <Link
25            to="/"
26            className="mt-2 text-sm font-medium text-center
27            text-bb-purple transition duration-150 ease-in-out
28            focus:outline-none focus:underline"
29          >
30            Or Login Now
31          </Link>
32        </div>
33        <form className="mt-8" onSubmit={handleSubmit}>
34          <Input
35            label="Name"
36            placeholder="Oliver"
37            onChange={e => setName(e.target.value)}
38          />
39          <Input
40            type="email"
41            label="Email"
42            placeholder="oliver@example.com"
43            onChange={e => setEmail(e.target.value)}
44          />
45          <Input
46            type="password"
47            label="Password"
48            placeholder="********"
49            onChange={e => setPassword(e.target.value)}
50          />
51          <Input
52            type="password"
53            label="Password Confirmation"
54            placeholder="********"
55            onChange={e => setPasswordConfirmation(e.target.value)}
56          />
57          <Button type="submit" buttonText="Register" loading={loading} />
58        </form>
59      </div>
60    </div>
61  );
62};
63
64export default SignupForm;

Now open Signup.jsx and add the following contents-

1import React, { useState } from "react";
2
3import SignupForm from "components/Authentication/Form/SignupForm";
4import authApi from "apis/auth";
5
6const Signup = ({ history }) => {
7  const [name, setName] = useState("");
8  const [email, setEmail] = useState("");
9  const [password, setPassword] = useState("");
10  const [passwordConfirmation, setPasswordConfirmation] = useState("");
11  const [loading, setLoading] = useState(false);
12
13  const handleSubmit = async event => {
14    event.preventDefault();
15    try {
16      setLoading(true);
17      await authApi.signup({
18        user: {
19          name,
20          email,
21          password,
22          password_confirmation: passwordConfirmation,
23        },
24      });
25      setLoading(false);
26      history.push("/");
27    } catch (error) {
28      setLoading(false);
29      logger.error(error);
30    }
31  };
32  return (
33    <SignupForm
34      setName={setName}
35      setEmail={setEmail}
36      setPassword={setPassword}
37      setPasswordConfirmation={setPasswordConfirmation}
38      loading={loading}
39      handleSubmit={handleSubmit}
40    />
41  );
42};
43
44export default Signup;

Thus, here, we have created our Signup component and we have abstracted our form logic to Signup.jsx and our form components to SignupForm.jsx. Now, we will be adding a route inside of App.jsx that renders our newly created Signup component.

To do so, open App.jsx and add the following lines-

1import React, { useEffect, useState } from "react";
2// ----previous imports if any----
3import Signup from "components/Authentication/Signup";
4
5import { Route, Switch, BrowserRouter as Router } from "react-router-dom";
6
7const App = () => {
8  const [loading, setLoading] = useState(true);
9
10  useEffect(() => {
11    initializeLogger();
12    setAuthHeaders(setLoading);
13  }, []);
14
15  if (loading) {
16    return (
17      <div className="h-screen">
18        <PageLoader />
19      </div>
20    );
21  }
22
23  return (
24    <Router>
25      <Switch>
26        // -----previous code if any-----
27        <Route exact path="/signup" component={Signup} />
28      </Switch>
29    </Router>
30  );
31};
32
33export default App;

Now let's run rails server and visit http://localhost:3000/signup and input the details of the new user. After filling in the details click on submit button and you will be notified whether the request was successful or not by the previously added Toastr component. In the following chapters we will implement the feature to redirect the user to login page on successful registration.

Now, let's commit the changes:

1git add -A
2git commit -m "Added ability to add new users"