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.
Files changed (34) hide show
  1. data/Gemfile.lock +1 -1
  2. data/History.txt +3 -1
  3. data/lib/active_fedora.rb +3 -3
  4. data/lib/active_fedora/associations.rb +0 -2
  5. data/lib/active_fedora/associations/association_collection.rb +15 -1
  6. data/lib/active_fedora/associations/belongs_to_association.rb +5 -1
  7. data/lib/active_fedora/associations/has_and_belongs_to_many_association.rb +5 -1
  8. data/lib/active_fedora/base.rb +36 -92
  9. data/lib/active_fedora/datastream.rb +1 -2
  10. data/lib/active_fedora/file_management.rb +73 -0
  11. data/lib/active_fedora/metadata_datastream_helper.rb +3 -1
  12. data/lib/active_fedora/model.rb +6 -18
  13. data/lib/active_fedora/relationships.rb +634 -0
  14. data/lib/active_fedora/samples/special_thing.rb +4 -4
  15. data/lib/active_fedora/semantic_node.rb +97 -236
  16. data/lib/active_fedora/version.rb +1 -1
  17. data/spec/integration/base_file_management_spec.rb +1 -0
  18. data/spec/integration/base_spec.rb +114 -68
  19. data/spec/integration/full_featured_model_spec.rb +0 -1
  20. data/spec/integration/mods_article_integration_spec.rb +0 -1
  21. data/spec/integration/nokogiri_datastream_spec.rb +0 -1
  22. data/spec/integration/rels_ext_datastream_spec.rb +10 -7
  23. data/spec/integration/semantic_node_spec.rb +10 -16
  24. data/spec/samples/models/hydrangea_article.rb +1 -2
  25. data/spec/samples/oral_history_sample_model.rb +1 -1
  26. data/spec/unit/base_spec.rb +26 -16
  27. data/spec/unit/metadata_datastream_spec.rb +1 -0
  28. data/spec/unit/qualified_dublin_core_datastream_spec.rb +1 -0
  29. data/spec/unit/relationship_spec.rb +1 -0
  30. data/spec/unit/relationships_spec.rb +846 -0
  31. data/spec/unit/semantic_node_spec.rb +2 -338
  32. metadata +8 -7
  33. data/lib/active_fedora/relationships_helper.rb +0 -881
  34. data/spec/unit/relationships_helper_spec.rb +0 -800
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active-fedora (3.2.0.pre1)
4
+ active-fedora (3.2.0.pre2)
5
5
  activeresource (>= 3.0.0)
6
6
  activesupport (>= 3.0.0)
7
7
  equivalent-xml
@@ -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
@@ -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
- #autoload :Relationship
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
- @owner.load_inbound_relationship(@reflection.name.to_s, @reflection.options[:property])
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.load_outbound_relationship(@reflection.name.to_s, @reflection.options[:property]).first
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.load_outbound_relationship(@reflection.name.to_s, @reflection.options[:property])
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
 
@@ -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
- def self.inherited(p)
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
- self.content = self.to_xml ##TODO only do this when the xml will have changed to avoid a load of the datastream content.
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:
@@ -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
- results = hits.map do |hit|
119
- obj = RubydoraConnection.instance.find_model(hit[SOLR_DOCUMENT_ID], self)
120
- end
121
- if return_multiple == true
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 results.first
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
- results = ActiveFedora::SolrService.instance.conn.query(query,query_opts)
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