The author of this article is a beginner just starting to learn programming. I would appreciate it if you could point out any mistakes.
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]
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.
raise ActionController::BadRequest unless request.xhr?
xhr? Determines if the request is from Ajax.
= truncate(content, length: 20)
The truncate method omits the string passed as an argument and displays it. The default is 30 characters.
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.
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```.
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.
Tag creation using Tag-it is omitted.
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.
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.
If these two conditions are met, the conditions for the same race condition will be met.
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.
Recommended Posts