Save a local attachment on the server - ruby-on-rails

Though paperclip gem is generally used for easy file uploads, I have a similar requirement at server side. I have generated an image at server and want to save it in various styles as configured in paperclip options.
I have generated the migration and my model looks like this:
attr_accessible :genimg
has_attached_file :genimg
styles: { mini: '48x48>', small: '100x100>',
product: '240x240>', large: '600x600>' },
default_style: :product
url: '/images/:basename.:extension',
path: ':rails_root/app/assets/images/:basename.:extension'
convert_options: { all: '-strip -auto-orient' }
Is there a way I can save my local image in various formats using paperclip. How can I do so? If that's not possible, what are the other possible way to try out?

class YourController < ActionController::Base
def your_action
#image = Image.find 1 # (might be other way how you get the asset)
file_path = '/path/to/your/file'
file = File.open(file_path, 'r')
#image.asset = file
#image.save
end
end
in console you might do:
Image.new(:data => File.new(path_to_your_file, "r"))

Related

Rails API cache endpoint with Carrierwave uploader

I'm experiencing some issues when caching some data in my Rails API, when the model has a Carrierwave uploader attached. The issue started when I added versions in my ImageUploader.
The model looks like this:
class Article < ApplicationRecord
validates_presence_of :title, :intro, :body, :image, :release_date, :slug
mount_uploader :image, ImageUploader
The ImageUploader now looks like this:
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
# Choose what kind of storage to use for this uploader:
storage :fog
# Override the directory where uploaded files will be stored.
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
process resize_to_fit: [1920,1080]
version :thumb do
process resize_to_fit: [400,400]
end
version :big do
process resize_to_fill: [1080,1080]
end
Before the change I had no versions, and did no processing the original image.
I have tried caching this endpoint, which looks like this:
today = Date.today
data = Rails.cache.fetch("articles_#{params[:slug]}", :expires_in => 12.hours) do
Article
.includes(:related_articles, :users, :external_authors, :categories)
.where('release_date <= ?', today)
.where(slug: params[:slug])
.first
.as_json(include: [{ related_articles: { except: [:body, :intro, :preview] } }, { categories: { } }, { users: { except: [:password_digest, :password_reset_token, :password_reset_sent_at] } }, { external_authors: { } }])
end
render json: data, status: :ok
The problem is that it initally works, then it suddenly does not work. Especially it does not work after restarting the server (but it has sometimes stopped working randomly after multiple fetches where it worked being cached), giving me error like the following: uninitialized constant ImageUploader::Uploader2293312720
If I then change the cacheKey, it works again, but suddenly the error comes back, either randomly (it works multiple times before it randomly gives me the error), or when restarting server.
For now, I've just removed the caching, and it works fine with no issues.
I'm using: Rails v5.2.5, and Carrierwave v2.2.2.

Rails paperclip default image only under certain conditions

I've got a Lead model which is splited by lead_status param on products and offers. Only leads with product status should contain images and offers should not. I've migrated attached product_image table to schema and tried to set a default image only for products. Like this:
class Lead < ApplicationRecord
has_attached_file :product_image, styles: { small: "150x150>", default: "350x350"}
validates_attachment_content_type :product_image, content_type: /\Aimage\/.*\z/
before_save :product_image_default_url
def product_image_default_url
if self.lead_status == "product" && self.product_image.url.nil?
self.product_image.url = "/images/:style/default_user_avatar.png"
end
end
Every time when I save a new lead without uploaded image I get "/product_images/original/missing.png" as a default url. No matter which status it has.
Model doesn't recognize new leads by it's status
How can I change that? Force my Lead model to save a default image url according to a "product" status and ignore all those with "offer" status?
My rails version is 5.2.1 and paperclip 6.0.0
Try with following,
has_attached_file
:product_image,
styles: { small: "150x150>", default: "350x350"},
default_url: ":style/default_user_avatar.png"
# app/assets/images/medium/default_user_avatar.png
# app/assets/images/thumbs/default_user_avatar.png
Existing method is,
def default_url
if #attachment_options[:default_url].respond_to?(:call)
#attachment_options[:default_url].call(#attachment)
elsif #attachment_options[:default_url].is_a?(Symbol)
#attachment.instance.send(#attachment_options[:default_url])
else
#attachment_options[:default_url]
end
end
In initializer, Provide monkey patch for following,
require 'uri'
require 'active_support/core_ext/module/delegation'
module Paperclip
class UrlGenerator
def default_url
if #attachment.instance.lead_status == 'product'
default_url = attachment_options[:default_url]
else
default_url = # provide another missing default_url
end
if default_url.respond_to?(:call)
default_url.call(#attachment)
elsif default_url.is_a?(Symbol)
#attachment.instance.send(default_url)
else
default_url
end
end
end
end
Update as per cases

Link existing S3 file as paperclip attachment

I have an external service that creates files and stores them into S3 (that my Rails app has access too).
I want to be able to use Paperclip in order to store that S3 object, as well as a thumbnail of it. I haven't been able to find documentation on how to use Paperclip with files that are already on S3. So, my code should look like:
page = Page.find(3)
page.s3_url = s3_url # I have the s3 route. And I would need to generate a thumbnail too.
page.save
So, in other words:
How can I tell PaperClip that my attachment is already in S3?
EDIT:
Here is what I have so far:
I know the file_name, the content_length and the content_type of the file that is already uploaded in S3. So, since the association is a Page has_attached_file :screenshot
This is what I do:
#page = Page.find(3)
#page.update_attributes(screenshot_content_type: screenshot_content_type, screenshot_file_size: screenshot_file_size, screenshot_update_at: screenshot_updated_at, screenshot_file_name: screenshot_file_name)
So, now I can do:
#page.screenshot and I see the paperclip object. However, when I do:
#page.screenshot.url => The url is not the one that I originally stored the image.
I've managed to solve this problem with paperclip interpolations:
Define in your model a field, witch would store path to the uploaded files
Load the paperclip attachment info to DB through property update
With interpolations specify a paperclip attachment :path, so it first looks for uploaded file, and then for default
Profit!
This will do the trick, because of how S3 storage composes urls:
path: This is the key under the bucket in which the file will be stored. The URL will be constructed from the bucket and the path. This is what you will want to interpolate. Keys should be unique, like filenames, and despite the fact that S3 (strictly speaking) does not support directories, you can still use a / to separate parts of your file name.
Here are more details with code:
Model
class MyModel
has_attached_file :file, path: ":existent_file_or_default", storage: :s3
end
Paperclip interpolations
Put this under config/initializers
Paperclip.interpolates :existent_file_or_default do |attachment, style|
attachment.instance.existent_file_path ||
attachment.interpolator.interpolate(":class/:attachment/:id_partition/:style/:filename", attachment, style)
end
Attach existent items
MyModel.create({
existent_file_path: "http://your-aws-region.amazonaws.com/your-bucket/path/to/existent/file.jpeg",
file_file_name: "some_pretty_file_name.jpeg",
file_content_type: "image/jpeg",
file_file_size: 123456
})
S3
Paperclip S3 integration is actually relatively simple - you'll be best looking at this Railscast on how to use Paperclip, and the afore-linked documentation to give you an idea on how Paperclip works; then you can just connect it to S3
--
In short, Paperclip basically takes a file object & saves the relevant data to your db. The storage of the file is dependent on the service you associate with Paperclip
What I'm trying to say is the two elements (data allocation & file storage) are two different elements of the gem, and if you can get Paperclip to handle the inbound files, you're half-way there:
Paperclip
Default implementation:
#app/models/page.rb
Class Page < ActiveRecord::Base
has_attached_file :photo
end
This will allow you to save an "attachment" to your Page database:
#app/controllers/pages_controller.rb
Class PagesController < ApplicationController
def new
#page = Page.new
end
def create
#page = Page.new(page_params)
end
private
def page_params
params.require(:page).permit(:title, :other, :information, :photo)
end
end
#app/views/pages/new.html.erb
<%= form_for #page do |f| %>
<%= f.file_field :photo %>
<%= f.submit %>
<% end %>
This will handle Paperclip uploads directly to your own app (storing the files in the public/system directory)
In order to get it to work with S3, you just have to add the S3 credentials to the model (taken from the Paperclip documentation):
#app/models/page.rb
Class Page < ActiveRecord::Base
has_attached_file :photo,
:storage => :s3,
:s3_credentials => Proc.new{|a| a.instance.s3_credentials }
def s3_credentials
{:bucket => "xxx", :access_key_id => "xxx", :secret_access_key => "xxx"}
end
end
Path
If you'd like to call the "S3" filepath for your file - you'll have to do this:
#app/controllers/pages_controller.rb
Class PagesController < ApplicationController
def show
#page = Page.find 3
#page.photo.url #-> yields S3 path ;)
end
end

How to make conditional styles in Paperclip?

I'm trying to make Paperclip convert every image to JPG unless it is an animated gif. Is there any way to specify a conditional style or I have to make a processor?
As far as i know Paperclip does not have any build in functionality to solve your problem except custom processors. You can do some thing like this:
module Paperclip
class ConditionalConverter < Thumbnail
def initialize(file, options = {}, attachment = nil)
super(file, options, attachment)
#format = :jpg unless self.animated?
end
end
end
ImageMagick should done it for you. For that you should add :jpg to your styles:
has_attached_file :avatar, :styles => { :thumb => ["32x32#", :jpg] }
Documentation

Save image from URL by paperclip

Please suggest me a way to save an image from an URL by Paperclip.
In Paperclip 3.1.4 it's become even simpler.
def picture_from_url(url)
self.picture = URI.parse(url)
end
This is slightly better than open(url). Because with open(url) you're going to get "stringio.txt" as the filename. With the above you're going to get a proper name of the file based on the URL. i.e.
self.picture = URI.parse("http://something.com/blah/avatar.png")
self.picture_file_name # => "avatar.png"
self.picture_content_type # => "image/png"
Here is a simple way:
require "open-uri"
class User < ActiveRecord::Base
has_attached_file :picture
def picture_from_url(url)
self.picture = open(url)
end
end
Then simply :
user.picture_from_url "http://www.google.com/images/logos/ps_logo2.png"
It didn't work for me until I used "open" for parsed URI.
once I added "open" it worked!
def picture_from_url(url)
self.picture = URI.parse(url).open
end
My paperclip version is 4.2.1
Before open it wouldn't detect the content type right, because it wasn't a file. It would say image_content_type: "binary/octet-stream", and even if I override it with the right content type it wouldn't work.
First download the image with the curb gem to a TempFile and then simply assign the tempfile object and save your model.
Into official documentation is reported here https://github.com/thoughtbot/paperclip/wiki/Attachment-downloaded-from-a-URL
Anyway it seems not updated, because in last version of paperclip something has changed and this line of code is no more valid:
user.picture = URI.parse(url)
It raise an error, in particular this error is raised:
Paperclip::AdapterRegistry::NoHandlerError: No handler found for #<URI:: ...
The new correct syntax is this one:
url = "https://www.example.com/photo.jpeg"
user.picture = Paperclip.io_adapters.for(URI.parse(url).to_s, { hash_digest: Digest::MD5 })
Also we need to add these lines into config/initializers/paperclip.rb file:
Paperclip::DataUriAdapter.register
Paperclip::HttpUrlProxyAdapter.register
Tested this with paperclip version 5.3.0 and it works.
It may helpful to you. Here is the code using paperclip and image present in remote URL .
require 'rubygems'
require 'open-uri'
require 'paperclip'
model.update_attribute(:photo,open(website_vehicle.image_url))
In model
class Model < ActiveRecord::Base
has_attached_file :photo, :styles => { :small => "150x150>", :thumb => "75x75>" }
end
As those are old Answer's here's a newer one:
Add Image Remote URL to your desired Controller in the Database
$ rails generate migration AddImageRemoteUrlToYour_Controller image_remote_url:string
$ rake db:migrate
Edit your Model
attr_accessible :description, :image, :image_remote_url
.
.
.
def image_remote_url=(url_value)
self.image = URI.parse(url_value) unless url_value.blank?
super
end
*In Rails4 you have to add the attr_accessible in the Controller.
Update your form, if you allow other to upload an Image from a URL
<%= f.input :image_remote_url, label: "Enter a URL" %>
This is a hardcore method:
original_url = url.gsub(/\?.*$/, '')
filename = original_url.gsub(/^.*\//, '')
extension = File.extname(filename)
temp_images = Magick::Image.from_blob open(url).read
temp_images[0].write(url = "/tmp/#{Uuid.uuid}#{extension}")
self.file = File.open(url)
where Uuid.uuid just makes some random ID.

Resources