From da8c2cb616d0304d83d3c39b3756c16077fb6a4b Mon Sep 17 00:00:00 2001 From: Abhinay Kumar Date: Mon, 2 Nov 2020 22:52:59 +0530 Subject: [PATCH] Search entity (API) (#12) * search entity * refactor search query for properties * refactor field search query * Update search.rb Co-authored-by: Pradeep Kumar --- api/app/graphql/resolvers/fields_resolver.rb | 23 ++++++++++ api/app/graphql/resolvers/records_resolver.rb | 22 ++++++++++ api/app/models/search.rb | 34 +++++++++++++++ api/spec/factories/properties.rb | 8 ++++ api/spec/models/search_spec.rb | 42 +++++++++++++++++++ 5 files changed, 129 insertions(+) create mode 100644 api/app/models/search.rb create mode 100644 api/spec/factories/properties.rb create mode 100644 api/spec/models/search_spec.rb diff --git a/api/app/graphql/resolvers/fields_resolver.rb b/api/app/graphql/resolvers/fields_resolver.rb index 42ca086..132a6ca 100644 --- a/api/app/graphql/resolvers/fields_resolver.rb +++ b/api/app/graphql/resolvers/fields_resolver.rb @@ -1,10 +1,33 @@ class FieldsResolver < ApplicationResolver parameter :entityId, types.ID + parameter :projectId, types.ID + parameter :filter, types.String def resolve + # For search + if search_params_is_present? + project = Project.find(params[:project_id]) + authorize! project, :view? + + return filtered_fields + end + entity = resolved_object || Entity.find(params[:entity_id]) authorize! entity, :view? entity.nested_fields end + + def search_params_is_present? + params[:filter].present? && + params[:project_id].present? + end + + def filtered_fields + Search.scope( + query: params[:filter], + scope: 'field', + project_id: params[:project_id] + ) + end end diff --git a/api/app/graphql/resolvers/records_resolver.rb b/api/app/graphql/resolvers/records_resolver.rb index ccab487..f4d4ca6 100644 --- a/api/app/graphql/resolvers/records_resolver.rb +++ b/api/app/graphql/resolvers/records_resolver.rb @@ -1,10 +1,32 @@ class RecordsResolver < ApplicationResolver parameter :entityId, types.ID + parameter :filter, types.String + parameter :projectId, types.ID def resolve + if search_params_is_present? + project = Project.find(params[:project_id]) + authorize! project, :view? + + return filtered_records + end + entity = resolved_object || Entity.find(params[:entity_id]) authorize! entity, :view? entity.records end + + def search_params_is_present? + params[:filter].present? && + params[:project_id].present? + end + + def filtered_records + Search.scope( + query: params[:filter], + scope: 'record', + project_id: params[:project_id] + ) + end end diff --git a/api/app/models/search.rb b/api/app/models/search.rb new file mode 100644 index 0000000..7e106d3 --- /dev/null +++ b/api/app/models/search.rb @@ -0,0 +1,34 @@ +class Search + ALLOWED_SCOPE = %w[field record].freeze + + class << self + def scope(query:, scope:, project_id:) + return unless ALLOWED_SCOPE.include?(scope) + + public_send("#{scope}_search", query, project_id) + end + + def field_search(query, project_id) + fields = Field.includes( + entity: :project, + referenced_entity: :project + ).where( + projects: { + id: project_id + } + ) + + fields.where('fields.name ILIKE?', "%#{query}%") + .or(fields.where('fields.label ILIKE?', "%#{query}%")) + end + + def record_search(query, project_id) + record_ids = Property.includes(record: { entity: :project }, linked_record: {entity: :project}) + .where(projects: { id: project_id }) + .where('value ILIKE ?', "%#{query}%") + .pluck(:record_id).uniq + + Record.where(id: record_ids) + end + end +end diff --git a/api/spec/factories/properties.rb b/api/spec/factories/properties.rb new file mode 100644 index 0000000..ba87014 --- /dev/null +++ b/api/spec/factories/properties.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :property do + record + field + + sequence(:value) { |n| "Property Name #{n}" } + end +end diff --git a/api/spec/models/search_spec.rb b/api/spec/models/search_spec.rb new file mode 100644 index 0000000..b90ff29 --- /dev/null +++ b/api/spec/models/search_spec.rb @@ -0,0 +1,42 @@ +require 'rails_helper' + +RSpec.describe Search do + describe '#scope' do + let(:project) { create(:project) } + let(:entity) { create(:entity, project: project) } + let(:record) { create(:record, entity: entity) } + + it 'should raise error when scope is not present as argument' do + expect { Search.scope(query: 'Test') }.to raise_error(ArgumentError) + end + + it 'should return nil, for scope which are not in in ALLOWED_SCOPE' do + expect(Search.scope(query: 'Test', scope: 'Test', project_id: project.id)).to be(nil) + end + + context 'when scope is entity' do + context 'when matching entities are found' do + it 'should return object of type Entity' do + 5.times { |i| create(:field, name: "Test-#{i}", entity: entity) } + + res = Search.scope(query: 'test', scope: 'field', project_id: project.id) + + expect(res.klass).to be(Field) + end + end + end + + context 'when scope is record' do + context 'when matching records are found' do + it 'should return object of type Record' do + field = create(:field, name: 'Test', entity: entity) + 5.times { |i| create(:property, field: field, record: record, value: "Test-#{i}") } + + res = Search.scope(query: 'test', scope: 'record', project_id: project.id) + + expect(res.klass).to be(Record) + end + end + end + end +end