Ruby on Rails6 Practical Guide [Extensions] cp10 ~ cp12 [Memo]

Introduction

The author of this article is a beginner just starting to learn programming. I would appreciate it if you could point out any mistakes.

Overview

This article is a personal memo of what I learned by reading the Ruby on Rails 6 Practical Guide. It seems to be difficult to read because it is excerpted and picked up. Excuse me. This book also has a sequel Extension, and both books have been studied at the stage of writing the article. I will write an article for review as well. I will skip cp1 and cp2 of the function extension because they explain the environment construction and the code of the main part. This article is the last.

Previous article Ruby on Rails6 Practical Guide cp4 ~ cp6 [Memo] Ruby on Rails6 Practical Guide cp7 ~ cp9 [Memo] Ruby on Rails6 Practical Guide cp10 ~ cp12 [Memo] Ruby on Rails6 Practical Guide cp13 ~ cp15 [Memo] Ruby on Rails6 Practical Guide cp16 ~ cp18 [Memo] Ruby on Rails6 Practical Guide [Extensions] cp3 ~ cp5 [Memo] Ruby on Rails6 Practical Guide [Extensions] cp7 ~ cp9 [Memo]

Function expansion Chapter 10 Ajax

Returns text as a response

render plain: Message.unprocessed.count

Reference: Rails Guide


JavaScript It is a program that checks and updates the number of new inquiries every minute.

function update_number_of_unprocessed_messages() {
  const elem = $("#number-of-unprocessed-messages")
  $.get(elem.data("path"), (data) => {
    if (data === "0") elem.text("")
    else elem.text("(" + data + ")")
  })
  .fail(() => window.location.href = "/login")
}

$(document).ready(() => {
  if ($("#number-of-unprocessed-messages").length)
    window.setInterval(update_number_of_unprocessed_messages, 1000 * 60)
})

The $ .get method part of JQuery is written in the following pattern.

  $.get(X, (data) => {
    Y
  })
  .fail(Z)

The URL of the API that accesses X with Ajax, and the code that Y receives the access result and executes. The data returned from the API is stored in the argument data, and you can refer to that value in Y. If you specify .fail (Z), Z will be executed when access by Ajax fails.

window.setInterval is a method that calls the function specified in the first argument at regular intervals.


Reject all but Ajax requests

raise ActionController::BadRequest unless request.xhr?

xhr? Determines if the request is from Ajax.

Function expansion Chapter 11 Tree structure

Display the character string omitted

= truncate(content, length: 20)

The truncate method omits the string passed as an argument and displays it. The default is 30 characters.


Display message tree

app/presenters/message_presenter.rb


def tree
  expand(object.root || object)
end

def expand(node)
    markup(:ul) do |m|
      m.li do
        if node.id == object.id
          m.strong(node.subject)
        else
          m << link_to(node.subject, view_context.staff_message_path(node))
        end
        node.children.each do |c|
          m << expand(c)
        end
      end
    end
  end

expand is defined as a recursive method. You are calling yourself in a method. It emphasizes only the subject of the main object displayed on that page and generates links from its parent and child objects. We are calling expand for the children of that object in order from root. As a result, HTML is generated in order from root and the tree is represented.


Performance improvements

The above code can also represent the tree, but the deeper the structure, the more often the database is accessed.

app/lib/simple_tree.rb


class SimpleTree
  attr_reader :root, :nodes

  def initialize(root, descendants)
    @root = root
    @descendants = descendants

    @nodes = {}
    ([ @root ] + @descendants).each do |d|
      d.child_nodes = []
      @nodes[d.id] = d
    end

    @descendants.each do |d|
      @nodes[d.parent_id].child_nodes << @nodes[d.id]
    end
  end
end

A class for handling tree-structured data. The first argument of the constructor is the root object, and the second argument is its descendant object. We are creating a hash `@ nodes``` with all the objects that belong to the tree as values. Each object has an array of offspring objects `child_nodes```.

model

app/models/message.rb


attr_accessor :child_nodes

def tree
  return @tree if @tree
  r = root || self
  messages = Message.where(root_id: r.id).select(:id, :parent_id, :subject)
  @tree = SimpleTree.new(r, messages)
end

Defines child_nodes for managing child objects. Objects other than root (starting point) belonging to the tree are set in messages. I am creating a SimpleTree object.

Rewrite the tree method of MessagePresenter.

app/presenters/message_presenter.rb


def tree
  expand(object.tree.root) #Change
end

def expand(node)
    markup(:ul) do |m|
      m.li do
        if node.id == object.id
          m.strong(node.subject)
        else
          m << link_to(node.subject, view_context.staff_message_path(node))
        end
        node.child_nodes.each do |c| #Change
          m << expand(c)
        end
      end
    end
  end

The number of queries to the database has been reduced.

Function expansion Chapter 12 tagging

Tag creation using Tag-it is omitted.

Create a table for exclusive control

def change
    create_table :hash_locks do |t|
      t.string :table, null: false
      t.string :column, null: false
      t.string :key, null: false

      t.timestamps
    end

    add_index :hash_locks, [ :table, :column, :key ], unique: true
  end

Create seed data for use in production.

db/seeds/hash_locks.rb


256.times do |i|
  HashLock.create!(table: "tags", column: "value", key: sprintf("%02x", i))
end

The target table is specified in the table attribute, and the target column is specified in the column attribute. The key expression ``` sprintf ("% 02x ", i)` `` returns the two-digit hexadecimal numbers "00" to "ff" as a string.

Add a class method to the HashLock class.

app/models/hash_lock.rb


class HashLock < ApplicationRecord
  class << self
    def acquire(table, column, value)
      HashLock.where(table: table, column: column,
        key: Digest::MD5.hexdigest(value)[0,2]).lock(true).first!
    end
  end
end

The Digest :: MD5 class method hexdigest generates a hash value from the value given as an argument and returns it as a 32-digit hexadecimal number. The same hash value is generated from the same string. Find the record with the first two digits of the generated hexadecimal key and acquire the exclusive lock.

Use HashLock.aquire

app/models/message.rb


def add_tag(label)
    self.class.transaction do
      HashLock.acquire("tags", "value", label)
      tag = Tag.find_by(value: label)
      tag ||= Tag.create!(value: label)
      unless message_tag_links.where(tag_id: tag.id).exists?
        message_tag_links.create!(tag_id: tag.id)
      end
    end
  end

It prevents tag contention by acquiring an exclusive lock on the records in the hash_locks table.

Timing to use HashLock

If these two conditions are met, the conditions for the same race condition will be met.

Impressions

The total of the two books was about 850 pages, but I was able to finish the race for two weeks without copying. It was overwhelmingly difficult compared to the Rails tutorial, but I don't understand the explanation, probably because of my experience of developing App! It didn't happen and I managed to continue. I was even more aware of my lack of knowledge. It's more difficult for beginners, but I'm glad I read it because it was packed with practical know-how. I will continue to study hard.

Quote source

Recommended Posts

Ruby on Rails6 Practical Guide [Extensions] cp7 ~ cp9 [Memo]
Ruby on Rails6 Practical Guide [Extensions] cp10 ~ cp12 [Memo]
Ruby on Rails6 Practical Guide [Extensions] cp3 ~ cp6 [Memo]
Ruby on Rails6 Practical Guide cp13 ~ cp15 [Memo]
Ruby on Rails6 Practical Guide cp7 ~ cp9 [Memo]
Ruby on Rails6 Practical Guide cp4 ~ cp6 [Memo]
Ruby on Rails6 Practical Guide cp10 ~ cp12 [Memo]
Ruby on Rails6 Practical Guide cp16 ~ cp18 [Memo]
Ruby on Rails 6.0 environment construction memo
[Ruby on Rails] Select2 introduction memo for Webpacker
[Personal memo] Ruby on Rails environment construction (Windows)
Ruby on Rails Elementary
Ruby on Rails basics
Ruby On Rails Association
Ruby on Rails5 Quick Learning Practice Guide 5.2 Compatible Chapter2
Ruby on Rails5 Quick Learning Practice Guide 5.2 Compatible Chapter3
Ruby on rails learning record -2020.10.03
Portfolio creation Ruby on Rails
Ruby on rails learning record -2020.10.04
[Ruby on Rails] Debug (binding.pry)
Ruby on rails learning record -2020.10.05
Ruby on rails learning record -2020.10.09
Ruby on Rails config configuration
Ruby on Rails basic learning ①
[Ruby on Rails] about has_secure_password
Ruby on rails learning record-2020.10.07 ②
Commentary on partial! --Ruby on Rails
Ruby on rails learning record-2020.10.07 ①
Cancel Ruby on Rails migration
Ruby on rails learning record -2020.10.06
Ruby on Rails validation summary
Ruby on Rails Basic Memorandum
Ruby on Rails Overview (Beginner Summary)
[Ruby on Rails] yarn install --check-files
Ruby on Rails variable, constant summary
Installing Ruby + Rails on Ubuntu 18.04 (rbenv)
[Ruby on Rails] Introduced paging function
Basic knowledge of Ruby on Rails
Progate Ruby on Rails5 Looking Back
How to use Ruby on Rails
[Ruby on Rails] Add / Remove Columns
Ruby on Rails Japanese-English support i18n
(Ruby on Rails6) "Erase" posted content
[Ruby on Rails] CSV output function
[Ruby on Rails] What is Bcrypt?
[Ruby on Rails] Confirmation page creation
Ruby On Rails devise routing conflict
[Ruby on Rails] Comment function implementation
[Ruby on Rails] DM, chat function
[Ruby on Rails] Convenient helper method
[Ruby on Rails] Stop "looping until ..."
[Ruby on Rails] Introduction of initial data
[Ruby on Rails] Creating an inquiry form
[Ruby on Rails] View test with RSpec
[Ruby on Rails] How to use CarrierWave
[Ruby on Rails] Code check using Rubocop-airbnb
[Ruby on Rails] 1 model CRUD (Routing Main)
Ruby on Rails installation method [Mac edition]
[Ruby on Rails] model, controller terminal command
Let's summarize "MVC" of Ruby on Rails
Ruby on Rails model creation / deletion command