sufia-models 5.0.0.beta1 → 5.0.0.rc1

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/app/actors/sufia/generic_file/actor.rb +10 -7
  3. data/app/jobs/active_fedora_pid_based_job.rb +3 -2
  4. data/app/jobs/audit_job.rb +31 -28
  5. data/app/jobs/batch_update_job.rb +8 -9
  6. data/app/jobs/import_url_job.rb +2 -2
  7. data/app/models/batch.rb +12 -11
  8. data/app/models/checksum_audit_log.rb +8 -7
  9. data/app/models/concerns/sufia/ability.rb +6 -4
  10. data/app/models/concerns/sufia/collection.rb +5 -4
  11. data/app/models/concerns/sufia/file_stat_utils.rb +3 -3
  12. data/app/models/concerns/sufia/generic_file.rb +14 -16
  13. data/app/models/concerns/sufia/generic_file/audit.rb +31 -50
  14. data/app/models/concerns/sufia/generic_file/characterization.rb +3 -3
  15. data/app/models/concerns/sufia/generic_file/derivatives.rb +5 -5
  16. data/app/models/concerns/sufia/generic_file/full_text_indexing.rb +2 -2
  17. data/app/models/concerns/sufia/generic_file/metadata.rb +11 -82
  18. data/app/models/concerns/sufia/generic_file/proxy_deposit.rb +3 -12
  19. data/app/models/concerns/sufia/generic_file/reload_on_save.rb +18 -0
  20. data/app/models/concerns/sufia/generic_file/versions.rb +4 -1
  21. data/app/models/concerns/sufia/generic_file/web_form.rb +6 -13
  22. data/app/models/concerns/sufia/model_methods.rb +9 -11
  23. data/app/models/concerns/sufia/properties_datastream_behavior.rb +32 -0
  24. data/app/models/concerns/sufia/user.rb +33 -11
  25. data/app/models/concerns/sufia/user_usage_stats.rb +15 -0
  26. data/app/models/datastreams/batch_rdf_datastream.rb +6 -0
  27. data/app/models/datastreams/file_content_datastream.rb +1 -1
  28. data/app/models/datastreams/fits_datastream.rb +1 -1
  29. data/app/models/datastreams/generic_file_rdf_datastream.rb +69 -0
  30. data/app/models/datastreams/paranoid_rights_datastream.rb +22 -0
  31. data/app/models/datastreams/properties_datastream.rb +4 -0
  32. data/app/models/file_download_stat.rb +2 -2
  33. data/app/models/file_usage.rb +9 -5
  34. data/app/models/file_view_stat.rb +2 -2
  35. data/app/models/local_authority.rb +2 -2
  36. data/app/models/proxy_deposit_request.rb +1 -1
  37. data/app/models/sufia/orcid_validator.rb +8 -0
  38. data/app/models/user_stat.rb +2 -0
  39. data/app/services/sufia/id_service.rb +5 -5
  40. data/app/services/sufia/noid.rb +7 -10
  41. data/lib/generators/sufia/models/abstract_migration_generator.rb +30 -0
  42. data/lib/generators/sufia/models/cached_stats_generator.rb +2 -31
  43. data/lib/generators/sufia/models/install_generator.rb +11 -31
  44. data/lib/generators/sufia/models/orcid_field_generator.rb +19 -0
  45. data/lib/generators/sufia/models/proxies_generator.rb +2 -31
  46. data/lib/generators/sufia/models/templates/config/sufia.rb +3 -10
  47. data/lib/generators/sufia/models/templates/migrations/add_orcid_to_users.rb +5 -0
  48. data/lib/generators/sufia/models/templates/migrations/create_user_stats.rb +19 -0
  49. data/lib/generators/sufia/models/upgrade400_generator.rb +2 -33
  50. data/lib/generators/sufia/models/user_stats_generator.rb +31 -0
  51. data/lib/sufia/models/engine.rb +4 -13
  52. data/lib/sufia/models/file_content/versions.rb +8 -12
  53. data/lib/sufia/models/stats/user_stat_importer.rb +89 -0
  54. data/lib/sufia/models/version.rb +1 -1
  55. data/lib/sufia/permissions/writable.rb +16 -34
  56. data/lib/tasks/stats_tasks.rake +12 -0
  57. data/sufia-models.gemspec +2 -4
  58. metadata +78 -90
@@ -3,7 +3,7 @@ module Sufia
3
3
  module Characterization
4
4
  extend ActiveSupport::Concern
5
5
  included do
6
- contains "characterization", class_name: 'FitsDatastream'
6
+ has_metadata "characterization", type: FitsDatastream
7
7
  has_attributes :mime_type, datastream: :characterization, multiple: false
8
8
  has_attributes :format_label, :file_size, :last_modified,
9
9
  :filename, :original_checksum, :rights_basis,
@@ -45,11 +45,11 @@ module Sufia
45
45
  metadata = content.extract_metadata
46
46
  characterization.ng_xml = metadata if metadata.present?
47
47
  append_metadata
48
- self.filename = [content.original_name]
48
+ self.filename = [self.label]
49
49
  save
50
50
  end
51
51
 
52
- # Populate GenericFile's properties with fields from FITS (e.g. Author from pdfs)
52
+ # Populate descMetadata with fields from FITS (e.g. Author from pdfs)
53
53
  def append_metadata
54
54
  terms = self.characterization_terms
55
55
  Sufia.config.fits_to_desc_mapping.each_pair do |k, v|
@@ -9,15 +9,15 @@ module Sufia
9
9
  makes_derivatives do |obj|
10
10
  case obj.mime_type
11
11
  when *pdf_mime_types
12
- obj.transform_file :content, { thumbnail: { format: 'jpg', size: '338x493', datastream: 'thumbnail' } }
12
+ obj.transform_datastream :content, { thumbnail: { format: 'jpg', size: '338x493', datastream: 'thumbnail' } }
13
13
  when *office_document_mime_types
14
- obj.transform_file :content, { thumbnail: { format: 'jpg', size: '200x150>', datastream: 'thumbnail' } }, processor: :document
14
+ obj.transform_datastream :content, { thumbnail: { format: 'jpg', size: '200x150>', datastream: 'thumbnail' } }, processor: :document
15
15
  when *audio_mime_types
16
- obj.transform_file :content, { mp3: { format: 'mp3', datastream: 'mp3' }, ogg: { format: 'ogg', datastream: 'ogg' } }, processor: :audio
16
+ obj.transform_datastream :content, { mp3: { format: 'mp3', datastream: 'mp3' }, ogg: { format: 'ogg', datastream: 'ogg' } }, processor: :audio
17
17
  when *video_mime_types
18
- obj.transform_file :content, { webm: { format: 'webm', datastream: 'webm' }, mp4: { format: 'mp4', datastream: 'mp4' }, thumbnail: { format: 'jpg', datastream: 'thumbnail' } }, processor: :video
18
+ obj.transform_datastream :content, { webm: { format: 'webm', datastream: 'webm' }, mp4: { format: 'mp4', datastream: 'mp4' }, thumbnail: { format: 'jpg', datastream: 'thumbnail' } }, processor: :video
19
19
  when *image_mime_types
20
- obj.transform_file :content, { thumbnail: { format: 'jpg', size: '200x150>', datastream: 'thumbnail' } }
20
+ obj.transform_datastream :content, { thumbnail: { format: 'jpg', size: '200x150>', datastream: 'thumbnail' } }
21
21
  end
22
22
  end
23
23
  end
@@ -4,7 +4,7 @@ module Sufia
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- contains 'full_text'
7
+ has_file_datastream 'full_text', versionable: false
8
8
  end
9
9
 
10
10
  def append_metadata
@@ -27,7 +27,7 @@ module Sufia
27
27
  extracted_text = JSON.parse(resp.body)[''].rstrip
28
28
  full_text.content = extracted_text if extracted_text.present?
29
29
  rescue => e
30
- logger.error("Error extracting content from #{self.id}: #{e.inspect}")
30
+ logger.error("Error extracting content from #{self.pid}: #{e.inspect}")
31
31
  end
32
32
  end
33
33
  end
@@ -4,88 +4,17 @@ module Sufia
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- contains "content", class_name: 'FileContentDatastream'
8
- contains "thumbnail"
9
-
10
- property :label, predicate: ::RDF::DC.title, multiple: false
11
-
12
- property :depositor, predicate: ::RDF::URI.new("http://id.loc.gov/vocabulary/relators/dpt"), multiple: false do |index|
13
- index.as :symbol, :stored_searchable
14
- end
15
-
16
- property :relative_path, predicate: ::RDF::URI.new('http://scholarsphere.psu.edu/ns#relativePath'), multiple: false
17
-
18
- property :import_url, predicate: ::RDF::URI.new('http://scholarsphere.psu.edu/ns#importUrl'), multiple: false do |index|
19
- index.as :symbol
20
- end
21
-
22
- property :part_of, predicate: ::RDF::DC.isPartOf
23
- property :resource_type, predicate: ::RDF::DC.type do |index|
24
- index.as :stored_searchable, :facetable
25
- end
26
- property :title, predicate: ::RDF::DC.title do |index|
27
- index.as :stored_searchable, :facetable
28
- end
29
- property :creator, predicate: ::RDF::DC.creator do |index|
30
- index.as :stored_searchable, :facetable
31
- end
32
- property :contributor, predicate: ::RDF::DC.contributor do |index|
33
- index.as :stored_searchable, :facetable
34
- end
35
- property :description, predicate: ::RDF::DC.description do |index|
36
- index.type :text
37
- index.as :stored_searchable
38
- end
39
- property :tag, predicate: ::RDF::DC.relation do |index|
40
- index.as :stored_searchable, :facetable
41
- end
42
- property :rights, predicate: ::RDF::DC.rights do |index|
43
- index.as :stored_searchable
44
- end
45
- property :publisher, predicate: ::RDF::DC.publisher do |index|
46
- index.as :stored_searchable, :facetable
47
- end
48
- property :date_created, predicate: ::RDF::DC.created do |index|
49
- index.as :stored_searchable
50
- end
51
- property :date_uploaded, predicate: ::RDF::DC.dateSubmitted, multiple: false do |index|
52
- index.type :date
53
- index.as :stored_sortable
54
- end
55
- property :date_modified, predicate: ::RDF::DC.modified, multiple: false do |index|
56
- index.type :date
57
- index.as :stored_sortable
58
- end
59
- property :subject, predicate: ::RDF::DC.subject do |index|
60
- index.as :stored_searchable, :facetable
61
- end
62
- property :language, predicate: ::RDF::DC.language do |index|
63
- index.as :stored_searchable, :facetable
64
- end
65
- property :identifier, predicate: ::RDF::DC.identifier do |index|
66
- index.as :stored_searchable
67
- end
68
- property :based_near, predicate: ::RDF::FOAF.based_near do |index|
69
- index.as :stored_searchable, :facetable
70
- end
71
- property :related_url, predicate: ::RDF::RDFS.seeAlso do |index|
72
- index.as :stored_searchable
73
- end
74
- property :bibliographic_citation, predicate: ::RDF::DC.bibliographicCitation do |index|
75
- index.as :stored_searchable
76
- end
77
- property :source, predicate: ::RDF::DC.source do |index|
78
- index.as :stored_searchable
79
- end
80
-
81
- # TODO: Move this somewhere more appropriate
82
- begin
83
- LocalAuthority.register_vocabulary(self, "subject", "lc_subjects")
84
- LocalAuthority.register_vocabulary(self, "language", "lexvo_languages")
85
- LocalAuthority.register_vocabulary(self, "tag", "lc_genres")
86
- rescue
87
- puts "tables for vocabularies missing"
88
- end
7
+ has_metadata "descMetadata", type: GenericFileRdfDatastream
8
+ has_metadata "properties", type: PropertiesDatastream
9
+ has_file_datastream "content", type: FileContentDatastream
10
+ has_file_datastream "thumbnail"
11
+
12
+ has_attributes :relative_path, :depositor, :import_url, datastream: :properties, multiple: false
13
+ has_attributes :date_uploaded, :date_modified, datastream: :descMetadata, multiple: false
14
+ has_attributes :related_url, :based_near, :part_of, :creator,
15
+ :contributor, :title, :tag, :description, :rights,
16
+ :publisher, :date_created, :subject,
17
+ :resource_type, :identifier, :language, datastream: :descMetadata, multiple: true
89
18
  end
90
19
 
91
20
  # Add a schema.org itemtype
@@ -4,27 +4,18 @@ module Sufia
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- property :proxy_depositor, predicate: ::RDF::URI.new('http://scholarsphere.psu.edu/ns#proxyDepositor'), multiple: false do |index|
8
- index.as :symbol
9
- end
10
-
11
- # This value is set when a user indicates they are depositing this for someone else
12
- property :on_behalf_of, predicate: ::RDF::URI.new('http://scholarsphere.psu.edu/ns#onBehalfOf'), multiple: false do |index|
13
- index.as :symbol
14
- end
15
-
7
+ has_attributes :proxy_depositor, :on_behalf_of, datastream: :properties, multiple: false
16
8
  after_create :create_transfer_request
17
9
  end
18
10
 
19
-
20
11
  def create_transfer_request
21
- Sufia.queue.push(ContentDepositorChangeEventJob.new(id, on_behalf_of)) if on_behalf_of.present?
12
+ Sufia.queue.push(ContentDepositorChangeEventJob.new(pid, on_behalf_of)) if on_behalf_of.present?
22
13
  end
23
14
 
24
15
  def request_transfer_to(target)
25
16
  raise ArgumentError, "Must provide a target" unless target
26
17
  deposit_user = ::User.find_by_user_key(depositor)
27
- ProxyDepositRequest.create!(pid: id, receiving_user: target, sending_user: deposit_user)
18
+ ProxyDepositRequest.create!(pid: pid, receiving_user: target, sending_user: deposit_user)
28
19
  end
29
20
  end
30
21
  end
@@ -0,0 +1,18 @@
1
+ # add this in here until we can use a version of Active Fedora that contains this ability
2
+ module Sufia
3
+ module GenericFile
4
+ module ReloadOnSave
5
+
6
+ attr_writer :reload_on_save
7
+
8
+ def reload_on_save?
9
+ !!@reload_on_save
10
+ end
11
+
12
+ def refresh
13
+ self.reload if reload_on_save?
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -5,7 +5,10 @@ module Sufia
5
5
  version = content.latest_version
6
6
  # content datastream not (yet?) present
7
7
  return if version.nil?
8
- VersionCommitter.create(version_id: version.to_s, committer_login: user.user_key)
8
+ VersionCommitter.create(obj_id: version.pid,
9
+ datastream_id: version.dsid,
10
+ version_id: version.versionID,
11
+ committer_login: user.user_key)
9
12
  end
10
13
 
11
14
  end
@@ -9,22 +9,15 @@ module Sufia
9
9
 
10
10
  def remove_blank_assertions
11
11
  terms_for_editing.each do |key|
12
- if self[key] == ['']
13
- self[key] = []
14
- changed_attributes.delete(key) if attribute_was(key) == []
15
- end
12
+ self[key] = nil if self[key] == ['']
16
13
  end
17
14
  end
18
15
 
19
16
  # override this method if you need to initialize more complex RDF assertions (b-nodes)
20
17
  def initialize_fields
21
- terms_for_editing.select { |key| self[key].blank? }.each do |key|
18
+ terms_for_editing.each do |key|
22
19
  # if value is empty, we create an one element array to loop over for output
23
- if self.class.multiple?(key)
24
- self[key] = ['']
25
- else
26
- self[key] = ''
27
- end
20
+ self[key] = [''] if self[key].empty?
28
21
  end
29
22
  end
30
23
 
@@ -47,10 +40,10 @@ module Sufia
47
40
 
48
41
  def to_jq_upload
49
42
  return {
50
- "name" => title,
51
- "size" => file_size,
43
+ "name" => self.title,
44
+ "size" => self.file_size,
52
45
  "url" => "/files/#{noid}",
53
- "thumbnail_url" => id,
46
+ "thumbnail_url" => self.pid,
54
47
  "delete_url" => "deleteme", # generic_file_path(id: id),
55
48
  "delete_type" => "DELETE"
56
49
  }
@@ -8,25 +8,23 @@ module Sufia
8
8
 
9
9
  # OVERRIDE to support Hydra::Datastream::Properties which does not
10
10
  # respond to :depositor_values but :depositor
11
- # Adds metadata about the depositor to the asset and ads +depositor_id+ to
12
- # its individual edit permissions.
11
+ # Adds metadata about the depositor to the asset
12
+ # Most important behavior: if the asset has a rightsMetadata datastream, this method will add +depositor_id+ to its individual edit permissions.
13
+
13
14
  def apply_depositor_metadata(depositor)
15
+ rights_ds = self.datastreams["rightsMetadata"]
16
+ prop_ds = self.datastreams["properties"]
14
17
  depositor_id = depositor.respond_to?(:user_key) ? depositor.user_key : depositor
15
18
 
16
- self.edit_users += [depositor_id]
17
- self.depositor = depositor_id
19
+ rights_ds.update_indexed_attributes([:edit_access, :person]=>depositor_id) unless rights_ds.nil?
20
+ prop_ds.depositor = depositor_id unless prop_ds.nil?
18
21
 
19
22
  return true
20
23
  end
21
24
 
22
25
  def to_s
23
- if title.present?
24
- Array(title).join(" | ")
25
- elsif label.present?
26
- Array(label).join(" | ")
27
- else
28
- "No Title"
29
- end
26
+ return Array(title).join(" | ") if title.present?
27
+ label || "No Title"
30
28
  end
31
29
 
32
30
  end
@@ -0,0 +1,32 @@
1
+ module Sufia
2
+ module PropertiesDatastreamBehavior
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ set_terminology do |t|
7
+ t.root(path: "fields")
8
+ # This is where we put the user id of the object depositor -- impacts permissions/access controls
9
+ t.depositor index_as: [:symbol, :stored_searchable]
10
+ # This is where we put the relative path of the file if submitted as a folder
11
+ t.relative_path
12
+ t.import_url path: 'importUrl', index_as: :symbol
13
+ t.proxy_depositor path: 'proxyDepositor', index_as: :symbol
14
+ # This value is set when a user indicates they are depositing this for someone else
15
+ t.on_behalf_of path: 'onBehalfOf', index_as: :symbol
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ def xml_template
21
+ builder = Nokogiri::XML::Builder.new do |xml|
22
+ xml.fields
23
+ end
24
+ builder.doc
25
+ end
26
+ end
27
+
28
+ def prefix
29
+ ""
30
+ end
31
+ end
32
+ end
@@ -18,33 +18,55 @@ module Sufia::User
18
18
  # Users should be followable
19
19
  acts_as_followable
20
20
 
21
- # Setup accessible (or protected) attributes for your model
21
+ # Set up proxy-related relationships
22
22
  has_many :proxy_deposit_requests, foreign_key: 'receiving_user_id'
23
-
24
23
  has_many :deposit_rights_given, foreign_key: 'grantor_id', class_name: 'ProxyDepositRights', dependent: :destroy
25
24
  has_many :can_receive_deposits_from, through: :deposit_rights_given, source: :grantee
26
-
27
25
  has_many :deposit_rights_received, foreign_key: 'grantee_id', class_name: 'ProxyDepositRights', dependent: :destroy
28
26
  has_many :can_make_deposits_for, through: :deposit_rights_received, source: :grantor
29
27
 
28
+ # Validate and normalize ORCIDs
29
+ validates_with OrcidValidator
30
+ after_validation :normalize_orcid
31
+
32
+ # Set up user profile avatars
30
33
  mount_uploader :avatar, AvatarUploader, mount_on: :avatar_file_name
31
34
  validates_with AvatarValidator
35
+
32
36
  has_many :trophies
33
37
  attr_accessor :update_directory
34
38
  end
35
39
 
40
+ # Coerce the ORCID into URL format
41
+ def normalize_orcid
42
+ # Skip normalization if:
43
+ # 1. validation has already flagged the ORCID as invalid
44
+ # 2. the orcid field is blank
45
+ # 3. the orcid is already in its normalized form
46
+ return if self.errors[:orcid].first.present? || self.orcid.blank? || self.orcid.starts_with?('http://orcid.org/')
47
+ bare_orcid = /\d{4}-\d{4}-\d{4}-\d{4}/.match(self.orcid).string
48
+ self.orcid = "http://orcid.org/#{bare_orcid}"
49
+ end
50
+
36
51
  # Format the json for select2 which requires just an id and a field called text.
37
52
  # If we need an alternate format we should probably look at a json template gem
38
53
  def as_json(opts = nil)
39
- {id: user_key, text: display_name ? "#{display_name} (#{user_key})" : user_key}
54
+ { id: user_key, text: display_name ? "#{display_name} (#{user_key})" : user_key }
55
+ end
56
+
57
+ # Populate user instance with attributes from remote system (e.g., LDAP)
58
+ # There is no default implementation -- override this in your application
59
+ def populate_attributes
40
60
  end
41
61
 
42
62
  def email_address
43
- return self.email
63
+ self.email
44
64
  end
45
65
 
46
66
  def name
47
- return self.display_name.titleize || self.user_key rescue self.user_key
67
+ self.display_name.titleize || raise
68
+ rescue
69
+ self.user_key
48
70
  end
49
71
 
50
72
  # Redefine this for more intuitive keys in Redis
@@ -55,13 +77,13 @@ module Sufia::User
55
77
 
56
78
  def trophy_files
57
79
  trophies.map do |t|
58
- ::GenericFile.load_instance_from_solr(t.generic_file_id)
80
+ ::GenericFile.load_instance_from_solr(Sufia::Noid.namespaceize(t.generic_file_id))
59
81
  end
60
82
  end
61
83
 
62
84
  # method needed for messaging
63
85
  def mailboxer_email(obj=nil)
64
- return nil
86
+ nil
65
87
  end
66
88
 
67
89
  # The basic groups method, override or will fallback to Sufia::Ldap::User
@@ -85,7 +107,9 @@ module Sufia::User
85
107
  [:email, :login, :display_name, :address, :admin_area,
86
108
  :department, :title, :office, :chat_id, :website, :affiliation,
87
109
  :telephone, :avatar, :group_list, :groups_last_update, :facebook_handle,
88
- :twitter_handle, :googleplus_handle, :linkedin_handle, :remove_avatar]
110
+ :twitter_handle, :googleplus_handle, :linkedin_handle, :remove_avatar,
111
+ :orcid
112
+ ]
89
113
  end
90
114
 
91
115
  def current
@@ -119,7 +143,5 @@ module Sufia::User
119
143
  def from_url_component(component)
120
144
  User.find_by_user_key(component.gsub(/-dot-/, '.'))
121
145
  end
122
-
123
146
  end
124
-
125
147
  end
@@ -0,0 +1,15 @@
1
+ module Sufia::UserUsageStats
2
+
3
+ def stats
4
+ @stats ||= UserStat.where(user_id: id).order(date: :asc)
5
+ end
6
+
7
+ def total_file_views
8
+ stats.reduce(0) { |total, stat| total + stat.file_views }
9
+ end
10
+
11
+ def total_file_downloads
12
+ stats.reduce(0) { |total, stat| total + stat.file_downloads }
13
+ end
14
+
15
+ end