diff --git a/.gitignore b/.gitignore index a7f1252..6a71107 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ .ruby-version .env.dev .env.development + +.direnv/ +.envrc diff --git a/apps/web/controllers/vacancies/index.rb b/apps/web/controllers/vacancies/index.rb index 4313484..a5fde13 100644 --- a/apps/web/controllers/vacancies/index.rb +++ b/apps/web/controllers/vacancies/index.rb @@ -9,7 +9,8 @@ class Index include Import[ 'libs.search_query_parser', - operation: 'vacancies.operations.list' + operation: 'vacancies.operations.list', + search_options_mapper: 'vacancies.mappers.search_options', ] EMPTY_SEARCH_QUERY = {}.freeze @@ -35,7 +36,9 @@ def call(params) private def search_query - params[:query] ? search_query_parser.call(params[:query]) : EMPTY_SEARCH_QUERY + query_attributes = params[:query] ? search_query_parser.call(params[:query]) : EMPTY_SEARCH_QUERY + initial_attributes = { remote: nil, position_type: nil, location: nil } + search_options_mapper.call(initial_attributes.merge(query_attributes)) end end end diff --git a/lib/core/queries/vacancy.rb b/lib/core/queries/vacancy.rb index 6a4acca..4ab45de 100644 --- a/lib/core/queries/vacancy.rb +++ b/lib/core/queries/vacancy.rb @@ -8,18 +8,35 @@ def initialize(repo = VacancyRepository.new) @repo = repo end - def all_with_contact(limit:, page:) - all_with_contact_relation(limit: limit, page: page).to_a + def all_with_contact(limit:, page:, search_query:) + all_with_contact_relation(limit: limit, page: page, search_query: search_query).to_a end - def pager_for_all_with_contact(limit:, page:) + def pager_for_all_with_contact(limit:, page:, search_query:) Hanami::Pagination::Pager.new( - all_with_contact_relation(limit: limit, page: page).pager + all_with_contact_relation(limit: limit, page: page, search_query: search_query).pager ) end private + QUERY_MODIFIERS = { + remote: ->(query, filter_value) { query.where(remote_available: filter_value) }, + position_type: ->(query, filter_value) { query.where(position_type: filter_value) }, + location: ->(query, filter_value) { query.where { location.ilike("%#{filter_value}%") } } + }.freeze + + def new_all_with_contact_relation(limit:, page:, search_query:) + query = repo.aggregate(:contact) + .where(published: true, archived: false, deleted_at: nil) + search_query.to_h.each do |key, value| + modifier = QUERY_MODIFIERS[key] + query = modifier.call(query, value) if modifier && value + end + query.map_to(::Vacancy).order { created_at.desc } + .per_page(limit).page(page || 1) + end + def all_with_contact_relation(limit:, page:) repo.aggregate(:contact) .where(published: true, deleted_at: nil) diff --git a/lib/vacancies/entities/search_options.rb b/lib/vacancies/entities/search_options.rb new file mode 100644 index 0000000..c5edc4a --- /dev/null +++ b/lib/vacancies/entities/search_options.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Vacancies + module Entities + class SearchOptions < Dry::Struct + attribute :remote, Core::Types::Strict::Bool.optional.default(nil) + attribute :position_type, Core::Types::VacancyPositionTypes.optional.default(nil) + attribute :location, Core::Types::Strict::String.optional.default(nil) + end + end +end diff --git a/lib/vacancies/mappers/search_options.rb b/lib/vacancies/mappers/search_options.rb new file mode 100644 index 0000000..98fcfc1 --- /dev/null +++ b/lib/vacancies/mappers/search_options.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Vacancies + module Mappers + Container = Module.new do + extend Transproc::Registry + import Transproc::HashTransformations + import Transproc::Coercions + import Transproc::ClassTransformations + end + + class SearchOptions < Transproc::Transformer[Container] + symbolize_keys + map_value :remote, t(:to_boolean) + constructor_inject ::Vacancies::Entities::SearchOptions + end + end +end diff --git a/lib/vacancies/operations/list.rb b/lib/vacancies/operations/list.rb index ff42984..1de14d2 100644 --- a/lib/vacancies/operations/list.rb +++ b/lib/vacancies/operations/list.rb @@ -9,9 +9,13 @@ class List < ::Libs::Operation PAGINATION_LIMIT = 10 - def call(search_query: {}, page: 1) # rubocop:disable Lint/UnusedMethodArgument - pager = vacancy_query.pager_for_all_with_contact(limit: PAGINATION_LIMIT, page: page || 1) - result = vacancy_query.all_with_contact(limit: PAGINATION_LIMIT, page: page || 1) + def call(search_query: {}, page: 1) + pager = vacancy_query.pager_for_all_with_contact( + limit: PAGINATION_LIMIT, + page: page || 1, + search_query: search_query + ) + result = vacancy_query.all_with_contact(limit: PAGINATION_LIMIT, page: page || 1, search_query: search_query) Success(result: result, pager: pager) end diff --git a/spec/core/queries/vacancy_spec.rb b/spec/core/queries/vacancy_spec.rb index 542c35f..655ee00 100644 --- a/spec/core/queries/vacancy_spec.rb +++ b/spec/core/queries/vacancy_spec.rb @@ -2,11 +2,27 @@ RSpec.describe Queries::Vacancy, type: :query do let(:repo) { described_class.new } + let(:query_attributes) { return { remote: nil, position_type: nil, location: nil } } + let(:search_query) { Vacancies::Entities::SearchOptions.new } describe '#all_with_contact' do - subject { repo.all_with_contact(limit: 10, page: 1) } + subject { repo.all_with_contact(limit: 10, page: 1, search_query: search_query) } - before { Fabricate.create(:vacancy, published: published, archived_at: archived_at, deleted_at: deleted_at) } + let(:remote_available) { nil } + let(:position_type) { nil } + let(:location) { nil } + + before do + Fabricate.create( + :vacancy, + published: published, + archived: archived, + deleted_at: deleted_at, + remote_available: remote_available, + position_type: position_type, + location: location + ) + end context 'when vacancy published and not archived or deleted' do let(:published) { true } @@ -16,6 +32,42 @@ it { expect(subject.count).to eq(1) } it { expect(subject).to all(be_a(Vacancy)) } it { expect(subject.first.contact).to be_a(Contact) } + + context 'and remote in search_query is true' do + let(:search_query) { Vacancies::Entities::SearchOptions.new(query_attributes.merge(remote: true)) } + + it { expect(subject).to eq([]) } + + context 'and remote_available in record is true as well' do + let(:remote_available) { true } + + it { expect(subject.count).to eq(1) } + end + end + + context 'and position_type in search_query is equal "other"' do + let(:search_query) { Vacancies::Entities::SearchOptions.new(query_attributes.merge(position_type: 'other')) } + + it { expect(subject).to eq([]) } + + context 'and position_type in record is "other" as well' do + let(:position_type) { 'other' } + + it { expect(subject.count).to eq(1) } + end + end + + context 'and location in search query is not empty' do + let(:search_query) { Vacancies::Entities::SearchOptions.new(query_attributes.merge(location: 'VASYUKI')) } + + it { expect(subject).to eq([]) } + + context 'and location in record includes location from search query' do + let(:location) { 'New Vasyuki' } + + it { expect(subject.count).to eq(1) } + end + end end context 'when vacancy published and archived' do diff --git a/spec/vacancies/mappers/search_options_spec.rb b/spec/vacancies/mappers/search_options_spec.rb new file mode 100644 index 0000000..2e1860f --- /dev/null +++ b/spec/vacancies/mappers/search_options_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +RSpec.describe Vacancies::Mappers::SearchOptions, type: :mapper do + subject { described_class.new.call(search_options_hash) } + + let(:search_options_hash) { { remote: nil, position_type: 'other', location: 'New Vasyuki' } } + + it { expect(subject).to be_a(Vacancies::Entities::SearchOptions) } + it { expect(subject.position_type).to eq(search_options_hash[:position_type]) } + it { expect(subject.location).to eq(search_options_hash[:location]) } + + context 'when remote is string equaled "true"' do + let(:search_options_hash) { { remote: 'true', position_type: nil, location: nil } } + + it { expect(subject.remote).to be_truthy } + end +end diff --git a/spec/web/controllers/vacancies/index_spec.rb b/spec/web/controllers/vacancies/index_spec.rb index c35f4be..463440a 100644 --- a/spec/web/controllers/vacancies/index_spec.rb +++ b/spec/web/controllers/vacancies/index_spec.rb @@ -20,11 +20,12 @@ context 'when params inlcludes query param' do let(:params) { { query: 'remote:true search text' } } + let(:search_query) { Vacancies::Entities::SearchOptions.new(remote: true, location: nil, position_type: nil) } it { expect(subject).to be_success } it do - expect(operation).to receive(:call).with(page: nil, search_query: { remote: 'true', text: 'search text' }) + expect(operation).to receive(:call).with(page: nil, search_query: search_query) subject end end