I originally had paperclip's default configuration contain
:url => "/some_path/:basename.:extension"
However, no all the files I'm uploading have extensions, and in those cases, I don't want the "." (between basename and extension in the configuration) to be there. So I'd like to do something like this:
:url => lambda { |i| "/some_path/:basename#{".:extension" if i.extension}" }
I tried that and got a "can't convert Proc into String" error, which tells me that paperclip doesn't support dynamic configuration of urls the way it does for styles and processors. Any ideas for how else I could do this, the less hack-ish the better?
The short answer is that you replace
:basename.:extension
with
:filename
The more detailed answer is that basename, filename and extension are all interpolations, and you can use any interpolations defined in 'paperclip/interpolations' (in paperclip's lib directory) or define your own.
For example:
Paperclip.interpolates(:temperature) do |attachment, style|
attachment.instance.city.temperature(:today)
end
assuming, of course, that the model that has_attached_file also belongs_to :city and that your city object knows today's temperature.
Related
My account model
has_many :articles
after_create :generate_default_articles
def generate_default_articles
article = articles.build(:title => 'User guide', :content => 'This is fake content', :author => 'Admin')
article.save
true
end
This works. However, I need content to be more complex and longer than a few words, i.e. a simple html page. So I would like to put that content in an external file. How do I do this ? What format should I use for that file ?
Edit after answer
Below is my final code using rii's answer and I18n for the title:
def generate_default_articles
content1 = File.open("#{Rails.root}/public/default_article_1_content.txt", "r+") {|f| f.read}
article1 = articles.build(:title => I18n.t('article1_title'), :content => content1, :author => 'Admin')
article1.save
true
end
You have several options here. If the content attribute is a text field you can store all the text content in there by opening a file you've stored in your system. The other option you have is using a gem like carrier_wave or paper_clip and store the reference to the file using the gem: https://github.com/carrierwaveuploader/carrierwave
https://github.com/thoughtbot/paperclip
The gem route is probably the most flexible way.
Now, how do you open a file in Ruby? Well, the File class is your friend:
content = File.open("your_file.txt", "r+")
file.close
As far as the file type, if it's just text you are storing, then have it be a .txt or maybe even an .html file. You can access your files path by doing something like so:
file = "#{RAILS_ROOT}/public/html_file/html_file1.html"
Hope that helps.
I have a model with a method to return a url to a person's avatar that looks like this:
def avatar_url
if self.avatar?
self.avatar.url # This uses paperclip
else
"/images/avatars/none.png"
end
end
I'm in the midst of upgrading to 3.1, so now the hard-coded none image needs be referenced through the asset pipeline. In a controller or view, I would just wrap it in image_path(), but I don't have that option in the model. How can I generate the correct url to the image?
I struggled with getting this right for a while so I thought I'd post the answer here. Whilst the above works for a standard default image (i.e. same one for each paperclip style), if you need multiple default styles you need a different approach.
If you want to have the default url play nice with the asset pipeline and asset sync and want different default images per style then you need to generate the asset path without fingerprints otherwise you'll get lots of AssetNotPrecompiled errors.
Like so:
:default_url => ActionController::Base.helpers.asset_path("/missing/:style.png", :digest => false)
or in your paperclip options:
:default_url => lambda { |a| "#{a.instance.create_default_url}" }
and then an instance method in the model that has the paperclip attachment:
def create_default_url
ActionController::Base.helpers.asset_path("/missing/:style.png", :digest => false)
end
In this case you can still use the interpolation (:style) but will have to turn off the asset fingerprinting/digest.
This all seems to work fine as long as you are syncing assets without the digest as well as those with the digest.
Personally, I don't think you should really be putting this default in a model, since it's a view detail. In your (haml) view:
= image_tag(#image.avatar_url || 'none.png')
Or, create your own helper and use it like so:
= avatar_or_default(#image)
When things like this are hard in rails, it's often a sign that it's not exactly right.
We solved this problem using draper: https://github.com/jcasimir/draper. Draper let us add a wrapper around our models (for use in views) that have access to helpers.
Paperclip has an option to specify default url
has_attached_file :avatar, :default_url => '/images/.../missing_:style.png'
You can use this to serve default image' in case user has not uploaded avatar.
Using rails active storage I solved this problem by doing this:
# Post.rb
def Post < ApplicationRecord
has_one_attached :image
def thumbnail
self.image.attached? ? self.image.variant(resize: "150x150").processed.service_url : 'placeholder.png';
end
def medium
self.image.attached? ? self.image.variant(resize: "300x300").processed.service_url : 'placeholder.png';
end
def large
self.image.attached? ? self.image.variant(resize: "600x600").processed.service_url : 'placeholder.png';
end
end
Then in your views simply call:
<%= image_tag #post.thumbnail %>,
In order to DRY up my code for attachments pictures, I created an initializer to override the #default_options variable used by Paperclip.
This way, I don't have to specify again and again the url, path and storage I want.
I'd like to go a step further and include the validation in it but I can't make it work...
Any Idea?
EDIT 1: I want at least to validate both presence and size.
EDIT 2: Part of my current code
module Paperclip
class Attachment
def self.default_options
if Rails.env != "production"
#default_options = {
:url => "/assets/:class/:attachment/:id/:style/:normalized_name",
:path => ":rails_root/public/assets/:class/:attachment/:id/:style/:normalized_name",
:default_style => :original,
:storage => :filesystem,
:whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails]
}
else
...
end
end
end
normalized_name is an outside function, feat: http://blog.wyeworks.com/2009/7/13/paperclip-file-rename
EDIT 3:
This blog: http://omgsean.com/2009/02/overriding-paperclip-defaults-for-your-entire-rails-app/ presnents the default_options hash with a validations key.
So it could be possible, not found yet though.
You will not be able to move the validations into a default_options hash (as these validations are performed outside the attachment class (inside a paperclip module). My thought is that if you have the same validations across all your models, you might need to look into using inheritance to decrease code duplication. I would advise against moving validations into an initializer.
I have byte streams on the server that I'd like to attach to a model class with Paperclip, and I'd like to be able to specify the name that they're saved as on the filesystem. Because I have a lot of these incoming files, I'd prefer to be able to create them as Tempfiles so that I don't have to worry about name collisions and deleting them manually and such. This is what I'm doing:
desired_file_name = 'foo.txt'
Tempfile.open([File.basename(desired_file_name), File.extname(desired_file_name)]) do |tf|
tf.write(content_stream)
tf.rewind
model_obj.paperclip_attachment = tf
end
That pretty much works. The only problem is, my Paperclip attachment ends up with a tempfile name like foo.txt.201029392u-gyh-foh96y.txt. So how can I tell Paperclip what to save my file as? Calling model_obj.paperclip_attachment_file_name = desired_file_name doesn't work. The DB field gets saved as that name, but on the filesystem I still have that tempfile name.
I think you can define your own interpolation interpolation to do that. You can then attach the file normally. For example:
# config/initializers/paperclip.rb
Paperclip.interpolates :custom_filename do |attachment, style|
# Generate your desired file name here.
# The values returned should be able to be regenerated in the future because
# this will also be called to get the attachment path.
# For example, you can use a digest of the file name and updated_at field.
# File name and updated_at will remain the same as long as the file is not
# changed, so this is a safe choice.
SHA1.sha1("#{attachment.original_filename}-#{attachment.updated_at}")
end
# app/models/post.rb
class Post < ActiveRecord::Base
has_attached_file :attachment,
:path => ':rails_root/public/system/:class/:attachment/:id/:style/:custom_filename',
:url => '/system/:class/:attachment/:id/:style/:custom_filename'
end
Note that this only changes the file name in the file system. model.attachment_file_name or model.attachment.original_filename will still keep the original file name.
I'm using the Polymorphic fork of Paperclip in Rails, but have been having some massive problems with regards to the overwriting of unique filenames. No matter whether I put a time-stamp (more on that in a second) or the id of the asset in the URL, if a file with the same name is uploaded subsequently, then the previous one is overwritten.
Also, it was working before, but the Time interpolation is now outputting just "0" instead of the timestamp.
module Paperclip
module Interpolations
def stamp(attachment, style)
attachment.instance_read(:created_at).to_i
end
end
end
Now just outputs;
0
This is what my URL field is;
:url => "/assets/images/:stamp/:id_:style.:extension"
Thanks.
Try adding this to config/initializers/paperclip.rb
Paperclip.interpolates :stamp do |attachment, style|
attachment.created_at.to_i
end