active-fedora 2.2.0 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.gitignore +30 -0
  2. data/.gitmodules +3 -0
  3. data/.rvmrc +33 -0
  4. data/CONSOLE_GETTING_STARTED.textile +337 -0
  5. data/Gemfile +6 -1
  6. data/Gemfile.lock +39 -23
  7. data/NOKOGIRI_DATASTREAMS.textile +107 -0
  8. data/README.textile +41 -17
  9. data/Rakefile +5 -30
  10. data/active-fedora.gemspec +34 -496
  11. data/lib/active_fedora.rb +6 -1
  12. data/lib/active_fedora/base.rb +7 -5
  13. data/lib/active_fedora/datastream.rb +9 -8
  14. data/lib/active_fedora/metadata_datastream.rb +10 -3
  15. data/lib/active_fedora/model.rb +8 -4
  16. data/lib/active_fedora/nokogiri_datastream.rb +30 -24
  17. data/lib/active_fedora/qualified_dublin_core_datastream.rb +3 -2
  18. data/lib/active_fedora/rels_ext_datastream.rb +14 -5
  19. data/lib/active_fedora/samples.rb +3 -0
  20. data/lib/active_fedora/samples/hydra-mods_article_datastream.rb +517 -0
  21. data/lib/active_fedora/samples/hydra-rights_metadata_datastream.rb +206 -0
  22. data/lib/active_fedora/samples/marpa-dc_datastream.rb +97 -0
  23. data/lib/active_fedora/samples/special_thing.rb +45 -0
  24. data/lib/active_fedora/semantic_node.rb +16 -13
  25. data/lib/active_fedora/version.rb +3 -0
  26. data/lib/fedora/base.rb +5 -5
  27. data/lib/fedora/datastream.rb +1 -1
  28. data/lib/fedora/fedora_object.rb +1 -1
  29. data/lib/fedora/repository.rb +4 -0
  30. data/lib/tasks/active_fedora.rake +126 -0
  31. data/lib/tasks/active_fedora_dev.rake +127 -0
  32. data/solr/conf/schema.xml +278 -0
  33. data/solr/conf/solrconfig.xml +840 -0
  34. data/spec/integration/full_featured_model_spec.rb +2 -2
  35. data/spec/integration/mods_article_integration_spec.rb +2 -2
  36. data/spec/integration/nokogiri_datastream_spec.rb +2 -2
  37. data/spec/rcov.opts +2 -0
  38. data/spec/samples/models/hydrangea_article.rb +12 -0
  39. data/spec/spec_helper.rb +1 -1
  40. data/spec/unit/nokogiri_datastream_spec.rb +10 -7
  41. metadata +189 -886
  42. data/NG_XML_DATASTREAM.textile +0 -25
  43. data/USING_OM_DATASTREAMS.textile +0 -60
  44. data/VERSION +0 -1
  45. data/lib/hydra.rb +0 -2
  46. data/lib/hydra/sample_mods_datastream.rb +0 -63
  47. data/tasks/hoe.rake +0 -0
  48. data/tasks/rspec.rake +0 -29
@@ -0,0 +1,206 @@
1
+ module Hydra
2
+ # This is an example of a NokogiriDatastream that defines a terminology for Hydra rightsMetadata xml
3
+ # The documentation for Hydra rightsMetadata is on the Hydra wiki at https://wiki.duraspace.org/display/hydra/Hydra+rights+metadata
4
+ # The real version of this Terminology is part of the hydra-head plugin. See https://github.com/projecthydra/hydra-head/blob/master/lib/hydra/rights_metadata.rb
5
+ #
6
+ # The purpose of rightsMetadata is to store information about access controls and licensing for the object that the rightsMetadata is attached to
7
+ #
8
+ # The most interesting thing going on in this Class is the use of custom methods to access and update permissions in intuitive ways.
9
+ # These methods allow you to do things like
10
+ # * Get reports on which groups & individuals have which permissions
11
+ # * Update permissions for individuals & groups with straightforward syntax
12
+ #
13
+ # Another interesting thing in this Class: it extends to_solr, first calling "super" (the default to_solr behavior) and then inserting an additional embargo_release_date_dt field
14
+ # It uses Solrizer::Extractor.insert_solr_field_value to do this. That method handles inserting new values into a Hash while ensuring that you don't destroy or overwrite any existing values in the hash.
15
+ class RightsMetadataDatastream < ActiveFedora::NokogiriDatastream
16
+
17
+ set_terminology do |t|
18
+ t.root(:path=>"rightsMetadata", :xmlns=>"http://hydra-collab.stanford.edu/schemas/rightsMetadata/v1", :schema=>"http://github.com/projecthydra/schemas/tree/v1/rightsMetadata.xsd")
19
+ t.copyright {
20
+ t.machine {
21
+ t.cclicense
22
+ }
23
+ t.human_readable(:path=>"human")
24
+ t.cclicense(:proxy=>[:machine, :cclicense ])
25
+ }
26
+ t.access {
27
+ t.human_readable(:path=>"human")
28
+ t.machine {
29
+ t.group
30
+ t.person
31
+ }
32
+ t.person(:proxy=>[:machine, :person])
33
+ t.group(:proxy=>[:machine, :group])
34
+ # accessor :access_person, :term=>[:access, :machine, :person]
35
+ }
36
+ t.discover_access(:ref=>[:access], :attributes=>{:type=>"discover"})
37
+ t.read_access(:ref=>[:access], :attributes=>{:type=>"read"})
38
+ t.edit_access(:ref=>[:access], :attributes=>{:type=>"edit"})
39
+ # A bug in OM prevnts us from declaring proxy terms at the root of a Terminology
40
+ # t.access_person(:proxy=>[:access,:machine,:person])
41
+ # t.access_group(:proxy=>[:access,:machine,:group])
42
+
43
+ t.embargo {
44
+ t.human_readable(:path=>"human")
45
+ t.machine{
46
+ t.date(:type =>"release")
47
+ }
48
+ t.embargo_release_date(:proxy => [:machine, :date])
49
+ }
50
+ end
51
+
52
+ # Generates an empty Mods Article (used when you call ModsArticle.new without passing in existing xml)
53
+ def self.xml_template
54
+ builder = Nokogiri::XML::Builder.new do |xml|
55
+ xml.rightsMetadata(:version=>"0.1", "xmlns"=>"http://hydra-collab.stanford.edu/schemas/rightsMetadata/v1") {
56
+ xml.copyright {
57
+ xml.human
58
+ xml.machine {
59
+ xml.uvalicense "no"
60
+ }
61
+ }
62
+ xml.access(:type=>"discover") {
63
+ xml.human
64
+ xml.machine
65
+ }
66
+ xml.access(:type=>"read") {
67
+ xml.human
68
+ xml.machine
69
+ }
70
+ xml.access(:type=>"edit") {
71
+ xml.human
72
+ xml.machine
73
+ }
74
+ xml.embargo{
75
+ xml.human
76
+ xml.machine
77
+ }
78
+ }
79
+ end
80
+ return builder.doc
81
+ end
82
+
83
+ # Returns the permissions for the selected person/group
84
+ # If new_access_level is provided, updates the selected person/group access_level to the one specified
85
+ # A new_access_level of "none" will remove all access_levels for the selected person/group
86
+ # @param [Hash] selector
87
+ # @param [String] new_access_level (default nil)
88
+ # @return [Hash]
89
+ #
90
+ # @example Query permissions for person123, Set the permissions to "read", then query again to see that they have changed.
91
+ # permissions({:person=>"person123"})
92
+ # => {"person123"=>"edit"}
93
+ # permissions({:person=>"person123"}, "read")
94
+ # => {"person123"=>"read"}
95
+ # permissions({:person=>"person123"})
96
+ # => {"person123"=>"read"}
97
+ def permissions(selector, new_access_level=nil)
98
+
99
+ type = selector.keys.first.to_sym
100
+ actor = selector.values.first
101
+ if new_access_level.nil?
102
+ xpath = self.class.terminology.xpath_for(:access, type, actor)
103
+ nodeset = self.find_by_terms(xpath)
104
+ if nodeset.empty?
105
+ return "none"
106
+ else
107
+ return nodeset.first.ancestors("access").first.attributes["type"].text
108
+ end
109
+ else
110
+ remove_all_permissions(selector)
111
+ unless new_access_level == "none"
112
+ access_type_symbol = "#{new_access_level}_access".to_sym
113
+ result = self.update_values([access_type_symbol, type] => {"-1"=>actor})
114
+ end
115
+ self.dirty = true
116
+ return new_access_level
117
+ end
118
+
119
+ end
120
+
121
+ # Reports on which groups have which permissions
122
+ # @return [Hash]
123
+ # @example
124
+ # sample_ds.permissions({"group"=>"group_zzz"}, "edit")
125
+ # sample_ds.permissions({"group"=>"public"}, "discover")
126
+ # sample_ds.groups #=> {"public"=>"discover", "group_zzz"=>"edit"}
127
+ def groups
128
+ return quick_search_by_type(:group)
129
+ end
130
+
131
+ # Reports on which groups have which permissions
132
+ # @return [Hash]
133
+ # @example
134
+ # sample_ds.permissions({"person"=>"person_123"}, "read")
135
+ # sample_ds.permissions({""person"=>"person_456"}, "edit")
136
+ # sample_ds.individuals #=> {"person_123"=>"read", "person_456"=>"edit"}
137
+ def individuals
138
+ return quick_search_by_type(:person)
139
+ end
140
+
141
+ # Updates permissions for all of the persons and groups in a hash
142
+ # @param [Hash] params example: {"group"=>{"group1"=>"discover","group2"=>"edit"}, "person"=>{"person1"=>"read","person2"=>"discover"}}
143
+ # Currently restricts actor type to group or person. Any others will be ignored
144
+ def update_permissions(params)
145
+ params.fetch("group", {}).each_pair {|group_id, access_level| self.permissions({"group"=>group_id}, access_level)}
146
+ params.fetch("person", {}).each_pair {|group_id, access_level| self.permissions({"person"=>group_id}, access_level)}
147
+ end
148
+
149
+ # This method limits the response to known access levels (:discover, :read, :edit). Probably runs a bit faster than {#permissions}.
150
+ # @param [:group,:person] type
151
+ # @return [Hash]
152
+ def quick_search_by_type(type)
153
+ result = {}
154
+ [{:discover_access=>"discover"},{:read_access=>"read"},{:edit_access=>"edit"}].each do |access_levels_hash|
155
+ access_level = access_levels_hash.keys.first
156
+ access_level_name = access_levels_hash.values.first
157
+ self.find_by_terms(*[access_level, type]).each do |entry|
158
+ result[entry.text] = access_level_name
159
+ end
160
+ end
161
+ return result
162
+ end
163
+
164
+ attr_reader :embargo_release_date
165
+ def embargo_release_date=(release_date)
166
+ release_date = release_date.to_s if release_date.is_a? Date
167
+ begin
168
+ Date.parse(release_date)
169
+ rescue
170
+ return "INVALID DATE"
171
+ end
172
+ self.update_values({[:embargo,:machine,:date]=>release_date})
173
+ end
174
+ def embargo_release_date(opts={})
175
+ embargo_release_date = self.find_by_terms(*[:embargo,:machine,:date]).first ? self.find_by_terms(*[:embargo,:machine,:date]).first.text : nil
176
+ if opts[:format] && opts[:format] == :solr_date
177
+ embargo_release_date << "T23:59:59Z"
178
+ end
179
+ embargo_release_date
180
+ end
181
+ def under_embargo?
182
+ (embargo_release_date && Date.today < embargo_release_date.to_date) ? true : false
183
+ end
184
+
185
+ def to_solr(solr_doc=Hash.new)
186
+ super(solr_doc)
187
+ ::Solrizer::Extractor.insert_solr_field_value(solr_doc, "embargo_release_date_dt", embargo_release_date(:format=>:solr_date)) if embargo_release_date
188
+ solr_doc
189
+ end
190
+
191
+
192
+
193
+
194
+
195
+ private
196
+ # Purge all access given group/person
197
+ def remove_all_permissions(selector)
198
+ type = selector.keys.first.to_sym
199
+ actor = selector.values.first
200
+ xpath = self.class.terminology.xpath_for(:access, type, actor)
201
+ nodes_to_purge = self.find_by_terms(xpath)
202
+ nodes_to_purge.each {|node| node.remove}
203
+ end
204
+
205
+ end
206
+ end
@@ -0,0 +1,97 @@
1
+ require "active-fedora"
2
+ module Marpa
3
+
4
+ # This is an example of a NokogiriDatastream that defines a terminology for Dublin Core xml
5
+ #
6
+ # Some things to observe about this Class:
7
+ # * Defines a couple of custom terms, tibetan_title and english_title, that map to dc:title with varying @language attributes
8
+ # * Indicates which terms should be indexed as facets using :index_as=>[:facetable]
9
+ # * Defines an xml template that is an empty dublin core xml document with three namespaces set
10
+ # * Sets the namespace using :xmlns argument on the root term
11
+ # * Does not override or extend to_solr, so the default solrization approach will be used (Solrizer::XML::TerminologyBasedSolrizer)
12
+ #
13
+ class DcDatastream < ActiveFedora::NokogiriDatastream
14
+
15
+ set_terminology do |t|
16
+ t.root(:path=>"dc", :xmlns=>'http://purl.org/dc/terms/')
17
+ t.tibetan_title(:path=>"title", :attributes=>{:language=>"tibetan"})
18
+ t.english_title(:path=>"title", :attributes=>{:language=>:none})
19
+ t.contributor(:index_as=>[:facetable])
20
+ t.coverage
21
+ t.creator
22
+ t.description
23
+ t.format
24
+ t.identifier
25
+ t.language(:index_as=>[:facetable])
26
+ t.publisher
27
+ t.relation
28
+ t.source
29
+ t.title
30
+ t.abstract
31
+ t.accessRights
32
+ t.accrualMethod
33
+ t.accrualPeriodicity
34
+ t.accrualPolicy
35
+ t.alternative
36
+ t.audience
37
+ t.available
38
+ t.bibliographicCitation
39
+ t.conformsTo
40
+ t.contributor
41
+ t.coverage
42
+ t.created
43
+ t.creator
44
+ t.date(:index_as=>[:facetable])
45
+ t.dateAccepted
46
+ t.dateCopyrighted
47
+ t.dateSubmitted
48
+ t.description
49
+ t.educationLevel
50
+ t.extent
51
+ t.format
52
+ t.hasFormat
53
+ t.hasPart
54
+ t.hasVersion
55
+ t.identifier
56
+ t.instructionalMethod
57
+ t.isFormatOf
58
+ t.isPartOf
59
+ t.isReferencedBy
60
+ t.isReplacedBy
61
+ t.isRequiredBy
62
+ t.issued
63
+ t.isVersionOf
64
+ t.language(:index_as=>[:facetable])
65
+ t.license
66
+ t.mediator
67
+ t.medium
68
+ t.modified
69
+ t.provenance
70
+ t.publisher
71
+ t.references
72
+ t.relation
73
+ t.replaces
74
+ t.requires
75
+ t.rights
76
+ t.rightsHolder
77
+ t.source
78
+ t.spatial(:index_as=>[:facetable])
79
+ t.subject(:index_as=>[:facetable])
80
+ t.tableOfContents
81
+ t.temporal
82
+ t.type
83
+ t.valid
84
+ end
85
+
86
+ def self.xml_template
87
+ builder = Nokogiri::XML::Builder.new do |xml|
88
+ xml.dc("xmlns"=>'http://purl.org/dc/terms/',
89
+ "xmlns:dcterms"=>'http://purl.org/dc/terms/',
90
+ "xmlns:xsi"=>'http://www.w3.org/2001/XMLSchema-instance') {
91
+ }
92
+ end
93
+ return builder.doc
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,45 @@
1
+ require "active-fedora"
2
+ require "active_fedora/samples/hydra-mods_article_datastream.rb"
3
+ require "active_fedora/samples/hydra-rights_metadata_datastream.rb"
4
+ require "active_fedora/samples/marpa-dc_datastream.rb"
5
+
6
+ # This is an example of an ActiveFedora Model
7
+ #
8
+ # Some of the datastream ids were chosen based on the Hydra modeling conventions. You don't have to follow them in your work. ActiveFedora itself has no notion of those conventions, but we do encourage you to use them.
9
+ #
10
+ # The Hydra conventions encourage you to have a datastream with this dsid whose contents are descriptive metadata like MODS or Dublin Core. They especially encourage MODS.
11
+ # The rightsMetadata datastream is also a convention provided by the Hydra Common Metadata "content model"
12
+ #
13
+ # For more info on the Hydra conventions, see the documentation on "Common Metadata content model" in https://wiki.duraspace.org/display/hydra/Hydra+content+models+and+disseminators
14
+ # Note that on the wiki, "content model" is often used to refer to Fedora CModels and/or abstract/notional models. The Common Metadata content model is an example of this.
15
+ # The wiki includes a page that attempts to shed some light on the question of "What is a content model?" https://wiki.duraspace.org/display/hydra/Don't+call+it+a+'content+model'!
16
+ class SpecialThing < ActiveFedora::Base
17
+
18
+ #
19
+ # DATASTREAMS
20
+ #
21
+
22
+ # This declares a datastream with Datastream ID (dsid) of "descMetadata"
23
+ # The descMetadata datastream is bound to the Hydra::ModsArticleDatastream class that's defined in lib/active_fedora/samples
24
+ # Any time you load a Fedora object using an instance of SampleModel, the instance will assume its descMetadata datastream conforms to the assumptions in Hydra::ModsArticleDatastream class
25
+ has_metadata :name => "descMetadata", :type=> Hydra::ModsArticleDatastream
26
+
27
+ # This declares a datastream with Datastream ID (dsid) of "rightsMetadata"
28
+ # Like the descMetadata datastream, any time you load a Fedora object using an instance of SampleModel, the instance will assume its descMetadata datastream conforms to the assumptions in Hydra::RightsMetadataDatastream class
29
+ has_metadata :name => "rightsMetadata", :type => Hydra::RightsMetadataDatastream
30
+
31
+ # This is not part of the Hydra conventions
32
+ # Adding an extra datastream called "extraMetadataForFun" that is bound to the Marpa::DcDatastream class
33
+ has_metadata :name => "extraMetadataForFun", :type => Marpa::DcDatastream
34
+
35
+ #
36
+ # RELATIONSHIPS
37
+ #
38
+
39
+ # This is an example of how you can add a custom relationship to a model
40
+ # This will allow you to call .derivations on instances of the model to get a list of all of the _outbound_ "hasDerivation" relationships in the RELS-EXT datastream
41
+ has_relationship "derivations", :has_derivation
42
+
43
+ # This will allow you to call .inspirations on instances of the model to get a list of all of the objects that assert "hasDerivation" relationships pointing at this object
44
+ has_relationship "inspirations", :has_derivation, :inbound => true
45
+ end
@@ -394,7 +394,8 @@ module ActiveFedora
394
394
  end
395
395
 
396
396
  # Creates a RELS-EXT datastream for insertion into a Fedora Object
397
- # @pid
397
+ # @param [String] pid
398
+ # @param [Hash] relationships (optional) @default self.relationships
398
399
  # Note: This method is implemented on SemanticNode instead of RelsExtDatastream because SemanticNode contains the relationships array
399
400
  def to_rels_ext(pid, relationships=self.relationships)
400
401
  starter_xml = <<-EOL
@@ -480,10 +481,10 @@ module ActiveFedora
480
481
 
481
482
  # Generates relationship finders for predicates that point in both directions
482
483
  #
483
- # @name Name of the relationship method(s) to create
484
- # @outbound_predicate Predicate used in outbound relationships
485
- # @inbound_predicate Predicate used in inbound relationships
486
- # @opts
484
+ # @param [String] name of the relationship method(s) to create
485
+ # @param [Symbol] outbound_predicate Predicate used in outbound relationships
486
+ # @param [Symbol] inbound_predicate Predicate used in inbound relationships
487
+ # @param [Hash] opts
487
488
  #
488
489
  # Example:
489
490
  # has_bidirectional_relationship("parts", :has_part, :is_part_of)
@@ -646,11 +647,10 @@ module ActiveFedora
646
647
  # Generates relationship finders for predicates that point in both directions
647
648
  # and registers predicate relationships for each direction.
648
649
  #
649
- # @name Name of the relationship method(s) to create
650
- # @outbound_predicate Predicate used in outbound relationships
651
- # @inbound_predicate Predicate used in inbound relationships
652
- # @opts
653
- #
650
+ # @param [String] name Name of the relationship method(s) to create
651
+ # @param [Symbol] outbound_predicate Predicate used in outbound relationships
652
+ # @param [Symbol] inbound_predicate Predicate used in inbound relationships
653
+ # @param [Hash] opts (optional)
654
654
  def create_bidirectional_relationship_finders(name, outbound_predicate, inbound_predicate, opts={})
655
655
  inbound_method_name = name.to_s+"_inbound"
656
656
  outbound_method_name = name.to_s+"_outbound"
@@ -693,7 +693,9 @@ module ActiveFedora
693
693
  END
694
694
  end
695
695
 
696
- # relationships are tracked as a hash of structure {subject => {predicate => [object]}}
696
+ # relationships are tracked as a hash of structure
697
+ # @example
698
+ # ds.relationships # => {:self=>{:has_model=>["afmodel:SimpleThing"],:has_part=>["demo:20"]},:inbound=>{:is_part_of=>["demo:6"]}
697
699
  def relationships
698
700
  @class_relationships ||= Hash[:self => {}]
699
701
  end
@@ -715,7 +717,8 @@ module ActiveFedora
715
717
  #alias_method :register_target, :register_object
716
718
 
717
719
  # Creates a RELS-EXT datastream for insertion into a Fedora Object
718
- # @pid
720
+ # @param [String] pid of the object that the RELS-EXT datastream belongs to
721
+ # @param [Hash] relationships the relationships hash to transform into RELS-EXT RDF. @default the object's relationships hash
719
722
  # Note: This method is implemented on SemanticNode instead of RelsExtDatastream because SemanticNode contains the relationships array
720
723
  def relationships_to_rels_ext(pid, relationships=self.relationships)
721
724
  starter_xml = <<-EOL
@@ -738,7 +741,7 @@ module ActiveFedora
738
741
 
739
742
  # If predicate is a symbol, looks up the predicate in the predicate_mappings
740
743
  # If predicate is not a Symbol, returns the predicate untouched
741
- # @throws UnregisteredPredicateError if the predicate is a symbol but is not found in the predicate_mappings
744
+ # @raise UnregisteredPredicateError if the predicate is a symbol but is not found in the predicate_mappings
742
745
  def predicate_lookup(predicate,namespace="info:fedora/fedora-system:def/relations-external#")
743
746
  if predicate.class == Symbol
744
747
  if predicate_mappings[namespace].has_key?(predicate)
@@ -0,0 +1,3 @@
1
+ module ActiveFedora
2
+ VERSION = "2.2.1"
3
+ end
@@ -1,8 +1,10 @@
1
1
  require 'xmlsimple'
2
2
 
3
3
  class Hash
4
- # {:q => 'test', :num => 5}.to_query # => 'q=test&num=5'
5
- # let's avoid stomping on rails' version eh?
4
+
5
+ # Produces a valid Fedora query based on the current hash
6
+ # @example
7
+ # {:q => 'test', :num => 5}.to_fedora_query # => 'q=test&num=5'
6
8
  def to_fedora_query
7
9
  self.collect { |key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" }.sort * '&'
8
10
  end
@@ -17,9 +19,7 @@ class Fedora::BaseObject
17
19
  attr_reader :errors, :uri
18
20
  attr_writer :new_object
19
21
 
20
- # == Parameters
21
- # attrs<Hash>:: object attributes
22
- #-
22
+ # @param [Hash] attrs object attributes
23
23
  def initialize(attrs = {})
24
24
  @new_object = true
25
25
  @attributes = attrs || {}
@@ -49,7 +49,7 @@ class Fedora::Datastream < Fedora::BaseObject
49
49
  "fedora:info/#{pid}/datastreams/#{dsid}"
50
50
  end
51
51
 
52
- # @returns the url of the datastream in Fedora, without the repository userinfo
52
+ # @return [String] url of the datastream in Fedora, without the repository userinfo
53
53
  def url
54
54
  return "#{Fedora::Repository.instance.base_url}/objects/#{pid}/datastreams/#{dsid}"
55
55
  end