Class: Project
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Project
- 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
- .approved_projects ⇒ Object
- .data_user_projects(user) ⇒ Object
- .default_storage_capacity ⇒ Object
- .default_storage_unit ⇒ Object
- .default_storage_usage ⇒ Object
- .managed_projects(manager) ⇒ Object
- .pending_projects ⇒ Object
-
.safe_name(name) ⇒ Object
Ensure that the project directory is a valid path.
- .sponsored_projects(sponsor) ⇒ Object
- .users_projects(user) ⇒ Object
Instance Method Summary collapse
- #activate!(collection_id:, current_user:) ⇒ Object
- #approve!(mediaflux_id:, current_user:) ⇒ Object
- #asset_count(session_id:) ⇒ Object
- #create!(initial_metadata:, user:) ⇒ Object
- #created_by_user ⇒ Object
- #departments ⇒ Object
- #draft_doi(user: nil) ⇒ Object
-
#file_list(session_id:, size: 10) ⇒ Object
Fetches the first n files.
-
#file_list_to_file(session_id:, filename:) ⇒ Object
Fetches the entire file list to a file.
- #in_mediaflux? ⇒ Boolean
-
#mediaflux_document ⇒ Nokogiri::XML::Document
The Mediaflux XML document for this project.
-
#mediaflux_meta_element ⇒ Nokogiri::XML::Element
The <meta> element from the Mediaflux XML document.
-
#mediaflux_meta_xml ⇒ String
XML representation of the <meta> element.
- #mediaflux_metadata(session_id:) ⇒ Object
-
#metadata ⇒ Object
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.
- #metadata=(metadata_model) ⇒ Object
- #metadata_model ⇒ Object
- #metadata_model=(new_metadata_model) ⇒ Object
- #pending? ⇒ Boolean
- #project_directory ⇒ Object
- #project_directory_parent_path ⇒ Object
- #project_directory_short ⇒ Object
- #reload ⇒ Object
- #save_in_mediaflux(user:) ⇒ Object
- #status ⇒ Object
- #storage_capacity(session_id:) ⇒ Object
- #storage_capacity_raw(session_id:) ⇒ Object
- #storage_usage(session_id:) ⇒ Object
- #storage_usage_raw(session_id:) ⇒ Object
- #title ⇒ Object
- #to_xml ⇒ Object
- #user_has_access?(user:) ⇒ Boolean
Class Method Details
.approved_projects ⇒ Object
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_capacity ⇒ Object
256 257 258 |
# File 'app/models/project.rb', line 256 def self.default_storage_capacity "0 GB" end |
.default_storage_unit ⇒ Object
238 239 240 |
# File 'app/models/project.rb', line 238 def self.default_storage_unit "KB" end |
.default_storage_usage ⇒ Object
242 243 244 |
# File 'app/models/project.rb', line 242 def self.default_storage_usage "0 #{default_storage_unit}" 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_projects ⇒ Object
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
331 332 333 334 |
# File 'app/models/project.rb', line 331 def self.safe_name(name) # only alphanumeric characters name.strip.gsub(/[^A-Za-z\d]/, "-") end |
.sponsored_projects(sponsor) ⇒ Object
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
233 234 235 236 |
# File 'app/models/project.rb', line 233 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_user ⇒ Object
196 197 198 |
# File 'app/models/project.rb', line 196 def created_by_user User.find_by(uid: .created_by) end |
#departments ⇒ Object
101 102 103 104 |
# File 'app/models/project.rb', line 101 def departments unsorted = .departments || [] unsorted.sort end |
#draft_doi(user: nil) ⇒ Object
71 72 73 74 |
# File 'app/models/project.rb', line 71 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
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 |
# File 'app/models/project.rb', line 277 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
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'app/models/project.rb', line 295 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
139 140 141 |
# File 'app/models/project.rb', line 139 def in_mediaflux? mediaflux_id.present? end |
#mediaflux_document ⇒ Nokogiri::XML::Document
Returns the Mediaflux XML document for this project.
205 206 207 |
# File 'app/models/project.rb', line 205 def mediaflux_document ProjectMediaflux.document(project: self) end |
#mediaflux_meta_element ⇒ Nokogiri::XML::Element
Returns the <meta> element from the Mediaflux XML document.
210 211 212 213 214 215 216 217 218 |
# File 'app/models/project.rb', line 210 def doc = mediaflux_document.clone # Remove the namespaces in order to simplify the XPath query doc.remove_namespaces! elements = doc.xpath("/request/service/args/meta") raise("Failed to extract the <meta> element found in the Mediaflux XML document for project #{self.id}") if elements.empty? elements.first end |
#mediaflux_meta_xml ⇒ String
Returns XML representation of the <meta> element.
221 222 223 |
# File 'app/models/project.rb', line 221 def .to_xml end |
#mediaflux_metadata(session_id:) ⇒ Object
225 226 227 228 229 230 231 |
# File 'app/models/project.rb', line 225 def (session_id:) @mediaflux_metadata ||= begin accum_req = Mediaflux::AssetMetadataRequest.new(session_token: session_id, id: mediaflux_id) accum_req. end @mediaflux_metadata end |
#metadata ⇒ Object
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 () # Convert our metadata to a hash so it can be saved on our JSONB field = JSON.parse(.to_json) self. = end |
#metadata_model ⇒ Object
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
135 136 137 |
# File 'app/models/project.rb', line 135 def pending? status == PENDING_STATUS end |
#project_directory ⇒ Object
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_path ⇒ Object
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_short ⇒ Object
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 |
#reload ⇒ Object
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
192 193 194 |
# File 'app/models/project.rb', line 192 def save_in_mediaflux(user:) ProjectMediaflux.save(project: self, user: user) end |
#status ⇒ Object
131 132 133 |
# File 'app/models/project.rb', line 131 def status .status end |
#storage_capacity(session_id:) ⇒ Object
260 261 262 263 264 265 266 267 268 |
# File 'app/models/project.rb', line 260 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
270 271 272 273 274 |
# File 'app/models/project.rb', line 270 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
246 247 248 249 |
# File 'app/models/project.rb', line 246 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
251 252 253 254 |
# File 'app/models/project.rb', line 251 def storage_usage_raw(session_id:) values = (session_id:) values.fetch(:quota_used_raw, 0) # if the storage raw is empty use zero end |
#title ⇒ Object
97 98 99 |
# File 'app/models/project.rb', line 97 def title self..title end |
#to_xml ⇒ Object
200 201 202 |
# File 'app/models/project.rb', line 200 def to_xml ProjectMediaflux.xml_payload(project: self) end |
#user_has_access?(user:) ⇒ Boolean
186 187 188 189 190 |
# File 'app/models/project.rb', line 186 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 |