Class: Project

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

Constant Summary collapse

PENDING_STATUS =

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

"pending"
APPROVED_STATUS =
"approved"
ACTIVE_STATUS =
"active"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.approved_projectsObject



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

def self.approved_projects
  Project.where("mediaflux_id IS NOT NULL")
end

.data_user_projects(user) ⇒ Object



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

def self.data_user_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
  query_ro = '{"data_user_read_only":["' + user + '"]}'
  query_rw = '{"data_user_read_write":["' + user + '"]}'
  Project.where("(metadata_json @> ? :: jsonb) OR (metadata_json @> ? :: jsonb)", query_ro, query_rw)
end

.default_storage_capacityObject



236
237
238
# File 'app/models/project.rb', line 236

def self.default_storage_capacity
  "0 GB"
end

.default_storage_usageObject



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

def self.default_storage_usage
  "0 KB"
end

.managed_projects(manager) ⇒ Object



166
167
168
# File 'app/models/project.rb', line 166

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

.pending_projectsObject



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

def self.pending_projects
  Project.where("mediaflux_id IS NULL")
end

.safe_name(name) ⇒ Object

Ensure that the project directory is a valid path

Examples:

Project.safe_name("My Project") # => "My-Project"


303
304
305
306
# File 'app/models/project.rb', line 303

def self.safe_name(name)
  # only alphanumeric characters
  name.strip.gsub(/[^A-Za-z\d]/, "-")
end


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

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

.users_projects(user) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'app/models/project.rb', line 143

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)"
  args = [query_ro, query_rw]
  if user.eligible_sponsor?
    query += "OR (metadata_json->>'data_sponsor' = ?)"
    args << uid
  end
  if user.eligible_manager?
    query += "OR (metadata_json->>'data_manager' = ?)"
    args << uid
  end
  Project.where( query, *args)
end

Instance Method Details

#activate!(collection_id:, current_user:) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'app/models/project.rb', line 52

def activate!(collection_id:, current_user:)
  response = Mediaflux::AssetMetadataRequest.new(session_token: current_user.mediaflux_session, id: collection_id)
   = response. # get the metadata of the collection from mediaflux

  return unless [:collection] == true # If the collection id exists

  # check if the project doi in rails matches the project doi in mediaflux
  return unless [:project_id] == self..project_id

  # activate a project by setting the status to 'active'
  self..status = Project::ACTIVE_STATUS

  # also read in the actual project directory
  self..project_directory = [:project_directory]
  self.save!

  ProvenanceEvent.generate_active_events(project: self, user: current_user)
end

#approve!(mediaflux_id:, current_user:) ⇒ Object



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

def approve!(mediaflux_id:, current_user:)
  self.mediaflux_id = mediaflux_id
  self..status = Project::APPROVED_STATUS
  self.save!

  # create two provenance events, one for approving the project and
    # another for changing the status of the project
  ProvenanceEvent.generate_approval_events(project: self, user: current_user)

end

#asset_count(session_id:) ⇒ Object



210
211
212
213
# File 'app/models/project.rb', line 210

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



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

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

#departmentsObject



101
102
103
104
# File 'app/models/project.rb', line 101

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

#draft_doi(user:) ⇒ Object



71
72
73
74
# File 'app/models/project.rb', line 71

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

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

Fetches the first n files



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'app/models/project.rb', line 257

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



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'app/models/project.rb', line 275

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


  File.open(filename, "w") do |file|
    # file header
    file.write("ID, PATH, NAME, COLLECTION?, LAST_MODIFIED, SIZE\r\n")
    loop do
      iterator_req = Mediaflux::IteratorRequest.new(session_token: session_id, iterator: iterator_id, size: 1000)
      iterator_resp = iterator_req.result
      lines = files_from_iterator(iterator_resp)
      file.write(lines.join("\r\n") + "\r\n")
      break if iterator_resp[:complete] || iterator_req.error?
    end
  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)


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

def in_mediaflux?
  mediaflux_id.present?
end

#mediaflux_documentObject



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

def mediaflux_document
  ProjectMediaflux.document(project: self)
end

#mediaflux_metadata(session_id:) ⇒ Object



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

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.



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

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

#metadata=(metadata_model) ⇒ Object



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

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



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

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

#metadata_model=(new_metadata_model) ⇒ Object



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

def ()
  @metadata_model = 
end

#pending?Boolean

Returns:

  • (Boolean)


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

def pending?
  status == PENDING_STATUS
end

#project_directoryObject



106
107
108
109
110
111
112
113
114
# File 'app/models/project.rb', line 106

def project_directory
  return nil if .project_directory.nil?
  dirname, basename = project_directory_pathname.split
  if (dirname.relative?)
    "#{Mediaflux::Connection.root_namespace}/#{safe_name(.project_directory)}"
  else
    project_directory_pathname.to_s
  end
end

#project_directory_parent_pathObject



116
117
118
119
120
121
122
123
124
# File 'app/models/project.rb', line 116

def project_directory_parent_path
  return Mediaflux::Connection.root_namespace if .project_directory.nil?
  dirname  = project_directory_pathname.dirname
  if (dirname.relative?)
    Mediaflux::Connection.root_namespace
  else
    dirname.to_s
  end
end

#project_directory_shortObject



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

def project_directory_short
  return nil if .project_directory.nil?
  project_directory_pathname.basename.to_s
end

#reloadObject



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

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

#save_in_mediaflux(user:) ⇒ Object



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

def save_in_mediaflux(user:)
  ProjectMediaflux.save(project: self, user: user)
end

#statusObject



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

def status
  .status
end

#storage_capacity(session_id:) ⇒ Object



240
241
242
243
244
245
246
247
248
# File 'app/models/project.rb', line 240

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



250
251
252
253
254
# File 'app/models/project.rb', line 250

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



219
220
221
222
223
224
225
226
227
228
# File 'app/models/project.rb', line 219

def storage_usage(session_id:)
  values = (session_id:)
  quota_value = values.fetch(:quota_used, '')

  if quota_value.blank?
    return self.class.default_storage_usage
  else
    return quota_value
  end
end

#storage_usage_raw(session_id:) ⇒ Object



230
231
232
233
234
# File 'app/models/project.rb', line 230

def storage_usage_raw(session_id:)
  values = (session_id:)
  quota_value = values.fetch(:quota_used_raw, 0)
  quota_value
end

#titleObject



97
98
99
# File 'app/models/project.rb', line 97

def title
  self..title
end

#to_xmlObject



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

def to_xml
  ProjectMediaflux.xml_payload(project: self)
end