active-fedora 1.1.13 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,11 +42,18 @@ module ActiveFedora
42
42
  # ds.update_attributes({:myfield=>{"0"=>"a","1"=>"b"},:myotherfield=>{"-1"=>"c"}})
43
43
  #
44
44
  def update_indexed_attributes(params={}, opts={})
45
+
46
+ ##FIX this bug, it should delete it from a copy of params in case passed to another datastream for update since this will modify params
47
+ ##for subsequent calls if updating more than one datastream in a single update_indexed_attributes call
48
+ current_params = params.clone
45
49
  # remove any fields from params that this datastream doesn't recognize
46
- params.delete_if {|field_name,new_values| !self.fields.include?(field_name.to_sym) }
50
+ current_params.delete_if {|field_name,new_values| !self.fields.include?(field_name.to_sym) }
47
51
 
48
- result = params.dup
49
- params.each do |field_name,new_values|
52
+ result = current_params.dup
53
+ current_params.each do |field_name,new_values|
54
+ ##FIX this bug, it should delete it from a copy of params in case passed to another datastream for update
55
+ #if field does not exist just skip it
56
+ next if !self.fields.include?(field_name.to_sym)
50
57
  field_accessor_method = "#{field_name}_values"
51
58
 
52
59
  if new_values.kind_of?(Hash)
@@ -1,3 +1,5 @@
1
+ require 'solrizer/field_name_mapper'
2
+
1
3
  #this class represents a MetadataDatastream, a special case of ActiveFedora::Datastream
2
4
  module ActiveFedora::MetadataDatastreamHelper
3
5
 
@@ -16,7 +18,7 @@ module ActiveFedora::MetadataDatastreamHelper
16
18
 
17
19
  def self.included(klass)
18
20
  klass.extend(ClassMethods)
19
- klass.send(:include, ActiveFedora::SolrMapper)
21
+ klass.send(:include, Solrizer::FieldNameMapper)
20
22
  end
21
23
 
22
24
  #constructor, calls up to ActiveFedora::Datastream's constructor
@@ -38,7 +40,7 @@ module ActiveFedora::MetadataDatastreamHelper
38
40
  def to_solr(solr_doc = Solr::Document.new) # :nodoc:
39
41
  fields.each do |field_key, field_info|
40
42
  if field_info.has_key?(:values) && !field_info[:values].nil?
41
- field_symbol = generate_solr_symbol(field_key, field_info[:type])
43
+ field_symbol = solr_name(field_key, field_info[:type])
42
44
  field_info[:values].each do |val|
43
45
  solr_doc << Solr::Field.new(field_symbol => val)
44
46
  end
@@ -48,6 +50,20 @@ module ActiveFedora::MetadataDatastreamHelper
48
50
  return solr_doc
49
51
  end
50
52
 
53
+ def from_solr(solr_doc)
54
+ fields.each do |field_key, field_info|
55
+ field_symbol = Solrizer::FieldNameMapper.solr_name(field_key, field_info[:type])
56
+ value = (solr_doc[field_symbol].nil? ? solr_doc[field_symbol.to_s]: solr_doc[field_symbol])
57
+ unless value.nil?
58
+ if value.is_a? Array
59
+ update_attributes({field_key=>value})
60
+ else
61
+ update_indexed_attributes({field_key=>{0=>value}})
62
+ end
63
+ end
64
+ end
65
+ end
66
+
51
67
  def to_xml(xml = Nokogiri::XML::Document.parse("<fields />")) #:nodoc:
52
68
  if xml.instance_of?(Nokogiri::XML::Builder)
53
69
  xml_insertion_point = xml.doc.root
@@ -71,10 +87,10 @@ module ActiveFedora::MetadataDatastreamHelper
71
87
  end
72
88
 
73
89
 
74
- protected
75
-
76
- def generate_solr_symbol(field_name, field_type) # :nodoc:
77
- solr_name(field_name, field_type)
78
- end
90
+ # protected
91
+ #
92
+ # def generate_solr_symbol(field_name, field_type) # :nodoc:
93
+ # solr_name(field_name, field_type)
94
+ # end
79
95
 
80
96
  end
@@ -56,7 +56,7 @@ module ActiveFedora
56
56
  def find(args)
57
57
  if args == :all
58
58
  escaped_class_name = self.name.gsub(/(:)/, '\\:')
59
- q = "#{SolrMapper.solr_name(:active_fedora_model, :symbol)}:#{escaped_class_name}"
59
+ q = "#{Solrizer::FieldNameMapper.solr_name(:active_fedora_model, :symbol)}:#{escaped_class_name}"
60
60
  elsif args.class == String
61
61
  escaped_id = args.gsub(/(:)/, '\\:')
62
62
  q = "#{SOLR_DOCUMENT_ID}:#{escaped_id}"
@@ -89,13 +89,115 @@ module ActiveFedora
89
89
  def find_by_solr(query, args={})
90
90
  if query == :all
91
91
  escaped_class_name = self.name.gsub(/(:)/, '\\:')
92
- SolrService.instance.conn.query("#{SolrMapper.solr_name(:active_fedora_model, :symbol)}:#{escaped_class_name}", args)
92
+ SolrService.instance.conn.query("#{Solrizer::FieldNameMapper.solr_name(:active_fedora_model, :symbol)}:#{escaped_class_name}", args)
93
93
  elsif query.class == String
94
94
  escaped_id = query.gsub(/(:)/, '\\:')
95
95
  SolrService.instance.conn.query("#{SOLR_DOCUMENT_ID}:#{escaped_id}", args)
96
96
  end
97
97
  end
98
98
 
99
+ # Find all ActiveFedora objects for this model that match arguments
100
+ # passed in by querying Solr. Like find_by_solr this returns a solr result.
101
+ #
102
+ # query_fields a hash of object field names and values to filter on
103
+ # opts specifies options for the solr query
104
+ #
105
+ # options may include:
106
+ #
107
+ # :sort => array of hash with one hash per sort by field... defaults to [{system_create=>:descending}]
108
+ # :default_field, :rows, :filter_queries, :debug_query,
109
+ # :explain_other, :facets, :highlighting, :mlt,
110
+ # :operator => :or / :and
111
+ # :start => defaults to 0
112
+ # :field_list => array, defaults to ["*", "score"]
113
+ #
114
+ def find_by_fields_by_solr(query_fields,opts={})
115
+ #create solr_args from fields passed in, needs to be comma separated list of form field1=value1,field2=value2,...
116
+ escaped_class_name = self.name.gsub(/(:)/, '\\:')
117
+ query = "#{Solrizer::FieldNameMapper.solr_name(:active_fedora_model, :symbol)}:#{escaped_class_name}"
118
+
119
+ query_fields.each_pair do |key,value|
120
+ unless value.nil?
121
+ solr_key = key
122
+ #convert to symbol if need be
123
+ key = key.to_sym if !class_fields.has_key?(key)&&class_fields.has_key?(key.to_sym)
124
+ #do necessary mapping with suffix in most cases, otherwise ignore as a solr field key that activefedora does not know about
125
+ if class_fields.has_key?(key) && class_fields[key].has_key?(:type)
126
+ type = class_fields[key][:type]
127
+ type = :string unless type.kind_of?(Symbol)
128
+ solr_key = Solrizer::FieldNameMapper.solr_name(key,type)
129
+ end
130
+
131
+ escaped_value = value.gsub(/(:)/, '\\:')
132
+ #escaped_value = escaped_value.gsub(/ /, '\\ ')
133
+ key = SOLR_DOCUMENT_ID if (key === :id || key === :pid)
134
+ query = key.to_s.eql?(SOLR_DOCUMENT_ID) ? "#{query} AND #{key}:#{escaped_value}" : "#{query} AND #{solr_key}:#{escaped_value}"
135
+ end
136
+ end
137
+
138
+ query_opts = {}
139
+ opts.each do |key,value|
140
+ key = key.to_sym
141
+ query_opts[key] = value
142
+ end
143
+
144
+ #set default sort to created date ascending
145
+ unless query_opts.include?(:sort)
146
+ query_opts.merge!({:sort=>[Solrizer::FieldNameMapper.solr_name(:system_create,:date)=>:ascending]})
147
+ else
148
+ #need to convert to solr names for all fields
149
+ sort_array =[]
150
+
151
+ opts[:sort].collect do |sort|
152
+ sort_direction = :ascending
153
+ if sort.respond_to?(:keys)
154
+ key = sort.keys[0]
155
+ sort_direction = sort[key]
156
+ sort_direction =~ /^desc/ ? sort_direction = :descending : :ascending
157
+ else
158
+ key = sort.to_s
159
+ end
160
+ field_name = key
161
+
162
+ if key.to_s =~ /^system_create/
163
+ field_name = :system_create_date
164
+ key = :system_create
165
+ elsif key.to_s =~ /^system_mod/
166
+ field_name = :system_modified_date
167
+ key = :system_modified
168
+ end
169
+
170
+ solr_name = field_name
171
+ if class_fields.include?(field_name.to_sym)
172
+ solr_name = Solrizer::FieldNameMapper.solr_name(key,class_fields[field_name.to_sym][:type])
173
+ end
174
+ sort_array.push({solr_name=>sort_direction})
175
+ end
176
+
177
+ query_opts[:sort] = sort_array
178
+ end
179
+
180
+ puts "Querying solr for #{self.name} objects with query: '#{query}'"
181
+ results = ActiveFedora::SolrService.instance.conn.query(query,query_opts)
182
+ #objects = []
183
+ # results.hits.each do |hit|
184
+ # puts "get object for #{hit[SOLR_DOCUMENT_ID]}"
185
+ # obj = Fedora::Repository.instance.find_model(hit[SOLR_DOCUMENT_ID], self)
186
+ # obj.inner_object.new_object = false
187
+ # objects.push(obj)
188
+ #end
189
+ #objects
190
+ #ActiveFedora::SolrService.reify_solr_results(results)
191
+ end
192
+
193
+ def class_fields
194
+ #create dummy object that is empty by passing in fake pid
195
+ object = self.new({:pid=>'FAKE'})
196
+ fields = object.fields
197
+ #reset id to nothing
198
+ fields[:id][:values] = []
199
+ return fields
200
+ end
99
201
 
100
202
  #wrapper around instance_variable_set, sets @name to value
101
203
  def attribute_set(name, value)
@@ -1,10 +1,14 @@
1
1
  require "nokogiri"
2
2
  require "om"
3
+ require "solrizer/xml"
4
+
3
5
  #this class represents a MetadataDatastream, a special case of ActiveFedora::Datastream
4
6
  class ActiveFedora::NokogiriDatastream < ActiveFedora::Datastream
5
7
 
6
8
  include ActiveFedora::MetadataDatastreamHelper
7
- include OM::XML
9
+ include OM::XML::Document
10
+ include Solrizer::XML::TerminologyBasedSolrizer # this adds support for calling .to_solr
11
+
8
12
  # extend(OM::XML::Container::ClassMethods)
9
13
 
10
14
  attr_accessor :ng_xml
@@ -75,15 +79,9 @@ class ActiveFedora::NokogiriDatastream < ActiveFedora::Datastream
75
79
  return xml.to_xml {|config| config.no_declaration}
76
80
  end
77
81
 
78
- def to_solr(solr_doc = Solr::Document.new) # :nodoc:
79
-
80
- unless self.class.accessors.nil?
81
- self.class.accessors.each_pair do |accessor_name,accessor_info|
82
- solrize_accessor(accessor_name, accessor_info, :solr_doc=>solr_doc)
83
- end
84
- end
85
-
86
- return solr_doc
82
+ #overriding this method just so metadatahelper method does not get called
83
+ def from_solr(solr_doc)
84
+ #do nothing for now
87
85
  end
88
86
 
89
87
  def solrize_accessor(accessor_name, accessor_info, opts={})
@@ -116,36 +114,43 @@ class ActiveFedora::NokogiriDatastream < ActiveFedora::Datastream
116
114
 
117
115
  def solrize_node(node, accessor_pointer, solr_doc = Solr::Document.new)
118
116
  generic_field_name_base = self.class.accessor_generic_name(*accessor_pointer)
119
- generic_field_name = generate_solr_symbol(generic_field_name_base, :text)
117
+ generic_field_name = Solrizer::FieldNameMapper.solr_name(generic_field_name_base, :text)
120
118
 
121
119
  solr_doc << Solr::Field.new(generic_field_name => node.text)
122
120
 
123
121
  if accessor_pointer.length > 1
124
122
  hierarchical_field_name_base = self.class.accessor_hierarchical_name(*accessor_pointer)
125
- hierarchical_field_name = generate_solr_symbol(hierarchical_field_name_base, :text)
123
+ hierarchical_field_name = Solrizer::FieldNameMapper.solr_name(hierarchical_field_name_base, :text)
126
124
  solr_doc << Solr::Field.new(hierarchical_field_name => node.text)
127
125
  end
128
126
  end
129
127
 
130
128
  def update_indexed_attributes(params={}, opts={})
129
+ if self.class.terminology.nil?
130
+ raise "No terminology is set for this NokogiriDatastream class. Cannot perform update_indexed_attributes"
131
+ end
131
132
  # remove any fields from params that this datastream doesn't recognize
132
- params.delete_if do |field_key,new_values|
133
- if field_key.kind_of?(String)
133
+ #make sure to make a copy of params so not to modify hash that might be passed to other methods
134
+ current_params = params.clone
135
+ current_params.delete_if do |term_pointer,new_values|
136
+ if term_pointer.kind_of?(String)
134
137
  true
135
138
  else
136
- self.class.accessor_xpath(*OM.destringify(field_key) ).nil?
139
+ !self.class.terminology.has_term?(*OM.destringify(term_pointer))
137
140
  end
138
141
  end
142
+
139
143
  result = {}
140
- unless params.empty?
141
- result = update_properties( params )
144
+ unless current_params.empty?
145
+ result = update_values( current_params )
142
146
  self.dirty = true
143
147
  end
148
+
144
149
  return result
145
150
  end
146
151
 
147
152
  def get_values(field_key,default=[])
148
- property_values(*field_key)
153
+ term_values(*field_key)
149
154
  end
150
155
 
151
156
  end
@@ -6,6 +6,7 @@ module ActiveFedora
6
6
  #
7
7
  #Fields can still be overridden if more specificity is desired (see ActiveFedora::Datastream#fields method).
8
8
  class QualifiedDublinCoreDatastream < MetadataDatastream
9
+
9
10
  #A frozen array of Dublincore Terms.
10
11
  DCTERMS = [
11
12
  :contributor, :coverage, :creator, :description, :format, :identifier, :language, :publisher, :relation, :source, :title, :abstract, :accessRights, :accrualMethod, :accrualPeriodicity, :accrualPolicy, :alternative, :audience, :available, :bibliographicCitation, :conformsTo, :contributor, :coverage, :created, :creator, :date, :dateAccepted, :dateCopyrighted, :dateSubmitted, :description, :educationLevel, :extent, :format, :hasFormat, :hasPart, :hasVersion, :identifier, :instructionalMethod, :isFormatOf, :isPartOf, :isReferencedBy, :isReplacedBy, :isRequiredBy, :issued, :isVersionOf, :language, :license, :mediator, :medium, :modified, :provenance, :publisher, :references, :relation, :replaces, :requires, :rights, :rightsHolder, :source, :spatial, :subject, :tableOfContents, :temporal, :type, :valid
@@ -1,9 +1,10 @@
1
+ require 'solrizer/field_name_mapper'
1
2
 
2
3
  module ActiveFedora
3
4
  class RelsExtDatastream < Datastream
4
5
 
5
6
  include ActiveFedora::SemanticNode
6
- include ActiveFedora::SolrMapper
7
+ include Solrizer::FieldNameMapper
7
8
 
8
9
 
9
10
  def initialize(attrs=nil)
@@ -52,5 +53,25 @@ module ActiveFedora
52
53
  end
53
54
  return solr_doc
54
55
  end
56
+
57
+ def from_solr(solr_doc)
58
+ #cycle through all possible predicates
59
+ PREDICATE_MAPPINGS.keys.each do |predicate|
60
+ predicate_symbol = Solrizer::FieldNameMapper.solr_name(predicate, :symbol)
61
+ value = (solr_doc[predicate_symbol].nil? ? solr_doc[predicate_symbol.to_s]: solr_doc[predicate_symbol])
62
+ unless value.nil?
63
+ if value.is_a? Array
64
+ value.each do |obj|
65
+ r = ActiveFedora::Relationship.new(:subject=>:self, :predicate=>predicate, :object=>obj)
66
+ add_relationship(r)
67
+ end
68
+ else
69
+ r = ActiveFedora::Relationship.new(:subject=>:self, :predicate=>predicate, :object=>value)
70
+ add_relationship(r)
71
+ end
72
+ end
73
+ end
74
+ @load_from_solr = true
75
+ end
55
76
  end
56
77
  end
@@ -1,9 +1,9 @@
1
1
  module ActiveFedora
2
2
  module SemanticNode
3
3
  include MediaShelfClassLevelInheritableAttributes
4
- ms_inheritable_attributes :class_relationships, :internal_uri
4
+ ms_inheritable_attributes :class_relationships, :internal_uri, :class_named_relationships_desc
5
5
 
6
- attr_accessor :internal_uri, :relationships
6
+ attr_accessor :internal_uri, :named_relationship_desc, :relationships_are_dirty, :load_from_solr
7
7
 
8
8
  PREDICATE_MAPPINGS = Hash[:is_member_of => "isMemberOf",
9
9
  :has_member => "hasMember",
@@ -40,6 +40,7 @@ module ActiveFedora
40
40
  def add_relationship(relationship)
41
41
  # Only accept ActiveFedora::Relationships as input arguments
42
42
  assert_kind_of 'relationship', relationship, ActiveFedora::Relationship
43
+ self.relationships_are_dirty = true
43
44
  register_triple(relationship.subject, relationship.predicate, relationship.object)
44
45
  end
45
46
 
@@ -62,6 +63,56 @@ module ActiveFedora
62
63
  end
63
64
  end
64
65
 
66
+ def register_named_subject(subject)
67
+ self.class.register_named_subject(subject)
68
+ end
69
+
70
+ def register_named_relationship(subject, name, predicate, opts)
71
+ self.class.register_named_relationship(subject, name, predicate, opts)
72
+ end
73
+
74
+ def remove_relationship(relationship)
75
+ @relationships_are_dirty = true
76
+ unregister_triple(relationship.subject, relationship.predicate, relationship.object)
77
+ end
78
+
79
+ def unregister_triple(subject, predicate, object)
80
+ if relationship_exists?(subject, predicate, object)
81
+ relationships[subject][predicate].delete_if {|curObj| curObj == object}
82
+ relationships[subject].delete(predicate) if relationships[subject][predicate].nil? || relationships[subject][predicate].empty?
83
+ else
84
+ return false
85
+ end
86
+ end
87
+
88
+ def relationship_exists?(subject, predicate, object)
89
+ outbound_only = (subject != :inbound)
90
+ #cache the call in case it is retrieving inbound as well, don't want to hit solr too many times
91
+ cached_relationships = relationships(outbound_only)
92
+ cached_relationships.has_key?(subject)&&cached_relationships[subject].has_key?(predicate)&&cached_relationships[subject][predicate].include?(object)
93
+ end
94
+
95
+ def inbound_relationships(response_format=:uri)
96
+ rel_values = {}
97
+ inbound_named_relationship_predicates.each_pair do |name,predicate|
98
+ objects = self.send("#{name}",{:response_format=>response_format})
99
+ items = []
100
+ objects.each do |object|
101
+ if (response_format == :uri)
102
+ #create a Relationship object so that it generates the appropriate uri
103
+ r = ActiveFedora::Relationship.new(:subject=>:self, :predicate=>predicate, :object=>object)
104
+ items.push(r.object)
105
+ else
106
+ items.push(object)
107
+ end
108
+ end
109
+ unless items.empty?
110
+ rel_values.merge!({predicate=>items})
111
+ end
112
+ end
113
+ return rel_values
114
+ end
115
+
65
116
  def outbound_relationships()
66
117
  if !internal_uri.nil? && !relationships[internal_uri].nil?
67
118
  return relationships[:self].merge(relationships[internal_uri])
@@ -70,8 +121,10 @@ module ActiveFedora
70
121
  end
71
122
  end
72
123
 
73
- def relationships
124
+ # If outbound_only is false, inbound relationships will be included.
125
+ def relationships(outbound_only=true)
74
126
  @relationships ||= relationships_from_class
127
+ outbound_only ? @relationships : @relationships.merge(:inbound=>inbound_relationships)
75
128
  end
76
129
 
77
130
  def relationships_from_class
@@ -82,10 +135,194 @@ module ActiveFedora
82
135
  rels[subj][pred_key] = []
83
136
  end
84
137
  end
85
- #puts "rels from class: #{rels.inspect}"
86
138
  return rels
87
139
  end
88
140
 
141
+ def named_relationship(name)
142
+ rels = nil
143
+ if inbound_relationship_names.include?(name)
144
+ rels = named_relationships(false)[:inbound][name]
145
+ elsif outbound_relationship_names.include?(name)
146
+ rels = named_relationships[:self][name]
147
+ end
148
+ rels = [] if rels.nil?
149
+ return rels
150
+ end
151
+
152
+ def named_relationships(outbound_only=true)
153
+ #make sure to update if relationships have been updated
154
+ if @relationships_are_dirty == true
155
+ @named_relationships = named_relationships_from_class()
156
+ @relationships_are_dirty = false
157
+ end
158
+
159
+ #this will get called normally on first fetch if relationships are not dirty
160
+ @named_relationships ||= named_relationships_from_class()
161
+ outbound_only ? @named_relationships : @named_relationships.merge(:inbound=>named_inbound_relationships)
162
+ end
163
+
164
+ def named_relationships_from_class()
165
+ rels = {}
166
+ named_relationship_predicates.each_pair do |subj, names|
167
+ if relationships.has_key?(subj)
168
+ rels[subj] = {}
169
+ names.each_pair do |name, predicate|
170
+ rels[subj][name] = (relationships[subj].has_key?(predicate) ? relationships[subj][predicate] : [])
171
+ end
172
+ end
173
+ end
174
+ return rels
175
+ end
176
+
177
+ def named_inbound_relationships
178
+ rels = {}
179
+ if named_relationships_desc.has_key?(:inbound)&&!named_relationships_desc[:inbound].empty?()
180
+ inbound_rels = inbound_relationships
181
+
182
+ if named_relationship_predicates.has_key?(:inbound)
183
+ named_relationship_predicates[:inbound].each do |name, predicate|
184
+ rels[name] = inbound_rels.has_key?(predicate) ? inbound_rels[predicate] : []
185
+ end
186
+ end
187
+ end
188
+ return rels
189
+ end
190
+
191
+ def outbound_named_relationship_predicates
192
+ named_relationship_predicates.has_key?(:self) ? named_relationship_predicates[:self] : {}
193
+ end
194
+
195
+ def inbound_named_relationship_predicates
196
+ named_relationship_predicates.has_key?(:inbound) ? named_relationship_predicates[:inbound] : {}
197
+ end
198
+
199
+ def named_relationship_predicates
200
+ @named_relationship_predicates ||= named_relationship_predicates_from_class
201
+ end
202
+
203
+ def named_relationship_predicates_from_class
204
+ rels = {}
205
+ named_relationships_desc.each_pair do |subj, names|
206
+ rels[subj] = {}
207
+ names.each_pair do |name, args|
208
+ rels[subj][name] = args[:predicate]
209
+ end
210
+ end
211
+ return rels
212
+ end
213
+
214
+ def relationship_names
215
+ names = []
216
+ named_relationships_desc.each_key do |subject|
217
+ names = names.concat(named_relationships_desc[subject].keys)
218
+ end
219
+ names
220
+ end
221
+
222
+ def inbound_relationship_names
223
+ named_relationships_desc.has_key?(:inbound) ? named_relationships_desc[:inbound].keys : []
224
+ end
225
+
226
+ def outbound_relationship_names
227
+ named_relationships_desc.has_key?(:self) ? named_relationships_desc[:self].keys : []
228
+ end
229
+
230
+ def named_outbound_relationships
231
+ named_relationships_desc.has_key?(:self) ? named_relationships[:self] : {}
232
+ end
233
+
234
+ def is_named_relationship?(name, outbound_only=true)
235
+ if outbound_only
236
+ outbound_relationship_names.include?(name)
237
+ else
238
+ (outbound_relationship_names.include?(name)||inbound_relationship_names.include?(name))
239
+ end
240
+ end
241
+
242
+ def named_relationships_desc
243
+ @named_relationships_desc ||= named_relationships_desc_from_class
244
+ end
245
+
246
+ def named_relationships_desc_from_class
247
+ self.class.named_relationships_desc
248
+ end
249
+
250
+ def named_relationship_type(name)
251
+ if is_named_relationship?(name,true)
252
+ subject = outbound_relationship_names.include?(name)? :self : :inbound
253
+ if named_relationships_desc[subject][name].has_key?(:type)
254
+ return class_from_name(named_relationships_desc[subject][name][:type])
255
+ end
256
+ end
257
+ return nil
258
+ end
259
+
260
+ def add_named_relationship(name, object)
261
+ if is_named_relationship?(name,true)
262
+ if named_relationships_desc[:self][name].has_key?(:type)
263
+ klass = class_from_name(named_relationships_desc[:self][name][:type])
264
+ unless klass.nil?
265
+ (assert_kind_of_model 'object', object, klass)
266
+ end
267
+ end
268
+ #r = ActiveFedora::Relationship.new({:subject=>:self,:predicate=>outbound_named_relationship_predicates[name],:object=>object})
269
+ #add_relationship(r)
270
+ add_relationship(outbound_named_relationship_predicates[name],object)
271
+ else
272
+ false
273
+ end
274
+ end
275
+
276
+ def remove_named_relationship(name, object)
277
+ if is_named_relationship?(name,true)
278
+ remove_relationship(outbound_named_relationship_predicates[name],object)
279
+ else
280
+ return false
281
+ end
282
+ end
283
+
284
+ def assert_kind_of_model(name, object, model_class)
285
+ raise "Assertion failure: #{name}: #{object.pid} does not have model #{model_class}, it has model #{relationships[:self][:has_model]}" unless object.kind_of_model?(model_class)
286
+ end
287
+
288
+ ############################################################################
289
+ # Checks that this class is either of type passed in or a child of that type.
290
+ # It also makes sure that this object was created as the same type by
291
+ # checking that hasmodel and the class match. This would not match
292
+ # if someone called load_instance on a Fedora Object that was created
293
+ # via a different model class than the one that was recreated from Fedora.
294
+ # This is a side-effect of ActiveFedora's behavior that will try to create
295
+ # the object type specified with the pid given whether it is actually that
296
+ # object type or not.
297
+ #
298
+ # If hasmodel does not match than this will return false indicated it does not
299
+ # have the correct model.
300
+ ############################################################################
301
+ def kind_of_model?(model_class)
302
+ if self.kind_of?(model_class)
303
+ #check has model and class match
304
+ if relationships[:self].has_key?(:has_model)
305
+ r = ActiveFedora::Relationship.new(:subject=>:self, :predicate=>:has_model, :object=>ActiveFedora::ContentModel.pid_from_ruby_class(self.class))
306
+ if relationships[:self][:has_model].first.to_s.eql?(r.object.to_s)
307
+ return true
308
+ else
309
+ raise "has_model relationship check failed for model #{model_class} raising exception, expected: '#{r.object.to_s}' actual: '#{relationships[:self][:has_model].to_s}'"
310
+ end
311
+ else
312
+ raise "has_model relationship does not exist for model #{model_class} check raising exception"
313
+ end
314
+ else
315
+ raise "kind_of? check failed for model #{model_class}, actual #{self.class} raising exception"
316
+ end
317
+ return false
318
+ end
319
+
320
+ def class_from_name(name)
321
+ klass = name.to_s.split('::').inject(Kernel) {|scope, const_name|
322
+ scope.const_get(const_name)}
323
+ (!klass.nil? && klass.is_a?(::Class)) ? klass : nil
324
+ end
325
+
89
326
  # Creates a RELS-EXT datastream for insertion into a Fedora Object
90
327
  # @pid
91
328
  # Note: This method is implemented on SemanticNode instead of RelsExtDatastream because SemanticNode contains the relationships array
@@ -134,15 +371,53 @@ module ActiveFedora
134
371
  # :is_member_of, :has_member, :is_part_of, :has_part
135
372
  def has_relationship(name, predicate, opts = {})
136
373
  opts = {:singular => nil, :inbound => false}.merge(opts)
137
- opts[:inbound] == true ? register_predicate(:inbound, predicate) : register_predicate(:self, predicate)
138
-
139
374
  if opts[:inbound] == true
375
+ raise "Duplicate use of predicate for named inbound relationship not allowed" if named_predicate_exists_with_different_name?(:inbound,name,predicate)
376
+ register_named_relationship(:inbound, name, predicate, opts)
377
+ register_predicate(:inbound, predicate)
140
378
  create_inbound_relationship_finders(name, predicate, opts)
141
- else
379
+ else
380
+ raise "Duplicate use of predicate for named outbound relationship not allowed" if named_predicate_exists_with_different_name?(:self,name,predicate)
381
+ register_named_relationship(:self, name, predicate, opts)
382
+ register_predicate(:self, predicate)
383
+ create_named_relationship_methods(name)
142
384
  create_outbound_relationship_finders(name, predicate, opts)
143
385
  end
386
+ end
144
387
 
388
+ #allow duplicate has_relationship calls with same name and predicate
389
+ def named_predicate_exists_with_different_name?(subject,name,predicate)
390
+ if named_relationships_desc.has_key?(subject)
391
+ named_relationships_desc[subject].each_pair do |existing_name, args|
392
+ return true if !args[:predicate].nil? && args[:predicate] == predicate && existing_name != name
393
+ end
394
+ end
395
+ return false
396
+ end
397
+
398
+ # named relationships desc are tracked as a hash of structure {name => args}}
399
+ def named_relationships_desc
400
+ @class_named_relationships_desc ||= Hash[:self => {}]
401
+ end
402
+
403
+ def register_named_subject(subject)
404
+ unless named_relationships_desc.has_key?(subject)
405
+ named_relationships_desc[subject] = {}
406
+ end
407
+ end
408
+
409
+ def register_named_relationship(subject, name, predicate, opts)
410
+ register_named_subject(subject)
411
+ opts.merge!({:predicate=>predicate})
412
+ named_relationships_desc[subject][name] = opts
145
413
  end
414
+
415
+ def create_named_relationship_methods(name)
416
+ append_method_name = "#{name.to_s.downcase}_append"
417
+ remove_method_name = "#{name.to_s.downcase}_remove"
418
+ self.send(:define_method,:"#{append_method_name}") {|object| add_named_relationship(name,object)}
419
+ self.send(:define_method,:"#{remove_method_name}") {|object| remove_named_relationship(name,object)}
420
+ end
146
421
 
147
422
  def create_inbound_relationship_finders(name, predicate, opts = {})
148
423
  class_eval <<-END
@@ -158,6 +433,8 @@ module ActiveFedora
158
433
  id_array << hit[SOLR_DOCUMENT_ID]
159
434
  end
160
435
  return id_array
436
+ elsif opts[:response_format] == :load_from_solr || self.load_from_solr
437
+ return ActiveFedora::SolrService.reify_solr_results(solr_result,{:load_from_solr=>true})
161
438
  else
162
439
  return ActiveFedora::SolrService.reify_solr_results(solr_result)
163
440
  end
@@ -165,7 +442,10 @@ module ActiveFedora
165
442
  end
166
443
  def #{name}_ids
167
444
  #{name}(:response_format => :id_array)
168
- end
445
+ end
446
+ def #{name}_from_solr
447
+ #{name}(:response_format => :load_from_solr)
448
+ end
169
449
  END
170
450
  end
171
451
 
@@ -185,6 +465,8 @@ module ActiveFedora
185
465
  solr_result = SolrService.instance.conn.query(query)
186
466
  if opts[:response_format] == :solr
187
467
  return solr_result
468
+ elsif opts[:response_format] == :load_from_solr || self.load_from_solr
469
+ return ActiveFedora::SolrService.reify_solr_results(solr_result,{:load_from_solr=>true})
188
470
  else
189
471
  return ActiveFedora::SolrService.reify_solr_results(solr_result)
190
472
  end
@@ -193,6 +475,9 @@ module ActiveFedora
193
475
  def #{name}_ids
194
476
  #{name}(:response_format => :id_array)
195
477
  end
478
+ def #{name}_from_solr
479
+ #{name}(:response_format => :load_from_solr)
480
+ end
196
481
  END
197
482
  end
198
483