[RUBY] Implement tagging function in form object

Implementation flow


Create tag model and intermediate table

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

Model association and validation

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

Set up routing

config/routes.rb


Rails.application.routes.draw do
 root to: 'items#index'
 resources :items
end

Creating a form object

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.


Controller processing

$ 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.


Create view

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

Implement tagging function in form object
Story of implementing update function in form object
Implement application function in Rails
Implement follow function in Rails
Implement simple login function in Rails
Implement PHP implode function in Java
Implement a contact form in Rails
Implement CSV download function in Rails
Error encountered in tagging function implementation
Immutable (immutable) List object conversion function in Java8
Implement star rating function using Raty in Rails6
Implement post search function in Rails application (where method)
[Rails] Implement credit card registration / deletion function in PAY.JP
Implement user follow function in Rails (I use Ajax) ②
Value object in 3 minutes
Implement user follow function in Rails (I use Ajax) ①
Implement CustomView in code
Try to implement tagging function using rails and js
Implement markdown in Rails
Implement login function in Rails simply by name and password (1)
Implement login function in Rails simply by name and password (2)
Implement the same function as C, C ++ system ("cls"); in Java
Implement login function simply with name and password in Rails (3)
Implement user registration function and corporate registration function separately in Rails devise
[Rails] Implement User search function
[Beginner] Implement NavigationBar in code
Implement two-step verification in Java
Implement LTI authentication in Rails
Finally implemented Rails Form object
Implement Basic authentication in Java
Implement math combinations in Java
2 Implement simple parsing in Java
Implement category function using ancestory
[Android] Implement Adblock in WebView
Implement Email Sending in Java
Implement Swift UITextField in code
Implement import process in Rails
Implement functional quicksort in Java
[Rails] Implement image posting function
Implement rm -rf in Java.
Implement search function with form_with
Validation function in Play Framework
Implement XML signature in Java
[Rails] A simple way to implement a self-introduction function in your profile
I tried to implement Ajax processing of like function in Rails
[Behavior confirmed in December 2020] How to implement the alert display function
Implement star evaluation function in Rails 6 (Webpacker is installed as standard)