Paperclip image name changed after updating any attribute - ruby-on-rails

This is my model
class Technology < ActiveRecord::Base
attr_accessible :name #etc ....
has_attached_file :logo, :path => ":rails_root/public/technologies/logos/:normalized_input_file_name"
Paperclip.interpolates :normalized_input_file_name do |attachment, style|
attachment.instance.normalized_input_file_name
end
def normalized_input_file_name
name = "#{self.name}".downcase
"#{self.tuid}_"+name.gsub(/[^a-zA-Z0-9]{2,}/,' ').strip.gsub(/\W/,'_').gsub(/\A_/,'').gsub(/_\z/,'')+"_150x"+".png"
end
end
When I create any technology, I upload a logo for it and the image stored in public directory with the new name as I want using the method "normalized_input_file_name".
e.g.technology name is HTML5 and file name becomes id_html5_150x.png
But when I need to update name the image path also changed.
e.g. HTML 5 file name becomes id_html_5_150x.png Here actual image file name is not updated
But path is updated. So I can't find the image.

Use a before_save hook to download and store the image again if you recognize that the name attribute going to change.
This code is totally untested but should give you an idea:
before_save do
if self.name_changed?
self.logo = logo.uploaded_file
end
end

Related

Rails Active Storage: Get relative disk service path for attachment

I'm switching to Rails Active Storage to handle upload and storage of images locally (using the disk service) for a product catalog, and I'm having trouble getting a usable url to the image to feed into an <img> tag. I'm using React on the frontend, so I can't (easily) use Rails helpers to generate the tag.
ActiveStorage puts the files in /public/images. I can hardcode relative links to the files (i.e. http://localhost:3000/images/Ab/CD/AbCDEfGhIjkL) and it works fine.
Relevant snippet from Product.rb:
class Product < ApplicationRecord
attr_accessor :image_url
has_one_attached :image
def as_json(options)
h = super(options)
if self.image.attached?
h[:image_url] = ActiveStorage::Blob.service.service_url(self.image.key)
end
h
end
end
as_json produces a JSON object to feed to React that has an entry image_url that is used for the <img>'s src attribute. With the code above, image_url contains the full path to the file (i.e. http://localhost:3000/srv/www/rails_app/public/images/Ab/CD/AbCDEfGhIjkL). Using url_for in the view produces the same result. I want it to only contain the path relative to rails root.
I could manipulate the string to remove everything before the relative path, but I foresee this causing bugs in the future if anything ever changes, so I'd much rather find a way to get ActiveStorage to just generate an appropriate string for me.
Thanks!
You need to use the routes helper to build of a URL to your Rails app.
https://guides.rubyonrails.org/active_storage_overview.html#linking-to-files
class Product < ApplicationRecord
attr_accessor :image_url
has_one_attached :image
def as_json(options)
h = super(options)
if self.image.attached?
h[:image_url] = Rails.application.routes.url_helpers.rails_blob_path(self.image)
end
h
end
end

Carrierwave upload - image or video

I have found this link that explains how not to make thumbnails if the upload is not a video.
But how can I check, afterwards, if the saved file IS or IS NOT an image ? (In my case, I can store either images or videos), by calling a method on the model that holds the uploader ? DO I need some sort of callback in is_image?(new_file) that will set a field on the model ?
I'd like to be able to call #model.is_image?or #model.iI would bes_video? or maybe even something like case #model.type; when :video ; ...
I have added a callback to set an attribute on the model that holds the uploader
# uploader/my_uploader.rb
...
def image?(new_file)
if new_file.content_type.include? 'image'
model.image = true
true
else
false
end
end
# models/my_model.rb (mongoid)
...
field :image, type: Boolean
mount_uploader MyUploader
# View
#my_model.image?

Storing an image based upon nothing but a URL in Paperclip

I am using paperclip for a profile picture upload feature in my rails app. This works nicely for the default case of uploading images to a profile, but I want to allow users without a picture to pick from one of a selection of precanned 'stock' images.
These images are hosted locally, within my assets images folder. Therefore on these occasions I want to be able to add an image to my EventImage object without actually uploading an image, more just referencing a URL at a local path.
I have tried pretty much every answer from this post : Save image from URL by paperclip but none of them seem to work. I am using paperclip version paperclip (4.3.1 37589f9)
When I try the solution of :
def photo_from_url(url)
puts "we got:"+url
Thread.new do
self.photo = URI.parse(url)
end
end
It results in no image reference being stored, and regardless of the URL to an image I pass into that method, it never displays my image when I do : <%= image_tag #event.event_images.first.photo.url %> - instead it shows the default image for when an image has not been located or stored.
I also have to put it in a new thread otherwise it gets tied up and blocks / resulting in a timeout which seems to be a problem with URI.parse, also the image ends up failing validation as photo is 'empty' which is not allowed in my validation, so I end up removing the validation presence line on :photo, which still does not solve the problem. I really just want the models paperclip method :photo - to point to a local url sometimes, and my correctly normally uploaded files other times.
See the whole class here:
# == Schema Information
#
# Table name: event_images
#
# id :integer not null, primary key
# caption :string
# event_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# photo_file_name :string
# photo_content_type :string
# photo_file_size :integer
# photo_updated_at :datetime
#
class EventImage < ActiveRecord::Base
attr_accessor :PAPERCLIP_STORAGE_OPTS
has_attached_file :photo , PAPERCLIP_STORAGE_OPTS
validates_attachment_presence :photo
validates_attachment_content_type :photo, :content_type => ["image/jpg", "image/jpeg", "image/gif", "image/png"]
validates_with AttachmentSizeValidator, :attributes => :photo, :less_than => 3.megabytes
belongs_to :event
def photo_from_url(url)
Thread.new do
self.photo = URI.parse(url)
end
end
end
Instead, I have decided that it would be best to add a 'canned_image_id' to the EventImage model, then use a photo_url method in the model which can choose to return either the paperclip url or the canned image url depending on whether or not the image is a canned image. It also hides this complexity behind a helper method :)

creating an alias_attribute for a paperclip image in Rails

I'm currently building an app which has a model Post. I'm using the paperclip gem to upload images, and everything is going well.
class Post < ActiveRecord::Base
has_attached_file :headerimage, styles: {banner => "400x300#"}
end
As you can see by my class above, if I were to get a Post object, I could get the banner image with the following in my view:
image = Post.first.headerimage(:banner)
Alias
However, in my app, it must have the image attribute image refer to the thumbnail image. So, in my models class, I wrote
class Post < ActiveRecord::Base
has_attached_file :headerimage, styles: {banner => "400x300#"}
alias_attribute :image, :headerimage
end
which allows me to get an image by calling the following:
image = Post.first.image
This is what I want - however, it gets the original image from paperclip, so it is equivalent to writing the following:
image = Post.first.headerimage instead of image = Post.first.headerimage(:banner)
How can I set up a proper alias_attribute to access the paperclip thumnail? I can't seem to find an answer anywhere else, and I am unsure how paperclip is actually working.
I thought I might logically be able to do something like
alias_attribute :image, :headerimage(:banner)
but that does not work.
You can just try this - basically call the original with arguments since aliases won't take parameter but we know the fixed parameter to pass:
alias :image, :headerimage
def headerimage(name=nil)
headerimage(name || :thumb)
end

Displaying paperclip attachments stored in a non-public folder

My conundrum is how to embed in an html page an image whose source is not available to the Internet at large.
Let's say I have, in a Rails/Paperclip setup, the following model:
class Figure < ActiveRecord::Base
has_attached_file :image
...
end
class User < ActiveRecord::Base
... (authentication code here)
has_many :figures
end
In the controller:
class FiguresController < ActionController::Base
def show
# users must be authenticated, and they can only access their own figures
#figure = current_user.figures.find(params[:id])
end
end
In the view:
<%= image_tag(#figure.image.url) %>
The problem with this, of course, is that with the default Paperclip settings images are stored in the public directory, and anyone with the link can access the stored image bypassing authentication/authorization.
Now, if we tell Paperclip to store attachments at a private locations:
class Figure < ActiveRecord::Base
has_attached_file :image, path: ":rails_root/private/:class/:attachment/:id_partition/:style/:filename",
url: ":rails_root/private/:class/:attachment/:id_partition/:style/:filename"
...
end
Then it's easy to control who the image gets served to:
class FiguresController < ActionController::Base
def show
#figure = current_user.figures.find(params[:id])
send_file #figure.image.path, type: 'image/jpeg', disposition: 'inline'
end
end
The effect of this action is to display the image in its own browser window/tab.
On the other hand, image_tag(#figure.image.url) will understandably produce a routing error, because the source cannot be accessed!
Thus, is there a way to display the image via image_tag in a regular HTML page, while still restricting access to it?
You need to change the :url option passed to has_attached_file so that it matches the route for your figures controller.
For example, if the correct url is /figures/123 for the figure with is 123 then the url you pass to has_attached_file should be
'/figures/:id'
Or even
'/:class/:id'
Since the :class segment will be interpolated to the pluralized lowercase underscore form of the name. You could also append the extension or the filename if you wanted (but you would then have to change the controller code slightly to extract the id)

Resources