[Ruby] Beginners can easily understand the mechanism by looking at Devise code [registrations new]

6 minute read

Introduction

This is the person who finished the Rails tutorial one lap at last. When I put Devise of gem, I was surprised that the implementation of 5 to 6 chapters in the Rails tutorial was completed in about 10 minutes.

I don’t know why it was done… And I want to write a test, but I don’t know how to write it. So I read it by saying “Let’s read the official and source code”, but I do not understand this at all …Although I want to understand little by little, I will read it.

While reading, I will summarize what I have investigated.

・I’m a beginner but want to know how Devise works

I hope it will be useful for such people.

Devise’s GitHub https://github.com/heartcombo/devise

Overview of Devise

At the very beginning of the GitHub README there is an overview.

-Devise is a flexible authentication solution for Rails based on Warden. -Rack based. -Complete MVC solution based on Rails engine. -You can sign in to multiple models at the same time. -Based on the concept of modularity. Use only what you really need.

The Warden that appears here is a gem for authentication, and it seems that it is being pulled in devise.

In addition, it is composed of 10 modules, and it seems that you need to use it while uncommenting out what you need. It’s a module or another function. There is a person who made a table in Qiita, so I will quote it below.

| Features | Overview | |:—————–|:—————-| |database_authenticatable | When signing in, the password is hashed and registered in the DB to verify the authenticity of the user. You can use POST request or HTTP Basic authentication as the authentication method. | |registerable | Signs up the user through the registration process. It also allows users to edit and delete their own accounts. | |recoverable |Reset the password and notify you. | |rememberable | Generates and deletes a token to remember a user from stored cookies. | |trackable | Record the number of sign-ins, sign-in time, and IP address. | |validatable | Provides email and password validation. You can also add your own validation. | |confirmable | We provide a common registration method such as clicking the URL in the email to complete the main registration. It also verifies if the account is authenticated during sign-in. | |lockable |Locks the account after a certain number of failed sign-ins. You can unlock by email or by a certain period of time. | |timeoutable | Discards sessions for accounts that have not been active for a certain period of time. | Supports |omniauthable |intridea/omniauth. Use this if you want to add authentication for Twitter, Facebook, etc. | Source: [Rails] How to use devise (rails6 version) https://qiita.com/cigalecigales/items/16ce0a9a7e79b9c3974e

Devise also creates helpers for use within controllers and views. This is a set of frequently used commands in advance.

The helper name is illustrated assuming that the device model is’User’, but If the device model is not user, then replace “_user” with “_yourmodel (any model name)” and the same logic applies.

This is also quoted because there was a person who made a table in Qiita.

| Method | Purpose |:—————–|:—————-| |before_action :authenticate_user!|Set in the controller to allow only logged-in users access| |user_signed_in?| Determine if the user is signed in | |current_user| Get the signed-in user | |user_session|Access user session information| Source: List of helper methods that can be used with Rails devise https://qiita.com/tobita0000/items/866de191635e6d74e392

registrations-Sign up, edit and delete accounts

The CRUD of the most basic account is handled by this registration, so if you do not understand this, code reading of the functional part of the application seems difficult.

I will fill in the entire code within the range I looked up from above, but it will be separated, so it may be difficult to understand. It may be a little easier to understand if you put the source code beside it and see it…

frozen_string_literal

devise/app/controllers/devise/registrations_controller.rb


# frozen_string_literal: true

It is commented out, but it seems to be one sentence in preparation for Ruby version upgrade. Reference: The principle of method design that I noticed with frozen_string_literal https://qiita.com/jkr_2255/items/300b5db8c1f04e1e2815

prepend_before_action

devise/app/controllers/devise/registrations_controller.rb


class Devise::RegistrationsController <DeviseController
  prepend_before_action :require_no_authentication, only: [:new, :create, :cancel]
  prepend_before_action :authenticate_scope!, only: [:edit, :update, :destroy]
  prepend_before_action :set_minimum_password_length, only: [:new, :edit]

-It inherits DeviseController. Looking at the source code file, devise_controller.rb is inherited not only to this module, but to all modules. ** -prepend_before_action is a method that executes before before_action**. The action that can be accessed is restricted according to the login status of the user. -Reference: Rails documentation https://railsdoc.com/page/prepend_before_action

new action

devise/app/controllers/devise/registrations_controller.rb


 # GET /resource/sign_up
  def new
    build_resource
    yield resource if block_given?
    respond_with resource
  end
  • The first part to login.
  • resource is already defined in devise_controller.

devise/app/controllers/devise_controller.rb


  def resource
    instance_variable_get(:"@#{resource_name}")
  end

  # Proxy to devise map name
  def resource_name
    devise_mapping.name
  end

  alias :scope_name :resource_name
  • The instance_variable_get method gets and returns the value of the instance variable. It seems to be the same as the definition from @user = . -Reference: Ruby 2.7.0 Reference Manual https://docs.ruby-lang.org/ja/latest/method/Object/i/instance_variable_get.html
  • There is a variable called #{resource_name} in resource, but it is defined in the lower part.
  • devise_mapping.name This is also already defined in devise_controller, but if you look at the quoted article, there seems to be a hint in another part.

devise/app/controllers/devise_controller.rb


  def devise_mapping
    @devise_mapping ||= request.env["devise.mapping"]
  end

Looking at >name and connecting the code, if the authentication model is User, @singular = :users.to_s.tr(‘/’,’_’).singularize.to_sym. singularize is a method to convert plural forms to singular forms, and finally @singular = :user and alias of singular is name, so :user can be obtained by mapping.name. Then, :user is passed as an argument of define_methods and authenticate_user! is completed.

Source: Learn Rails with Devise code reading https://qiita.com/irisAsh/items/513b8b58f54421b9a1a0

In short, it means that since :user can be obtained by mapping.name, it is set as resource_name.

  • There is a build_resource under the registrations_controller. It seems to mean creating a new session.

devise/app/controllers/devise/registrations_controller.rb


 def build_resource(hash = {}) #build_resource(hash = {}) definition
    self.resource = resource_class.new_with_session(hash, session)
  end
```-Let's go back to the top of the original `registrations_controller`. `build` plays a role similar to `new`. The one statement `build_resource` means to create a session with the user's instance variables fetched from the database.
- `block_given?` returns true if a block was passed when executing the method, false otherwise.
    -What is a block?

> You can evaluate a block from within a method by calling it with a code snippet (called a block) enclosed in do ... end or {... }. Use the yield expression to define your own blocked methods.
Source: Ruby 2.7.0 Reference Manual
https://docs.ruby-lang.org/ja/latest/doc/spec=2fcall.html#block

- In this case, the resource block is defined as `yield` and the truth is confirmed by `block_given?`. It seems that we need to know more about yield.
    -Reference: What is yield @variable if block_given?
https://kossy-web-engineer.hatenablog.com/entry/2020/01/19/094958-
    -Reference: [Introduction to Ruby] Summary of how to use yield
https://www.sejuku.net/blog/20478


- I think that `respond_with resource` is called with this side involved,



#### **`devise/app/controllers/devise_controller.rb`**
```ruby

  def respond_with_navigational(*args, &block)
    respond_with(*args) do |format|
      format.any(*navigational_formats, &block)
    end
  end

devise/app/controllers/devise_controller.rb


  def navigational_formats
    @navigational_formats ||= Devise.navigational_formats.select {|format|Mime::EXTENSION_LOOKUP[format.to_s]}
  end

Since I am not in touch with it, I would like to study it and add or correct it.

It’s so complicated Gem… It’s been a lot of learning as it’s all condensed into just a few lines of code. I could only write new actions, but in the end I want to keep a record that I read only each action of registrations_controller …!

Thank you for your association.