Merge pull request #10876 from demarches-simplifiees/feat/10333
ETQ admin, dans la page "toutes les démarches", je veux que les tags soient harmonisés
This commit is contained in:
commit
8e1b636901
16 changed files with 328 additions and 48 deletions
|
@ -42,7 +42,7 @@ trix-editor.fr-input {
|
|||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3rem;
|
||||
margin-bottom: 0.3rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -476,7 +476,16 @@ module Administrateurs
|
|||
procedures_result = procedures_result.where(procedures_zones: { zone_id: filter.zone_ids }) if filter.zone_ids.present?
|
||||
procedures_result = procedures_result.where(hidden_at_as_template: nil)
|
||||
procedures_result = procedures_result.where(aasm_state: filter.statuses) if filter.statuses.present?
|
||||
procedures_result = procedures_result.where("tags @> ARRAY[?]::text[]", filter.tags) if filter.tags.present?
|
||||
if filter.tags.present?
|
||||
tag_ids = ProcedureTag.where(name: filter.tags).pluck(:id).flatten
|
||||
|
||||
if tag_ids.any?
|
||||
procedures_result = procedures_result
|
||||
.joins(:procedure_tags)
|
||||
.where(procedure_tags: { id: tag_ids })
|
||||
.distinct
|
||||
end
|
||||
end
|
||||
procedures_result = procedures_result.where(template: true) if filter.template?
|
||||
procedures_result = procedures_result.where(published_at: filter.from_publication_date..) if filter.from_publication_date.present?
|
||||
procedures_result = procedures_result.where(service: service) if filter.service_siret.present?
|
||||
|
@ -532,7 +541,7 @@ module Administrateurs
|
|||
:lien_dpo,
|
||||
:opendata,
|
||||
:procedure_expires_when_termine_enabled,
|
||||
{ zone_ids: [], tags: [] }
|
||||
{ zone_ids: [], procedure_tag_names: [] }
|
||||
]
|
||||
|
||||
editable_params << :piece_justificative_multiple if @procedure && !@procedure.piece_justificative_multiple?
|
||||
|
@ -545,6 +554,12 @@ module Administrateurs
|
|||
if permited_params[:auto_archive_on].present?
|
||||
permited_params[:auto_archive_on] = Date.parse(permited_params[:auto_archive_on]) + 1.day
|
||||
end
|
||||
|
||||
if permited_params[:procedure_tag_names].present?
|
||||
tag_ids = ProcedureTag.where(name: permited_params[:procedure_tag_names]).pluck(:id)
|
||||
permited_params[:procedure_tag_ids] = tag_ids
|
||||
permited_params.delete(:procedure_tag_names)
|
||||
end
|
||||
permited_params
|
||||
end
|
||||
|
||||
|
|
6
app/controllers/manager/procedure_tags_controller.rb
Normal file
6
app/controllers/manager/procedure_tags_controller.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Manager
|
||||
class ProcedureTagsController < Manager::ApplicationController
|
||||
end
|
||||
end
|
63
app/dashboards/procedure_tag_dashboard.rb
Normal file
63
app/dashboards/procedure_tag_dashboard.rb
Normal file
|
@ -0,0 +1,63 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "administrate/base_dashboard"
|
||||
|
||||
class ProcedureTagDashboard < Administrate::BaseDashboard
|
||||
# ATTRIBUTE_TYPES
|
||||
# a hash that describes the type of each of the model's fields.
|
||||
#
|
||||
# Each different type represents an Administrate::Field object,
|
||||
# which determines how the attribute is displayed
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
id: Field::Number,
|
||||
name: Field::String,
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime
|
||||
}.freeze
|
||||
|
||||
# COLLECTION_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's index page.
|
||||
#
|
||||
# By default, it's limited to four items to reduce clutter on index pages.
|
||||
# Feel free to add, remove, or rearrange items.
|
||||
COLLECTION_ATTRIBUTES = [
|
||||
:id,
|
||||
:name
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = [
|
||||
:id,
|
||||
:name,
|
||||
:created_at,
|
||||
:updated_at
|
||||
].freeze
|
||||
|
||||
# FORM_ATTRIBUTES
|
||||
# an array of attributes that will be displayed
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = [
|
||||
:name
|
||||
].freeze
|
||||
|
||||
# COLLECTION_FILTERS
|
||||
# a hash that defines filters that can be used while searching via the search
|
||||
# field of the dashboard.
|
||||
#
|
||||
# For example to add an option to search for open resources by typing "open:"
|
||||
# in the search field:
|
||||
#
|
||||
# COLLECTION_FILTERS = {
|
||||
# open: ->(resources) { resources.where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how procedure tags are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
def display_resource(procedure_tag)
|
||||
"ProcedureTag ##{procedure_tag.id}"
|
||||
end
|
||||
end
|
|
@ -56,6 +56,7 @@ class Procedure < ApplicationRecord
|
|||
belongs_to :service, optional: true
|
||||
belongs_to :zone, optional: true
|
||||
has_and_belongs_to_many :zones
|
||||
has_and_belongs_to_many :procedure_tags
|
||||
|
||||
has_many :bulk_messages, dependent: :destroy
|
||||
|
||||
|
|
7
app/models/procedure_tag.rb
Normal file
7
app/models/procedure_tag.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ProcedureTag < ApplicationRecord
|
||||
has_and_belongs_to_many :procedures
|
||||
|
||||
validates :name, presence: true, uniqueness: { case_sensitive: false }
|
||||
end
|
110
app/tasks/maintenance/create_procedure_tags_task.rb
Normal file
110
app/tasks/maintenance/create_procedure_tags_task.rb
Normal file
|
@ -0,0 +1,110 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# this task is used to create the procedure_tags and backfill the procedures that have the tag in their tags array
|
||||
|
||||
module Maintenance
|
||||
class CreateProcedureTagsTask < MaintenanceTasks::Task
|
||||
include RunnableOnDeployConcern
|
||||
include StatementsHelpersConcern
|
||||
run_on_first_deploy
|
||||
|
||||
def collection
|
||||
[
|
||||
"Aap",
|
||||
"Accompagnement",
|
||||
"Action sociale",
|
||||
"Adeli",
|
||||
"Affectation",
|
||||
"Agrément",
|
||||
"Agriculture",
|
||||
"agroécologie",
|
||||
"Aide aux entreprises",
|
||||
"Aide financière",
|
||||
"Appel à manifestation d'intérêt",
|
||||
"AMI",
|
||||
"Animaux",
|
||||
"Appel à projets",
|
||||
"Association",
|
||||
"Auto-école",
|
||||
"Autorisation",
|
||||
"Autorisation d'exercer",
|
||||
"Bilan",
|
||||
"Biodiversité",
|
||||
"Candidature",
|
||||
"Cerfa",
|
||||
"Chasse",
|
||||
"Cinéma",
|
||||
"Cmg",
|
||||
"Collectivé territoriale",
|
||||
"Collège",
|
||||
"Convention",
|
||||
"Covid",
|
||||
"Culture",
|
||||
"Dérogation",
|
||||
"Diplôme",
|
||||
"Drone",
|
||||
"DSDEN",
|
||||
"Eau",
|
||||
"Ecoles",
|
||||
"Education",
|
||||
"Elections",
|
||||
"Energie",
|
||||
"Enseignant",
|
||||
"ENT",
|
||||
"Environnement",
|
||||
"Étrangers",
|
||||
"Formation",
|
||||
"FPRNM",
|
||||
"Funéraire",
|
||||
"Handicap",
|
||||
"Hygiène",
|
||||
"Industrie",
|
||||
"innovation",
|
||||
"Inscription",
|
||||
"Logement",
|
||||
"Lycée",
|
||||
"Manifestation",
|
||||
"Médicament",
|
||||
"Micro-crèche",
|
||||
"MODELE DS",
|
||||
"Numérique",
|
||||
"Permis",
|
||||
"Pompiers",
|
||||
"Préfecture",
|
||||
"Professionels de santé",
|
||||
"Recrutement",
|
||||
"Rh",
|
||||
"Santé",
|
||||
"Scolaire",
|
||||
"SDIS",
|
||||
"Sécurité",
|
||||
"Sécurité routière",
|
||||
"Sécurité sociale",
|
||||
"Séjour",
|
||||
"Service civique",
|
||||
"Subvention",
|
||||
"Supérieur",
|
||||
"Taxi",
|
||||
"Télétravail",
|
||||
"Tirs",
|
||||
"Transition écologique",
|
||||
"Transport",
|
||||
"Travail",
|
||||
"Université",
|
||||
"Urbanisme"
|
||||
]
|
||||
end
|
||||
|
||||
def process(tag)
|
||||
procedure_tag = ProcedureTag.find_or_create_by(name: tag)
|
||||
|
||||
Procedure.where("? ILIKE ANY(tags)", tag).find_each(batch_size: 500) do |procedure|
|
||||
procedure.procedure_tags << procedure_tag unless procedure.procedure_tags.include?(procedure_tag)
|
||||
end
|
||||
end
|
||||
|
||||
def count
|
||||
collection.size
|
||||
end
|
||||
end
|
||||
end
|
|
@ -121,15 +121,17 @@
|
|||
|
||||
.fr-fieldset__element
|
||||
= f.label :tags, 'Associez des thématiques à la démarche', class: 'fr-label'
|
||||
%p.fr-hint-text Par des mots ou des expressions que vous attribuez aux démarches pour décrire leur contenu et pour les retrouver. Les tags sont partagés avec la communauté, ce qui vous permet de voir les tags attribués aux démarches créées par les autres administrateurs.
|
||||
%p.fr-hint-text
|
||||
Par des mots ou des expressions que vous attribuez aux démarches pour décrire leur contenu et pour les retrouver.
|
||||
Les thèmes sont partagées avec la communauté, ce qui vous permet de voir les thèmes attribués aux démarches créées par les autres administrateurs.
|
||||
%react-fragment
|
||||
= render ReactComponent.new "ComboBox/MultiComboBox",
|
||||
id: "procedure_tags_combo",
|
||||
items: Procedure.tags,
|
||||
selected_keys: @procedure.tags,
|
||||
name: 'procedure[tags][]',
|
||||
items: ProcedureTag.order(:name).pluck(:name),
|
||||
selected_keys: @procedure.procedure_tags.pluck(:name),
|
||||
name: 'procedure[procedure_tag_names][]',
|
||||
value_separator: ',|;',
|
||||
allows_custom_value: true,
|
||||
allows_custom_value: false,
|
||||
'aria-label': 'Tags',
|
||||
'aria-describedby': 'procedure-tags'
|
||||
|
||||
|
|
|
@ -25,6 +25,21 @@
|
|||
%span.fr-icon-arrow-go-back-line Réinitialiser
|
||||
%ul
|
||||
|
||||
%li.fr-py-2w.fr-pl-2w{ 'data-controller': "expand" }
|
||||
.fr-mb-1w
|
||||
%button{ 'data-action': 'expand#toggle' }
|
||||
%span.fr-icon-add-line.fr-icon--sm.fr-mr-1w.fr-text-action-high--blue-france{ 'aria-hidden': 'true', 'data-expand-target': 'icon' }
|
||||
Thématique
|
||||
.fr-ml-1w.hidden{ 'data-expand-target': 'content' }
|
||||
%div
|
||||
= f.search_field :tags, placeholder: 'Choisissez un thème', list: 'tags_list', class: 'fr-input', data: { no_autosubmit: 'input', turbo_force: :server }, multiple: true
|
||||
%datalist#tags_list
|
||||
- ProcedureTag.order(:name).each do |tag|
|
||||
%option{ value: tag.name, data: { id: tag.id } }
|
||||
- if @filter.tags.present?
|
||||
- @filter.tags.each do |tag|
|
||||
= f.hidden_field :tags, value: tag, multiple: true, id: "tag-#{tag.tr(' ', '_')}"
|
||||
|
||||
%li.fr-py-2w.fr-pl-2w{ 'data-controller': "expand" }
|
||||
.fr-mb-1w
|
||||
%button{ 'data-action': 'expand#toggle' }
|
||||
|
@ -105,22 +120,6 @@
|
|||
= b.check_box(checked: @filter.status_filtered?(b.value))
|
||||
= b.label(class: 'fr-label') { t b.text, scope: 'activerecord.attributes.procedure.aasm_state' }
|
||||
|
||||
|
||||
%li.fr-py-2w.fr-pl-2w{ 'data-controller': "expand" }
|
||||
.fr-mb-1w
|
||||
%button{ 'data-action': 'expand#toggle' }
|
||||
%span.fr-icon-add-line.fr-icon--sm.fr-mr-1w.fr-text-action-high--blue-france{ 'aria-hidden': 'true', 'data-expand-target': 'icon' }
|
||||
Thématique
|
||||
.fr-ml-1w.hidden{ 'data-expand-target': 'content' }
|
||||
%div
|
||||
= f.search_field :tags, placeholder: 'Choisissez un thème', list: 'tags_list', class: 'fr-input', data: { no_autosubmit: 'input', turbo_force: :server }, multiple: true
|
||||
%datalist#tags_list
|
||||
- Procedure.tags.each do |tag|
|
||||
%option{ value: tag }
|
||||
- if @filter.tags.present?
|
||||
- @filter.tags.each do |tag|
|
||||
= f.hidden_field :tags, value: tag, multiple: true, id: "tag-#{tag.tr(' ', '_')}"
|
||||
|
||||
.fr-col-9
|
||||
= yield(:results)
|
||||
= render template: 'layouts/application'
|
||||
|
|
|
@ -33,6 +33,8 @@ Rails.application.routes.draw do
|
|||
resources :administrateur_confirmations, only: [:new, :create]
|
||||
end
|
||||
|
||||
resources :procedure_tags, only: [:index, :show, :new, :create, :edit, :update, :destroy]
|
||||
|
||||
resources :archives, only: [:index, :show]
|
||||
|
||||
resources :dossiers, only: [:index, :show] do
|
||||
|
|
12
db/migrate/20240929124802_create_procedure_tags.rb
Normal file
12
db/migrate/20240929124802_create_procedure_tags.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateProcedureTags < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_table :procedure_tags do |t|
|
||||
t.string :name, null: false
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :procedure_tags, :name, unique: true
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateJoinTableProceduresProcedureTags < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_join_table :procedures, :procedure_tags do |t|
|
||||
t.index [:procedure_id, :procedure_tag_id], name: 'index_procedures_tags_on_procedure_id_and_tag_id'
|
||||
t.index [:procedure_tag_id, :procedure_id], name: 'index_procedures_tags_on_tag_id_and_procedure_id'
|
||||
end
|
||||
end
|
||||
end
|
17
db/schema.rb
17
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_09_23_125619) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_09_29_141825) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_buffercache"
|
||||
enable_extension "pg_stat_statements"
|
||||
|
@ -913,6 +913,21 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_23_125619) do
|
|||
t.index ["procedure_id"], name: "index_procedure_revisions_on_procedure_id"
|
||||
end
|
||||
|
||||
create_table "procedure_tags", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.text "description"
|
||||
t.string "name", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["name"], name: "index_procedure_tags_on_name", unique: true
|
||||
end
|
||||
|
||||
create_table "procedure_tags_procedures", id: false, force: :cascade do |t|
|
||||
t.bigint "procedure_id", null: false
|
||||
t.bigint "procedure_tag_id", null: false
|
||||
t.index ["procedure_id", "procedure_tag_id"], name: "index_procedures_tags_on_procedure_id_and_tag_id"
|
||||
t.index ["procedure_tag_id", "procedure_id"], name: "index_procedures_tags_on_tag_id_and_procedure_id"
|
||||
end
|
||||
|
||||
create_table "procedures", id: :serial, force: :cascade do |t|
|
||||
t.string "aasm_state", default: "brouillon"
|
||||
t.boolean "accuse_lecture", default: false, null: false
|
||||
|
|
|
@ -15,7 +15,8 @@ describe Administrateurs::ProceduresController, type: :controller do
|
|||
let(:lien_site_web) { 'http://mon-site.gouv.fr' }
|
||||
let(:zone) { create(:zone) }
|
||||
let(:zone_ids) { [zone.id] }
|
||||
let(:tags) { ["planete", "environnement"] }
|
||||
let!(:tag1) { ProcedureTag.create(name: 'Aao') }
|
||||
let!(:tag2) { ProcedureTag.create(name: 'Accompagnement') }
|
||||
|
||||
describe '#apercu' do
|
||||
subject { get :apercu, params: { id: procedure.id } }
|
||||
|
@ -64,7 +65,7 @@ describe Administrateurs::ProceduresController, type: :controller do
|
|||
monavis_embed: monavis_embed,
|
||||
zone_ids: zone_ids,
|
||||
lien_site_web: lien_site_web,
|
||||
tags: tags
|
||||
procedure_tag_names: ['Aao', 'Accompagnement']
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,21 +279,34 @@ describe Administrateurs::ProceduresController, type: :controller do
|
|||
end
|
||||
|
||||
context 'with specific tag' do
|
||||
let!(:tags_procedure) { create(:procedure, :published, tags: ['environnement', 'diplomatie']) }
|
||||
let!(:tag_environnement) { ProcedureTag.create(name: 'environnement') }
|
||||
let!(:tag_diplomatie) { ProcedureTag.create(name: 'diplomatie') }
|
||||
let!(:tag_football) { ProcedureTag.create(name: 'football') }
|
||||
|
||||
let!(:procedure) do
|
||||
procedure = create(:procedure, :published)
|
||||
procedure.procedure_tags << [tag_environnement, tag_diplomatie]
|
||||
procedure
|
||||
end
|
||||
|
||||
it 'returns procedure who contains at least one tag included in params' do
|
||||
get :all, params: { tags: ['environnement'] }
|
||||
expect(assigns(:procedures).any? { |p| p.id == tags_procedure.id }).to be_truthy
|
||||
expect(assigns(:procedures).find { |p| p.id == procedure.id }).to be_present
|
||||
end
|
||||
|
||||
it 'returns procedures who contains all tags included in params' do
|
||||
get :all, params: { tags: ['environnement', 'diplomatie'] }
|
||||
expect(assigns(:procedures).any? { |p| p.id == tags_procedure.id }).to be_truthy
|
||||
expect(assigns(:procedures).find { |p| p.id == procedure.id }).to be_present
|
||||
end
|
||||
|
||||
it 'does not returns the procedure' do
|
||||
it 'returns the procedure when at least one tag is include' do
|
||||
get :all, params: { tags: ['environnement', 'diplomatie', 'football'] }
|
||||
expect(assigns(:procedures).any? { |p| p.id == tags_procedure.id }).to be_falsey
|
||||
expect(assigns(:procedures).find { |p| p.id == procedure.id }).to be_present
|
||||
end
|
||||
|
||||
it 'does not return procedure not having the queried tag' do
|
||||
get :all, params: { tags: ['football'] }
|
||||
expect(assigns(:procedures)).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -495,8 +509,7 @@ describe Administrateurs::ProceduresController, type: :controller do
|
|||
expect(subject.organisation).to eq(organisation)
|
||||
expect(subject.administrateurs).to eq([admin])
|
||||
expect(subject.duree_conservation_dossiers_dans_ds).to eq(duree_conservation_dossiers_dans_ds)
|
||||
expect(subject.tags).to eq(["planete", "environnement"])
|
||||
|
||||
expect(subject.procedure_tags.pluck(:name)).to match_array(['Aao', 'Accompagnement'])
|
||||
expect(response).to redirect_to(champs_admin_procedure_path(Procedure.last))
|
||||
expect(flash[:notice]).to be_present
|
||||
end
|
||||
|
|
|
@ -58,23 +58,16 @@ describe 'Administrateurs can edit procedures', js: true do
|
|||
end
|
||||
|
||||
context 'when we associate tags' do
|
||||
scenario 'the administrator can edit and persist the tags' do
|
||||
procedure.update!(tags: ['social'])
|
||||
let!(:social_tag) { ProcedureTag.create(name: 'social') }
|
||||
let!(:planete_tag) { ProcedureTag.create(name: 'planete') }
|
||||
|
||||
scenario 'the tags are persisted when not interacting with the tags combobox' do
|
||||
procedure.procedure_tags << social_tag
|
||||
|
||||
visit edit_admin_procedure_path(procedure)
|
||||
select_combobox('procedure_tags_combo', 'planete', custom_value: true)
|
||||
|
||||
click_on 'Enregistrer'
|
||||
|
||||
expect(procedure.reload.tags).to eq(['social', 'planete'])
|
||||
end
|
||||
|
||||
scenario 'the tags are persisted when non interacting with the tags combobox' do
|
||||
procedure.update!(tags: ['social'])
|
||||
|
||||
visit edit_admin_procedure_path(procedure)
|
||||
click_on 'Enregistrer'
|
||||
|
||||
expect(procedure.reload.tags).to eq(['social'])
|
||||
expect(procedure.procedure_tags.pluck(:name)).to match_array(['social'])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
32
spec/tasks/maintenance/create_procedure_tags_task_spec.rb
Normal file
32
spec/tasks/maintenance/create_procedure_tags_task_spec.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
module Maintenance
|
||||
RSpec.describe CreateProcedureTagsTask do
|
||||
describe "#process" do
|
||||
subject(:process) { described_class.new.process(tag) }
|
||||
|
||||
let(:tag) { "Accompagnement" }
|
||||
let!(:procedure) { create(:procedure, tags: ["Accompagnement"]) }
|
||||
|
||||
it "creates the ProcedureTag if it does not exist" do
|
||||
expect { process }.to change { ProcedureTag.count }.by(1)
|
||||
expect(ProcedureTag.last.name).to eq(tag)
|
||||
end
|
||||
|
||||
context "when the ProcedureTag already exists" do
|
||||
let!(:procedure_tag) { ProcedureTag.create(name: tag) }
|
||||
|
||||
it "does not create a duplicate ProcedureTag" do
|
||||
expect { process }.not_to change { ProcedureTag.count }
|
||||
end
|
||||
end
|
||||
|
||||
it "associates procedures with the ProcedureTag" do
|
||||
process
|
||||
expect(procedure.reload.procedure_tags.map(&:name)).to include(tag)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue