I have a model with a binary field that contains a file. I'd like to save this file to disk as part of a process I need to do. For some reason, I can't find anything on how to do this.
The model contains a filename field and a file_contents field. I'd like to do something like this:
model = SomeModel.find :first
model.file_contents.save_to_file(model.filename)
Any help would be appreciated!
I don't know why you'd want to call #save_to_file on the file contents instead of the model. Since you defined file_contents as an AR attribute I guess you want to save it to the DB and save it to disk. If that's the case you can simply add a method like this to your model:
class YourModel < ActiveRecord::Base
# ... your stuff ...
def save_to_file
File.open(filename, "w") do |f|
f.write(file_contents)
end
end
end
And then you'd simply do:
obj = YourModel.find(:first)
obj.save_to_file
In ActiveRecord, the :binary type that you use to define your column type in your migration is going to map to a blob type in the database. So that wouldn't allow you to save to a file.
I think you would need to define a model class that is not a subclass of ActiveRecord::Base and define a custom save_to_file method for that class using the file i/o support in Ruby (the IO class and its subclass, File).
class SomeModel
attr_accessor :file
attr_accessor :contents
def initialize
#file = File.new("file.xyz", "w")
end
def save_and_close
#file << contents
#file.close
end
end
Related
I see this has been asked a few times over the years (eg Upload path based on a record value for Carrier wave Direct, Passing a parameter to the uploader / accessing a model's attribute from within the uploader / letting the user pick the thumbnail size), but I'm convinced I must be overcomplicating this, as it seems like a very simple problem...
I have a very straightforward Video model that mounts an uploader:
class Video < ApplicationRecord
mount_uploader :file, VideoUploader
end
In the controller, I allow two parameters:
def video_params
params.require(:video).permit(:title, :file)
end
In the actual VideoUploader, I seem to have access to a number of variables derived from the :file column using class builtins (eg original_filename), and I can process the file using ffmpeg parameters. However, I want the parameters to be conditional based on the :title string, and I have no idea how to scope it or access it. What is the absolute simplest way to make sure this variable is accessible to those methods?
Edit: here's the uploader code:
class VideoUploader < CarrierWave::Uploader::Base
require 'streamio-ffmpeg'
include CarrierWave::Video
case #title # not working
when "tblend_glitch"
process encode_video: [:mp4,
resolution: "1280x960",
custom: %w(-to 5 -vf scale=-2:720,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference,spp=4:10,tblend=all_mode=average,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference,spp=4:10,tblend=all_mode=average,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference,spp=4:10,tblend=all_mode=average,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference)]
...
end
def full_filename(for_file)
super.chomp(File.extname(super)) + '.mp4'
end
def filename
original_filename.chomp(File.extname(original_filename)) + '.mp4'
end
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
Thanks!
You should be able to access the instance in the uploader using the model method.
You haven't defined #title — it's nil. You can create a conditional version with the following code.
class VideoUploader < CarrierWave::Uploader::Base
version :tblend, if: :tblend_glitch? do # use a better version name
process encode_video: [:mp4,
resolution: "1280x960",
custom: %w(-to 5 -vf scale=-2:720,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference,spp=4:10,tblend=all_mode=average,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference,spp=4:10,tblend=all_mode=average,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference,spp=4:10,tblend=all_mode=average,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference)]
end
# rest of the code
private
def tblend_glitch?
model.title == 'tblend_glitch'
end
end
Ref: https://github.com/carrierwaveuploader/carrierwave#conditional-versions
In theory you can access the model and its attributes from the uploader. However, it looks like the mounted uploader gets invoked before the other attributes are assigned.
For me it worked to create the model with the regular parameters first, and assign the attribute that has the uploader mounted (in your case :file) in a second step. Then I could read all model attributes correctly from within the uploader.
In your case that would be something like this in the controller:
#video = Video.new(video_params.except(:file))
#video.file = video_params[:file] # Invoke uploader last to access the other attributes
I'm searching for quite some time now a handy Method or even better, a Gem, that would allow me to CRUD Files.
Specific needs are:
Create a file from a :text string. (and control file type. e.g. test.js)
Edit the File (Rewrite) if an update has occurred to :text
Basically the whole CRUD Powerhouse.
Now, of course i know that there is no Magic Willie that will do this all by himself, i'm just looking for a right direction, from where i can start.
Use Case:
We have a documents controller with the variables:
:title
:body
:user_id
The :body (a simple text_area input) contains some .js code. How can i create a file (e.g test.js) based on :body which was entered by the User.
E.g. for simply creating the file => File.open('text.js', 'w') { |file| file.write(#something.body) }
So far i'm here
def create
#script = current_user.scripts.build(script_params)
# Creating Folder (if it doesn't exist already)
directory_name = "#{Rails.root}/public/userscripts/#{#script.user_id}"
Dir.mkdir(directory_name) unless File.exists?(directory_name)
# Write the file with model attribute :source
File.open("public/userscripts/#{#script.user_id}/#{#script.name}.user.js", 'w') { |file| file.write(#script.source) }
end
Paperclip will allow you to attach files and you can update them as well.
You can create a temporary file, assign it to the attachment attribute, then save the model.
# create and write a file with the contents you want
open "/tmp/tmpfile" do |f|
model.attachment = f
model.save!
end
You can put this in a before_save filter to always update the file attachment.
class Model < ActiveRecord::Base
has_attached_file attachment
before_save :update_attachment
def update_attachment
File.open("/tmp/tmpfile", 'w') { |file| file.write(self.body) }
open "/tmp/tmpfile" do |f|
self.attachment = f
self.attachment_content_type = "text/javascript"
end
end
I am trying to allow uploading pdf file. When the pdf file is uploaded, a Paperclip::Processor will convert it into a text file in its make instance method. Then, I would like to parse the text file to populate ActiveRecord class. How can I access the processed file returned by the Processor in its make instance method?
The code skeleton is here:
module Paperclip
class PdfToText < Processor
...
def make
dst = File.new(..)
#do something to dst
dst
end
end
end
class Xyz < ActiveRecord::Base
has_attached_file :source_file, ..., :processors =>[:pdftotext]
after_post_process :do_it
def do_it
# I would like to access the dst file returned in make of the processor PdfToText
end
end
Appreciate your thoughts.
What about this?
source_file.queued_for_write[:original].path
I am trying to display the filename of a Carrierwave attachment in a Rails erb template. The following does not work:
<%= #page.form.filename %>
This seems in line with the documentation. Is some additional step needed?
My page model looks like this:
class Page < ActiveRecord::Base
mount_uploader :form, FormUploader
end
The form uploader looks like this:
class FormUploader < CarrierWave::Uploader::Base
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def extension_white_list
%w(pdf)
end
end
I have been able to get the filename via the file internal parameter:
<%= #page.form.file.filename %>
The documentation you're looking at is the sanitized file, it's what it uses for actually storing a file. The part you're looking for is FormUploader, which is an Uploader, and part of http://rubydoc.info/gems/carrierwave/0.5.2/CarrierWave/Uploader
If you want to get the file name, you could either read it from the database column directly, or use File.basename(#page.form.path) to extract it easily.
The Carrierwave docs might be a bit off, but recommended way seems to be:
#page.form.file.identifier
#adamonduty's solution is great. Another solution I used before, just create a method on the model:
def name
file.path.split("/").last
end
You're right #epylinkn. Documentation points towards using:
#page.form.file.identifier
But when I use that, I always get nil (just as #Cheng commented).
I then inspected my objects methods (#page.form.file.methods.inspect), and found the following to work:
#page.form.file_identifier
In your model's associated uploader class, define a filename method.
def filename
File.basename(path)
end
You can then call
model_instance.file.filename
Works as of CarrierWave 1.1.0. This is a succinct restatement/amalgamation of kikito and Chris Alley's responses above.
If you're using ActiveRecord, you can directly access the field named form in two ways:
def my_method
self[:form]
end
or
def my_method
form_before_type_cast
end
The second method is read-only.
CarrierWave::SanitizedFile has a private original_filename method containing the filename of the uploaded file. (docs: http://rdoc.info/github/jnicklas/carrierwave/master/CarrierWave/SanitizedFile:original_filename)
After reading through this thread from the CarrierWave mailing list, none seemed to fit my needs. With something like
class Upload < ActiveRecord::Base
mount_uploader :file, FileUploader
# ...
I heavily modify the :file column value from the original filename. Due to this I decided to track the original filename in a separate column from the one bound to CarrierWave. In my FileUploader I simply added a reader that wraps the private original_filename method:
def original_file
original_filename
end
I then added a before_create event to the Upload class (my Upload records are never modified, so a before_create is acceptable for my needs)
before_create do
self.original_file = self.file.original_file
end
I'm assuming you've got models like this?
class Page
mount_uploader :form, FormUploader
end
If so you should be able to call:
#page.form.url
#page.form.filename
Are you sure you've uploaded/attached the file correctly? What do you see when you inspect #page.form? Remember, the attachment will not be saved until you've fully processed the upload.
This is my solution:
before_save :update_file_attributes
def update_file_attributes
if file.present? && file_changed?
self.content_type = file.file.content_type
self.file_size = file.file.size
self.file_name = read_attribute(:file)
end
end
Is there a better way to save some string as an attachment via Paperlip as making a tmp file, putting the string into it, opening it again and saving it as an attachment ?
Like this :
def save_string data
tmp_file = "/some/path"
File.open(tmp_file,'w') do |f|
f.write(data)
end
File.open(tmp_file,'r') do |f|
ceneo_xml = f
save!
end
end
There is actually a better way - you can wrap it to StringIO which Paperclip enhances and you will get a pseudo uploaded file in no time. You can customize it by defining instance methods or directly create a subclass of StringIO like this
class InvoiceAttachment < StringIO
def initialize(invoice, content)
#invoice = invoice
super(content)
end
def original_filename
from = #invoice.from
to = #invoice.to
date = #invoice.created_at.strftime('%B-%Y').downcase
"invoice_#{date}_from_#{from}_to_#{to}.pdf"
end
def content_type
'application/pdf'
end
end
Enjoy!
Paperclip stores files alongside your models -- this is what it has been written to do, so I think the short answer is "no".
If you look in attachment.rb in the Paperclip source you'll see a method called def assign uploaded_file. If you look at the implementation of this method you can see that it expects the uploaded file object to have a certain methods defined on it.
You could create your own class which followed the same interface as Paperclip expects, but to be honest your solution of saving a file and assigning that to Paperclip is probably the easiest approach.