How can I reference images in the asset pipeline from a model? - ruby-on-rails

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 %>,

Related

Default Value for ActiveStorage Rails 5.2.0

I am using Rails ActiveStorage. I want that whenever the value of attachment (in my case image is null then replace it with "abc.png" which is present in assets folder..)
This is what my model.rb file looks like but this code does not seem to work. I am looking for how to set default / nil value for avatar.
has_one_attached :avatar #bot icon
after_create_commit check_avatar(self)
def check_avatar(self)
if(!self.avatar.present?)
{
self.avatar = "abc.png"
}
end
Active storage doesn't provide default option like paperclip does, however, you can do write your method to attach default file in case image is nil. you can use attach method to do so.
def image_nil
if !self.image?
user.image.attach(io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'image/jpeg')
end
end
you can omit content_type but it is good to provide it, the content type you provide will serve as a fallback in case analyzer can't do it.
Hope this helps!

How can I get url of my attachment stored in active storage in my rails controller

How can I get url of my has_one model attachment stored in active storage in my rails controller. So, that I would be able to send it as full link as api in json.
So far, I have tried following methods but each of them are giving various issues:
current_user.image.service_url ---- undefined method `service_url' for #<ActiveStorage::Attached::One:0x....
Rails.application.routes.url_helpers.rails_disk_blob_path(current_user.image, only_path: true), it gives me an output like:
"/rails/blobs/%23%3CActiveStorage::Attached::One:0x007f991c7b41b8%3E"
but this is not a url, right? I am not able to hit and get image on browser.
url_for ----
undefined method `active_storage_attachment_url' for #<Api::V1::UsersController:0x007f991c1eaa98
Use the method rails_blob_path for attachements in a controller and models
For example, if you need to assign a variable (e.g. cover_url) in a controller, first you should include url_helpers and after use method rails_blob_path with some parameters. You can do the same in any model, worker etc.
Complete example below:
class ApplicationController < ActionController::Base
include Rails.application.routes.url_helpers
def index
#event = Event.first
cover_url = rails_blob_path(#event.cover, disposition: "attachment", only_path: true)
end
end
Sometimes, e.g. an API needs to return the full url with host / protocol for the clients (e.g. mobile phones etc.). In this case, passing the host parameter to all of the rails_blob_url calls is repetitive and not DRY. Even, you might need different settings in dev/test/prod to make it work.
If you are using ActionMailer and have already configuring that host/protocol in the environments/*.rb you can reuse the setting with rails_blob_url or rails_representation_url.
# in your config/environments/*.rb you might be already configuring ActionMailer
config.action_mailer.default_url_options = { host: 'www.my-site.com', protocol: 'https' }
I would recommend just calling the full Rails.application.url_helpers.rails_blob_url instead of dumping at least 50 methods into your model class (depending on your routes.rb), when you only need 2.
class MyModel < ApplicationModel
has_one_attached :logo
# linking to a variant full url
def logo_medium_variant_url
variant = logo.variant(resize: "1600x200>")
Rails.application.routes.url_helpers.rails_representation_url(
variant,
Rails.application.config.action_mailer.default_url_options
)
end
# linking to a original blob full url
def logo_blob_url
Rails.application.routes.url_helpers.rails_blob_url(
logo.blob,
Rails.application.config.action_mailer.default_url_options
)
end
end
I didn't have used rails active storage but what i have read in documentation this might help you
Try rails_blob_url(model.image)
For more http://edgeguides.rubyonrails.org/active_storage_overview.html
I was able to view the image in the browser using the following:
<%= link_to image_tag(upload.variant(resize: "100x100")), upload %>
Where upload is an attached image.

Paperclip dynamic url?

I have a Rails ActiveModel Product with a Paperclip image attachment column that needs to get it's image.url from 2 sources. One is an old S3 bucket/CloudFront, other is our new S3 bucket/CloudFront. They have completely different credentials.
If the instance Product.image_file_name is containing "old:" I want the URL to be something like cloudfront_url/products/file_name, if it doesn't - it should use the new S3 bucket/CloudFront. Upload is going to happen only on the new S3 bucket, but it'll fallback on the old one if image_file_name is containing old: as I mentioned.
Currently I'm authorized only with the new S3 bucket, not with the old one.
I have read that I should do something like:
class Product
has_attached_file: :image, url: dynamic_url_method
def dynamic_url_method
.... do some logic based on image_file_name
return constructed_url
end
end
However when I do that, I get undefined local variable dynamic_url_method.
If I wrap it in a lambda as said in https://stackoverflow.com/a/10493048 I get Error "no implicit conversion of Proc into String".
Have you guys successfully gotten Paperclip to work with a dynamic URL? It would be a life-saver if you know how to do so.
Completely scrap the whole idea of dynamic URL parameter given to the Paperclip attachment. It breaks S3 image uploading cause Paperclip can't figure out which URL to use.
The solution is to introduce a new column in your schema called image_url.
The column will be updated on initialize/update in the ActiveModel and used in the web pages.
In code
class Product
has_attached_file: :image
after_create :update_image_url
after_update :update_image_url
def update_image_url
new_image_url = # some logic based on image_file_name that would either return the CloudFront URL or save the URL from image.url which is generated by Paperclip
# Paperclip does not update image_file_name_changed? so we can't say
# after_create or after_update if: image_file_name_changed? instead
# we have to manually check that image_url and new_image_url are different
update(image_url: new_image_url) if image_url != new_image_url
end
end

how to cache a rails action with url parameters and be able to invalidate it

I'm serving a users default picture from the url /:username/picture (much like facebook). The action takes several get parameters to specify the image dimension and style wanted. Here is an example:
/john/picture?d[]=50&d[]=50&s=square
This would return johns default picture at a size of 50px x 50px cropped as a square.
Hitting the whole rails stack for every picture request is obviously not efficient. I'd like to cache the image versions. This seems possible with something like the solution to this so question.
caches_action :my_action, :cache_path => Proc.new { |c| c.params }
However, I need to find a way to clear the cache for all the image versions when a user changes their default picture. Basically when john changes his default picture I wish I could clear with a regex similar to the below so that all of the default thumbnails will be regenerated with the new default picture:
clear cache /john/picture*
How would I accomplish this in rails?
I could be misunderstanding your question, but I think you need a sweeper. http://guides.rubyonrails.org/caching_with_rails.html#sweepers
alongside your cache_index, you can assign a class to cache_sweeper in your controller.
# assign the cache_sweeper
caches_action :my_action, :cache_path => Proc.new { |c| c.params }
cache_sweeper :picture_sweeper
then, alongside of your controllers, add in a new class
# class to help with sweeping up cached pictures
class PictureSweeper < ActionController::Caching::Sweeper
observe Picture # or whatever your class is called
# ... be sure to read on how to setup a cache sweeper
def after_update( record )
# you should probably check if record.is_a? Picture or whatever your class is
self.class::sweep( record )
end
# note there is also after_create, after_destroy, after_save
def self.sweep( record )
# the cache directory is in ActionController::Base.page_cache_directory
# use the record to help reconstruct the path to your cached pictures
# and delete whatever you need to
end
# note, i showed the sweeping split out from the after_update method in case you
# wanted to sweep after several of the events... trying to be DRY...
end
Cache Sweeping Docs
Checkout https://github.com/pennymac/action_param_caching for a simpler version of this.

Rails Paperclip, DRY configuration

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.

Resources