Class: Neofiles::File
- Inherits:
-
Object
- Object
- Neofiles::File
- Includes:
- Mongoid::Document, Mongoid::Timestamps, DataStore::Mongo::FileHelper
- Defined in:
- app/models/neofiles/file.rb
Overview
This model stores file metadata like name, size, md5 hash etc. A model ID is essentially what is a “file” in the rest of an application. In some way Neofiles::File may be seen as remote filesystem, where you drop in files and keep their generated IDs to fetch them later, or setup web frontend via Neofiles::Files/ImagesController and request file bytes by ID from there.
When persisting new file to the MongoDB database one must initialize new instance with metadata and set field #file= to IO-like object that holds real bytes. When #save method is called, file metadata are saved into Neofiles::File and file content is read and saved into collection of Neofiles::FileChunk, each of maximum length of #chunk_size bytes:
logo = Neofiles::File.new
logo.description = 'ACME inc logo'
logo.file = '~/my-first-try.png' # or some opened file handle, or IO stream
logo.filename = 'acme.png'
logo.save
logo.chunks.to_a # return an array of Neofiles::FileChunk in order
logo.data # byte string of file contents
# in view.html.slim
- logo = Neofiles::File.find 'xxx'
= neofiles_file_url logo # 'http://doma.in/neofiles/serve-file/#{logo.id}'
= neofiles_link logo, 'Our logo' # '<a href="...#{logo.id}">Our logo</a>'
This file/chunks concept is called Mongo GridFS (Grid File System) and is described as a standard way of storing files in MongoDB.
MongoDB collection & client (session) can be changed via Rails.application.config.neofiles.mongo_files_collection and Rails.application.config.neofiles.mongo_client
Model fields:
filename - real name of file, is guessed when setting #file= but can be changed manually later
content_type - MIME content type, is guessed when setting #file= but can be changed manually later
length - file size in bytes
chunk_size - max Neofiles::FileChunk size in bytes
md5 - md5 hash of file (to find duplicates for example)
description - arbitrary description
owner_type/id - as in Mongoid polymorphic belongs_to relation, a class name & ID of object this file belongs to
is_deleted - flag that file was once marked as deleted (just a flag for future use, affects nothing)
There is no sense in deleting a file since space it used to hold is not reallocated by MongoDB, so files are considered forever lasting. But technically it is possible to delete model instance and it’s chunks will be deleted as well.
Instance Attribute Summary collapse
-
#file ⇒ Object
Returns the value of attribute file.
Class Method Summary collapse
-
.class_by_content_type(content_type) ⇒ Object
Guess descendant class of Neofiles::File by MIME content type to use special purpose class for different file types:.
-
.class_by_file_name(file_name) ⇒ Object
Same as file_class_by_content_type but for file name string.
-
.class_by_file_object(file_object) ⇒ Object
Same as file_class_by_content_type but for file-like object.
-
.cleanname(pathname) ⇒ Object
Extract only file name partion from path.
-
.extract_basename(object) ⇒ Object
Try different methods to extract file name or path from argument object.
-
.extract_content_type(basename) ⇒ Object
Try different methods to extract MIME content type from file name, e.g.
-
.get_stores_class_name(stores) ⇒ Object
return array with names for each store.
- .read_data_stores ⇒ Object
- .write_data_stores ⇒ Object
Instance Method Summary collapse
-
#admin_compact_view(template) ⇒ Object
Representation of file in admin “compact” mode, @see Neofiles::AdminController#file_compact.
-
#base64 ⇒ Object
Encode bytes in base64.
-
#data ⇒ Object
Chunks bytes concatenated, that is the whole file content.
-
#data_uri(options = {}) ⇒ Object
Encode bytes id data uri.
-
#nullify_unpersisted_file ⇒ Object
Reset @file after save.
-
#save_file ⇒ Object
Real file saving goes here.
-
#unpersisted_file? ⇒ Boolean
Are we going to save file bytes on next #save?.
Instance Attribute Details
#file ⇒ Object
Returns the value of attribute file.
87 88 89 |
# File 'app/models/neofiles/file.rb', line 87 def file @file end |
Class Method Details
.class_by_content_type(content_type) ⇒ Object
Guess descendant class of Neofiles::File by MIME content type to use special purpose class for different file types:
Neofiles::File.file_class_by_content_type('image/jpeg') # -> Neofiles::Image
Neofiles::File.file_class_by_content_type('some/unknown') # -> Neofiles::File
Can be used when persisting new files or loading from database.
181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'app/models/neofiles/file.rb', line 181 def self.class_by_content_type(content_type) case content_type when 'image/svg+xml' ::Neofiles::File when /\Aimage\// ::Neofiles::Image when 'application/x-shockwave-flash' ::Neofiles::Swf else self end end |
.class_by_file_name(file_name) ⇒ Object
Same as file_class_by_content_type but for file name string.
195 196 197 |
# File 'app/models/neofiles/file.rb', line 195 def self.class_by_file_name(file_name) class_by_content_type(extract_content_type(file_name)) end |
.class_by_file_object(file_object) ⇒ Object
Same as file_class_by_content_type but for file-like object.
200 201 202 |
# File 'app/models/neofiles/file.rb', line 200 def self.class_by_file_object(file_object) class_by_file_name(extract_basename(file_object)) end |
.cleanname(pathname) ⇒ Object
Extract only file name partion from path.
170 171 172 |
# File 'app/models/neofiles/file.rb', line 170 def self.cleanname(pathname) ::File.basename(pathname.to_s) end |
.extract_basename(object) ⇒ Object
Try different methods to extract file name or path from argument object.
138 139 140 141 142 143 144 145 146 147 |
# File 'app/models/neofiles/file.rb', line 138 def self.extract_basename(object) filename = nil %i{ original_path original_filename path filename pathname }.each do |msg| if object.respond_to?(msg) filename = object.send(msg) break end end filename ? cleanname(filename) : nil end |
.extract_content_type(basename) ⇒ Object
Try different methods to extract MIME content type from file name, e.g. jpeg -> image/jpeg
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'app/models/neofiles/file.rb', line 150 def self.extract_content_type(basename) if defined?(MIME) content_type = MIME::Types.type_for(basename.to_s).first else ext = ::File.extname(basename.to_s).downcase.sub(/[.]/, '') if ext.in? %w{ jpeg jpg gif png } content_type = 'image/' + ext.sub(/jpg/, 'jpeg') elsif ext == 'swf' content_type = 'application/x-shockwave-flash' elsif ext == 'svg' content_type = 'image/svg+xml' else content_type = nil end end content_type.to_s if content_type end |
.get_stores_class_name(stores) ⇒ Object
return array with names for each store
213 214 215 216 217 218 219 |
# File 'app/models/neofiles/file.rb', line 213 def self.get_stores_class_name(stores) if stores.is_a?(Array) stores.map { |store| Neofiles::DataStore.const_get(store.camelize) } else get_stores_class_name [stores] end end |
.read_data_stores ⇒ Object
204 205 206 |
# File 'app/models/neofiles/file.rb', line 204 def self.read_data_stores get_stores_class_name Rails.application.config.neofiles.read_data_stores end |
.write_data_stores ⇒ Object
208 209 210 |
# File 'app/models/neofiles/file.rb', line 208 def self.write_data_stores get_stores_class_name Rails.application.config.neofiles.write_data_stores end |
Instance Method Details
#admin_compact_view(template) ⇒ Object
Representation of file in admin “compact” mode, @see Neofiles::AdminController#file_compact. To be redefined by descendants.
133 134 135 |
# File 'app/models/neofiles/file.rb', line 133 def admin_compact_view(template) template.neofiles_link self, nil, target: '_blank' end |
#base64 ⇒ Object
Encode bytes in base64.
75 76 77 |
# File 'app/models/neofiles/file.rb', line 75 def base64 Array(data).pack('m') end |
#data ⇒ Object
Chunks bytes concatenated, that is the whole file content.
64 65 66 67 68 69 70 71 72 |
# File 'app/models/neofiles/file.rb', line 64 def data self.class.read_data_stores.each do |store| begin return store.find(id).data rescue Neofiles::DataStore::NotFoundException next end end end |
#data_uri(options = {}) ⇒ Object
Encode bytes id data uri.
80 81 82 83 |
# File 'app/models/neofiles/file.rb', line 80 def data_uri( = {}) data = base64.chomp "data:#{content_type};base64,#{data}" end |
#nullify_unpersisted_file ⇒ Object
Reset @file after save.
127 128 129 |
# File 'app/models/neofiles/file.rb', line 127 def nullify_unpersisted_file @file = nil end |
#save_file ⇒ Object
Real file saving goes here. File length and md5 hash are computed automatically.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'app/models/neofiles/file.rb', line 110 def save_file if @file self.class.write_data_stores.each do |store| begin data_store_object = store.new id data_store_object.write @file self.length = data_store_object.length self.md5 = data_store_object.md5 rescue => ex notify_airbrake(ex) if defined? notify_airbrake next end end end end |
#unpersisted_file? ⇒ Boolean
Are we going to save file bytes on next #save?
104 105 106 |
# File 'app/models/neofiles/file.rb', line 104 def unpersisted_file? not @file.nil? end |