Class: Project

Inherits:
ApplicationRecord show all
Defined in:
app/models/project.rb

Constant Summary collapse

APPROVED_STATUS =

Valid project status described in ADR 7 See ‘architecture-decisions/0007-valid-project-statuses.md`

"approved"
ACTIVE_STATUS =
"active"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.all_projectsObject



118
119
120
# File 'app/models/project.rb', line 118

def self.all_projects
  Project.all
end

.default_storage_capacityObject



173
174
175
# File 'app/models/project.rb', line 173

def self.default_storage_capacity
  "0 GB"
end

.default_storage_unitObject



155
156
157
# File 'app/models/project.rb', line 155

def self.default_storage_unit
  "KB"
end

.default_storage_usageObject



159
160
161
# File 'app/models/project.rb', line 159

def self.default_storage_usage
  "0 #{default_storage_unit}"
end

.managed_projects(manager) ⇒ Object



114
115
116
# File 'app/models/project.rb', line 114

def self.managed_projects(manager)
  Project.where("metadata_json->>'data_manager' = ?", manager)
end


110
111
112
# File 'app/models/project.rb', line 110

def self.sponsored_projects(sponsor)
  Project.where("metadata_json->>'data_sponsor' = ?", sponsor)
end

.users_projects(user) ⇒ Object



99
100
101
102
103
104
105
106
107
108
# File 'app/models/project.rb', line 99

def self.users_projects(user)
  # See https://scalegrid.io/blog/using-jsonb-in-postgresql-how-to-effectively-store-index-json-data-in-postgresql/
  # for information on the @> operator
  uid = user.uid
  query_ro = '{"data_user_read_only":["' + uid + '"]}'
  query_rw = '{"data_user_read_write":["' + uid + '"]}'
  query = "(metadata_json @> ? :: jsonb) OR (metadata_json @> ? :: jsonb) OR (metadata_json->>'data_sponsor' = ?) OR (metadata_json->>'data_manager' = ?)"
  args = [query_ro, query_rw, uid, uid]
  Project.where( query, *args)
end

Instance Method Details

#activate(current_user:) ⇒ Object

Raises:

  • (StandardError)


35
36
37
38
39
40
41
42
43
44
45
46
# File 'app/models/project.rb', line 35

def activate(current_user:)
  raise StandardError.new("Only approved projects can be activated") if self.status != Project::APPROVED_STATUS
   = Mediaflux::AssetMetadataRequest.new(session_token: current_user.mediaflux_session, id: self.mediaflux_id)
  .resolve
  raise .response_error if .error?
  if self.title == .[:title]
    self..status = Project::ACTIVE_STATUS
    self.save!
  else
    raise StandardError.new("Title mismatch: #{self.title} != #{.[:title]}")
  end
end

#asset_count(session_id:) ⇒ Object



150
151
152
153
# File 'app/models/project.rb', line 150

def asset_count(session_id:)
  values = (session_id:)
  values.fetch(:total_file_count, 0)
end

#create!(initial_metadata:, user:) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'app/models/project.rb', line 18

def create!(initial_metadata:, user:)
  self. = 
  if self.valid?
    if .project_id == ProjectMetadata::DOI_NOT_MINTED
      self.draft_doi(user: user)
      self.save!
      ProvenanceEvent.generate_submission_events(project: self, user: user)
    else
      self.save!
    end
    # return doi
    self..project_id
  else
    nil
  end
end

#created_by_userObject



128
129
130
# File 'app/models/project.rb', line 128

def created_by_user
  User.find_by(uid: .created_by)
end

#departmentsObject



78
79
80
81
# File 'app/models/project.rb', line 78

def departments
  unsorted = .departments || []
  unsorted.sort
end

#draft_doi(user: nil) ⇒ Object



48
49
50
51
# File 'app/models/project.rb', line 48

def draft_doi(user: nil)
  puldatacite = PULDatacite.new
  self..project_id = puldatacite.draft_doi
end

#file_list(session_id:, size: 10) ⇒ Object

Fetches the first n files



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'app/models/project.rb', line 194

def file_list(session_id:, size: 10)
  return { files: [] } if mediaflux_id.nil?

  query_req = Mediaflux::QueryRequest.new(session_token: session_id, collection: mediaflux_id, deep_search: true, aql_query: "type!='application/arc-asset-collection'")
  iterator_id = query_req.result

  iterator_req = Mediaflux::IteratorRequest.new(session_token: session_id, iterator: iterator_id, size: size)
  results = iterator_req.result

  # Destroy _after_ fetching the first set of results from iterator_req.
  # This call is required since it possible that we have read less assets than
  # what the collection has but we are done with the iterator.
  Mediaflux::IteratorDestroyRequest.new(session_token: session_id, iterator: iterator_id).resolve

  results
end

#file_list_to_file(session_id:, filename:) ⇒ Object

Fetches the entire file list to a file



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'app/models/project.rb', line 212

def file_list_to_file(session_id:, filename:)
  return { files: [] } if mediaflux_id.nil?

  query_req = Mediaflux::QueryRequest.new(session_token: session_id, collection: mediaflux_id, deep_search: true,  aql_query: "type!='application/arc-asset-collection'")
  iterator_id = query_req.result

  start_time = Time.zone.now
  prefix = "file_list_to_file #{session_id[0..7]} #{self..project_id}"
  log_elapsed(start_time, prefix, "STARTED")

  File.open(filename, "w") do |file|
    page_number = 0
    # file header
    file.write("ID, PATH, NAME, COLLECTION?, LAST_MODIFIED, SIZE\r\n")
    loop do
      iterator_start_time = Time.zone.now
      page_number += 1
      iterator_req = Mediaflux::IteratorRequest.new(session_token: session_id, iterator: iterator_id, size: 1000)
      iterator_resp = iterator_req.result
      log_elapsed(iterator_start_time, prefix, "FETCHED page #{page_number} from iterator")
      lines = files_from_iterator(iterator_resp)
      file.write(lines.join("\r\n") + "\r\n")
      break if iterator_resp[:complete] || iterator_req.error?
    end
    log_elapsed(start_time, prefix, "ENDED")
  end

  # Destroy _after_ fetching the results from iterator_req
  # This call is technically not necessary since Mediaflux automatically deletes the iterator
  # once we have ran through it and by now we have. But it does not hurt either.
  Mediaflux::IteratorDestroyRequest.new(session_token: session_id, iterator: iterator_id).resolve
end

#in_mediaflux?Boolean

Returns:

  • (Boolean)


95
96
97
# File 'app/models/project.rb', line 95

def in_mediaflux?
  mediaflux_id.present?
end

#mediaflux_meta_xml(user:) ⇒ String

Returns XML representation of the <meta> element.

Returns:

  • (String)

    XML representation of the <meta> element



137
138
139
140
# File 'app/models/project.rb', line 137

def mediaflux_meta_xml(user:)
  doc = ProjectMediaflux.document(project: self, user: user)
  doc.xpath("/response/reply/result/asset/meta").to_s
end

#mediaflux_metadata(session_id:) ⇒ Object



142
143
144
145
146
147
148
# File 'app/models/project.rb', line 142

def (session_id:)
  @mediaflux_metadata ||= begin
    accum_req = Mediaflux::AssetMetadataRequest.new(session_token: session_id, id: mediaflux_id)
    accum_req.
  end
  @mediaflux_metadata
end

#metadataObject

Ideally this method should return a ProjectMetadata object (like ‘metadata_model` does) but we’ll keep them both while we are refactoring the code so that we don’t break everything at once since ‘metadata` is used everywhere.



56
57
58
# File 'app/models/project.rb', line 56

def 
  @metadata_hash = ( || {}).with_indifferent_access
end

#metadata=(metadata_model) ⇒ Object



68
69
70
71
72
# File 'app/models/project.rb', line 68

def metadata=()
  # Convert our metadata to a hash so it can be saved on our JSONB field
   = JSON.parse(.to_json)
  self. = 
end

#metadata_modelObject



60
61
62
# File 'app/models/project.rb', line 60

def 
  @metadata_model ||= ProjectMetadata.new_from_hash(self.)
end

#metadata_model=(new_metadata_model) ⇒ Object



64
65
66
# File 'app/models/project.rb', line 64

def ()
  @metadata_model = 
end

#project_directoryObject



83
84
85
# File 'app/models/project.rb', line 83

def project_directory
  .project_directory || ""
end

#project_directory_shortObject



87
88
89
# File 'app/models/project.rb', line 87

def project_directory_short
  project_directory
end

#statusObject



91
92
93
# File 'app/models/project.rb', line 91

def status
  .status
end

#storage_capacity(session_id:) ⇒ Object



177
178
179
180
181
182
183
184
185
# File 'app/models/project.rb', line 177

def storage_capacity(session_id:)
  values = (session_id:)
  quota_value = values.fetch(:quota_allocation, '') #if quota does not exist, set value to an empty string
  if quota_value.blank?
    return self.class.default_storage_capacity
  else
    return quota_value
  end
end

#storage_capacity_raw(session_id:) ⇒ Object



187
188
189
190
191
# File 'app/models/project.rb', line 187

def storage_capacity_raw(session_id:)
  values = (session_id:)
  quota_value = values.fetch(:quota_allocation_raw, 0) #if quota does not exist, set value to 0
  quota_value
end

#storage_usage(session_id:) ⇒ Object



163
164
165
166
# File 'app/models/project.rb', line 163

def storage_usage(session_id:)
  values = (session_id:)
  values.fetch(:quota_used, self.class.default_storage_usage) # if the storage is empty use the default
end

#storage_usage_raw(session_id:) ⇒ Object



168
169
170
171
# File 'app/models/project.rb', line 168

def storage_usage_raw(session_id:)
  values = (session_id:)
  values.fetch(:quota_used_raw, 0) # if the storage raw is empty use zero
end

#titleObject



74
75
76
# File 'app/models/project.rb', line 74

def title
  self..title
end

#to_xmlObject



132
133
134
# File 'app/models/project.rb', line 132

def to_xml
  ProjectShowPresenter.new(self).to_xml
end

#user_has_access?(user:) ⇒ Boolean

Returns:

  • (Boolean)


122
123
124
125
126
# File 'app/models/project.rb', line 122

def user_has_access?(user:)
  return true if user.eligible_sysadmin?
  .data_sponsor == user.uid || .data_manager == user.uid ||
  .data_user_read_only.include?(user.uid) || .data_user_read_write.include?(user.uid)
end