Storing an image based upon nothing but a URL in Paperclip - ruby-on-rails

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 :)

Related

Why are Active Record callbacks bypassed on associated records?

I'm building a web application with Rails 5 and have run into an issue updating associated records through a parent record when I have defined non-persistent attributes (with attr_accessor) on the associated records. Specifically, I have the user supply the non-persistent attributes on the child records in some way, and, based on the values of those attributes, assign values to persistent attributes in a before_save callback. The problem is that the child records are not saved to the database (and hence the save callback is not called) unless persistent attributes are changed on the child records through the parent.
I've run into this issue in several different situations, but the (simplified) example here deals with using the Paperclip Gem to process images uploaded to AWS S3 by a client browser.
app/models/dog.rb
class Dog < ApplicationRecord
has_many :certificates, :dependent => :destroy
accepts_nested_attributes_for :certificates, :allow_destroy => true
end
app/models/certificate.rb
class Certificate < ApplicationRecord
# load with path to client-uploaded file on S3 and save to
# update digitized_proof attachment
attr_accessor :s3_key
belongs_to :dog
has_attached_file :digitized_proof,
:content_type => { :content_type => ['image/jpg', 'image/png'] }
before_save :fetch_digitized_proof_from_s3
def fetch_digitized_proof_from_s3
return unless self.s3_key.present?
# note `S3_BUCKET` is set in the aws initializer
s3_obj = S3_BUCKET.object(self.s3_key)
# load paperclip attachment via S3 presigned URL
s3_presigned_url = s3_obj.presigned_url(:get,
:expires_in => 10.minutes.to_i)
self.digitized_proof = URI.parse(s3_presigned_url)
end
end
apps/controllers/dogs_controller.rb excerpt
def update
#dog = Dog.find(params[:id])
if #dog.update(dog_params)
redirect_to ...
...
end
private
def dog_params
params.require(:dog).permit(
...,
:certificates_attributes => [:id, :_destroy, :s3_key]
)
end
I've written javascript that uploads images to a temporary folder in an S3 bucket directly from the client's browser and adds the s3_key to the update form so the image can be identified and processed server-side (see the fetch_digitized_proof_from_s3 method in certificate.rb). The issue is that the certificates are never updated unless an actual database attribute has changed in the update parameters.
Why is this occurring and how can I work around it?
Sample parameters
{
...,
certificates_attributes: [
{id: '1', _destroy: '0', s3_key: 'tmp/uploads/certificates/.../photo.jpg'},
{id: '2', _destroy: '0', s3_key: 'tmp/uploads/certificates/.../photo2.jpg'}
]
}
Gem Versions
rails-5.0.0
activerecord-5.0.0
paperclip-5.1.0
aws-sdk-2.10.0
EDIT
I'm able to accomplish the update on the certificates by calling fetch_digitized_proof_from_s3 from within the setter method for s3_key (and removing the before_save callback):
# app/models/certificate.rb
def s3_key=(key)
#s3_key = key
self.fetch_digitized_proof_from_s3
end
This triggers the associated certificates to save properly (I'm thinking this occurs since digitized_proof, which is a persistent attribute, is updated by the call to fetch_digitized_proof_from_s3). This works, but I'd still rather fetch the image from S3 when the record is saved.
It appears the associated records will not update unless a change is registered with ActiveModel::Dirty. This does not occur when non-persisted attributes are set:
cert = Certificate.find(1)
cert.s3_key = 'tmp/uploads/certificates/...'
cert.changed? # => false
Adding the following method to Certificate.rb produces the desired behavior:
def s3_key=(key)
attribute_will_change!('s3_key') unless s3_key == key
#s3_key = key
end
Now the result is
cert = Certificate.find(1)
cert.s3_key = 'tmp/uploads/certificates/...'
cert.changed? # => true
and the associated records update appropriately when s3_key is set.

Paperclip validating when no attachment

I have a rails model in which an image is uploaded using Paperclip.
I have added validation of size for the image.
validates_attachment-size :image, less_than => 5.megabytes
When trying to save the model when there is no attachment it validates the image which is absent and fails the save.
I need to save the model if there is no image and the validation should work only when there is an image.
First of you have a typo in your code. validates_attachment-size should be validates_attachment_size.
You wanted to do:
validates_attachment_size :image, less_than => 5.megabytes
This built in helper would work normally. But, this validation will force the validation of an actual attachment, means it won't work if the image is not present.
So, if you want to be sure if an image is present, you can add a custom validator where you will check the image presence. Like this:
validate :image_presence_and_size
def image_presence_and_size
if image.present? && image_file_size < 5.megabytes
errors.add(:file_size, "file size must be less than 5 megabytes.")
end
end
Try the following code.
validate :image_present
def image_present
if image.present? && image_file_size < 2.megabytes
errors.add(:file_size, "file size must be between 0 and 2 megabytes.")
end
end
Here, the validation will work if there is image present in the model and will skip the validation if there is no image.

Scraping images from user input URL using MetaInspector (Rails)

I'm trying to create an app where a user can submit a URL link, a title and description, and it'll create a post with the title, description and an image. I want to be able to scrape the best or main image from directly from the URL path that the user submitted and display it on the show page using MetaInspector. (The reason I didn't use Nokogiri or Mechanize is because I didn't understand it all that well and MetaInspector seems alot less daunting)
The problem is I'm very new to rails and I'm having a hard time following most tutorials.
Is anyone able to explain to me step by step how to do this or show me a source that's very detailed and noob friendly?
I have a Post model that contains the link, and should also save the scraped image as a Paperclip attachment:
class Post < ActiveRecord::Base
belongs_to :user
has_attached_file :image
end
# == Schema Information
#
# Table name: posts
#
# id :integer not null, primary key
# title :string
# link :string
# description :text
# created_at :datetime
# updated_at :datetime
# user_id :integer
# image_file_name :string
# image_content_type :string
# image_file_size :integer
# image_updated_at :datetime
The full code of my app is available at github.com/johnnyji/wanderful.
I really appreciate any help at all! Thank you
Let's walk through this step by step.
First, add the MetaInspector gem to your Gemfile
gem 'metainspector'
and run the bundle command.
We need another bit of code: open-uri. With it, we can read remote files from URLs as if they were local files. It is part of Rubys standard library, so it's already built in, but we still need to require it at the top of your post.rb:
require 'open-uri'
class Post < ActiveRecord::Base
belongs_to :user
has_attached_file :image
end
We want to grab an image whenever a Posts link changes, so we make a before_save callback that triggers whenever that happens:
class Post < ActiveRecord::Base
belongs_to :user
has_attached_file :image
before_save :get_image_from_link,
if: ->(post) { post.link_changed? }
end
you can find more about before_save and other callbacks in the ActiveRecord::Callbacks guide.
the link_changed? method is part of the "dirty tracking" functionality ActiveModel::Dirty provides
that if: ->(post) thing is called a "stabby lambda" - it's basically just a Ruby function that is called with the current post as an argument. If it returns true, the before_action is run. It could also be written as if: Proc.new { |post| post.link_changed? }
Now we need our get_image_from_link method. Since it's only supposed to be called from within the Post model itself and not from the outside (say, Post.find(5).get_image_from_link), we make it a private method:
class Post < ActiveRecord::Base
belongs_to :user
has_attached_file :image
before_save :get_image_from_link,
if: ->(post) { post.link_changed? }
private
def get_image_from_link
end
end
Reading MetaInspectors README, it has a cool method called page.images.best that does the hard work for us selecting the right image from that page. So we are going to
parse the link with MetaInspector
open the image it selected as best with open-uri as a File-like object
give that File-like object to Paperclip to save as an attachment
So:
def get_image_from_link
# `link` here is `self.link` = the current post.
# At least when reading attributes, `self` is implicit
# in Ruby
page = MetaInspector.new(link)
# maybe the page didn't have images?
return unless page.images.best.present?
# when you use IO resources such as files, you need
# to take care that you `.close` everything you open.
# Using the block form takes care of that automatically.
open(page.images.best) do |file|
# when writing/assigning a value, `self` is not
# implicit, because when you write `something = 5`,
# Ruby cannot know whether you want to assign to
# `self.something` or create a new local variable
# called `something`
self.image = file
end
end
This is far from perfect, because it lacks some error handling (what if MetaInspector fails to open the page? Or open-uri cannot read the image URL?). Also, this has the drawback that all that parsing, downloading and so on takes place right when the user submits or updates her post, so when she clicks on the save button, she'll have to wait for all this to complete.
For the next iteration, look into doing things like these asynchronously, for example with a job queue. Rails' new Active Job system might be a good starting point.

Rails 3.2 + Paperclip: Assign default image on user creation

I am working on adding a profile picture to the User model in my Rails app. I've already gotten screenshots successfully working with another model, but for some reason I'm having a lot of difficulties with profile pictures. To handle profile pictures, I've created a new ProfilePics model:
class ProfilePic < ActiveRecord::Base
attr_accessible :image, :user_id
has_attached_file :profile_pic, :default_url => "/system/user_profile_pics/profile.png",
:url => "/system/user_profile_pics/:id/:basename.:extension",
:path => ':rails_root/public:url'
:styles => { :large => "800x400", :thumb => "36x36" }
# **** Associations ****
# State that each profile picture can have an associated user
belongs_to :users
# **** Validations ****
# Only allow the user to upload .bmp, .gif, .jpg, .jpeg, and .png files
validates_attachment_content_type :image, :content_type => /^image\/(bmp|gif|jpg|jpeg|png)/
# Validate the presence of the user id
validates :user_id, :presence => true
# Order all profile pictures by ID, from first to last
default_scope :order => 'profile_pics.id ASC'
end
When a user signs up, he/she should be set the default profile picture. This picture is the image file specified in the :default_url argument for the has_attached_file method. However, I can't seem to figure out how to assign the user the default profile picture in the controller, after the User has been created. I don't want to add the profile picture to the sign up form, and if I just omit it from the controller, I receive the following error message:
undefined method `before_image_post_process'
I haven't made the profile picture a requirement on user creation. I believe I have all of the correct database tables set up, but for some reason I keep getting this error. Here's my attempt at assigning the user the default profile picture in the controller:
if #user.save
# Create a profile picture for the user
#user.profile_pic = ProfilePic.new(:image => nil, :user_id => #user.id)
...
end
When debugging, immediately after saving the user, typing "#user.profile_pic" in the console returns the same 'before_image_post_process' error.
Does anyone have any insight on this issue? Thank you very much in advance!
You are getting this error because you defined the attached file attribute as profile_pic but you are doing the Paperclip validation on the image attribute.
When you define a has_attached_file attribute, Paperclip automatically creates a <name>_post_process callback which it uses later in the validation (where is the name of the has_attached_file attribute).
You created profile_pic_post_process but then the validation is looking for image_post_process, hence the error.
Change your validation line in the ProfilePic model:
# Only allow the user to upload .bmp, .gif, .jpg, .jpeg, and .png files
validates_attachment_content_type :profile_pic, :content_type => /^image\/(bmp|gif|jpg|jpeg|png)/

Rails 3 Dragonfly uid of attachment is correct but the file iself is nil

Im using Dragonfly and Amazon s3 for the image uploads. For some reason when I upload a picture, it saves to the right folder on amazon, and the uid is the right path, but it is not showing up!
Every time I call user.avatar it is nil even though user.avatar_uid is correct. How can I get the image to display properly with user.avatar.remote_url?
class User < ActiveRecord::Base
image_accessor :avatar do
storage_path { |file|
"#{self[:id]}/avatar/pic#{rand(1000)}.#{file.format.to_s.downcase}"
}
after_assign { |a|
self.avatar = a.jpg.thumb('300x300#n') if (VALID_PHOTO_TYPES.include? self.avatar.format)
}
end
attr_accessible :avatar_url, :retained_avatar, :avatar
attr_reader :id, :avatar_uid
The problem is the :avatar_uid in any of attr_reader, attr_writer, attr_accessible.
If you have that in your model, it will break. Pretend that the *_uid does not exist for any model with Dragonfly and only use user.avatar.

Resources