diff --git a/Gemfile b/Gemfile index c537515aa..d5f101996 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -source "http://rubygems.org" +source "https://rubygems.org" # Bundler will rely on active-fedora.gemspec for dependency information. diff --git a/lib/active_fedora.rb b/lib/active_fedora.rb index 7f815d37e..0e37caa88 100644 --- a/lib/active_fedora.rb +++ b/lib/active_fedora.rb @@ -22,6 +22,7 @@ class ConfigurationError < RuntimeError; end # :nodoc: class AssociationTypeMismatch < RuntimeError; end # :nodoc: class UnregisteredPredicateError < RuntimeError; end # :nodoc: class RecordNotSaved < RuntimeError; end # :nodoc: + class IllegalOperation < RuntimeError; end # :nodoc: eager_autoload do @@ -35,6 +36,7 @@ class RecordNotSaved < RuntimeError; end # :nodoc: autoload :Config autoload :Core autoload :Datastream + autoload :DatastreamAttribute autoload :DatastreamHash autoload :Datastreams autoload :DigitalObject diff --git a/lib/active_fedora/attributes.rb b/lib/active_fedora/attributes.rb index 9dc07a17c..644fe3dfe 100644 --- a/lib/active_fedora/attributes.rb +++ b/lib/active_fedora/attributes.rb @@ -44,9 +44,9 @@ def array_reader(field, *args) end raise UnknownAttributeError, "#{self.class} does not have an attribute `#{field}'" unless self.class.defined_attributes.key?(field) if args.present? - instance_exec(*args, &self.class.defined_attributes[field][:reader]) + instance_exec(*args, &self.class.defined_attributes[field].reader) else - instance_exec &self.class.defined_attributes[field][:reader] + instance_exec &self.class.defined_attributes[field].reader end end @@ -57,7 +57,7 @@ def array_setter(field, args) return association.id_writer(args) if association end raise UnknownAttributeError, "#{self.class} does not have an attribute `#{field}'" unless self.class.defined_attributes.key?(field) - instance_exec(args, &self.class.defined_attributes[field][:setter]) + instance_exec(args, &self.class.defined_attributes[field].writer) end # @return [Boolean] true if there is an reader method and it returns a @@ -108,29 +108,18 @@ def unique?(field) # @param [Symbol] field the field to query # @return [Boolean] def multiple?(field) - defined_attributes[field][:multiple] + defined_attributes[field].multiple end + private + def find_or_create_defined_attribute(field, dsid, args) + self.defined_attributes[field] ||= DatastreamAttribute.new(field, dsid, datastream_class_for_name(dsid), args) + end - private - def create_attribute_reader(field, dsid, args) - self.defined_attributes[field] ||= {} - self.defined_attributes[field][:reader] = lambda do |*opts| - ds = datastream_for_attribute(dsid) - if ds.kind_of?(ActiveFedora::RDFDatastream) - ds.send(field) - else - terminology = args[:at] || [field] - if terminology.length == 1 && opts.present? - ds.send(terminology.first, *opts) - else - ds.send(:term_values, *terminology) - end - end - end - self.defined_attributes[field][:multiple] = args[:multiple].nil? ? false : args[:multiple] + def create_attribute_reader(field, dsid, args) + find_or_create_defined_attribute(field, dsid, args) define_method field do |*opts| val = array_reader(field, *opts) @@ -138,19 +127,8 @@ def create_attribute_reader(field, dsid, args) end end - def create_attribute_setter(field, dsid, args) - self.defined_attributes[field] ||= {} - self.defined_attributes[field][:setter] = lambda do |v| - ds = datastream_for_attribute(dsid) - mark_as_changed(field) if value_has_changed?(field, v) - if ds.kind_of?(ActiveFedora::RDFDatastream) - ds.send("#{field}=", v) - else - terminology = args[:at] || [field] - ds.send(:update_indexed_attributes, {terminology => v}) - end - end + find_or_create_defined_attribute(field, dsid, args) define_method "#{field}=".to_sym do |v| self[field]=v end diff --git a/lib/active_fedora/datastream_attribute.rb b/lib/active_fedora/datastream_attribute.rb new file mode 100644 index 000000000..c2644d2bc --- /dev/null +++ b/lib/active_fedora/datastream_attribute.rb @@ -0,0 +1,61 @@ +module ActiveFedora + # Represents the mapping between a model attribute and a field in a datastream + class DatastreamAttribute + + attr_accessor :dsid, :field, :klass, :at, :reader, :writer, :multiple + + def initialize(field, dsid, klass, args={}) + self.field = field + self.dsid = dsid + self.klass = klass + self.multiple = args[:multiple].nil? ? false : args[:multiple] + self.at = args[:at] + + initialize_reader! + initialize_writer! + end + + # Gives the primary solr name for a column. If there is more than one indexer on the field definition, it gives the first + def primary_solr_name + if klass.respond_to?(:primary_solr_name) + klass.primary_solr_name(dsid, field) + else + raise IllegalOperation, "the class '#{klass}' doesn't respond to 'primary_solr_name'" + end + end + + private + + def initialize_writer! + this = self + self.writer = lambda do |v| + ds = datastream_for_attribute(this.dsid) + mark_as_changed(this.field) if value_has_changed?(this.field, v) + if ds.kind_of?(ActiveFedora::RDFDatastream) + ds.send("#{this.field}=", v) + else + terminology = this.at || [this.field] + ds.send(:update_indexed_attributes, {terminology => v}) + end + end + end + + def initialize_reader! + this = self + self.reader = lambda do |*opts| + ds = datastream_for_attribute(this.dsid) + if ds.kind_of?(ActiveFedora::RDFDatastream) + ds.send(this.field) + else + terminology = this.at || [this.field] + if terminology.length == 1 && opts.present? + ds.send(terminology.first, *opts) + else + ds.send(:term_values, *terminology) + end + end + end + end + + end +end diff --git a/lib/active_fedora/rdf_datastream.rb b/lib/active_fedora/rdf_datastream.rb index b68b238f0..3c3437ffc 100644 --- a/lib/active_fedora/rdf_datastream.rb +++ b/lib/active_fedora/rdf_datastream.rb @@ -1,8 +1,6 @@ module ActiveFedora class RDFDatastream < Datastream - include Solrizer::Common - before_save do if content.blank? logger.warn "Cowardly refusing to save a datastream with empty content: #{self.inspect}" @@ -31,9 +29,18 @@ def metadata? end def prefix(name) + self.class.prefix(dsid, name) + end + + def self.prefix(dsid, name) "#{dsid.underscore}__#{name}".to_sym end + # Gives the primary solr name for a column. If there is more than one indexer on the field definition, it gives the first + def self.primary_solr_name(dsid, field) + ActiveFedora::SolrService.solr_name(prefix(dsid, field), config_for_term_or_uri(field).behaviors.first) + end + # Overriding so that one can call ds.content on an unsaved datastream and they will see the serialized format def content serialize @@ -54,11 +61,9 @@ def content_changed? def to_solr(solr_doc = Hash.new) # :nodoc: fields.each do |field_key, field_info| values = get_values(rdf_subject, field_key) - if values - Array(values).each do |val| - val = val.to_s if val.kind_of? RDF::URI - self.class.create_and_insert_terms(prefix(field_key), val, field_info[:behaviors], solr_doc) - end + Array(values).each do |val| + val = val.to_s if val.kind_of? RDF::URI + Solrizer.insert_field(solr_doc, prefix(field_key), val, *field_info[:behaviors]) end end solr_doc diff --git a/lib/active_fedora/rdf_node.rb b/lib/active_fedora/rdf_node.rb index 44cca1712..99227916b 100644 --- a/lib/active_fedora/rdf_node.rb +++ b/lib/active_fedora/rdf_node.rb @@ -161,13 +161,9 @@ def insert_child(predicate, node) end - def config_for_term_or_uri(term) - case term - when RDF::URI - self.class.config.each { |k, v| return v if v.predicate == term} - else - self.class.config[term.to_sym] - end + # In Rails 4 you can do "delegate :config_for_term_or_uri, :class", but in rails 3 it breaks. + def config_for_term_or_uri(val) + self.class.config_for_term_or_uri(val) end # @param [Symbol, RDF::URI] term predicate the predicate to insert into the graph @@ -300,6 +296,15 @@ def rdf_type(uri_or_string=nil) @rdf_type end + def config_for_term_or_uri(term) + case term + when RDF::URI + config.each { |k, v| return v if v.predicate == term} + else + config[term.to_sym] + end + end + def config_for_predicate(predicate) config.each do |term, value| return term, value if value.predicate == predicate diff --git a/spec/integration/field_to_solr_name_spec.rb b/spec/integration/field_to_solr_name_spec.rb new file mode 100644 index 000000000..1bede2cc2 --- /dev/null +++ b/spec/integration/field_to_solr_name_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe "An object with RDF backed attributes" do + + before do + class TestOne < ActiveFedora::Base + class MyMetadata < ActiveFedora::NtriplesRDFDatastream + map_predicates do |map| + map.title(in: RDF::DC) do |index| + index.as :stored_searchable + end + end + end + has_metadata 'descMetadata', type: MyMetadata + has_attributes :title, datastream: 'descMetadata' + end + end + + after do + Object.send(:remove_const, :TestOne) + end + + it "should be able to grab the solr name" do + expect(TestOne.defined_attributes[:title].primary_solr_name).to eq 'desc_metadata__title_tesim' + end +end diff --git a/spec/unit/ntriples_datastream_spec.rb b/spec/unit/ntriples_datastream_spec.rb index 3f7e74f5e..75b13bf13 100644 --- a/spec/unit/ntriples_datastream_spec.rb +++ b/spec/unit/ntriples_datastream_spec.rb @@ -178,6 +178,12 @@ class MyDatastream < ActiveFedora::NtriplesRDFDatastream @subject.should respond_to(:to_solr) @subject.to_solr.should be_kind_of(Hash) end + + it "should have a solr_name method" do + expect(MyDatastream.primary_solr_name('descMetadata', :based_near)).to eq 'desc_metadata__based_near_sim' + expect(MyDatastream.primary_solr_name('props', :title)).to eq 'props__title_tesim' + end + it "should optionally allow you to provide the Solr::Document to add fields to and return that document when done" do doc = Hash.new @subject.to_solr(doc).should == doc