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
15 changes: 12 additions & 3 deletions app/controllers/topics_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ class TopicsController < ApplicationController
before_action :check_admin!, only: :destroy

def index
@topics = scope.includes(:language, :provider)
@topics = scope.search_with_params(search_params)
@providers = scope.map(&:provider).uniq.sort_by(&:name)
@languages = scope.map(&:language).uniq.sort_by(&:name)
end

def new
Expand Down Expand Up @@ -47,6 +49,13 @@ def topic_params
params.require(:topic).permit(:title, :description, :uid, :language_id, :provider_id)
end

helper_method :search_params
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to use these params for view

def search_params
return {} unless params[:search].present?

params.require(:search).permit(:query, :state, :provider_id, :language_id, :year, :month, :order)
end

def set_topic
@topic = Topic.find(params[:id])
end
Expand All @@ -55,7 +64,7 @@ def scope
@scope ||= if Current.user.is_admin?
Topic.all
else
Topic.active
end
Current.user.topics
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we should filter by state, all topics are available for admins.
But for regular user only topics from his providers are available

end.includes(:language, :provider)
end
end
48 changes: 48 additions & 0 deletions app/models/concerns/searcheable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module Searcheable
extend ActiveSupport::Concern

SORTS = %i[asc desc].freeze

class_methods do
def search(query)
where("title ILIKE :query OR description ILIKE :query", query: "%#{query}%")
end

def by_year(year)
where("extract(year from created_at) = ?", year)
end

def by_month(month)
where("extract(month from created_at) = ?", month)
end

def by_provider(provider_id)
where(provider_id: provider_id)
end

def by_language(language_id)
where(language_id: language_id)
end

def by_state(state)
where(state: state)
end

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

order_from_params
end

def search_with_params(params)
self
.then { |scope| params[:state].present? ? scope.by_state(params[:state]) : scope }
.then { |scope| params[:provider_id].present? ? scope.by_provider(params[:provider_id]) : scope }
.then { |scope| params[:language_id].present? ? scope.by_language(params[:language_id]): scope }
.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| scope.order(created_at: sort_order(params[:order])) }
end
end
end
2 changes: 2 additions & 0 deletions app/models/topic.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class Topic < ApplicationRecord
include Searcheable

belongs_to :language
belongs_to :provider

Expand Down
4 changes: 4 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ class User < ApplicationRecord

validates :email, presence: true, uniqueness: true, format: URI::MailTo::EMAIL_REGEXP
validates :password_digest, presence: true

def topics
Topic.where(provider_id: providers.pluck(:id))
end
end
50 changes: 50 additions & 0 deletions app/views/topics/_search.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h2 class="card-title">Search</h2>
</div>
<div class="card-content">
<div class="card-body">
<p class="card-text"> Use these params settings to search topics</p>
<%= form_for :search, url: topics_path, method: :get do |f| %>
<div class="form-body">
<div class="row">
<div class="col-12">
<div class="form-group">
<%= f.label :provider %>
<%= f.select :provider_id, options_from_collection_for_select(providers, :id, :name, params[:provider_id]), { prompt: "Select provider" }, class: "form-select" %>
</div>
<div class="form-group">
<%= f.label :language %>
<%= f.select :language_id, options_from_collection_for_select(languages, :id, :name, params[:provider_id]), { prompt: "Select language" }, class: "form-select" %>
</div>
<div class="form-group">
<%= f.label :query %>
<%= f.text_field :query, value: params[:query], class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :year %>
<%= f.select :year, options_for_select((Date.today.year-10..Date.today.year).to_a, params[:year]), { prompt: "Select year" }, class: "form-select" %>
</div>
<div class="form-group">
<%= f.label :month %>
<%= f.select :month, options_for_select((1..12).to_a, params[:month]), { prompt: "Select month" }, class: "form-select" %>
</div>
<div class="form-group">
<%= f.label :state %>
<%= f.select :state, options_for_select(Topic::STATES.index_with(&:itself), params[:state]), { prompt: "Select state" }, class: "form-select" %>
</div>
<div class="form-group">
<%= f.label :order %>
<%= f.select :order, options_for_select(Topic::SORTS.reverse.index_with(&:itself), params[:order]), {}, class: "form-select" %>
</div>
<div class="col-12 d-flex justify-content-end">
<%= f.submit "Search", class: "btn btn-primary me-1 mb-1" %>
<%= link_to "Clear", topics_path, class: "btn btn-light-secondary me-1 mb-1" %>
</div>
</div>
</div>
</div>
<% end %>
</div>
</div>
</div>
7 changes: 5 additions & 2 deletions app/views/topics/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<section class="section">
<div class="row" id="table-striped">
<div class="col-12 cold-md-12">
<%= render "topics/search", providers: @providers, languages: @languages, params: search_params %>
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h2 class="card-title">Topics</h2>
Expand Down Expand Up @@ -42,8 +43,10 @@
<%= link_to edit_topic_path(topic), class: "btn btn-secondary btn-sm" do %>
<i class="bi bi-pencil"></i> Edit
<% end %>
<%= link_to archive_topic_path(topic), method: :put, data: { confirm: "Are you sure?" }, class: "btn btn-danger btn-sm" do %>
<i class="bi bi-archive"></i> Archive
<% unless topic.archived? %>
<%= link_to archive_topic_path(topic), method: :put, data: { confirm: "Are you sure?" }, class: "btn btn-danger btn-sm" do %>
<i class="bi bi-archive"></i> Archive
<% end %>
<% end %>
<% if Current.user.is_admin? %>
<%= link_to topic, method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-danger btn-sm" do %>
Expand Down
6 changes: 5 additions & 1 deletion db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@
puts "Topics created!"
puts "Creating users..."

User.create(email: "[email protected]", password: "test123")
User.create(email: "[email protected]", password: "test123", is_admin: true)
me = User.create(email: "[email protected]", password: "test123")
Provider.each do |provider|
provider.users << me
end

10.times do
User.create(
Expand Down
6 changes: 6 additions & 0 deletions spec/factories/contributor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FactoryBot.define do
factory :contributor do
association :provider
association :user
end
end
69 changes: 66 additions & 3 deletions spec/requests/topics/index_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,80 @@

describe "Topics", type: :request do
describe "GET /topics" do
let(:provider) { create(:provider) }
let(:user) { create(:user) }

before { sign_in(user) }
before do
provider.users << user
sign_in(user)
end

it "renders a successful response" do
create(:topic)
topic = create(:topic, provider:)

get topics_url

expect(response).to be_successful
expect(assigns(:topics)).to eq(Topic.active)
expect(assigns(:topics)).to eq([ topic ])
end

describe "searching" do
context "with a query" do
it "filters topics by query" do
topic = create(:topic, provider:, title: "Introduction to English")

get topics_url, params: { search: { query: "English" } }

expect(response).to be_successful
expect(assigns(:topics)).to eq([ topic ])
end
end

context "with a state" do
it "filters topics by state" do
active_topic = create(:topic, provider:, state: :active)
archived_topic = create(:topic, provider:, state: :archived)

get topics_url, params: { search: { state: "active" } }

expect(response).to be_successful
expect(assigns(:topics)).to eq([ active_topic ])
end
end

context "by provider" do
it "filters topics by provider" do
topic = create(:topic, provider:)

get topics_url, params: { search: { provider_id: provider.id } }

expect(response).to be_successful
expect(assigns(:topics)).to eq([ topic ])
end
end

context "by language" do
it "filters topics by language" do
language = create(:language)
topic = create(:topic, provider:, language:)

get topics_url, params: { search: { language_id: language.id } }

expect(response).to be_successful
expect(assigns(:topics)).to eq([ topic ])
end
end

context "by year and month" do
it "filters topics by date" do
topic = create(:topic, provider:, created_at: Time.zone.local(2021, 1, 1))

get topics_url, params: { search: { year: 2021, month: 1 } }

expect(response).to be_successful
expect(assigns(:topics)).to eq([ topic ])
end
end
end
end
end