[Model Association and Validation](#-Model Association and Validation)
Set Routing (#-Set Routing)
[Create form object](# -Create form object)
[Controller processing](# -controller processing)
Posts and tags have a many-to-many relationship, so create an intermediate table.
Create a model for your post.
$ rails g model item name:string user:references
Create a tag model.
$ rails g model tag word:string
Create an intermediate table.
$ rails g model item_tag_relation item:references tag:references
Check the migration file.
db/migrate/create_items.rb
class CreateItems < ActiveRecord::Migration[6.0]
def change
create_table :items do |t|
t.string :name
t.references :user, foreign_key: true
t.timestamps
end
end
end
db/migrate/create_tags.rb
class CreateTags < ActiveRecord::Migration[6.0]
def change
create_table :tags do |t|
t.string :word
t.timestamps
end
end
end
db/migrate/create_item_tag_relations.rb
class CreateItemTagRelations < ActiveRecord::Migration[6.0]
def change
create_table :item_tag_relations do |t|
t.references :item, foreign_key: true
t.references :tag, foreign_key: true
t.timestamps
end
end
end
You can prevent the same tag name from being registered with unique: true, but I couldn't implement it well, so I made it unique in another way.
$ rails db:migrate
app/models/item.rb
class Item < ApplicationRecord
has_many :item_tag_relations
has_many :tags, through: :item_tag_relations, dependent: :destroy
belongs_to :user
end
Get the tags associated with the item through the item_tag_relations model by writing "has_many: tags, through:: item_tag_relations".
app/models/tag.rb
class Tag < ApplicationRecord
has_many :item_tag_relations, dependent: :destroy
has_many :items, through: :item_tag_relations
validates :word, uniqueness: true
end
The description of "validates: word, uniqueness: true" prevents duplicate tag names from being registered. (For some reason it didn't work ...)
app/models/item_tag_relation.rb
class ItemTagRelation < ApplicationRecord
belongs_to :item
belongs_to :tag
end
config/routes.rb
Rails.application.routes.draw do
root to: 'items#index'
resources :items
end
Form objects are tools used to update multiple models in a single form submission. You can treat your own class like a model.
Create an "app/models/item_tag.rb" file under the models directory.
app/models/item_tag.rb
class ItemTag
include ActiveModel::Model
attr_accessor :name, :user_id, :item_id, :tag_ids
end
By including ActiveModel :: Model, the instance of that class can be treated as an argument of helper methods such as form_with and render like the instance of the class that inherits ActiveRecord, and the validation function can be used.
Set the column name you want to use in attr_accessor.
Next, add a process to save the information sent as a parameter from the form to the table.
app/models/item_tag.rb
class ItemTag
include ActiveModel::Model
attr_accessor :name, :user_id, :item_id, :tag_ids
def save
@item = Item.create(name: name, user_id: user_id)
tag_list = tag_ids.split(/[[:blank:]]+/).select(&:present?)
tag_list.each do |tag_name|
@tag = Tag.where(word: tag_name).first_or_initialize
@tag.save
unless ItemTagRelation.where(item_id: @item.id,tag_id: @tag.id).exists?
ItemTagRelation.create(item_id: @item.id, tag_id: @tag.id)
end
end
end
end
The item information is saved and assigned to the variable "@item".
tag_list = tag_ids.split (/ [[: blank:]] +/). select (&: present?) sends the tags sent from f.text_field of the input form as tag_ids in params.
Then, split (/ [[: blank:]] + /) separates the character strings in tag_ids with spaces and puts them in an array as separate words. Finally, select (&: present?) Determines each arrayed value with the present? Method and retrieves it if true.
@tag = Tag.where (word: tag_name) .first_or_initialize to determine if it is a new tag or an existing tag.
If it is an existing tag, use the existing id. If new, generate id.
unless ItemTagRelation.where (item_id: @ item.id, tag_id: @ tag.id) .exists? Wanted to prevent duplicate tags from being saved this time, but even if the validation and migration files are unique Since it has been saved, I dealt with it with a model as a measure of pain.
In ItemTagRelation.where (item_id: @ item.id, tag_id: @ tag.id) .exists ?, check if a tag with the same name exists for the post of the item_tag_relation model which is an intermediate table I am judging. If the tags are duplicated, it will be ture, so the conditional expression is applied with unless.
$ rails g controller items
app/controllers/items_controller.rb
def new
@item = ItemTag.new
end
def create
@item = ItemTag.new(itemtags_params)
if @item.valid?
@item.save
redirect_to items_path(@item)
else
render :new
end
end
private
def itemtags_params
params.require(:item_tag).permit(:name, :text, :image, :tag_ids).merge(user_id: current_user.id)
end
I'm using the new method on a form object.
Describe only the relevant parts.
ruby:app/views/items/new.html.erb
<%= form_with model: @item, url: items_path, local: true do |f| %>
<%= f.text_field :tag_ids %>
<%= f.submit "Post" %>
tag_ids can be used for item objects by associating "has_many: tags, through:: item_tag_relations" in the item model.
Now you can implement the tagging feature.
Articles that I used as a reference
[Ruby on Rails] I implemented a tag search function https://qiita.com/E6YOteYPzmFGfOD/items/177f18e706df05f9b42e
Beginners fumbling to implement Rails tagged features without gems https://qiita.com/ryutaro9595/items/042a1ec713c8c1f2c1d6
rails Implement the function to tag posted articles. https://shirohige3.hatenablog.com/entry/2020/11/08/013327
Recommended Posts