Skip to content

Commit f3d3c9f

Browse files
authored
Merge pull request #60 from davydovanton/fix/fix-vacancy-query
Fix vacancy query logic
2 parents 1ac5c74 + 899f206 commit f3d3c9f

File tree

9 files changed

+111
-37
lines changed

9 files changed

+111
-37
lines changed

apps/web/controllers/vacancies/index.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ def call(params)
3636
private
3737

3838
def search_query
39-
query_attributes = params[:query] ? search_query_parser.call(params[:query]) : EMPTY_SEARCH_QUERY
40-
initial_attributes = { remote: nil, position_type: nil, location: nil }
41-
search_options_mapper.call(initial_attributes.merge(query_attributes))
39+
search_options_mapper.call(
40+
search_query_parser.call(params[:query])
41+
)
4242
end
4343
end
4444
end

lib/core/libs/search_query_parser.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ class SearchQueryParser
99

1010
SEPARATOR_CHAR = ':'
1111

12+
EMPTY_RESULT = { text: nil }.freeze
13+
1214
def call(query)
15+
return EMPTY_RESULT if query.nil? || query.empty?
16+
1317
scanner = StringScanner.new(query.to_s)
1418
options = scan_options(scanner)
1519
text = scanner.scan(/.+/)

lib/core/queries/vacancy.rb

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@
22

33
module Queries
44
class Vacancy
5+
EMPTY_HASH = {}.freeze
6+
57
attr_reader :repo
68

79
def initialize(repo = VacancyRepository.new)
810
@repo = repo
911
end
1012

11-
def all_with_contact(limit:, page:, search_query:)
12-
all_with_contact_relation(limit: limit, page: page, search_query: search_query).to_a
13+
def all_with_contact(limit:, page:, search_query: nil)
14+
all_with_contact_relation(limit: limit, page: page, search_query: search_query || EMPTY_HASH).to_a
1315
end
1416

15-
def pager_for_all_with_contact(limit:, page:, search_query:)
17+
def pager_for_all_with_contact(limit:, page:, search_query: nil)
1618
Hanami::Pagination::Pager.new(
17-
all_with_contact_relation(limit: limit, page: page, search_query: search_query).pager
19+
all_with_contact_relation(limit: limit, page: page, search_query: search_query || EMPTY_HASH).pager
1820
)
1921
end
2022

@@ -26,23 +28,24 @@ def pager_for_all_with_contact(limit:, page:, search_query:)
2628
location: ->(query, filter_value) { query.where { location.ilike("%#{filter_value}%") } }
2729
}.freeze
2830

29-
def new_all_with_contact_relation(limit:, page:, search_query:)
30-
query = repo.aggregate(:contact)
31-
.where(published: true, archived: false, deleted_at: nil)
31+
def all_with_contact_relation(limit:, page:, search_query:)
32+
query = base_query
33+
3234
search_query.to_h.each do |key, value|
35+
next if value.nil?
36+
3337
modifier = QUERY_MODIFIERS[key]
34-
query = modifier.call(query, value) if modifier && value
38+
query = modifier.call(query, value) if modifier
3539
end
36-
query.map_to(::Vacancy).order { created_at.desc }
37-
.per_page(limit).page(page || 1)
40+
41+
query.per_page(limit).page(page || 1)
3842
end
39-
40-
def all_with_contact_relation(limit:, page:)
43+
44+
def base_query
4145
repo.aggregate(:contact)
42-
.where(published: true, deleted_at: nil)
46+
.where(published: true, archived: false, deleted_at: nil)
4347
.where('archived_at > ?', Date.today)
4448
.map_to(::Vacancy).order { created_at.desc }
45-
.per_page(limit).page(page || 1)
4649
end
4750
end
4851
end

lib/vacancies/entities/search_options.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
module Vacancies
44
module Entities
55
class SearchOptions < Dry::Struct
6+
constructor_type :schema
7+
68
attribute :remote, Core::Types::Strict::Bool.optional.default(nil)
79
attribute :position_type, Core::Types::VacancyPositionTypes.optional.default(nil)
810
attribute :location, Core::Types::Strict::String.optional.default(nil)

lib/vacancies/mappers/search_options.rb

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,25 @@
22

33
module Vacancies
44
module Mappers
5-
Container = Module.new do
6-
extend Transproc::Registry
7-
import Transproc::HashTransformations
8-
import Transproc::Coercions
9-
import Transproc::ClassTransformations
10-
end
5+
class SearchOptions
6+
def call(params)
7+
payload = {
8+
remote: to_bool(params[:remote]),
9+
position_type: params[:position_type],
10+
location: params[:location]
11+
}
12+
13+
Vacancies::Entities::SearchOptions.new(payload)
14+
end
15+
16+
private
1117

12-
class SearchOptions < Transproc::Transformer[Container]
13-
symbolize_keys
14-
map_value :remote, t(:to_boolean)
15-
constructor_inject ::Vacancies::Entities::SearchOptions
18+
def to_bool(value)
19+
case value
20+
when 'true' then true
21+
when 'false' then false
22+
end
23+
end
1624
end
1725
end
1826
end

spec/core/libs/search_query_parser_spec.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
RSpec.describe Libs::SearchQueryParser do
44
let(:parser) { described_class.new }
55

6+
it { expect(parser.call(nil)).to eq(text: nil) }
67
it { expect(parser.call('')).to eq(text: nil) }
78
it { expect(parser.call('text')).to eq(text: 'text') }
89
it { expect(parser.call('author:davydovanton')).to eq(author: 'davydovanton', text: nil) }
910
it { expect(parser.call('tag:test tag:other text')).to eq(tag: %w[test other], text: 'text') }
1011
it { expect(parser.call('author:davy lang:ruby text')).to eq(author: 'davy', lang: 'ruby', text: 'text') }
1112
it { expect(parser.call(' author:davy lang:ruby text')).to eq(author: 'davy', lang: 'ruby', text: 'text') }
13+
it { expect(parser.call('remote:true location:moscow text')).to eq(text: 'text', remote: 'true', location: 'moscow') }
1214
end

spec/core/queries/vacancy_spec.rb

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
RSpec.describe Queries::Vacancy, type: :query do
44
let(:repo) { described_class.new }
5-
let(:query_attributes) { return { remote: nil, position_type: nil, location: nil } }
65
let(:search_query) { Vacancies::Entities::SearchOptions.new }
76

87
describe '#all_with_contact' do
@@ -16,8 +15,8 @@
1615
Fabricate.create(
1716
:vacancy,
1817
published: published,
19-
archived: archived,
2018
deleted_at: deleted_at,
19+
archived_at: archived_at,
2120
remote_available: remote_available,
2221
position_type: position_type,
2322
location: location
@@ -34,7 +33,7 @@
3433
it { expect(subject.first.contact).to be_a(Contact) }
3534

3635
context 'and remote in search_query is true' do
37-
let(:search_query) { Vacancies::Entities::SearchOptions.new(query_attributes.merge(remote: true)) }
36+
let(:search_query) { Vacancies::Entities::SearchOptions.new(remote: true) }
3837

3938
it { expect(subject).to eq([]) }
4039

@@ -45,8 +44,15 @@
4544
end
4645
end
4746

47+
context 'and remote in search_query is false' do
48+
let(:search_query) { Vacancies::Entities::SearchOptions.new(remote: false) }
49+
let(:remote_available) { true }
50+
51+
it { expect(subject.count).to eq(0) }
52+
end
53+
4854
context 'and position_type in search_query is equal "other"' do
49-
let(:search_query) { Vacancies::Entities::SearchOptions.new(query_attributes.merge(position_type: 'other')) }
55+
let(:search_query) { Vacancies::Entities::SearchOptions.new(position_type: 'other') }
5056

5157
it { expect(subject).to eq([]) }
5258

@@ -58,7 +64,7 @@
5864
end
5965

6066
context 'and location in search query is not empty' do
61-
let(:search_query) { Vacancies::Entities::SearchOptions.new(query_attributes.merge(location: 'VASYUKI')) }
67+
let(:search_query) { Vacancies::Entities::SearchOptions.new(location: 'VASYUKI') }
6268

6369
it { expect(subject).to eq([]) }
6470

spec/vacancies/mappers/search_options_spec.rb

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,31 @@
33
RSpec.describe Vacancies::Mappers::SearchOptions, type: :mapper do
44
subject { described_class.new.call(search_options_hash) }
55

6-
let(:search_options_hash) { { remote: nil, position_type: 'other', location: 'New Vasyuki' } }
6+
let(:search_options_hash) { {} }
77

88
it { expect(subject).to be_a(Vacancies::Entities::SearchOptions) }
9-
it { expect(subject.position_type).to eq(search_options_hash[:position_type]) }
10-
it { expect(subject.location).to eq(search_options_hash[:location]) }
119

1210
context 'when remote is string equaled "true"' do
13-
let(:search_options_hash) { { remote: 'true', position_type: nil, location: nil } }
11+
let(:search_options_hash) { { remote: 'true', position_type: 'part_time' } }
1412

15-
it { expect(subject.remote).to be_truthy }
13+
it { expect(subject.remote).to eq true }
14+
it { expect(subject.position_type).to eq 'part_time' }
15+
it { expect(subject.location).to eq nil }
16+
end
17+
18+
context 'when remote is string equaled "false"' do
19+
let(:search_options_hash) { { remote: 'false', position_type: nil, location: 'New Vasyuki' } }
20+
21+
it { expect(subject.remote).to eq false }
22+
it { expect(subject.position_type).to eq nil }
23+
it { expect(subject.location).to eq 'New Vasyuki' }
24+
end
25+
26+
context 'when position_type is invalid' do
27+
let(:search_options_hash) { { position_type: 'anything' } }
28+
29+
it do
30+
expect { subject }.to raise_error(Dry::Struct::Error)
31+
end
1632
end
1733
end

spec/web/features/index_page_spec.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
Fabricate(:vacancy)
1010
Fabricate(:vacancy, position: 'Ruby developer')
1111
Fabricate(:vacancy, position: 'Java developer', published: false)
12+
Fabricate(:vacancy, position: 'C# developer', remote_available: true)
1213
end
1314

1415
it 'returns a index page with all vacancies' do
@@ -17,6 +18,38 @@
1718
expect(page.status_code).to eq 200
1819
expect(page.body).to include 'Senior mecha pilot'
1920
expect(page.body).to include 'Ruby developer'
21+
expect(page.body).to include 'C# developer'
22+
2023
expect(page.body).to_not include 'Java developer'
2124
end
25+
26+
context 'with remote:true query parameter' do
27+
let(:url) { '/?query=remote:true' }
28+
29+
it 'returns a index page with all vacancies' do
30+
visit(url)
31+
32+
expect(page.status_code).to eq 200
33+
expect(page.body).to include 'C# developer'
34+
35+
expect(page.body).to_not include 'Senior mecha pilot'
36+
expect(page.body).to_not include 'Ruby developer'
37+
expect(page.body).to_not include 'Java developer'
38+
end
39+
end
40+
41+
context 'with remote:false query parameter' do
42+
let(:url) { '/?query=remote:false' }
43+
44+
it 'returns a index page with all vacancies' do
45+
visit(url)
46+
47+
expect(page.status_code).to eq 200
48+
expect(page.body).to_not include 'C# developer'
49+
50+
expect(page.body).to include 'Senior mecha pilot'
51+
expect(page.body).to include 'Ruby developer'
52+
expect(page.body).to_not include 'Java developer'
53+
end
54+
end
2255
end

0 commit comments

Comments
 (0)