Skip to content

Commit

Permalink
Extract SolrHit class to wrap Solr response documents
Browse files Browse the repository at this point in the history
  • Loading branch information
cbeer committed Mar 16, 2016
1 parent 7887521 commit 29f237f
Show file tree
Hide file tree
Showing 13 changed files with 209 additions and 74 deletions.
1 change: 1 addition & 0 deletions lib/active_fedora.rb
Expand Up @@ -120,6 +120,7 @@ module ActiveFedora #:nodoc:
autoload :Serialization
autoload :SimpleDatastream
autoload :SchemaIndexingStrategy
autoload :SolrHit
autoload :SolrInstanceLoader
autoload :SolrQueryBuilder
autoload :SolrService
Expand Down
4 changes: 1 addition & 3 deletions lib/active_fedora/associations/collection_association.rb
Expand Up @@ -27,9 +27,7 @@ def ids_reader
if loaded?
load_target.map(&:id)
else
load_from_solr.map do |solr_record|
solr_record['id']
end
load_from_solr.map(&:id)
end
end

Expand Down
3 changes: 1 addition & 2 deletions lib/active_fedora/associations/contained_finder.rb
Expand Up @@ -42,8 +42,7 @@ def relation_subjects(record)
query = ActiveFedora::SolrQueryBuilder.construct_query_for_rel(
[[:has_model, proxy_class.to_class_uri], [:proxyFor, record.id]]
)
results = ActiveFedora::SolrService.query(query, fl: 'id')
results.map { |res| ::RDF::URI(ActiveFedora::Base.id_to_uri(res['id'])) }
ActiveFedora::SolrService.query(query, fl: 'id').map(&:rdf_uri)
end
end
end
21 changes: 1 addition & 20 deletions lib/active_fedora/associations/rdf.rb
Expand Up @@ -60,26 +60,7 @@ def filter_by_class(candidate_uris)
ids = candidate_uris.map { |uri| ActiveFedora::Base.uri_to_id(uri) }
results = ActiveFedora::SolrService.query(ActiveFedora::SolrQueryBuilder.construct_query_for_ids(ids), rows: 10_000)

docs = results.select do |result|
ActiveFedora::QueryResultBuilder.classes_from_solr_document(result).any? do |klass|
class_ancestors(klass).include? reflection.klass
end
end

docs.map { |doc| ::RDF::URI.new(ActiveFedora::Base.id_to_uri(doc['id'])) }
end

##
# Returns a list of all the ancestor classes up to ActiveFedora::Base including the class itself
# @param [Class] klass
# @return [Array<Class>]
# @example
# class Car < ActiveFedora::Base; end
# class SuperCar < Car; end
# class_ancestors(SuperCar)
# # => [SuperCar, Car, ActiveFedora::Base]
def class_ancestors(klass)
klass.ancestors.select { |k| k.instance_of?(Class) } - [Object, BasicObject]
results.select { |result| result.model? reflection.klass }.map(&:rdf_uri)
end
end
end
Expand Down
14 changes: 7 additions & 7 deletions lib/active_fedora/query_result_builder.rb
Expand Up @@ -4,7 +4,7 @@ def self.lazy_reify_solr_results(solr_results, opts = {})
return to_enum(:lazy_reify_solr_results, solr_results, opts) unless block_given?

solr_results.each do |hit|
yield reify_solr_result(hit, opts)
yield ActiveFedora::SolrHit.for(hit).reify(opts)
end
end

Expand All @@ -13,20 +13,20 @@ def self.reify_solr_results(solr_results, opts = {})
end

def self.reify_solr_result(hit, _opts = {})
klass = class_from_solr_document(hit)
klass.find(hit[ActiveFedora.id_field], cast: true)
Deprecation.warn(ActiveFedora::Base, 'ActiveFedora::QueryResultBuilder.reify_solr_result is deprecated and will be removed in ActiveFedora 10.0; call #reify on the SolrHit instead.')
ActiveFedora::SolrHit.for(hit).reify
end

# Returns all possible classes for the solr object
def self.classes_from_solr_document(hit, _opts = {})
ActiveFedora.model_mapper.classifier(hit).models
Deprecation.warn(ActiveFedora::Base, 'ActiveFedora::QueryResultBuilder.classes_from_solr_document is deprecated and will be removed in ActiveFedora 10.0; call #models on the SolrHit instead.')
ActiveFedora::SolrHit.for(hit).models
end

# Returns the best singular class for the solr object
def self.class_from_solr_document(hit, opts = {})
best_model_match = ActiveFedora.model_mapper.classifier(hit).best_model(opts)
ActiveFedora::Base.logger.warn "Could not find a model for #{hit['id']}, defaulting to ActiveFedora::Base" if ActiveFedora::Base.logger && best_model_match == ActiveFedora::Base
best_model_match
Deprecation.warn(ActiveFedora::Base, 'ActiveFedora::QueryResultBuilder.class_from_solr_document is deprecated and will be removed in ActiveFedora 10.0; call #model on the SolrHit instead.')
ActiveFedora::SolrHit.for(hit).model(opts)
end

HAS_MODEL_SOLR_FIELD = SolrQueryBuilder.solr_name("has_model", :symbol).freeze
Expand Down
71 changes: 71 additions & 0 deletions lib/active_fedora/solr_hit.rb
@@ -0,0 +1,71 @@
module ActiveFedora
class SolrHit < Delegator
def self.for(hit)
return hit if hit.is_a? ActiveFedora::SolrHit

SolrHit.new(hit)
end

delegate :models, to: :classifier

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)
super
@document = document
end

def id
document[ActiveFedora.id_field]
end

def rdf_uri
::RDF::URI.new(ActiveFedora::Base.id_to_uri(id))
end

def model(opts = {})
best_model_match = classifier.best_model(opts)
ActiveFedora::Base.logger.warn "Could not find a model for #{id}, defaulting to ActiveFedora::Base" if ActiveFedora::Base.logger && best_model_match == ActiveFedora::Base
best_model_match
end

def model?(model_to_check)
models.any? do |model|
model_to_check >= model
end
end

def instantiate_with_json
model.allocate.init_with_json(profile_json) do |allocated_object|
create_key = allocated_object.indexing_service.class.create_time_solr_name
modified_key = allocated_object.indexing_service.class.modified_time_solr_name
allocated_object.resource.set_value(:create_date, DateTime.parse(document[create_key])) if document[create_key]
allocated_object.resource.set_value(:modified_date, DateTime.parse(document[modified_key])) if document[modified_key]
end
end

def reify(opts = {})
model(opts).find(id, cast: true)
end

private

def classifier
ActiveFedora.model_mapper.classifier(document)
end

def profile_json
Array(document[ActiveFedora::IndexingService.profile_solr_name]).first
end
end
end
37 changes: 8 additions & 29 deletions lib/active_fedora/solr_instance_loader.rb
Expand Up @@ -17,52 +17,31 @@ def initialize(context, id, solr_doc = nil)

def object
return @object if @object
@object = allocate_object
@object = solr_doc.instantiate_with_json
@object.readonly!
@object.freeze
@object
end

private

def allocate_object
active_fedora_class.allocate.init_with_json(profile_json) do |allocated_object|
create_key = allocated_object.indexing_service.class.create_time_solr_name
modified_key = allocated_object.indexing_service.class.modified_time_solr_name
allocated_object.resource.set_value(:create_date, DateTime.parse(solr_doc[create_key])) if solr_doc[create_key]
allocated_object.resource.set_value(:modified_date, DateTime.parse(solr_doc[modified_key])) if solr_doc[modified_key]
end
end

def solr_doc
@solr_doc ||= begin
self.solr_doc = context.search_by_id(id)
end
end

def solr_doc=(solr_doc)
validate_solr_doc_and_id!(@solr_doc) unless @solr_doc.nil?
@solr_doc = solr_doc
unless solr_doc.nil?
solr_doc = ActiveFedora::SolrHit.for(solr_doc)
validate_solr_doc_and_id!(solr_doc)
@solr_doc = solr_doc
end
end

def validate_solr_doc_and_id!(document)
solr_id = document[ActiveFedora.id_field]
return if id == solr_id
raise ActiveFedora::FedoraSolrMismatchError, id, solr_id
end

def active_fedora_class
@active_fedora_class ||= ActiveFedora::QueryResultBuilder.class_from_solr_document(solr_doc)
end

def profile_json
@profile_json ||= begin
profile_json = Array(solr_doc[ActiveFedora::IndexingService.profile_solr_name]).first
unless profile_json.present?
raise ActiveFedora::ObjectNotFoundError, "Object #{id} does not contain a solrized profile"
end
profile_json
end
return if id == document.id
raise ActiveFedora::FedoraSolrMismatchError, id, document.id
end
end
end
10 changes: 6 additions & 4 deletions lib/active_fedora/solr_service.rb
Expand Up @@ -49,19 +49,19 @@ def reify_solr_results(solr_results, opts = {})
end

def reify_solr_result(hit, opts = {})
Deprecation.warn SolrService, "SolrService.reify_solr_result is deprecated. Use QueryResultBuilder.reify_solr_result instead. This will be removed in active-fedora 10.0"
Deprecation.warn SolrService, "SolrService.reify_solr_result is deprecated. Use SolrHit#reify instead. This will be removed in active-fedora 10.0"
QueryResultBuilder.reify_solr_result(hit, opts)
end

# Returns all possible classes for the solr object
def classes_from_solr_document(hit, opts = {})
Deprecation.warn SolrService, "SolrService.classes_from_solr_document is deprecated. Use QueryResultBuilder.classes_from_solr_document instead. This will be removed in active-fedora 10.0"
Deprecation.warn SolrService, "SolrService.classes_from_solr_document is deprecated. Use SolrHit#models instead. This will be removed in active-fedora 10.0"
QueryResultBuilder.classes_from_solr_document(hit, opts)
end

# Returns the best singular class for the solr object
def class_from_solr_document(hit, opts = {})
Deprecation.warn SolrService, "SolrService.class_from_solr_document is deprecated. Use QueryResultBuilder.class_from_solr_document instead. This will be removed in active-fedora 10.0"
Deprecation.warn SolrService, "SolrService.class_from_solr_document is deprecated. Use SolrHit#model instead. This will be removed in active-fedora 10.0"
QueryResultBuilder.class_from_solr_document(hit, opts)
end

Expand Down Expand Up @@ -110,7 +110,9 @@ def query(query, args = {})
args = args.merge(q: query, qt: 'standard')
result = SolrService.instance.conn.get(select_path, params: args)
return result if raw
result['response']['docs']
result['response']['docs'].map do |doc|
ActiveFedora::SolrHit.new(doc)
end
end

def delete(id)
Expand Down
4 changes: 2 additions & 2 deletions spec/integration/associations_spec.rb
Expand Up @@ -234,8 +234,8 @@ class Publisher < ActiveFedora::Base

solr_resp = @library.books(response_format: :solr)
expect(solr_resp.size).to eq 2
expect(solr_resp[0]['id']).to eq @book.id
expect(solr_resp[1]['id']).to eq @book2.id
expect(solr_resp[0].id).to eq @book.id
expect(solr_resp[1].id).to eq @book2.id
end
end

Expand Down
2 changes: 1 addition & 1 deletion spec/integration/has_many_associations_spec.rb
Expand Up @@ -61,7 +61,7 @@ class Book < ActiveFedora::Base
expect(library.books).to be_loaded
end
it "loads from solr" do
expect(library.books.load_from_solr.map { |r| r["id"] }).to eq([book.id])
expect(library.books.load_from_solr.map(&:id)).to eq([book.id])
end
it "loads from solr with options" do
expect(library.books.load_from_solr(rows: 0).size).to eq(0)
Expand Down
52 changes: 52 additions & 0 deletions spec/integration/solr_hit_spec.rb
@@ -0,0 +1,52 @@
require 'spec_helper'

describe ActiveFedora::SolrHit do
before do
class Foo < ActiveFedora::Base
has_metadata 'descMetadata', type: ActiveFedora::SimpleDatastream do |m|
m.field "foo", :text
m.field "bar", :text
end
Deprecation.silence(ActiveFedora::Attributes) do
has_attributes :foo, datastream: 'descMetadata', multiple: true
has_attributes :bar, datastream: 'descMetadata', multiple: false
end
property :title, predicate: ::RDF::Vocab::DC.title, multiple: false
end
end

let(:another) { Foo.create }

let!(:obj) { Foo.create!(
id: 'test-123',
foo: ["baz"],
bar: 'quix',
title: 'My Title'
) }

let(:profile) { { "foo" => ["baz"], "bar" => "quix", "title" => "My Title" }.to_json }
let(:doc) { { 'id' => 'test-123', 'has_model_ssim' => ['Foo'], 'object_profile_ssm' => profile } }
let(:solr_hit) { described_class.new(doc) }

after do
Object.send(:remove_const, :Foo)
end

describe "#reify" do
subject { solr_hit.reify }

it "finds the document in solr" do
expect(subject).to be_instance_of Foo
expect(subject.title).to eq 'My Title'
end
end

describe "#instantiate_with_json" do
subject { solr_hit.instantiate_with_json }

it "finds the document in solr" do
expect(subject).to be_instance_of Foo
expect(subject.title).to eq 'My Title'
end
end
end
6 changes: 0 additions & 6 deletions spec/unit/query_result_builder_spec.rb
Expand Up @@ -12,12 +12,6 @@ def self.connection_for_id(_id)
{ "id" => "my:_ID2_", ActiveFedora::SolrQueryBuilder.solr_name("has_model", :symbol) => ["AudioRecord"] },
{ "id" => "my:_ID3_", ActiveFedora::SolrQueryBuilder.solr_name("has_model", :symbol) => ["AudioRecord"] }]
end
describe ".reify_solr_result" do
it "uses .find to instantiate objects" do
expect(AudioRecord).to receive(:find).with("my:_ID1_", cast: true)
described_class.reify_solr_result(@sample_solr_hits.first)
end
end
describe ".reify_solr_results" do
it "uses AudioRecord.find to instantiate objects" do
expect(AudioRecord).to receive(:find).with("my:_ID1_", cast: true)
Expand Down

0 comments on commit 29f237f

Please sign in to comment.