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.
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