I have implemented a tag search function in the app I am currently developing, so I will summarize it here as a memorandum. I didn't dare to use gems, so I was able to understand the essentials.
【environment】
When you enter a tag in the new post form, the entered tag will be reflected in the post list and sidebar. It is a function that you can narrow down the posts with that tag by clicking the tag reflected in the sidebar.
Enter a tag from the post form
The tag entered in ↑ is displayed in the sidebar and post list By clicking the tag displayed in the sidebar, you can narrow down the articles that include that tag. (Since this is a sample, only one case)
I will talk on the assumption that the Item model and Post model have already been created. (Here, it is an item model. Please replace it by yourself.)
First, create the Tag model and Tagmap model as usual.
$ rails g model Tagmap item:references tag:references
$ rails g model tag tag_name:string
Since Tagmap is linked to Item model and Tag model, references are attached to the data type. If you do this, Rails will do a good job, so if you want to know more, please google.
class CreateTagmaps < ActiveRecord::Migration[6.0]
def change
create_table :tagmaps do |t|
t.references :item, null: false, foreign_key: true
t.references :tag, null: false, foreign_key: true
t.timestamps
end
end
end
This is indexed.
class CreateTags < ActiveRecord::Migration[6.0]
def change
create_table :tags do |t|
t.string :tag_name, null: false
t.timestamps
end
add_index :tags, :tag_name, unique: true
end
end
Then migrate as usual.
$ rails db:migrate
item.rb
has_many :tagmaps, dependent: :destroy
has_many :tags, through: :tagmaps
tag.rb
class Tag < ApplicationRecord
has_many :tagmaps, dependent: :destroy
has_many :items, through: :tagmaps
end
tag is also associated with tagmaps. ↑
By using thorough, you can access items via tagmaps.
tagmap.rb
class Tagmap < ApplicationRecord
belongs_to :item
belongs_to :tag
end
↑ Rails will automatically describe the belongs_to part in the references form!
items_controller.rb
def index
if params[:search].present?
items = Item.items_serach(params[:search])
elsif params[:tag_id].present?
@tag = Tag.find(params[:tag_id])
items = @tag.items.order(created_at: :desc)
else
items = Item.all.order(created_at: :desc)
end
@tag_lists = Tag.all
@items = Kaminari.paginate_array(items).page(params[:page]).per(10)
end
I will explain the code here. The if statement is used in the index action to separate the three patterns, and the value assigned to the item variable is changed according to this result.
items_controller
def create
@item = Item.new(item_params)
tag_list = params[:item][:tag_name].split(nil)
@item.image.attach(params[:item][:image])
@item.user_id = current_user.id
if @item.save
@item.save_items(tag_list)
redirect_to items_path
else
flash.now[:alert] = 'Posting failed'
render 'new'
end
end
The point in the create action of the items controller is the assignment part to the tag_list variable on the third line. ** String # split ** of ruby splits the string by the separator specified by the first argument (nil this time) and returns the result as an array of strings. If you specify a block, it calls the block with a split string instead of returning an array. Also, if you send tag data separated by a 1-byte whitespace character'' from the form, it will be separated by a whitespace character string after removing the leading and trailing whitespace. This time we will use this.
The parameters sent from the new post form are separated by whitespace characters (nil), arranged, and saved in the database using the ** save_items ** method defined in the Item class. (Ask the user to enter tags in the form separated by spaces.)
item.rb
#Fuzzy search method, title and content
def self.items_serach(search)
Item.where(['title LIKE ? OR content LIKE ?', "%#{search}%", "%#{search}%"])
end
def save_items(tags)
current_tags = self.tags.pluck(:tag_name) unless self.tags.nil?
old_tags = current_tags - tags
new_tags = tags - current_tags
# Destroy
old_tags.each do |old_name|
self.tags.delete Tag.find_by(tag_name:old_name)
end
# Create
new_tags.each do |new_name|
item_tag = Tag.find_or_create_by(tag_name:new_name)
self.tags << item_tag
end
end
↑ When saving tag data, if there is even one tag name that already exists among the tag data sent from the form, use the pluck method from the tag_name column of the tags table to pull all the data once and set it to current_tags. Substitute. (If everything is new, it will be nil.) And old tags (old_tags) can be defined by subtracting the array of tags passed as an argument from the controller from current_tags which is a set of tag data that already exists. This is because in ruby, common elements can be extracted by subtracting an array.
Concrete example:
tags(tag_list = params[:item][:tag_name].split(nil)Array that comes from the controller) = ["Rails" "ruby" "React"]
current_tags(Tag data that currently exists in the DB) = ["Rails" "ruby" "Vue.js"]
old_tags = ["Rails" "ruby" "Vue.js" "Docker"] - ["Rails" "ruby" "React"]
old_tags = ["Rails" "ruby"]
↑ The common element of the two arrays remains (old tags that already exist can be calculated)
** Excerpt from the description of the new post form (UIkit is used for the CSS framework) **
.uk-form.new_post_form
= form_with(model: [@tag,@item], local: true) do |f|
.uk-margin-small
= f.text_field :title, placeholder: "Enter a title (up to 35 characters)", class: 'uk-input'
.uk-margin-small
= f.text_field :tag_name, placeholder: "Enter up to 5 tags related to programming technology and recruitment requirements separated by spaces(Example Ruby Rails)", class: 'uk-input uk-form-small'
** Partial sidebar view excerpt **
haml:index.html.haml
%li.search_friend_by_categorize
.uk-text-secondary.uk-text-bold
Search by tag
%ul.uk-flex.uk-flex-wrap
- @tag_lists.each do |list|
%li
= link_to list.tag_name, items_path(tag_id: list.id), class: 'tag_list'
Displaying tags in a list of posted articles
haml:index.html.haml
%p.tag_list_box
- item.tags.each do |tag|
= link_to "##{tag.tag_name}", items_path(tag), class: 'smaller tag_list'
The view looks like this. It's not particularly difficult!
I wrote it in a hurry, so there may be mistakes. If you have any suggestions or impressions, I would appreciate it if you could comment! !!
Recommended Posts