From cce6147978b4763ccaad4c656628ba38b199d0dc Mon Sep 17 00:00:00 2001 From: asusikov Date: Mon, 24 Jun 2019 21:13:42 +0300 Subject: [PATCH 1/8] Added empty search query hash to query-object --- lib/core/queries/vacancy.rb | 24 +++++++++++++++--------- lib/vacancies/operations/list.rb | 10 +++++++--- spec/core/queries/vacancy_spec.rb | 3 ++- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/core/queries/vacancy.rb b/lib/core/queries/vacancy.rb index 7e58ace..62938bf 100644 --- a/lib/core/queries/vacancy.rb +++ b/lib/core/queries/vacancy.rb @@ -8,23 +8,29 @@ 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 - def all_with_contact_relation(limit:, page:) - repo.aggregate(:contact) - .where(published: true, archived: false, deleted_at: nil) - .map_to(::Vacancy).order { created_at.desc } - .per_page(limit).page(page || 1) + QUERY_MODIFIERS = {}.freeze + + def all_with_contact_relation(limit:, page:, search_query:) + query = repo.aggregate(:contact) + .where(published: true, archived: false, deleted_at: nil) + search_query.each do |key, value| + modifier = QUERY_MODIFIERS[key] || ->(initial_query, _filter_value) { initial_query } + query = modifier.call(query, value) + end + query.map_to(::Vacancy).order { created_at.desc } + .per_page(limit).page(page || 1) 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 72700a0..ec63fd1 100644 --- a/spec/core/queries/vacancy_spec.rb +++ b/spec/core/queries/vacancy_spec.rb @@ -2,9 +2,10 @@ RSpec.describe Queries::Vacancy, type: :query do let(:repo) { described_class.new } + let(:search_query) { return {} } 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: archived, deleted_at: deleted_at) } From 63708ca3bb34e16bc7087f5d87eaa150252a8d36 Mon Sep 17 00:00:00 2001 From: asusikov Date: Mon, 24 Jun 2019 21:19:55 +0300 Subject: [PATCH 2/8] Added filter by remote_available --- lib/core/queries/vacancy.rb | 4 +++- spec/core/queries/vacancy_spec.rb | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/core/queries/vacancy.rb b/lib/core/queries/vacancy.rb index 62938bf..3051620 100644 --- a/lib/core/queries/vacancy.rb +++ b/lib/core/queries/vacancy.rb @@ -20,7 +20,9 @@ def pager_for_all_with_contact(limit:, page:, search_query:) private - QUERY_MODIFIERS = {}.freeze + QUERY_MODIFIERS = { + remote: ->(initial_query, filter_value) { initial_query.where(remote_available: filter_value) } + }.freeze def all_with_contact_relation(limit:, page:, search_query:) query = repo.aggregate(:contact) diff --git a/spec/core/queries/vacancy_spec.rb b/spec/core/queries/vacancy_spec.rb index ec63fd1..5b6aacb 100644 --- a/spec/core/queries/vacancy_spec.rb +++ b/spec/core/queries/vacancy_spec.rb @@ -17,6 +17,12 @@ 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) { return { remote: true } } + + it { expect(subject).to eq([]) } + end end context 'when vacancy published and archived' do From 6e795d13e505ce1cd4171227f91b2b93b7d3f36b Mon Sep 17 00:00:00 2001 From: asusikov Date: Mon, 24 Jun 2019 22:27:22 +0300 Subject: [PATCH 3/8] Added filters for location and position_type --- lib/core/queries/vacancy.rb | 4 ++- spec/core/queries/vacancy_spec.rb | 46 ++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/lib/core/queries/vacancy.rb b/lib/core/queries/vacancy.rb index 3051620..871c7ec 100644 --- a/lib/core/queries/vacancy.rb +++ b/lib/core/queries/vacancy.rb @@ -21,7 +21,9 @@ def pager_for_all_with_contact(limit:, page:, search_query:) private QUERY_MODIFIERS = { - remote: ->(initial_query, filter_value) { initial_query.where(remote_available: filter_value) } + 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 all_with_contact_relation(limit:, page:, search_query:) diff --git a/spec/core/queries/vacancy_spec.rb b/spec/core/queries/vacancy_spec.rb index 5b6aacb..226ee5d 100644 --- a/spec/core/queries/vacancy_spec.rb +++ b/spec/core/queries/vacancy_spec.rb @@ -7,7 +7,21 @@ describe '#all_with_contact' do subject { repo.all_with_contact(limit: 10, page: 1, search_query: search_query) } - before { Fabricate.create(:vacancy, published: published, archived: archived, 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 } @@ -22,6 +36,36 @@ let(:search_query) { return { 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) { return { 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) { return { 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 From 77d842016acfb20eea88605044e0a502453cd081 Mon Sep 17 00:00:00 2001 From: asusikov Date: Tue, 25 Jun 2019 09:29:15 +0300 Subject: [PATCH 4/8] Changed condition of running query modifier --- lib/core/queries/vacancy.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/queries/vacancy.rb b/lib/core/queries/vacancy.rb index 871c7ec..141f9f7 100644 --- a/lib/core/queries/vacancy.rb +++ b/lib/core/queries/vacancy.rb @@ -30,8 +30,8 @@ def all_with_contact_relation(limit:, page:, search_query:) query = repo.aggregate(:contact) .where(published: true, archived: false, deleted_at: nil) search_query.each do |key, value| - modifier = QUERY_MODIFIERS[key] || ->(initial_query, _filter_value) { initial_query } - query = modifier.call(query, value) + modifier = QUERY_MODIFIERS[key] + query = modifier.call(query, value) if modifier end query.map_to(::Vacancy).order { created_at.desc } .per_page(limit).page(page || 1) From 3bf4ac22cd0d23269ca2239fae2e5b6830b92ecd Mon Sep 17 00:00:00 2001 From: asusikov Date: Tue, 25 Jun 2019 14:09:49 +0300 Subject: [PATCH 5/8] Added struct for vacancy search query --- apps/web/controllers/vacancies/index.rb | 17 ++++++++++++++++- lib/core/queries/vacancy.rb | 12 ++++++++++-- spec/core/queries/vacancy_spec.rb | 9 +++++---- spec/web/controllers/vacancies/index_spec.rb | 2 +- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/apps/web/controllers/vacancies/index.rb b/apps/web/controllers/vacancies/index.rb index 4313484..652ec14 100644 --- a/apps/web/controllers/vacancies/index.rb +++ b/apps/web/controllers/vacancies/index.rb @@ -34,8 +34,23 @@ def call(params) private + Container = Module.new do + extend Transproc::Registry + import Transproc::HashTransformations + import Transproc::Coercions + import Transproc::ClassTransformations + end + + class Transformer < Transproc::Transformer[Container] + symbolize_keys + map_value :remote, t(:to_boolean) + constructor_inject Queries::Vacancy::SearchQuery + end + 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 } + Transformer.new.call(initial_attributes.merge(query_attributes)) end end end diff --git a/lib/core/queries/vacancy.rb b/lib/core/queries/vacancy.rb index 141f9f7..5df9f4f 100644 --- a/lib/core/queries/vacancy.rb +++ b/lib/core/queries/vacancy.rb @@ -1,6 +1,14 @@ # frozen_string_literal: true module Queries + class Vacancy + class SearchQuery < 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 + class Vacancy attr_reader :repo @@ -29,9 +37,9 @@ def pager_for_all_with_contact(limit:, page:, search_query:) def all_with_contact_relation(limit:, page:, search_query:) query = repo.aggregate(:contact) .where(published: true, archived: false, deleted_at: nil) - search_query.each do |key, value| + search_query.to_h.each do |key, value| modifier = QUERY_MODIFIERS[key] - query = modifier.call(query, value) if modifier + query = modifier.call(query, value) if modifier && value end query.map_to(::Vacancy).order { created_at.desc } .per_page(limit).page(page || 1) diff --git a/spec/core/queries/vacancy_spec.rb b/spec/core/queries/vacancy_spec.rb index 226ee5d..fecd64b 100644 --- a/spec/core/queries/vacancy_spec.rb +++ b/spec/core/queries/vacancy_spec.rb @@ -2,7 +2,8 @@ RSpec.describe Queries::Vacancy, type: :query do let(:repo) { described_class.new } - let(:search_query) { return {} } + let(:query_attributes) { return { remote: nil, position_type: nil, location: nil } } + let(:search_query) { Queries::Vacancy::SearchQuery.new } describe '#all_with_contact' do subject { repo.all_with_contact(limit: 10, page: 1, search_query: search_query) } @@ -33,7 +34,7 @@ it { expect(subject.first.contact).to be_a(Contact) } context 'and remote in search_query is true' do - let(:search_query) { return { remote: true } } + let(:search_query) { Queries::Vacancy::SearchQuery.new(query_attributes.merge(remote: true)) } it { expect(subject).to eq([]) } @@ -45,7 +46,7 @@ end context 'and position_type in search_query is equal "other"' do - let(:search_query) { return { position_type: 'other' } } + let(:search_query) { Queries::Vacancy::SearchQuery.new(query_attributes.merge(position_type: 'other')) } it { expect(subject).to eq([]) } @@ -57,7 +58,7 @@ end context 'and location in search query is not empty' do - let(:search_query) { return { location: 'VASYUKI' } } + let(:search_query) { Queries::Vacancy::SearchQuery.new(query_attributes.merge(location: 'VASYUKI')) } it { expect(subject).to eq([]) } diff --git a/spec/web/controllers/vacancies/index_spec.rb b/spec/web/controllers/vacancies/index_spec.rb index cb7b39e..817c891 100644 --- a/spec/web/controllers/vacancies/index_spec.rb +++ b/spec/web/controllers/vacancies/index_spec.rb @@ -24,7 +24,7 @@ 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: Queries::Vacancy::SearchQuery.new(remote: true, location: nil, position_type: nil)) subject end end From 2f13e850b4d74dd89116351941fb85414148fd11 Mon Sep 17 00:00:00 2001 From: asusikov Date: Fri, 28 Jun 2019 10:39:23 +0300 Subject: [PATCH 6/8] Moved SearchOptions to own file --- apps/web/controllers/vacancies/index.rb | 2 +- lib/core/queries/vacancy.rb | 8 -------- spec/core/queries/vacancy_spec.rb | 8 ++++---- spec/web/controllers/vacancies/index_spec.rb | 3 ++- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/apps/web/controllers/vacancies/index.rb b/apps/web/controllers/vacancies/index.rb index 652ec14..30f0790 100644 --- a/apps/web/controllers/vacancies/index.rb +++ b/apps/web/controllers/vacancies/index.rb @@ -44,7 +44,7 @@ def call(params) class Transformer < Transproc::Transformer[Container] symbolize_keys map_value :remote, t(:to_boolean) - constructor_inject Queries::Vacancy::SearchQuery + constructor_inject ::Vacancies::Entities::SearchOptions end def search_query diff --git a/lib/core/queries/vacancy.rb b/lib/core/queries/vacancy.rb index 5df9f4f..5bff267 100644 --- a/lib/core/queries/vacancy.rb +++ b/lib/core/queries/vacancy.rb @@ -1,14 +1,6 @@ # frozen_string_literal: true module Queries - class Vacancy - class SearchQuery < 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 - class Vacancy attr_reader :repo diff --git a/spec/core/queries/vacancy_spec.rb b/spec/core/queries/vacancy_spec.rb index fecd64b..d3167d0 100644 --- a/spec/core/queries/vacancy_spec.rb +++ b/spec/core/queries/vacancy_spec.rb @@ -3,7 +3,7 @@ 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) { Queries::Vacancy::SearchQuery.new } + let(:search_query) { Vacancies::Entities::SearchOptions.new } describe '#all_with_contact' do subject { repo.all_with_contact(limit: 10, page: 1, search_query: search_query) } @@ -34,7 +34,7 @@ it { expect(subject.first.contact).to be_a(Contact) } context 'and remote in search_query is true' do - let(:search_query) { Queries::Vacancy::SearchQuery.new(query_attributes.merge(remote: true)) } + let(:search_query) { Vacancies::Entities::SearchOptions.new(query_attributes.merge(remote: true)) } it { expect(subject).to eq([]) } @@ -46,7 +46,7 @@ end context 'and position_type in search_query is equal "other"' do - let(:search_query) { Queries::Vacancy::SearchQuery.new(query_attributes.merge(position_type: 'other')) } + let(:search_query) { Vacancies::Entities::SearchOptions.new(query_attributes.merge(position_type: 'other')) } it { expect(subject).to eq([]) } @@ -58,7 +58,7 @@ end context 'and location in search query is not empty' do - let(:search_query) { Queries::Vacancy::SearchQuery.new(query_attributes.merge(location: 'VASYUKI')) } + let(:search_query) { Vacancies::Entities::SearchOptions.new(query_attributes.merge(location: 'VASYUKI')) } it { expect(subject).to eq([]) } diff --git a/spec/web/controllers/vacancies/index_spec.rb b/spec/web/controllers/vacancies/index_spec.rb index 817c891..3370d8f 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: Queries::Vacancy::SearchQuery.new(remote: true, location: nil, position_type: nil)) + expect(operation).to receive(:call).with(page: nil, search_query: search_query) subject end end From ec683c3f0fb09e649a6c7c9c4e4a612ed7e907d2 Mon Sep 17 00:00:00 2001 From: asusikov Date: Fri, 28 Jun 2019 10:45:33 +0300 Subject: [PATCH 7/8] Added SearchOptions file and mapper --- .gitignore | 3 +++ apps/web/controllers/vacancies/index.rb | 18 +++--------------- lib/vacancies/entities/search_options.rb | 11 +++++++++++ lib/vacancies/mappers/search_options.rb | 18 ++++++++++++++++++ 4 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 lib/vacancies/entities/search_options.rb create mode 100644 lib/vacancies/mappers/search_options.rb 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 30f0790..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 @@ -34,23 +35,10 @@ def call(params) private - Container = Module.new do - extend Transproc::Registry - import Transproc::HashTransformations - import Transproc::Coercions - import Transproc::ClassTransformations - end - - class Transformer < Transproc::Transformer[Container] - symbolize_keys - map_value :remote, t(:to_boolean) - constructor_inject ::Vacancies::Entities::SearchOptions - end - def search_query query_attributes = params[:query] ? search_query_parser.call(params[:query]) : EMPTY_SEARCH_QUERY initial_attributes = { remote: nil, position_type: nil, location: nil } - Transformer.new.call(initial_attributes.merge(query_attributes)) + search_options_mapper.call(initial_attributes.merge(query_attributes)) end end end 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 From 79638777df74257cd643024ad2de9126baba73a9 Mon Sep 17 00:00:00 2001 From: asusikov Date: Fri, 28 Jun 2019 11:08:15 +0300 Subject: [PATCH 8/8] Added spec to mapper --- spec/vacancies/mappers/search_options_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 spec/vacancies/mappers/search_options_spec.rb 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