active-fedora 3.2.0.pre1 → 3.2.0.pre2
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.
- 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
|