diff --git a/app/models/solr_hit.rb b/app/models/solr_hit.rb new file mode 100644 index 0000000000..a3591b53b0 --- /dev/null +++ b/app/models/solr_hit.rb @@ -0,0 +1,29 @@ +class SolrHit < Delegator + def self.for(hit) + return hit if hit.is_a? ActiveFedora::SolrHit + + SolrHit.new(hit) + end + + def __getobj__ + @document # return object we are delegating to, required + end + + alias static_config __getobj__ + + def __setobj__(obj) + @document = obj + end + + attr_reader :document + + def initialize(document) + document = document.with_indifferent_access + super + @document = document + end + + def id + document[Hyrax.config.id_field] + end +end diff --git a/app/services/hyrax/solr_service.rb b/app/services/hyrax/solr_service.rb index ce747e40f8..72336d8332 100644 --- a/app/services/hyrax/solr_service.rb +++ b/app/services/hyrax/solr_service.rb @@ -7,7 +7,7 @@ module Hyrax class SolrService extend Forwardable - def_delegators :@old_service, :add, :commit, :count, :delete, :get, :instance, :post, :query + def_delegators :@old_service, :add, :commit, :count, :delete, :instance def_delegators :instance, :conn def initialize @@ -25,5 +25,44 @@ def select_path delegate :add, :commit, :count, :delete, :get, :instance, :post, :query, to: :new end + + # Wraps rsolr get + # @return [Hash] the hash straight form rsolr + def get(query, args = {}) + args = args.merge(q: query, qt: 'standard') + SolrService.instance.conn.get(Hyrax.config.solr_select_path, params: args) + end + + # Wraps rsolr post + # @return [Hash] the hash straight form rsolr + def post(query, args = {}) + args = args.merge(q: query, qt: 'standard') + SolrService.instance.conn.post(Hyrax.config.solr_select_path, data: args) + end + + # Wraps get by default + # @return [Array] the response docs wrapped in SolrHit objects + def query(query, args = {}) + Rails.logger.warn rows_warning unless args.key?(:rows) + method = args.delete(:method) || :get + + result = case method + when :get + get(query, args) + when :post + post(query, args) + else + raise "Unsupported HTTP method for querying SolrService (#{method.inspect})" + end + result['response']['docs'].map do |doc| + ::SolrHit.new(doc) + end + end + + private + + def rows_warning + "Calling Hyrax::SolrService.get without passing an explicit value for ':rows' is not recommended. You will end up with Solr's default (usually set to 10)\nCalled by #{caller[0]}" + end end end diff --git a/spec/models/solr_hit_spec.rb b/spec/models/solr_hit_spec.rb new file mode 100644 index 0000000000..51d7674bd0 --- /dev/null +++ b/spec/models/solr_hit_spec.rb @@ -0,0 +1,9 @@ +RSpec.describe SolrHit do + subject(:solr_hit) { described_class.new "id" => "my:_ID1_" } + + describe "#id" do + it "extracts the id from the solr hit" do + expect(solr_hit.id).to eq "my:_ID1_" + end + end +end diff --git a/spec/services/hyrax/solr_service_spec.rb b/spec/services/hyrax/solr_service_spec.rb index 7baab4c353..864c21138c 100644 --- a/spec/services/hyrax/solr_service_spec.rb +++ b/spec/services/hyrax/solr_service_spec.rb @@ -1,7 +1,67 @@ RSpec.describe Hyrax::SolrService do + let(:mock_conn) { instance_double(RSolr::Client) } + describe '.select_path' do it 'raises NotImplementedError' do expect { described_class.select_path }.to raise_error NotImplementedError end end + + describe "#get" do + it "calls solr" do + stub_result = double("Result") + expect(mock_conn).to receive(:get).with('select', params: { q: 'querytext', qt: 'standard' }).and_return(stub_result) + allow(described_class).to receive(:instance).and_return(double("instance", conn: mock_conn)) + expect(described_class.get('querytext')).to eq stub_result + end + end + + describe "#post" do + it "calls solr" do + stub_result = double("Result") + expect(mock_conn).to receive(:post).with('select', data: { q: 'querytext', qt: 'standard' }).and_return(stub_result) + allow(described_class).to receive(:instance).and_return(double("instance", conn: mock_conn)) + expect(described_class.post('querytext')).to eq stub_result + end + end + + describe "#query" do + let(:doc) { { 'id' => 'x' } } + let(:docs) { [doc] } + let(:stub_result) { { 'response' => { 'docs' => docs } } } + + before do + allow(described_class).to receive(:instance).and_return(double("instance", conn: mock_conn)) + end + + it "defaults to HTTP GET method" do + expect(mock_conn).to receive(:get).with('select', params: { rows: 2, q: 'querytext', qt: 'standard' }).and_return(stub_result) + described_class.query('querytext', rows: 2) + end + + it "allows callers to specify HTTP POST method" do + expect(mock_conn).to receive(:post).with('select', data: { rows: 2, q: 'querytext', qt: 'standard' }).and_return(stub_result) + described_class.query('querytext', rows: 2, method: :post) + end + + it "raises if method not GET or POST" do + expect(mock_conn).not_to receive(:head).with('select', data: { rows: 2, q: 'querytext', qt: 'standard' }) + expect do + described_class.query('querytext', rows: 2, method: :head) + end.to raise_error(RuntimeError, "Unsupported HTTP method for querying SolrService (:head)") + end + + it "wraps the solr response documents in Solr hits" do + expect(mock_conn).to receive(:get).with('select', params: { rows: 2, q: 'querytext', qt: 'standard' }).and_return(stub_result) + result = described_class.query('querytext', rows: 2) + expect(result.size).to eq 1 + expect(result.first.id).to eq 'x' + end + + it "warns about not passing rows" do + allow(mock_conn).to receive(:get).and_return(stub_result) + expect(Rails.logger).to receive(:warn).with(/^Calling Hyrax::SolrService\.get without passing an explicit value for ':rows' is not recommended/) + described_class.query('querytext') + end + end end