[JAVA] [Ruby on Rails] Comment implémenter la fonction de balisage / recherche incrémentielle pour les articles (sans gemme)

Préface avant le marquage

Résumé de cet article

-Autoriser les messages à être étiquetés. -Mettre en œuvre une fonction (recherche incrémentale) qui recherche automatiquement chaque fois qu'un caractère est saisi dans une balise.

Environnement de développement

Mac OS Catalina 10.15.4 série ruby 2.6 rails série 6.0

Image terminée de la fonction de marquage

demo

Comme Gif dans la figure ci-dessus, lorsque vous commencez à saisir des balises, les balises recommandées peuvent être affichées en fonction des balises enregistrées dans le DB. Si nous pouvons implémenter la fonction de balisage basée sur cet article, je pense que nous pouvons facilement implémenter la recherche de balises et ainsi de suite.

Flux d'implémentation de la fonction de marquage

  1. Créer une balise, une publication, une relation PostTag, un modèle utilisateur
  2. Modifier les fichiers de migration pour différents modèles
  3. Introduire l'objet Form
  4. Paramètres de routage
  5. Créer un contrôleur de messages, définition d'action
  6. Créer un fichier de vue
  7. Mise en œuvre de la recherche incrémentielle (JavaScript)

Suivez les étapes ci-dessus pour mettre en œuvre.

1. Créer une balise, une publication, une relation PostTag, un modèle utilisateur

er-figure

Tout d'abord, introduisons différents modèles.

%  rails g model tag
%  rails g model post
%  rails g model post_tag_relation
%  rails g devise user

En l'état, décrivons la validation en associant chaque modèle introduit.

post.rb


class Post < ApplicationRecord
  has_many :post_tag_relations
  has_many :tags, through: :post_tag_relations
  belongs_to :user
end

tag.rb


class Tag < ApplicationRecord
  has_many :post_tag_relations
  has_many :posts, through: :post_tag_relations

  validates :name, uniqueness: true
end

En définissant "through :: table intermédiaire", le modèle Post et le modèle Tag, qui ont une relation plusieurs-à-plusieurs, sont associés. En guise de mise en garde, il est nécessaire de lier la table intermédiaire avant de référencer par through. (Puisque le code est lu par le haut, si vous écrivez dans l'ordre has_many: posts, through :: post_tag_relations → has_many: post_tag_relations, une erreur se produira.)

post_tag_relation


class PostTagRelation < ApplicationRecord
  belongs_to :post
  belongs_to :tag
end

user.rb



class User < ApplicationRecord
   
  #<réduction>
  has_many :posts, dependent: :destroy
  validates :name, presence: true

L'option has_many dans le modèle User est étiquetée depend :: destroy de sorte que lorsque les informations utilisateur de l'élément parent sont supprimées, cette publication humaine est également supprimée.

De plus, la description (valide: 〇〇, présence: true) pour empêcher l'enregistrement des données vides dans le modèle Post et le modèle Tag sera spécifiée collectivement dans l'objet de formulaire à créer ultérieurement, ce n'est donc pas nécessaire maintenant. ..

2. Modifier les fichiers de migration pour différents modèles

Ensuite, ajoutez des colonnes au modèle créé. (L'exigence minimale est la colonne du nom de la balise, alors arrangez les autres comme vous le souhaitez.)

fichier de post-migration


class CreatePosts < ActiveRecord::Migration[6.0]
  def change
    create_table :posts do |t|
      t.string :title, null: false
      t.text :content, null: false
      t.date :date
      t.time :time_first
      t.time :time_end
      t.integer :people
      t.references :user, foreign_key: true
      t.timestamps
    end
  end
end

La raison pour laquelle l'utilisateur est référencé en tant que clé externe dans le fichier de post-migration est d'afficher le nom d'utilisateur dans la liste de publication ultérieurement.

fichier de migration de balise


class CreateTags < ActiveRecord::Migration[6.0]
  def change
    create_table :tags do |t|
      t.string :name, null: false, uniqueness: true
      t.timestamps
    end
  end
end

Application de l'unicité: true à la colonne de nom ci-dessus est introduite pour éviter les noms de balises en double. (Comme il est supposé que les balises portant le même nom seront utilisées plusieurs fois, vous vous demandez peut-être si cela ne fonctionnera pas comme fonction de balisage si vous évitez la duplication, mais comment refléter les balises existantes dans les articles sera décrite plus tard. Apparaîtra.)

post_tag_fichier de migration de relation


class CreatePostTagRelations < ActiveRecord::Migration[6.0]
  def change
    create_table :post_tag_relations do |t|
      t.references :post, foreign_key: true
      t.references :tag, foreign_key: true
      t.timestamps
    end
  end
end

Ce modèle post_tag_relation joue le rôle d'une table intermédiaire entre le modèle de publication et le modèle de balise, qui a une relation plusieurs-à-plusieurs.

fichier de migration utilisateur


class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :name,               null: false
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
     
   #<réduction>

Je voulais utiliser le nom d'utilisateur, j'ai donc ajouté une colonne de nom.

N'oubliez pas d'exécuter la commande suivante après avoir édité la colonne.

%  rails db:migrate

3. Introduire l'objet Form

Dans cette implémentation, nous voulons enregistrer les valeurs d'entrée du formulaire de publication dans la table des publications et la table des balises en même temps, nous utiliserons donc l'objet Form.

Tout d'abord, créez un répertoire de formulaires dans le répertoire de l'application et créez-y un fichier posts_tag.rb. Ensuite, définissez une méthode de sauvegarde pour enregistrer les valeurs dans la table des publications et la table des balises en même temps, comme indiqué ci-dessous.

posts_tag.rb


class PostsTag

  include ActiveModel::Model
  attr_accessor :title, :content, :date, :time_first, :time_end, :people, :name, :user_id

  with_options presence: true do
    validates :title
    validates :content
    validates :name
  end

  def save
    post = Post.create(title: title, content: content, date: date, time_first: time_first, time_end: time_end, people: people, user_id: user_id)
    tag = Tag.where(name: name).first_or_initialize
    tag.save
    PostTagRelation.create(post_id: post.id, tag_id: tag.id)
  end
end

4. Paramètres de routage

Ensuite, définissez le routage pour exécuter les actions index, new et create du contrôleur de publications.

routes.rb


  resources :posts, only: [:index, :new, :create] do
    collection do
      get 'search'
    end
  end

Le routage vers l'action de recherche définie dans la collection est utilisé par la fonction de recherche incrémentielle.

5. Créer un contrôleur de messages, définition d'action

Générez un contrôleur dans le terminal.

% rails g controller posts

Le code dans le fichier de contrôleur de messages généré ressemble à ceci:

posts_controller.rb


class PostsController < ApplicationController
  before_action :authenticate_user!, only: [:new]

  def index
    @posts = Post.all.order(created_at: :desc)
  end

  def new
    @post = PostsTag.new
  end

  def create
    @post = PostsTag.new(posts_params)

    if @post.valid?
      @post.save
      return redirect_to posts_path
    else
      render :new
    end
  end

  def search
    return nil if params[:input] == ""
    tag = Tag.where(['name LIKE ?',  "%#{params[:input]}%"])
    render json: {keyword: tag}
  end

   private

  def posts_params
    params.require(:post).permit(:title, :content, :date, :time_first, :time_end, :people, :name).merge(user_id: current_user.id)
  end
end

Dans l'action de création, la valeur reçue par posts_params est enregistrée dans le modèle Posts et la table Tags à l'aide de la méthode de sauvegarde définie précédemment dans l'objet Form.

Dans l'action de recherche, basée sur les données acquises côté JS (la chaîne de caractères saisie dans le formulaire d'entrée de balise), les données sont extraites de la table des balises avec la clause where + LIKE et renvoyées à JS avec json plus rouge. (Les fichiers JS apparaîtront plus tard.)

C'est pourquoi l'action de recherche ci-dessus n'est pas nécessaire si vous n'implémentez pas de recherche incrémentielle.

6. Créer un fichier de vue

new.html.erb



<%= form_with model: @post, url: posts_path, class: 'registration-main', local: true do |f| %>
  <div class='form-wrap'>
    <div class='form-header'>
      <h2 class='form-header-text'>Page de publication de la chronologie</h2>
    </div>
   <%= render "devise/shared/error_messages", resource: @post %> 

    <div class="post-area">
      <div class="form-text-area">
        <label class="form-text">Titre</label><br>
        <span class="indispensable">Obligatoire</span>
      </div>
      <%= f.text_field :title, class:"post-box" %>
    </div>

    <div class="long-post-area">
      <div class="form-text-area">
        <label class="form-text">Aperçu</label>
        <span class="indispensable">Obligatoire</span>
      </div>
      <%= f.text_area :content, class:"input-text" %>
    </div>

    <div class="tag-area">
      <div class="form-text-area">
        <label class="form-text">marque</label>
        <span class="indispensable">Obligatoire</span>
      </div>
      <%= f.text_field :name, class: "text-box", autocomplete: 'off' %>
    </div>
    <div>[Balise recommandée]</div>
    <div id="search-result">
    </div>

    <div class="long-post-area">
      <div class="form-text-area">
        <label class="form-text">Événement programmé</label>
        <span class="optional">Tout</span>
      </div>
      <div class="schedule-area">
        <div class="date-area">
          <label>Date</label>
          <%= f.date_field :date %>
        </div>
        <div class="time-area">
          <label>Heure de début</label>
          <%= f.time_field :time_first %>
          <label class="end-time">Heure de fin</label>
          <%= f.time_field :time_end %>
        </div>
      </div>
    </div>

    <div class="register-btn">
      <%= f.submit "Publier",class:"register-blue-btn" %>
    </div>

  </div>
<% end %>

Le fichier de vue utilisé dans l'implémentation de mon application étant solidement collé, le code est redondant, mais le fait est qu'il n'y a pas de problème si le contenu du formulaire peut être envoyé au routage par @post etc.

:index.html.erb


<div class="registration-main">
  <div class="form-wrap">
     <div class='form-header'>
      <h2 class='form-header-text'>Page de liste chronologique</h2>
    </div>
    <div class="register-btn">
      <%= link_to "Accéder à la page de publication de la chronologie", new_post_path, class: :register_blue_btn %>
    </div>
    <% @posts.each do |post| %>
    <div class="post-content">
      <div class="post-headline">
        <div class="post-title">
          <span class="under-line"><%= post.title %></span>
        </div>
        <div class="more-list">
          <%= link_to 'Éditer', edit_post_path(post.id), class: "edit-btn" %>
          <%= link_to 'Effacer', post_path(post.id), method: :delete, class: "delete-btn" %>
        </div>
      </div>
      <div class="post-text">
        <p>■ Aperçu</p>
        <%= post.content %>
      </div>
      <div class="post-detail">
        <% if post.time_end != nil && post.time_first != nil %>
              <p>■ Calendrier</p>
        <div class="post-date">
          <%= post.date %>
          <%= post.time_first.strftime("%H heure%M minutes") %> 〜
          <%= post.time_end.strftime("%H heure%M minutes") %>
        </div>
        <% end %>
        <div class="post-user-tag">
          <div class="post-user">
          <% if post.user_id != nil %>
■ Publié par: <%= link_to "#{post.user.name}", user_path(post.user_id), class:'user-name' %>
          <% end %>
          </div>
          <div class="post-tag">
            <% post.tags.each do |tag| %>
              #<%= tag.name %>
            <% end %>
          </div>
        </div>
      </div>
    </div>
    <% end %>
  </div>
</div>

Ceci est également redondant, veuillez donc vous référer uniquement si nécessaire ...

7. Implémentation de la recherche incrémentale (JavaScript)

Ici, je joue avec le fichier JS.

tag.js


if (location.pathname.match("posts/new")){
  window.addEventListener("load", (e) => {
    const inputElement = document.getElementById("post_name");
    inputElement.addEventListener('keyup', (e) => {
      const input = document.getElementById("post_name").value;
      const xhr = new XMLHttpRequest();
      xhr.open("GET", `search/?input=${input}`, true);
      xhr.responseType = "json";
      xhr.send();
      xhr.onload = () => {
        const tagName = xhr.response.keyword;
        const searchResult = document.getElementById('search-result')
        searchResult.innerHTML = ''
        tagName.forEach(function(tag){
          const parentsElement = document.createElement('div');
          const childElement = document.createElement("div");

          parentsElement.setAttribute('id', 'parents')
          childElement.setAttribute('id', tag.id)
          childElement.setAttribute('class', 'child')

          parentsElement.appendChild(childElement)
          childElement.innerHTML = tag.name
          searchResult.appendChild(parentsElement)

          const clickElement = document.getElementById(tag.id);
          clickElement.addEventListener('click', () => {
            document.getElementById("post_name").value = clickElement.textContent;
            clickElement.remove();
          })
        })
      }
    });
  })
};

J'utilise location.pathname.match pour charger le code lorsque la nouvelle action dans le contrôleur de messages se déclenche.

En tant que processus approximatif dans JS, ① Déclenchez un événement avec keyup et envoyez la valeur d'entrée du formulaire de balise au contrôleur (environ xhr. 〇〇) (2) Affichez la balise de prédiction au recto en fonction des informations renvoyées par le contrôleur sous xhr.onload. ③ Lorsque vous cliquez sur l'étiquette de prédiction, cette étiquette sera reflétée dans le formulaire.

Ceci termine la mise en œuvre de la fonction de balisage et la mise en œuvre de la recherche incrémentielle. C'est un article approximatif, mais merci d'avoir lu jusqu'au bout!

Recommended Posts

[Ruby on Rails] Comment implémenter la fonction de balisage / recherche incrémentielle pour les articles (sans gemme)
Implémentez une fonction de recherche affinée pour plusieurs modèles sans gemme Rails5.
[Pour les débutants de Rails] Implémentation de la fonction de recherche multiple sans Gem
Comment utiliser Ruby on Rails
[Ruby on Rails] Comment éviter de créer des routes inutiles pour concevoir
[Ruby on Rails] Fonction de recherche (non sélectionnée)
Comment implémenter la fonctionnalité de recherche dans Rails
Paramètres de validation pour la fonction de connexion Ruby on Rails
[Rails] Comment implémenter un test unitaire d'un modèle
[Pour les débutants] Comment implémenter la fonction de suppression
Comment implémenter la pagination dans GraphQL (pour ruby)
[Ruby on Rails] Comment écrire enum en japonais
Rails / Ruby: Comment obtenir du texte HTML pour Mail
[Ruby on Rails] Comment changer le nom de la colonne
[Ruby On Rails] Comment réinitialiser DB dans Heroku
(Ruby on Rails6) Comment créer un modèle et une table
[Ruby on Rails] Fonction de recherche (modèle, formule de sélection de méthode)
Explication de Ruby on rails pour les débutants ④ ~ À propos des règles de dénomination et comment utiliser form_Tag ~
[Rails] Comment mettre en œuvre le scraping
[Ruby on Rails] Implémenter la fonction de connexion par add_token_to_users avec l'API
Comment afficher des graphiques dans Ruby on Rails (LazyHighChart)
[Ruby on Rails] Introduction de la fonction de pagination
Je souhaite ajouter une fonction de navigation avec ruby on rails
Comment résoudre OpenSSL :: SSL :: SSLError: SSL_connect qui se produit dans Ruby paypal-sdk-rest gem
[R Spec on Rails] Comment écrire du code de test pour les débutants par les débutants
Comment déployer Bootstrap sur Rails
[Ruby on Rails] Fonction de sortie CSV
[Ruby on Rails] Implémentation de la fonction de commentaire
[Rails] Comment mettre en œuvre le classement par étoiles
[Ruby on Rails] DM, fonction de chat
(Ruby on Rails6) Créer une fonction pour modifier le contenu publié
[Ruby on Rails] Comment se connecter avec seulement votre nom et mot de passe en utilisant le bijou
[Ruby on Rails] Lors de la première connexion ・ Comment diviser l'écran en deux à l'aide de jQuery
[Rails] Procédure d'implémentation de la fonction pour taguer les posts sans gem + la fonction pour affiner et afficher les posts par tags
Comment créer une requête à l'aide de variables dans GraphQL [Utilisation de Ruby on Rails]
Comment créer un environnement de développement Ruby on Rails avec Docker (Rails 6.x)