sufia-models 5.0.0.beta1 → 5.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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