Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions app/controllers/api/v1/tags_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

module Api
module V1
class TagsController < ApplicationController
before_action :validate_language

def index
@tags = ActsAsTaggableOn::Tag.for_context(language_tag_context)

render json: @tags
end

private

def language_tag_context
Language.find(params[:language_id]).code.to_sym
end

def validate_language
render_error unless language_id_param
end

def language_id_param
params.require(:language_id)
end

def render_error
render json: { error: "Language is required" }, status: :bad_request
end
end
end
end
56 changes: 44 additions & 12 deletions app/controllers/tags_controller.rb
Original file line number Diff line number Diff line change
@@ -1,28 +1,60 @@

class TagsController < ApplicationController
before_action :validate_language
before_action :set_tag, only: [ :show, :edit, :update, :destroy ]

def index
@tags = ActsAsTaggableOn::Tag.for_context(language_tag_context)
@tags = Tag.includes(:cognates, :reverse_cognates).references(:tag)
end

render json: @tags
def new
@tag = Tag.new
end

private
def create
@tag = Tag.new(tag_params)

if @tag.save
redirect_to tags_path
else
render :new, status: :unprocessable_entity
end
end

def show
end

def edit
end

def language_tag_context
Language.find(params[:language_id]).code.to_sym
def update
if @tag.update!(tag_params)
redirect_to tags_path, notice: "Tag was successfully updated."
else
render :edit, status: :unprocessable_entity
end
end

def validate_language
render_error unless language_id_param
def destroy
redirect_to tags_path and return unless Current.user.is_admin?

if params[:confirmed]
@tag.destroy
redirect_to tags_path, notice: "Tag was successfully destroyed."
else
@confirmation_required = @tag.taggings_count.positive?
respond_to do |format|
format.turbo_stream
end
end
end

def language_id_param
params.require(:language_id)
private

def tag_params
params.require(:tag).permit(:name, cognates_list: [])
end

def render_error
render json: { error: "Language is required" }, status: :bad_request
def set_tag
@tag = Tag.find(params[:id])
end
end
2 changes: 1 addition & 1 deletion app/controllers/topics_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def topic_tags_params
def search_params
return {} unless params[:search].present?

params.require(:search).permit(:query, :state, :provider_id, :language_id, :year, :month, :order)
params.require(:search).permit(:query, :state, :provider_id, :language_id, :year, :month, :order, tag_list: [])
end
helper_method :search_params

Expand Down
7 changes: 7 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
module ApplicationHelper
def flash_class(level)
case level
when "notice" then "alert-light-success"
when "alert" then "alert-light-danger"
else "alert-light-info"
end
end
end
8 changes: 6 additions & 2 deletions app/javascript/controllers/select_tags_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export default class extends Controller {
this.initializeTags()
}

notify() {
this.dispatch("notify", { detail: { content: Array.from(this.tagListTarget.selectedOptions).map(option => option.value) } })
}

/**
* Handle language change event and update tags accordingly
* @param {Event} event - Change event
Expand Down Expand Up @@ -44,7 +48,7 @@ export default class extends Controller {
*/
async fetchTags(languageId) {
try {
const response = await get(`/tags?language_id=${languageId}`, {
const response = await get(`/api/v1/tags?language_id=${languageId}`, {
responseKind: "json"
})

Expand Down Expand Up @@ -99,6 +103,6 @@ export default class extends Controller {
* @param {Object} options - Configuration options for bootstrap5-tags
*/
initializeTags(options = {}, reset = false) {
Tags.init("select#topic_tag_list", options, reset)
Tags.init(`select#${this.tagListTarget.id}`, options, reset)
}
}
9 changes: 9 additions & 0 deletions app/models/concerns/localized_taggable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ def available_tags
ActsAsTaggableOn::Tag.for_context(language_tag_context)
end

# Retrieves associated tags for the current language context
#
# @return [Array<Tag>] list of tags
def current_tags
return [] if language_tag_context.nil?

tags_on(language_tag_context)
end

# Retrieves associated tags for the current language context
#
# @return [Array<String>] list of tag names
Expand Down
5 changes: 5 additions & 0 deletions app/models/concerns/searcheable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ def by_state(state)
where(state: state)
end

def by_tag_list(tag_list)
tagged_with(tag_list, any: true)
end

def sort_order(order_from_params)
return :desc unless SORTS.include?(order_from_params)

Expand All @@ -44,6 +48,7 @@ def sort_order(order_from_params)
.then { |scope| params[:year].present? ? scope.by_year(params[:year]) : scope }
.then { |scope| params[:month].present? ? scope.by_month(params[:month]) : scope }
.then { |scope| params[:query].present? ? scope.search(params[:query]) : scope }
.then { |scope| params[:tag_list].present? ? scope.by_tag_list(params[:tag_list]) : scope }
.then { |scope| scope.order(created_at: sort_order(params[:order].present? ? params[:order].to_sym : :desc)) }
end
end
Expand Down
63 changes: 63 additions & 0 deletions app/models/tag.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
class Tag < ActsAsTaggableOn::Tag
has_many :tag_cognates, dependent: :destroy
has_many :cognates, through: :tag_cognates
accepts_nested_attributes_for :tag_cognates, allow_destroy: true

# Reverse relationship for cognates referencing this tag
has_many :reverse_tag_cognates, class_name: "TagCognate", foreign_key: :cognate_id
has_many :reverse_cognates, through: :reverse_tag_cognates, source: :tag

# Returns a unique list of all cognate tags, including both direct and reverse relationships
#
# @return [Array<Tag>] unique array of associated cognate tags
def cognates_tags
(cognates + reverse_cognates).uniq
end

# Returns a list of all cognate tag names
#
# @return [Array<String>] array of cognate tag names
def cognates_list
cognates.pluck(:name) + reverse_cognates.pluck(:name)
end

# Sets cognate relationships based on a list of tag names
#
# @param str_list [Array<String>] list of tag names to set as cognates
def cognates_list=(str_list)
if persisted?
remove_cognates(str_list)
create_cognates(str_list)
else
self.tag_cognates_attributes = str_list.filter_map do |name|
next if name.blank?

cognate = Tag.find_or_create_with_like_by_name(name)
{ cognate_id: cognate.id } if cognate.id != id
end
end
end

# Returns tags that are available to be set as cognates
#
# @return [ActiveRecord::Relation] collection of Tag records excluding self
def all_available_tags
Tag.where.not(id: id)
end

private

def create_cognates(str_list)
str_list.filter_map do |name|
next if name.blank?

Tag.find_or_create_with_like_by_name(name).then do |tag|
tag_cognates.create(cognate: tag)
end
end
end

def remove_cognates(str_list)
tag_cognates.where.not(cognate_id: Tag.where(name: str_list).pluck(:id)).destroy_all
end
end
34 changes: 34 additions & 0 deletions app/models/tag_cognate.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# == Schema Information
#
# Table name: tag_cognates
#
# id :bigint not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# cognate_id :bigint
# tag_id :bigint
#
# Indexes
#
# index_tag_cognates_on_cognate_id (cognate_id)
# index_tag_cognates_on_tag_id (tag_id)
# index_tag_cognates_on_tag_id_and_cognate_id (tag_id,cognate_id) UNIQUE
#
# Foreign Keys
#
# fk_rails_... (cognate_id => tags.id)
# fk_rails_... (tag_id => tags.id)
#
class TagCognate < ApplicationRecord
belongs_to :tag, class_name: "Tag"
belongs_to :cognate, class_name: "Tag"

validates :tag_id, uniqueness: { scope: :cognate_id }
validate :no_self_reference

private

def no_self_reference
errors.add(:base, "Tag can't be its own cognate") if tag_id == cognate_id
end
end
54 changes: 30 additions & 24 deletions app/views/layouts/_sidebar.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -54,33 +54,39 @@

<% if Current.user.is_admin? %>
<li class="sidebar-title">Administration</li>
<li class="sidebar-item">
<%= link_to regions_path, class: "sidebar-link" do %>
<i class="bi bi-globe"></i>
<span>Regions</span>
<% end %>
</li>
<li class="sidebar-item">
<%= link_to regions_path, class: "sidebar-link" do %>
<i class="bi bi-globe"></i>
<span>Regions</span>
<% end %>
</li>

<li class="sidebar-item">
<%= link_to providers_path, class: "sidebar-link" do %>
<i class="bi bi-hospital-fill"></i>
<span>Providers</span>
<% end %>
</li>
<li class="sidebar-item">
<%= link_to providers_path, class: "sidebar-link" do %>
<i class="bi bi-hospital-fill"></i>
<span>Providers</span>
<% end %>
</li>

<li class="sidebar-item">
<%= link_to languages_path, class: "sidebar-link" do %>
<i class="bi bi-translate"></i>
<span>Languages</span>
<% end %>
</li>
<li class="sidebar-item">
<%= link_to languages_path, class: "sidebar-link" do %>
<i class="bi bi-translate"></i>
<span>Languages</span>
<% end %>
</li>

<li class="sidebar-item">
<%= link_to tags_path, class: "sidebar-link" do %>
<i class="bi bi-tags-fill"></i>
<span>Tags</span>
<% end %>
</li>

<li class="sidebar-item">
<%= link_to users_path, class: 'sidebar-link' do %>
<i class="bi bi-people"></i>
<span>Users</span>
<% end %>
</li>
<li class="sidebar-item">
<%= link_to users_path, class: 'sidebar-link' do %>
<i class="bi bi-people"></i>
<span>Users</span>
<% end %>
</li>
<% end %>
</ul>
Expand Down
2 changes: 2 additions & 0 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
<%= render "layouts/sidebar" %>
<div id="main" class='layout-navbar navbar-fixed'>
<div id="main-content">
<%= render "shared/flash" %>

<%= yield %>
</div>
<footer>
Expand Down
8 changes: 8 additions & 0 deletions app/views/shared/_flash.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<% flash.each do |name, msg| %>
<% if msg.is_a?(String) %>
<div class="alert <%= flash_class(name) %> alert-dismissible fade show" role="alert">
<%= msg %>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<% end %>
<% end %>
Loading