active-fedora 3.2.0.pre1 → 3.2.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/History.txt +3 -1
- data/lib/active_fedora.rb +3 -3
- data/lib/active_fedora/associations.rb +0 -2
- data/lib/active_fedora/associations/association_collection.rb +15 -1
- data/lib/active_fedora/associations/belongs_to_association.rb +5 -1
- data/lib/active_fedora/associations/has_and_belongs_to_many_association.rb +5 -1
- data/lib/active_fedora/base.rb +36 -92
- data/lib/active_fedora/datastream.rb +1 -2
- data/lib/active_fedora/file_management.rb +73 -0
- data/lib/active_fedora/metadata_datastream_helper.rb +3 -1
- data/lib/active_fedora/model.rb +6 -18
- data/lib/active_fedora/relationships.rb +634 -0
- data/lib/active_fedora/samples/special_thing.rb +4 -4
- data/lib/active_fedora/semantic_node.rb +97 -236
- data/lib/active_fedora/version.rb +1 -1
- data/spec/integration/base_file_management_spec.rb +1 -0
- data/spec/integration/base_spec.rb +114 -68
- data/spec/integration/full_featured_model_spec.rb +0 -1
- data/spec/integration/mods_article_integration_spec.rb +0 -1
- data/spec/integration/nokogiri_datastream_spec.rb +0 -1
- data/spec/integration/rels_ext_datastream_spec.rb +10 -7
- data/spec/integration/semantic_node_spec.rb +10 -16
- data/spec/samples/models/hydrangea_article.rb +1 -2
- data/spec/samples/oral_history_sample_model.rb +1 -1
- data/spec/unit/base_spec.rb +26 -16
- data/spec/unit/metadata_datastream_spec.rb +1 -0
- data/spec/unit/qualified_dublin_core_datastream_spec.rb +1 -0
- data/spec/unit/relationship_spec.rb +1 -0
- data/spec/unit/relationships_spec.rb +846 -0
- data/spec/unit/semantic_node_spec.rb +2 -338
- metadata +8 -7
- data/lib/active_fedora/relationships_helper.rb +0 -881
- data/spec/unit/relationships_helper_spec.rb +0 -800
data/Gemfile.lock
CHANGED
data/History.txt
CHANGED
@@ -3,10 +3,12 @@
|
|
3
3
|
Don't create pids until save
|
4
4
|
inspect and equality methods
|
5
5
|
Deprecate datastream_collections
|
6
|
-
Rails 3.1 compatibility
|
7
6
|
Disseminators have been added
|
7
|
+
HYDRA-729 don't update xml datastreams unless they've changed
|
8
8
|
Adapt To
|
9
9
|
ActiveFedora::Relationship is deprecated
|
10
|
+
Rails 3.1 compatibility
|
11
|
+
Ruby 1.9 compatibility
|
10
12
|
|
11
13
|
3.1.5
|
12
14
|
HYDRA-722 updating AF::Base#label= and then saving doesn't persist the change
|
data/lib/active_fedora.rb
CHANGED
@@ -4,6 +4,7 @@ require 'active_support'
|
|
4
4
|
require 'active_fedora/solr_service'
|
5
5
|
require 'active_fedora/rubydora_connection'
|
6
6
|
require 'active_support/core_ext/class/attribute'
|
7
|
+
require 'active_support/core_ext/object'
|
7
8
|
|
8
9
|
SOLR_DOCUMENT_ID = ActiveFedora::SolrService.id_field unless defined?(SOLR_DOCUMENT_ID)
|
9
10
|
ENABLE_SOLR_UPDATES = true unless defined?(ENABLE_SOLR_UPDATES)
|
@@ -17,9 +18,9 @@ module ActiveFedora #:nodoc:
|
|
17
18
|
autoload :Base
|
18
19
|
autoload :ContentModel
|
19
20
|
autoload :Reflection
|
20
|
-
|
21
|
+
autoload :Relationships
|
22
|
+
autoload :FileManagement
|
21
23
|
autoload :RelationshipGraph
|
22
|
-
autoload :RelationshipsHelper
|
23
24
|
autoload :Datastream
|
24
25
|
autoload :Delegating
|
25
26
|
autoload :DigitalObject
|
@@ -31,7 +32,6 @@ module ActiveFedora #:nodoc:
|
|
31
32
|
autoload :Property
|
32
33
|
autoload :QualifiedDublinCoreDatastream
|
33
34
|
autoload :RelsExtDatastream
|
34
|
-
autoload :RelationshipsHelper
|
35
35
|
autoload :ServiceDefinitions
|
36
36
|
autoload :SemanticNode
|
37
37
|
autoload :NestedAttributes
|
@@ -34,7 +34,6 @@ module ActiveFedora
|
|
34
34
|
|
35
35
|
def has_many(association_id, options={})
|
36
36
|
raise "You must specify a property name for #{name}" if !options[:property]
|
37
|
-
has_relationship association_id.to_s, options[:property], :inbound => true
|
38
37
|
reflection = create_has_many_reflection(association_id, options)
|
39
38
|
collection_accessor_methods(reflection, HasManyAssociation)
|
40
39
|
end
|
@@ -75,7 +74,6 @@ module ActiveFedora
|
|
75
74
|
# belongs_to :author, :class_name => "Person", :property => :author_of
|
76
75
|
def belongs_to(association_id, options = {})
|
77
76
|
raise "You must specify a property name for #{name}" if !options[:property]
|
78
|
-
has_relationship association_id.to_s, options[:property]
|
79
77
|
reflection = create_belongs_to_reflection(association_id, options)
|
80
78
|
|
81
79
|
association_accessor_methods(reflection, BelongsToAssociation)
|
@@ -129,9 +129,23 @@ module ActiveFedora
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def find_target
|
132
|
-
|
132
|
+
load_inbound_relationship(@owner.pid, @reflection.options[:property])
|
133
133
|
end
|
134
134
|
|
135
|
+
def load_inbound_relationship(pid, predicate, opts={})
|
136
|
+
opts = {:rows=>25}.merge(opts)
|
137
|
+
query = inbound_relationship_query(pid, predicate)
|
138
|
+
return [] if query.empty?
|
139
|
+
solr_result = SolrService.instance.conn.query(query, :rows=>opts[:rows])
|
140
|
+
#TODO, don't reify, just store the solr results and lazily reify.
|
141
|
+
return ActiveFedora::SolrService.reify_solr_results(solr_result)
|
142
|
+
end
|
143
|
+
|
144
|
+
def inbound_relationship_query(pid,predicate)
|
145
|
+
internal_uri = "info:fedora/#{pid}"
|
146
|
+
escaped_uri = internal_uri.gsub(/(:)/, '\\:')
|
147
|
+
"#{predicate}_s:#{escaped_uri}"
|
148
|
+
end
|
135
149
|
|
136
150
|
def add_record_to_target_with_callbacks(record)
|
137
151
|
# callback(:before_add, record)
|
@@ -24,7 +24,11 @@ module ActiveFedora
|
|
24
24
|
|
25
25
|
private
|
26
26
|
def find_target
|
27
|
-
@owner.
|
27
|
+
pid = @owner.ids_for_outbound(@reflection.options[:property]).first
|
28
|
+
return if pid.nil?
|
29
|
+
query = ActiveFedora::SolrService.construct_query_for_pids([pid])
|
30
|
+
solr_result = SolrService.instance.conn.query(query)
|
31
|
+
return ActiveFedora::SolrService.reify_solr_results(solr_result).first
|
28
32
|
end
|
29
33
|
|
30
34
|
def foreign_key_present
|
@@ -7,7 +7,11 @@ module ActiveFedora
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def find_target
|
10
|
-
@owner.
|
10
|
+
pids = @owner.ids_for_outbound(@reflection.options[:property])
|
11
|
+
return [] if pids.empty?
|
12
|
+
query = ActiveFedora::SolrService.construct_query_for_pids(pids)
|
13
|
+
solr_result = SolrService.instance.conn.query(query)
|
14
|
+
return ActiveFedora::SolrService.reify_solr_results(solr_result)
|
11
15
|
end
|
12
16
|
|
13
17
|
|
data/lib/active_fedora/base.rb
CHANGED
@@ -30,12 +30,46 @@ module ActiveFedora
|
|
30
30
|
# =Implementation
|
31
31
|
# This class is really a facade for a basic Fedora::FedoraObject, which is stored internally.
|
32
32
|
class Base
|
33
|
-
include RelationshipsHelper
|
34
33
|
include SemanticNode
|
35
34
|
|
35
|
+
def self.method_missing (name, *args)
|
36
|
+
if [:has_relationship, :has_bidirectional_relationship, :register_relationship_desc].include? name
|
37
|
+
ActiveSupport::Deprecation.warn("Deprecation: Relationships will not be included by default in the next version. To use #{name} add 'include ActiveFedora::Relationships' to your model")
|
38
|
+
include Relationships
|
39
|
+
send name, *args
|
40
|
+
elsif name == :has_datastream
|
41
|
+
ActiveSupport::Deprecation.warn("Deprecation: DatastreamCollections will not be included by default in the next version. To use has_datastream add 'include ActiveFedora::DatastreamCollections' to your model")
|
42
|
+
include DatastreamCollections
|
43
|
+
has_datastream(*args)
|
44
|
+
else
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def method_missing(name, *args)
|
51
|
+
if [:collection_members, :part_of, :parts, :part_of_append, :file_objects].include? name
|
52
|
+
ActiveSupport::Deprecation.warn("Deprecation: FileManagement will not be included by default in the next version. To use #{name} add 'include ActiveFedora::FileManagement' to your model")
|
53
|
+
self.class.send :include, FileManagement
|
54
|
+
send name, *args
|
55
|
+
else
|
56
|
+
dsid = corresponding_datastream_name(name)
|
57
|
+
if dsid
|
58
|
+
### Create and invoke a proxy method
|
59
|
+
self.class.send :define_method, name do
|
60
|
+
datastreams[dsid]
|
61
|
+
end
|
62
|
+
self.send(name)
|
63
|
+
else
|
64
|
+
super
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
36
70
|
class_attribute :ds_specs
|
37
71
|
|
38
|
-
|
72
|
+
def self.inherited(p)
|
39
73
|
# each subclass should get a copy of the parent's datastream definitions, it should not add to the parent's definition table.
|
40
74
|
p.ds_specs = p.ds_specs.dup
|
41
75
|
super
|
@@ -43,13 +77,6 @@ module ActiveFedora
|
|
43
77
|
|
44
78
|
self.ds_specs = {'RELS-EXT'=> {:type=> ActiveFedora::RelsExtDatastream, :label=>"", :block=>nil}}
|
45
79
|
|
46
|
-
|
47
|
-
|
48
|
-
has_relationship "collection_members", :has_collection_member
|
49
|
-
has_relationship "part_of", :is_part_of
|
50
|
-
has_bidirectional_relationship "parts", :has_part, :is_part_of
|
51
|
-
|
52
|
-
|
53
80
|
# Has this object been saved?
|
54
81
|
def new_object?
|
55
82
|
inner_object.new?
|
@@ -112,29 +139,6 @@ module ActiveFedora
|
|
112
139
|
ds_specs[args[:name]]= {:type => args[:type], :label => args.fetch(:label,""), :control_group => args.fetch(:control_group,"X"), :disseminator => args.fetch(:disseminator,""), :url => args.fetch(:url,""),:block => block}
|
113
140
|
end
|
114
141
|
|
115
|
-
def self.method_missing (name, args)
|
116
|
-
if name == :has_datastream
|
117
|
-
ActiveSupport::Deprecation.warn("Deprecation: DatastreamCollections will not be included by default in the next version. To use has_datastream add 'include ActiveFedora::DatastreamCollections' to your model")
|
118
|
-
include DatastreamCollections
|
119
|
-
has_datastream(args)
|
120
|
-
else
|
121
|
-
super
|
122
|
-
end
|
123
|
-
|
124
|
-
end
|
125
|
-
|
126
|
-
def method_missing(name, *args)
|
127
|
-
dsid = corresponding_datastream_name(name)
|
128
|
-
if dsid
|
129
|
-
### Create and invoke a proxy method
|
130
|
-
self.class.send :define_method, name do
|
131
|
-
datastreams[dsid]
|
132
|
-
end
|
133
|
-
self.send(name)
|
134
|
-
else
|
135
|
-
super
|
136
|
-
end
|
137
|
-
end
|
138
142
|
|
139
143
|
## Given a method name, return the best-guess dsid
|
140
144
|
def corresponding_datastream_name(method_name)
|
@@ -349,65 +353,6 @@ module ActiveFedora
|
|
349
353
|
add_datastream(ds)
|
350
354
|
end
|
351
355
|
|
352
|
-
# List the objects that assert isPartOf pointing at this object _plus_ all objects that this object asserts hasPart for
|
353
|
-
# Note: Previous versions of ActiveFedora used hasCollectionMember to represent this type of relationship.
|
354
|
-
# To accommodate this, until active-fedora-1.3, .file_assets will also return anything that this asserts hasCollectionMember for and will output a warning in the logs.
|
355
|
-
#
|
356
|
-
# @param [Hash] opts -- same options as auto-generated methods for relationships (ie. :response_format)
|
357
|
-
# @return [Array of ActiveFedora objects, Array of PIDs, or Solr::Result] -- same options as auto-generated methods for relationships (ie. :response_format)
|
358
|
-
def file_objects(opts={})
|
359
|
-
cm_array = collection_members(:response_format=>:id_array)
|
360
|
-
|
361
|
-
if !cm_array.empty?
|
362
|
-
logger.warn "This object has collection member assertions. hasCollectionMember will no longer be used to track file_object relationships after active_fedora 1.3. Use isPartOf assertions in the RELS-EXT of child objects instead."
|
363
|
-
if opts[:response_format] == :solr || opts[:response_format] == :load_from_solr
|
364
|
-
logger.warn ":solr and :load_from_solr response formats for file_objects search only uses parts relationships (usage of hasCollectionMember is no longer supported)"
|
365
|
-
result = parts(opts)
|
366
|
-
else
|
367
|
-
cm_result = collection_members(opts)
|
368
|
-
parts_result = parts(opts)
|
369
|
-
ary = cm_result+parts_result
|
370
|
-
result = ary.uniq
|
371
|
-
end
|
372
|
-
else
|
373
|
-
result = parts(opts)
|
374
|
-
end
|
375
|
-
return result
|
376
|
-
end
|
377
|
-
|
378
|
-
# Add the given obj as a child to the current object using an inbound is_part_of relationship
|
379
|
-
#
|
380
|
-
# @param [ActiveFedora::Base,String] obj the object or the pid of the object to add
|
381
|
-
# @return [Boolean] whether saving the child object was successful
|
382
|
-
# @example This will add an is_part_of relationship to the child_object's RELS-EXT datastream pointing at parent_object
|
383
|
-
# parent_object.file_objects_append(child_object)
|
384
|
-
def file_objects_append(obj)
|
385
|
-
# collection_members_append(obj)
|
386
|
-
unless obj.kind_of? ActiveFedora::Base
|
387
|
-
begin
|
388
|
-
obj = ActiveFedora::Base.load_instance(obj)
|
389
|
-
rescue ActiveFedora::ObjectNotFoundError
|
390
|
-
"You must provide either an ActiveFedora object or a valid pid to add it as a file object. You submitted #{obj.inspect}"
|
391
|
-
end
|
392
|
-
end
|
393
|
-
obj.add_relationship(:is_part_of, self)
|
394
|
-
obj.save
|
395
|
-
end
|
396
|
-
|
397
|
-
# Add the given obj as a collection member to the current object using an outbound has_collection_member relationship.
|
398
|
-
#
|
399
|
-
# @param [ActiveFedora::Base] obj the file to add
|
400
|
-
# @return [ActiveFedora::Base] obj returns self
|
401
|
-
# @example This will add a has_collection_member relationship to the parent_object's RELS-EXT datastream pointing at child_object
|
402
|
-
# parent_object.collection_members_append(child_object)
|
403
|
-
def collection_members_append(obj)
|
404
|
-
add_relationship(:has_collection_member, obj)
|
405
|
-
return self
|
406
|
-
end
|
407
|
-
|
408
|
-
def collection_members_remove()
|
409
|
-
# will rely on SemanticNode.remove_relationship once it is implemented
|
410
|
-
end
|
411
356
|
|
412
357
|
def create_datastream(type, dsid, opts={})
|
413
358
|
dsid = generate_dsid(opts[:prefix] || "DS") if dsid == nil
|
@@ -821,7 +766,6 @@ module ActiveFedora
|
|
821
766
|
include NestedAttributes
|
822
767
|
include Reflection
|
823
768
|
include NamedRelationships
|
824
|
-
# include DatastreamCollections
|
825
769
|
end
|
826
770
|
|
827
771
|
end
|
@@ -28,8 +28,7 @@ module ActiveFedora
|
|
28
28
|
dsid.gsub(/\./, '%2e')
|
29
29
|
end
|
30
30
|
|
31
|
-
# Test whether this datastream been modified since it was last saved
|
32
|
-
# TODO deprecate this, just use changed?
|
31
|
+
# Test whether this datastream been modified since it was last saved
|
33
32
|
def dirty?
|
34
33
|
@dirty || changed?
|
35
34
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module ActiveFedora
|
2
|
+
module FileManagement
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
has_relationship "collection_members", :has_collection_member
|
7
|
+
has_relationship "part_of", :is_part_of
|
8
|
+
has_bidirectional_relationship "parts", :has_part, :is_part_of
|
9
|
+
end
|
10
|
+
|
11
|
+
# List the objects that assert isPartOf pointing at this object _plus_ all objects that this object asserts hasPart for
|
12
|
+
# Note: Previous versions of ActiveFedora used hasCollectionMember to represent this type of relationship.
|
13
|
+
# To accommodate this, until active-fedora-1.3, .file_assets will also return anything that this asserts hasCollectionMember for and will output a warning in the logs.
|
14
|
+
#
|
15
|
+
# @param [Hash] opts -- same options as auto-generated methods for relationships (ie. :response_format)
|
16
|
+
# @return [Array of ActiveFedora objects, Array of PIDs, or Solr::Result] -- same options as auto-generated methods for relationships (ie. :response_format)
|
17
|
+
def file_objects(opts={})
|
18
|
+
cm_array = collection_members(:response_format=>:id_array)
|
19
|
+
|
20
|
+
if !cm_array.empty?
|
21
|
+
logger.warn "This object has collection member assertions. hasCollectionMember will no longer be used to track file_object relationships after active_fedora 1.3. Use isPartOf assertions in the RELS-EXT of child objects instead."
|
22
|
+
if opts[:response_format] == :solr || opts[:response_format] == :load_from_solr
|
23
|
+
logger.warn ":solr and :load_from_solr response formats for file_objects search only uses parts relationships (usage of hasCollectionMember is no longer supported)"
|
24
|
+
result = parts(opts)
|
25
|
+
else
|
26
|
+
cm_result = collection_members(opts)
|
27
|
+
parts_result = parts(opts)
|
28
|
+
ary = cm_result+parts_result
|
29
|
+
result = ary.uniq
|
30
|
+
end
|
31
|
+
else
|
32
|
+
result = parts(opts)
|
33
|
+
end
|
34
|
+
return result
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add the given obj as a child to the current object using an inbound is_part_of relationship
|
38
|
+
#
|
39
|
+
# @param [ActiveFedora::Base,String] obj the object or the pid of the object to add
|
40
|
+
# @return [Boolean] whether saving the child object was successful
|
41
|
+
# @example This will add an is_part_of relationship to the child_object's RELS-EXT datastream pointing at parent_object
|
42
|
+
# parent_object.file_objects_append(child_object)
|
43
|
+
def file_objects_append(obj)
|
44
|
+
# collection_members_append(obj)
|
45
|
+
unless obj.kind_of? ActiveFedora::Base
|
46
|
+
begin
|
47
|
+
obj = ActiveFedora::Base.load_instance(obj)
|
48
|
+
rescue ActiveFedora::ObjectNotFoundError
|
49
|
+
"You must provide either an ActiveFedora object or a valid pid to add it as a file object. You submitted #{obj.inspect}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
obj.add_relationship(:is_part_of, self)
|
53
|
+
obj.save
|
54
|
+
end
|
55
|
+
|
56
|
+
# Add the given obj as a collection member to the current object using an outbound has_collection_member relationship.
|
57
|
+
#
|
58
|
+
# @param [ActiveFedora::Base] obj the file to add
|
59
|
+
# @return [ActiveFedora::Base] obj returns self
|
60
|
+
# @example This will add a has_collection_member relationship to the parent_object's RELS-EXT datastream pointing at child_object
|
61
|
+
# parent_object.collection_members_append(child_object)
|
62
|
+
def collection_members_append(obj)
|
63
|
+
add_relationship(:has_collection_member, obj)
|
64
|
+
return self
|
65
|
+
end
|
66
|
+
|
67
|
+
def collection_members_remove()
|
68
|
+
# will rely on SemanticNode.remove_relationship once it is implemented
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
@@ -33,7 +33,9 @@ module ActiveFedora::MetadataDatastreamHelper
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def serialize! # :nodoc:
|
36
|
-
|
36
|
+
if dirty?
|
37
|
+
self.content = self.to_xml ##TODO only do this when the xml will have changed to avoid a load of the datastream content.
|
38
|
+
end
|
37
39
|
end
|
38
40
|
|
39
41
|
def to_solr(solr_doc = Hash.new) # :nodoc:
|
data/lib/active_fedora/model.rb
CHANGED
@@ -102,9 +102,7 @@ module ActiveFedora
|
|
102
102
|
return_multiple = false
|
103
103
|
if args == :all
|
104
104
|
return_multiple = true
|
105
|
-
# escaped_class_name = self.name.gsub(/(:)/, '\\:')
|
106
105
|
escaped_class_uri = SolrService.escape_uri_for_query(self.to_class_uri)
|
107
|
-
# q = "#{ActiveFedora::SolrService.solr_name(:active_fedora_model, :symbol)}:#{escaped_class_name}"
|
108
106
|
q = "#{ActiveFedora::SolrService.solr_name(:has_model, :symbol)}:#{escaped_class_uri}"
|
109
107
|
elsif args.class == String
|
110
108
|
escaped_id = args.gsub(/(:)/, '\\:')
|
@@ -115,13 +113,12 @@ module ActiveFedora
|
|
115
113
|
else
|
116
114
|
hits = SolrService.instance.conn.query(q).hits
|
117
115
|
end
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
return results
|
116
|
+
if return_multiple
|
117
|
+
return hits.map do |hit|
|
118
|
+
RubydoraConnection.instance.find_model(hit[SOLR_DOCUMENT_ID], self)
|
119
|
+
end
|
123
120
|
else
|
124
|
-
return
|
121
|
+
return RubydoraConnection.instance.find_model(hits.first[SOLR_DOCUMENT_ID], self) if hits.first
|
125
122
|
end
|
126
123
|
end
|
127
124
|
|
@@ -233,16 +230,7 @@ module ActiveFedora
|
|
233
230
|
end
|
234
231
|
|
235
232
|
logger.debug "Querying solr for #{self.name} objects with query: '#{query}'"
|
236
|
-
|
237
|
-
#objects = []
|
238
|
-
# results.hits.each do |hit|
|
239
|
-
# puts "get object for #{hit[SOLR_DOCUMENT_ID]}"
|
240
|
-
# obj = Fedora::Repository.instance.find_model(hit[SOLR_DOCUMENT_ID], self)
|
241
|
-
# obj.inner_object.new_object = false
|
242
|
-
# objects.push(obj)
|
243
|
-
#end
|
244
|
-
#objects
|
245
|
-
#ActiveFedora::SolrService.reify_solr_results(results)
|
233
|
+
results = ActiveFedora::SolrService.instance.conn.query(query,query_opts)
|
246
234
|
end
|
247
235
|
|
248
236
|
def class_fields
|
@@ -0,0 +1,634 @@
|
|
1
|
+
module ActiveFedora
|
2
|
+
module Relationships
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
|
6
|
+
# Return array of objects for a given relationship name
|
7
|
+
# @param [String] Name of relationship to find
|
8
|
+
# @return [Array] Returns array of objects linked via the relationship name given
|
9
|
+
def find_relationship_by_name(name)
|
10
|
+
rels = nil
|
11
|
+
if inbound_relationship_names.include?(name)
|
12
|
+
rels = relationships_by_name(false)[:inbound][name]
|
13
|
+
elsif outbound_relationship_names.include?(name)
|
14
|
+
rels = relationships_by_name[:self][name]
|
15
|
+
end
|
16
|
+
rels = [] if rels.nil?
|
17
|
+
return rels
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return array of relationship names for all inbound relationships (coming from other objects' RELS-EXT and Solr)
|
21
|
+
# @return [Array] of inbound relationship names for relationships declared via has_relationship in the class
|
22
|
+
def inbound_relationship_names
|
23
|
+
relationships_desc.has_key?(:inbound) ? relationships_desc[:inbound].keys : []
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return hash of relationships_by_name defined within other objects' RELS-EXT
|
27
|
+
# It returns a hash of relationship name to arrays of objects. It requeries
|
28
|
+
# solr each time this method is called.
|
29
|
+
# @return [Hash] Return hash of each relationship name mapped to an Array of objects linked to this object via inbound relationships
|
30
|
+
def inbound_relationships_by_name
|
31
|
+
rels = {}
|
32
|
+
if relationships_desc.has_key?(:inbound)&&!relationships_desc[:inbound].empty?()
|
33
|
+
inbound_rels = inbound_relationships
|
34
|
+
|
35
|
+
if relationship_predicates.has_key?(:inbound)
|
36
|
+
relationship_predicates[:inbound].each do |name, predicate|
|
37
|
+
rels[name] = inbound_rels.has_key?(predicate) ? inbound_rels[predicate] : []
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
return rels
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
# Call this method to return the query used against solr to retrieve any
|
46
|
+
# objects linked via the relationship name given.
|
47
|
+
#
|
48
|
+
# Instead of this method you can also use the helper method
|
49
|
+
# [relationship_name]_query, i.e. method "parts_query" for relationship "parts" to return the same value
|
50
|
+
# @param [String] The name of the relationship defined in the model
|
51
|
+
# @return [String] The query used when querying solr for objects for this relationship
|
52
|
+
# @example
|
53
|
+
# Class SampleAFObjRelationshipFilterQuery < ActiveFedora::Base
|
54
|
+
# #points to all parents linked via is_member_of
|
55
|
+
# has_relationship "parents", :is_member_of
|
56
|
+
# #returns only parents that have a level value set to "series"
|
57
|
+
# has_relationship "series_parents", :is_member_of, :solr_fq=>level_t:series"
|
58
|
+
# end
|
59
|
+
# s = SampleAFObjRelationshipFilterQuery.new
|
60
|
+
# obj = ActiveFedora::Base.new
|
61
|
+
# s.parents_append(obj)
|
62
|
+
# s.series_parents_query
|
63
|
+
# #=> "(id:changeme\\:13020 AND level_t:series)"
|
64
|
+
# SampleAFObjRelationshipFilterQuery.relationship_query("series_parents")
|
65
|
+
# #=> "(id:changeme\\:13020 AND level_t:series)"
|
66
|
+
def relationship_query(relationship_name)
|
67
|
+
query = ""
|
68
|
+
if self.class.is_bidirectional_relationship?(relationship_name)
|
69
|
+
predicate = outbound_relationship_predicates["#{relationship_name}_outbound"]
|
70
|
+
id_array = ids_for_outbound(predicate)
|
71
|
+
query = self.class.bidirectional_relationship_query(pid,relationship_name,id_array)
|
72
|
+
elsif outbound_relationship_names.include?(relationship_name)
|
73
|
+
predicate = outbound_relationship_predicates[relationship_name]
|
74
|
+
id_array = ids_for_outbound(predicate)
|
75
|
+
query = self.class.outbound_relationship_query(relationship_name,id_array)
|
76
|
+
elsif inbound_relationship_names.include?(relationship_name)
|
77
|
+
query = self.class.inbound_relationship_query(pid,relationship_name)
|
78
|
+
end
|
79
|
+
query
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# Gets the relationships hash with subject mapped to relationship
|
84
|
+
# names instead of relationship predicates (unlike the "relationships" method in SemanticNode)
|
85
|
+
# It has an optional parameter of outbound_only that defaults true.
|
86
|
+
# If false it will include inbound relationships in the results.
|
87
|
+
# Also, it will only reload outbound relationships if the relationships hash has changed
|
88
|
+
# since the last time this method was called.
|
89
|
+
# @param [Boolean] if false it will include inbound relationships (defaults to true)
|
90
|
+
# @return [Hash] Returns a hash of subject name (:self or :inbound) mapped to nested hashs of each relationship name mapped to an Array of objects linked via the relationship
|
91
|
+
def relationships_by_name(outbound_only=true)
|
92
|
+
@relationships_by_name = relationships_by_name_from_class()
|
93
|
+
outbound_only ? @relationships_by_name : @relationships_by_name.merge(:inbound=>inbound_relationships_by_name)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Gets relationships by name from the class using the current relationships hash
|
97
|
+
# and relationship name,predicate pairs.
|
98
|
+
# @return [Hash] returns the outbound relationships with :self mapped to nested hashs of each relationship name mapped to an Array of objects linked via the relationship
|
99
|
+
def relationships_by_name_from_class()
|
100
|
+
rels = {}
|
101
|
+
relationship_predicates.each_pair do |subj, names|
|
102
|
+
case subj
|
103
|
+
when :self
|
104
|
+
rels[:self] = {}
|
105
|
+
names.each_pair do |name, predicate|
|
106
|
+
set = []
|
107
|
+
res = relationships.query(:predicate => Predicates.find_graph_predicate(predicate))
|
108
|
+
res.each_object do |o|
|
109
|
+
set << o.to_s
|
110
|
+
end
|
111
|
+
rels[:self][name] = set
|
112
|
+
end
|
113
|
+
when :inbound
|
114
|
+
#nop
|
115
|
+
end
|
116
|
+
end
|
117
|
+
return rels
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
# Throws an assertion error if conforms_to? returns false for object and model_class
|
123
|
+
# @param [String] Name of object (just label for output)
|
124
|
+
# @param [ActiveFedora::Base] Expects to be an object that has ActiveFedora::Base as an ancestor of its class
|
125
|
+
# @param [Class] The model class used in conforms_to? check on object
|
126
|
+
def assert_conforms_to(name, object, model_class)
|
127
|
+
raise "Assertion failure: #{name}: #{object.pid} does not have model #{model_class}, it has model #{relationships[:self][:has_model]}" unless object.conforms_to?(model_class)
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
|
132
|
+
|
133
|
+
# Returns a Class symbol for the given string for the class name
|
134
|
+
# @param [String] the class name as a string
|
135
|
+
# @return [Class] the class as a Class object
|
136
|
+
def class_from_name(name)
|
137
|
+
klass = name.to_s.split('::').inject(Kernel) {|scope, const_name|
|
138
|
+
scope.const_get(const_name)}
|
139
|
+
(!klass.nil? && klass.is_a?(::Class)) ? klass : nil
|
140
|
+
end
|
141
|
+
|
142
|
+
# Return array of relationship names for all outbound relationships (coming from this object's RELS-EXT)
|
143
|
+
# @return [Array] of outbound relationship names for relationships declared via has_relationship in the class
|
144
|
+
def outbound_relationship_names
|
145
|
+
relationships_desc.has_key?(:self) ? relationships_desc[:self].keys : []
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns true if the given relationship name is a relationship
|
149
|
+
# @param [String] Name of relationship
|
150
|
+
# @param [Boolean] If false checks inbound relationships as well (defaults to true)
|
151
|
+
def is_relationship_name?(name, outbound_only=true)
|
152
|
+
if outbound_only
|
153
|
+
outbound_relationship_names.include?(name)
|
154
|
+
else
|
155
|
+
(outbound_relationship_names.include?(name)||inbound_relationship_names.include?(name))
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
def load_inbound_relationship(name, predicate, opts={})
|
161
|
+
opts = {:rows=>25}.merge(opts)
|
162
|
+
query = self.class.inbound_relationship_query(self.pid,"#{name}")
|
163
|
+
return [] if query.empty?
|
164
|
+
solr_result = SolrService.instance.conn.query(query, :rows=>opts[:rows])
|
165
|
+
if opts[:response_format] == :solr
|
166
|
+
return solr_result
|
167
|
+
else
|
168
|
+
if opts[:response_format] == :id_array
|
169
|
+
id_array = []
|
170
|
+
solr_result.hits.each do |hit|
|
171
|
+
id_array << hit[SOLR_DOCUMENT_ID]
|
172
|
+
end
|
173
|
+
return id_array
|
174
|
+
elsif opts[:response_format] == :load_from_solr || self.load_from_solr
|
175
|
+
return ActiveFedora::SolrService.reify_solr_results(solr_result,{:load_from_solr=>true})
|
176
|
+
else
|
177
|
+
return ActiveFedora::SolrService.reify_solr_results(solr_result)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
|
184
|
+
def load_bidirectional(name, inbound_method_name, outbound_method_name, opts)
|
185
|
+
opts = {:rows=>25}.merge(opts)
|
186
|
+
if opts[:response_format] == :solr || opts[:response_format] == :load_from_solr
|
187
|
+
predicate = outbound_relationship_predicates["#{name}_outbound"]
|
188
|
+
outbound_id_array = ids_for_outbound(predicate)
|
189
|
+
query = self.class.bidirectional_relationship_query(self.pid,"#{name}",outbound_id_array)
|
190
|
+
solr_result = SolrService.instance.conn.query(query, :rows=>opts[:rows])
|
191
|
+
|
192
|
+
if opts[:response_format] == :solr
|
193
|
+
return solr_result
|
194
|
+
elsif opts[:response_format] == :load_from_solr || self.load_from_solr
|
195
|
+
return ActiveFedora::SolrService.reify_solr_results(solr_result,{:load_from_solr=>true})
|
196
|
+
else
|
197
|
+
return ActiveFedora::SolrService.reify_solr_results(solr_result)
|
198
|
+
end
|
199
|
+
else
|
200
|
+
ary = send(inbound_method_name,opts) + send(outbound_method_name, opts)
|
201
|
+
return ary.uniq
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def load_outbound_relationship(name, predicate, opts={})
|
206
|
+
id_array = ids_for_outbound(predicate)
|
207
|
+
if opts[:response_format] == :id_array && !self.relationship_has_solr_filter_query?(:self,"#{name}")
|
208
|
+
return id_array
|
209
|
+
else
|
210
|
+
query = self.class.outbound_relationship_query(name,id_array)
|
211
|
+
solr_result = SolrService.instance.conn.query(query)
|
212
|
+
if opts[:response_format] == :solr
|
213
|
+
return solr_result
|
214
|
+
elsif opts[:response_format] == :id_array
|
215
|
+
id_array = []
|
216
|
+
solr_result.hits.each do |hit|
|
217
|
+
id_array << hit[SOLR_DOCUMENT_ID]
|
218
|
+
end
|
219
|
+
return id_array
|
220
|
+
elsif opts[:response_format] == :load_from_solr || self.load_from_solr
|
221
|
+
return ActiveFedora::SolrService.reify_solr_results(solr_result,{:load_from_solr=>true})
|
222
|
+
else
|
223
|
+
return ActiveFedora::SolrService.reify_solr_results(solr_result)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Check if a relationship has any solr query filters defined by has_relationship call
|
229
|
+
# @param [Symbol] subject to use such as :self or :inbound
|
230
|
+
# @param [String] relationship name
|
231
|
+
# @return [Boolean] true if the relationship has a query filter defined
|
232
|
+
def relationship_has_solr_filter_query?(subject, relationship_name)
|
233
|
+
relationships_desc.has_key?(subject) && relationships_desc[subject].has_key?(relationship_name) && relationships_desc[subject][relationship_name].has_key?(:solr_fq)
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
# Add an outbound relationship for given relationship name
|
238
|
+
# See ActiveFedora::SemanticNode::ClassMethods.has_relationship
|
239
|
+
# @param [String] Name of relationship
|
240
|
+
# @param [ActiveFedora::Base] object to add to the relationship (expects ActvieFedora::Base to be an ancestor)
|
241
|
+
# @return [Boolean] returns true if add operation successful
|
242
|
+
def add_relationship_by_name(name, object)
|
243
|
+
if is_relationship_name?(name,true)
|
244
|
+
if relationships_desc[:self][name].has_key?(:type)
|
245
|
+
klass = class_from_name(relationships_desc[:self][name][:type])
|
246
|
+
unless klass.nil?
|
247
|
+
(assert_conforms_to 'object', object, klass)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
add_relationship(outbound_relationship_predicates[name],object)
|
251
|
+
else
|
252
|
+
false
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# Remove an object for the given relationship name
|
257
|
+
# @param [String] Relationship name
|
258
|
+
# @param [ActiveFedora::Base] object to remove
|
259
|
+
# @return [Boolean] return true if remove operation successful
|
260
|
+
def remove_relationship_by_name(name, object)
|
261
|
+
if is_relationship_name?(name,true)
|
262
|
+
remove_relationship(outbound_relationship_predicates[name],object)
|
263
|
+
else
|
264
|
+
return false
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
|
269
|
+
module ClassMethods
|
270
|
+
# Tests if the relationship name passed is in bidirectional
|
271
|
+
# @param [String] relationship name to test
|
272
|
+
# @return [Boolean]
|
273
|
+
def is_bidirectional_relationship?(relationship_name)
|
274
|
+
(relationships_desc.has_key?(:self)&&relationships_desc.has_key?(:inbound)&&relationships_desc[:self].has_key?("#{relationship_name}_outbound") && relationships_desc[:inbound].has_key?("#{relationship_name}_inbound"))
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
# Returns a solr query for retrieving objects specified in an outbound relationship.
|
279
|
+
# This method is mostly used by internal method calls.
|
280
|
+
# It utilizes any solr_fq value defined within a relationship to attach a query filter when
|
281
|
+
# querying solr on top of just the predicate being used.
|
282
|
+
# Because it is static it
|
283
|
+
# needs the pids defined within RELS-EXT for this relationship to be passed in.
|
284
|
+
# If you are calling this method directly to get the query you should use the
|
285
|
+
# ActiveFedora::SemanticNode.relationship_query instead or use the helper method
|
286
|
+
# [relationship_name]_query, i.e. method "parts_query" for relationship "parts". This
|
287
|
+
# method would only be called directly if you had something like an array of outbound pids
|
288
|
+
# already in something like a solr document for object that has these relationships.
|
289
|
+
# @param [String] The name of the relationship defined in the model
|
290
|
+
# @param [Array] An array of pids to include in the query
|
291
|
+
# @return [String]
|
292
|
+
# @example
|
293
|
+
# Class SampleAFObjRelationshipFilterQuery < ActiveFedora::Base
|
294
|
+
# #points to all parents linked via is_member_of
|
295
|
+
# has_relationship "parents", :is_member_of
|
296
|
+
# #returns only parents that have a level value set to "series"
|
297
|
+
# has_relationship "series_parents", :is_member_of, :solr_fq=>"level_t:series"
|
298
|
+
# end
|
299
|
+
# s = SampleAFObjRelationshipFilterQuery.new
|
300
|
+
# obj = ActiveFedora::Base.new
|
301
|
+
# s.series_parents_append(obj)
|
302
|
+
# s.series_parents_query
|
303
|
+
# #=> "(id:changeme\\:13020 AND level_t:series)"
|
304
|
+
# SampleAFObjRelationshipFilterQuery.outbound_relationship_query("series_parents",["id:changeme:13020"])
|
305
|
+
# #=> "(id:changeme\\:13020 AND level_t:series)"
|
306
|
+
def outbound_relationship_query(relationship_name,outbound_pids)
|
307
|
+
query = ActiveFedora::SolrService.construct_query_for_pids(outbound_pids)
|
308
|
+
subject = :self
|
309
|
+
if relationships_desc.has_key?(subject) && relationships_desc[subject].has_key?(relationship_name) && relationships_desc[subject][relationship_name].has_key?(:solr_fq)
|
310
|
+
solr_fq = relationships_desc[subject][relationship_name][:solr_fq]
|
311
|
+
unless query.empty?
|
312
|
+
#substitute in the filter query for each pid so that it is applied to each in the query
|
313
|
+
query_parts = query.split(/OR/)
|
314
|
+
query = ""
|
315
|
+
query_parts.each_with_index do |query_part,index|
|
316
|
+
query_part.strip!
|
317
|
+
query << " OR " if index > 0
|
318
|
+
query << "(#{query_part} AND #{solr_fq})"
|
319
|
+
end
|
320
|
+
else
|
321
|
+
query = solr_fq
|
322
|
+
end
|
323
|
+
end
|
324
|
+
query
|
325
|
+
end
|
326
|
+
|
327
|
+
|
328
|
+
# Returns a solr query for retrieving objects specified in an inbound relationship.
|
329
|
+
# This method is mostly used by internal method calls.
|
330
|
+
# It utilizes any solr_fq value defined within a relationship to attach a query filter
|
331
|
+
# on top of just the predicate being used. Because it is static it
|
332
|
+
# needs the pid of the object that has the inbound relationships passed in.
|
333
|
+
# If you are calling this method directly to get the query you should use the
|
334
|
+
# ActiveFedora::SemanticNode.relationship_query instead or use the helper method
|
335
|
+
# [relationship_name]_query, i.e. method "parts_query" for relationship "parts". This
|
336
|
+
# method would only be called directly if you were working only with Solr and already
|
337
|
+
# had the pid for the object in something like a solr document.
|
338
|
+
# @param [String] The pid for the object that has these inbound relationships
|
339
|
+
# @param [String] The name of the relationship defined in the model
|
340
|
+
# @return [String]
|
341
|
+
# @example
|
342
|
+
# Class SampleAFObjRelationshipFilterQuery < ActiveFedora::Base
|
343
|
+
# #returns all parts
|
344
|
+
# has_relationship "parts", :is_part_of, :inbound=>true
|
345
|
+
# #returns only parts that have level to "series"
|
346
|
+
# has_relationship "series_parts", :is_part_of, :inbound=>true, :solr_fq=>"level_t:series"
|
347
|
+
# end
|
348
|
+
# s = SampleAFObjRelationshipFilterQuery.new
|
349
|
+
# s.pid
|
350
|
+
# #=> id:changeme:13020
|
351
|
+
# s.series_parts_query
|
352
|
+
# #=> "is_part_of_s:info\\:fedora/changeme\\:13021 AND level_t:series"
|
353
|
+
# SampleAFObjRelationshipFilterQuery.inbound_relationship_query(s.pid,"series_parts")
|
354
|
+
# #=> "is_part_of_s:info\\:fedora/changeme\\:13021 AND level_t:series"
|
355
|
+
def inbound_relationship_query(pid,relationship_name)
|
356
|
+
query = ""
|
357
|
+
subject = :inbound
|
358
|
+
if relationships_desc.has_key?(subject) && relationships_desc[subject].has_key?(relationship_name)
|
359
|
+
predicate = relationships_desc[subject][relationship_name][:predicate]
|
360
|
+
internal_uri = "info:fedora/#{pid}"
|
361
|
+
escaped_uri = internal_uri.gsub(/(:)/, '\\:')
|
362
|
+
query = "#{predicate}_s:#{escaped_uri}"
|
363
|
+
if relationships_desc.has_key?(subject) && relationships_desc[subject].has_key?(relationship_name) && relationships_desc[subject][relationship_name].has_key?(:solr_fq)
|
364
|
+
solr_fq = relationships_desc[subject][relationship_name][:solr_fq]
|
365
|
+
query << " AND " unless query.empty?
|
366
|
+
query << solr_fq
|
367
|
+
end
|
368
|
+
end
|
369
|
+
query
|
370
|
+
end
|
371
|
+
|
372
|
+
# Returns a solr query for retrieving objects specified in a bidirectional relationship.
|
373
|
+
# This method is mostly used by internal method calls.
|
374
|
+
# It usea of solr_fq value defined within a relationship to attach a query filter
|
375
|
+
# on top of just the predicate being used. Because it is static it
|
376
|
+
# needs the pids defined within RELS-EXT for the outbound relationship as well as the pid of the
|
377
|
+
# object for the inbound portion of the relationship.
|
378
|
+
# If you are calling this method directly to get the query you should use the
|
379
|
+
# ActiveFedora::SemanticNode.relationship_query instead or use the helper method
|
380
|
+
# [relationship_name]_query, i.e. method "bi_parts_query" for relationship "bi_parts". This
|
381
|
+
# method would only be called directly if you had something like an array of outbound pids
|
382
|
+
# already in something like a solr document for object that has these relationships.
|
383
|
+
# @param [String] The pid for the object that has these inbound relationships
|
384
|
+
# @param [String] The name of the relationship defined in the model
|
385
|
+
# @param [Array] An array of pids to include in the query
|
386
|
+
# @return [String]
|
387
|
+
# @example
|
388
|
+
# Class SampleAFObjRelationshipFilterQuery < ActiveFedora::Base
|
389
|
+
# has_bidirectional_relationship "bi_series_parts", :has_part, :is_part_of, :solr_fq=>"level_t:series"
|
390
|
+
# end
|
391
|
+
# s = SampleAFObjRelationshipFilterQuery.new
|
392
|
+
# obj = ActiveFedora::Base.new
|
393
|
+
# s.bi_series_parts_append(obj)
|
394
|
+
# s.pid
|
395
|
+
# #=> "changeme:13025"
|
396
|
+
# obj.pid
|
397
|
+
# #=> id:changeme:13026
|
398
|
+
# s.bi_series_parts_query
|
399
|
+
# #=> "(id:changeme\\:13026 AND level_t:series) OR (is_part_of_s:info\\:fedora/changeme\\:13025 AND level_t:series)"
|
400
|
+
# SampleAFObjRelationshipFilterQuery.bidirectional_relationship_query(s.pid,"series_parents",["id:changeme:13026"])
|
401
|
+
# #=> "(id:changeme\\:13026 AND level_t:series) OR (is_part_of_s:info\\:fedora/changeme\\:13025 AND level_t:series)"
|
402
|
+
def bidirectional_relationship_query(pid,relationship_name,outbound_pids)
|
403
|
+
outbound_query = outbound_relationship_query("#{relationship_name}_outbound",outbound_pids)
|
404
|
+
inbound_query = inbound_relationship_query(pid,"#{relationship_name}_inbound")
|
405
|
+
query = outbound_query # use outbound_query by default
|
406
|
+
if !inbound_query.empty?
|
407
|
+
query << " OR (" + inbound_relationship_query(pid,"#{relationship_name}_inbound") + ")"
|
408
|
+
end
|
409
|
+
return query
|
410
|
+
end
|
411
|
+
|
412
|
+
|
413
|
+
# Internal method that ensures a relationship subject such as :self and :inbound
|
414
|
+
# exist within the relationships_desc hash tracking relationships metadata.
|
415
|
+
# @param [Symbol] Subject name to register (will probably be something like :self or :inbound)
|
416
|
+
def register_relationship_desc_subject(subject)
|
417
|
+
unless relationships_desc.has_key?(subject)
|
418
|
+
relationships_desc[subject] = {}
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
# Internal method that adds relationship name and predicate pair to either an outbound (:self)
|
423
|
+
# or inbound (:inbound) relationship types. Refer to ActiveFedora::SemanticNode.has_relationship for information on what metadata will be persisted.
|
424
|
+
# @param [Symbol] Subject name to register
|
425
|
+
# @param [String] Name of relationship being registered
|
426
|
+
# @param [Symbol] Fedora ontology predicate to use
|
427
|
+
# @param [Hash] Any options passed to has_relationship such as :type, :solr_fq, etc.
|
428
|
+
def register_relationship_desc(subject, name, predicate, opts={})
|
429
|
+
register_relationship_desc_subject(subject)
|
430
|
+
opts.merge!({:predicate=>predicate})
|
431
|
+
relationships_desc[subject][name] = opts
|
432
|
+
end
|
433
|
+
|
434
|
+
# relationships are tracked as a hash of structure
|
435
|
+
# @example
|
436
|
+
# ds.relationships # => {:self=>{:has_model=>["afmodel:SimpleThing"],:has_part=>["demo:20"]},:inbound=>{:is_part_of=>["demo:6"]}
|
437
|
+
def relationships
|
438
|
+
@class_relationships ||= Hash[:self => {}]
|
439
|
+
end
|
440
|
+
|
441
|
+
|
442
|
+
def register_subject(subject)
|
443
|
+
if !relationships.has_key?(subject)
|
444
|
+
relationships[subject] = {}
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
def register_predicate(subject, predicate)
|
449
|
+
register_subject(subject)
|
450
|
+
if !relationships[subject].has_key?(predicate)
|
451
|
+
relationships[subject][predicate] = []
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
# Allows for a relationship to be treated like any other attribute of a model class. You define
|
456
|
+
# relationships in your model class using this method. You then have access to several
|
457
|
+
# helper methods to list, append, and remove objects from the list of relationships.
|
458
|
+
# ====Examples to define two relationships
|
459
|
+
# class AudioRecord < ActiveFedora::Base
|
460
|
+
#
|
461
|
+
# has_relationship "oral_history", :has_part, :inbound=>true, :type=>OralHistory
|
462
|
+
# # returns all similar audio
|
463
|
+
# has_relationship "similar_audio", :has_part, :type=>AudioRecord
|
464
|
+
# #returns only similar audio with format wav
|
465
|
+
# has_relationship "similar_audio_wav", :has_part, :solr_fq=>"format_t:wav"
|
466
|
+
#
|
467
|
+
# The first two parameters are required:
|
468
|
+
# name: relationship name
|
469
|
+
# predicate: predicate for the relationship
|
470
|
+
# opts:
|
471
|
+
# possible parameters
|
472
|
+
# :inbound => if true loads an external relationship via Solr (defaults to false)
|
473
|
+
# :type => The type of model to use when instantiated an object from the pid in this relationship (defaults to ActiveFedora::Base)
|
474
|
+
# :solr_fq => Define a solr query here if you want to filter out some objects in your relationship (must be a properly formatted solr query)
|
475
|
+
#
|
476
|
+
# If inbound is true it expects the relationship to be defined by another object's RELS-EXT
|
477
|
+
# and to load that relationship from Solr. Otherwise, if inbound is true the relationship is stored in
|
478
|
+
# this object's RELS-EXT datastream
|
479
|
+
#
|
480
|
+
# Word of caution - The same predicate may not be used twice for two inbound or two outbound relationships. However, it may be used twice if one is inbound
|
481
|
+
# and one is outbound as shown in the example above. A full list of possible predicates are defined by predicate_mappings
|
482
|
+
#
|
483
|
+
# For the oral_history relationship in the example above the following helper methods are created:
|
484
|
+
# oral_history: returns array of OralHistory objects that have this AudioRecord with predicate :has_part
|
485
|
+
# oral_history_ids: Return array of pids for OralHistory objects that have this AudioRecord with predicate :has_part
|
486
|
+
# oral_history_query: Return solr query that can be used to retrieve related objects as solr documents
|
487
|
+
#
|
488
|
+
# For the outbound relationship "similar_audio" there are two additional methods to append and remove objects from that relationship
|
489
|
+
# since it is managed internally:
|
490
|
+
# similar_audio: Return array of AudioRecord objects that have been added to similar_audio relationship
|
491
|
+
# similar_audio_ids: Return array of AudioRecord object pids that have been added to similar_audio relationship
|
492
|
+
# similar_audio_query: Return solr query that can be used to retrieve related objects as solr documents
|
493
|
+
# similar_audio_append: Add an AudioRecord object to the similar_audio relationship
|
494
|
+
# similar_audio_remove: Remove an AudioRecord from the similar_audio relationship
|
495
|
+
def has_relationship(name, predicate, opts = {})
|
496
|
+
opts = {:singular => nil, :inbound => false}.merge(opts)
|
497
|
+
if opts[:inbound] == true
|
498
|
+
register_relationship_desc(:inbound, name, predicate, opts)
|
499
|
+
register_predicate(:inbound, predicate)
|
500
|
+
create_inbound_relationship_finders(name, predicate, opts)
|
501
|
+
else
|
502
|
+
#raise "Duplicate use of predicate for named outbound relationship \"#{predicate.inspect}\" not allowed" if named_predicate_exists_with_different_name?(:self,name,predicate)
|
503
|
+
register_relationship_desc(:self, name, predicate, opts)
|
504
|
+
register_predicate(:self, predicate)
|
505
|
+
create_relationship_name_methods(name)
|
506
|
+
create_outbound_relationship_finders(name, predicate, opts)
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
# Used in has_relationship call to create dynamic helper methods to
|
511
|
+
# append and remove objects to and from a relationship
|
512
|
+
# @param [String] relationship name to create helper methods for
|
513
|
+
# @example
|
514
|
+
# For the following relationship
|
515
|
+
#
|
516
|
+
# has_relationship "audio_records", :has_part, :type=>AudioRecord
|
517
|
+
#
|
518
|
+
# Methods audio_records_append and audio_records_remove are created.
|
519
|
+
# Boths methods take an object that is kind_of? ActiveFedora::Base as a parameter
|
520
|
+
def create_relationship_name_methods(name)
|
521
|
+
append_method_name = "#{name.to_s.downcase}_append"
|
522
|
+
remove_method_name = "#{name.to_s.downcase}_remove"
|
523
|
+
self.send(:define_method,:"#{append_method_name}") {|object| add_relationship_by_name(name,object)}
|
524
|
+
self.send(:define_method,:"#{remove_method_name}") {|object| remove_relationship_by_name(name,object)}
|
525
|
+
end
|
526
|
+
|
527
|
+
|
528
|
+
# Generates relationship finders for predicates that point in both directions
|
529
|
+
#
|
530
|
+
# @param [String] name of the relationship method(s) to create
|
531
|
+
# @param [Symbol] outbound_predicate Predicate used in outbound relationships
|
532
|
+
# @param [Symbol] inbound_predicate Predicate used in inbound relationships
|
533
|
+
# @param [Hash] opts
|
534
|
+
#
|
535
|
+
# Example:
|
536
|
+
# has_bidirectional_relationship("parts", :has_part, :is_part_of)
|
537
|
+
#
|
538
|
+
# will create three instance methods: parts_outbound, and parts_inbound and parts
|
539
|
+
# the inbound and outbound methods are the same that would result from calling
|
540
|
+
# create_inbound_relationship_finders and create_outbound_relationship_finders
|
541
|
+
# The third method combines the results of both and handles generating appropriate
|
542
|
+
# solr queries where necessary.
|
543
|
+
def has_bidirectional_relationship(name, outbound_predicate, inbound_predicate, opts={})
|
544
|
+
create_bidirectional_relationship_finders(name, outbound_predicate, inbound_predicate, opts)
|
545
|
+
end
|
546
|
+
|
547
|
+
def create_inbound_relationship_finders(name, predicate, opts = {})
|
548
|
+
class_eval <<-END, __FILE__, __LINE__
|
549
|
+
def #{name}(opts={})
|
550
|
+
load_inbound_relationship('#{name}', '#{predicate}', opts)
|
551
|
+
end
|
552
|
+
def #{name}_ids
|
553
|
+
#{name}(:response_format => :id_array)
|
554
|
+
end
|
555
|
+
def #{name}_from_solr
|
556
|
+
#{name}(:response_format => :load_from_solr)
|
557
|
+
end
|
558
|
+
def #{name}_query
|
559
|
+
relationship_query("#{name}")
|
560
|
+
end
|
561
|
+
END
|
562
|
+
end
|
563
|
+
|
564
|
+
def create_outbound_relationship_finders(name, predicate, opts = {})
|
565
|
+
class_eval <<-END, __FILE__, __LINE__
|
566
|
+
def #{name}(opts={})
|
567
|
+
load_outbound_relationship(#{name.inspect}, #{predicate.inspect}, opts)
|
568
|
+
end
|
569
|
+
def #{name}_ids
|
570
|
+
#{name}(:response_format => :id_array)
|
571
|
+
end
|
572
|
+
def #{name}_from_solr
|
573
|
+
#{name}(:response_format => :load_from_solr)
|
574
|
+
end
|
575
|
+
def #{name}_query
|
576
|
+
relationship_query("#{name}")
|
577
|
+
end
|
578
|
+
END
|
579
|
+
end
|
580
|
+
|
581
|
+
|
582
|
+
|
583
|
+
# Generates relationship finders for predicates that point in both directions
|
584
|
+
# and registers predicate relationships for each direction.
|
585
|
+
#
|
586
|
+
# @param [String] name Name of the relationship method(s) to create
|
587
|
+
# @param [Symbol] outbound_predicate Predicate used in outbound relationships
|
588
|
+
# @param [Symbol] inbound_predicate Predicate used in inbound relationships
|
589
|
+
# @param [Hash] opts (optional)
|
590
|
+
def create_bidirectional_relationship_finders(name, outbound_predicate, inbound_predicate, opts={})
|
591
|
+
inbound_method_name = name.to_s+"_inbound"
|
592
|
+
outbound_method_name = name.to_s+"_outbound"
|
593
|
+
has_relationship(outbound_method_name, outbound_predicate, opts)
|
594
|
+
has_relationship(inbound_method_name, inbound_predicate, opts.merge!(:inbound=>true))
|
595
|
+
|
596
|
+
#create methods that mirror the outbound append and remove with our bidirectional name, assume just add and remove locally
|
597
|
+
create_bidirectional_relationship_name_methods(name,outbound_method_name)
|
598
|
+
|
599
|
+
class_eval <<-END, __FILE__, __LINE__
|
600
|
+
def #{name}(opts={})
|
601
|
+
load_bidirectional("#{name}", :#{inbound_method_name}, :#{outbound_method_name}, opts)
|
602
|
+
end
|
603
|
+
def #{name}_ids
|
604
|
+
#{name}(:response_format => :id_array)
|
605
|
+
end
|
606
|
+
def #{name}_from_solr
|
607
|
+
#{name}(:response_format => :load_from_solr)
|
608
|
+
end
|
609
|
+
def #{name}_query
|
610
|
+
relationship_query("#{name}")
|
611
|
+
end
|
612
|
+
END
|
613
|
+
end
|
614
|
+
|
615
|
+
# Similar to +create_relationship_name_methods+ except it is used when an ActiveFedora::Base model class
|
616
|
+
# declares has_bidirectional_relationship. we are merely creating an alias for outbound portion of bidirectional
|
617
|
+
# @param [String] bidirectional relationship name
|
618
|
+
# @param [String] outbound relationship method name associated with the bidirectional relationship ([bidirectional_name]_outbound)
|
619
|
+
# @example
|
620
|
+
# has_bidirectional_relationship "members", :has_collection_member, :is_member_of_collection
|
621
|
+
#
|
622
|
+
# Method members_outbound_append and members_outbound_remove added
|
623
|
+
# This method will create members_append which does same thing as members_outbound_append
|
624
|
+
# and will create members_remove which does same thing as members_outbound_remove
|
625
|
+
def create_bidirectional_relationship_name_methods(name,outbound_name)
|
626
|
+
append_method_name = "#{name.to_s.downcase}_append"
|
627
|
+
remove_method_name = "#{name.to_s.downcase}_remove"
|
628
|
+
self.send(:define_method,:"#{append_method_name}") {|object| add_relationship_by_name(outbound_name,object)}
|
629
|
+
self.send(:define_method,:"#{remove_method_name}") {|object| remove_relationship_by_name(outbound_name,object)}
|
630
|
+
end
|
631
|
+
|
632
|
+
end
|
633
|
+
end
|
634
|
+
end
|