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



144
145
146
# File 'app/models/project.rb', line 144

def self.all_projects
  Project.all
end

.default_storage_capacityObject



199
200
201
# File 'app/models/project.rb', line 199

def self.default_storage_capacity
  "0 GB"
end

.default_storage_unitObject



181
182
183
# File 'app/models/project.rb', line 181

def self.default_storage_unit
  "KB"
end

.default_storage_usageObject



185
186
187
# File 'app/models/project.rb', line 185

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

.managed_projects(manager) ⇒ Object



140
141
142
# File 'app/models/project.rb', line 140

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


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

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

.users_projects(user) ⇒ Object



125
126
127
128
129
130
131
132
133
134
# File 'app/models/project.rb', line 125

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)


50
51
52
53
54
55
56
57
58
59
60
61
# File 'app/models/project.rb', line 50

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: #{title} != #{.title}")
  end
end

#approve!(current_user:) ⇒ Object

TODO: Remove this method github.com/pulibrary/tigerdata-app/issues/1707 has been completed



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

def approve!(current_user:)
  # This code is duplicated with Request.approve() and it should
  # be removed. We keep it for now since we have way too many tests
  # wired to it already. The goal is that projects won't be approved,
  # instead Request are approved (and that process creates the project)
  create_project_operation = ProjectCreate.new
  result = create_project_operation.call(request: nil, approver: current_user, project: self)
  if result.success?
     self.mediaflux_id
  else
    raise ProjectCreate::ProjectCreateError, result.failure
  end
end

#asset_count(session_id:) ⇒ Object



176
177
178
179
# File 'app/models/project.rb', line 176

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



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

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

#departmentsObject



99
100
101
102
# File 'app/models/project.rb', line 99

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

#draft_doi(user: nil) ⇒ Object



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

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



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'app/models/project.rb', line 220

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



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'app/models/project.rb', line 238

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)


121
122
123
# File 'app/models/project.rb', line 121

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



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

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



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

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.



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

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

#metadata=(metadata_model) ⇒ Object



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

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



81
82
83
# File 'app/models/project.rb', line 81

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

#metadata_model=(new_metadata_model) ⇒ Object



85
86
87
# File 'app/models/project.rb', line 85

def ()
  @metadata_model = 
end

#project_directoryObject



104
105
106
# File 'app/models/project.rb', line 104

def project_directory
  .project_directory || ""
end

#project_directory_parent_pathObject



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

def project_directory_parent_path
  # The tigerdata.project.create expectes every project to be under "tigerdata"
  Mediaflux::Connection.root
end

#project_directory_shortObject



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

def project_directory_short
  project_directory
end

#reloadObject



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

def reload
  super
  @metadata_model = ProjectMetadata.new_from_hash(self.)
  self
end

#statusObject



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

def status
  .status
end

#storage_capacity(session_id:) ⇒ Object



203
204
205
206
207
208
209
210
211
# File 'app/models/project.rb', line 203

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



213
214
215
216
217
# File 'app/models/project.rb', line 213

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



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

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



194
195
196
197
# File 'app/models/project.rb', line 194

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



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

def title
  self..title
end

#to_xmlObject



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

def to_xml
  ProjectShowPresenter.new(self).to_xml
end

#user_has_access?(user:) ⇒ Boolean

Returns:

  • (Boolean)


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

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