Rails Carrierwave acessing parent model from within a version - ruby-on-rails

I'm trying to create a version on my carrierwave uploader that checks to see whether it's parent model has some data on how to resize and crop the image, if not do a default resize to fill. I've been trying to reference the model as demonstrated here: https://github.com/carrierwaveuploader/carrierwave
If I run the version code like this:
version :title do
if model.dimensions_hash["title"]
process :image_crop => [model.dimensions_hash["title"], 960, 384]
else
process :resize_to_fill => [960, 384]
end
end
I get this error:
NameError: undefined local variable or method `model' for #<Class:0x007f9eae7cfed0>
from /Users/RyanKing/Sites/test/app/uploaders/page_image_uploader.rb:45:in `block in <class:PageImageUploader>'
Line 45 being process :image_crop => [model.dimensions_hash["title"], 960, 384
If the if statement returns true why does the line 45 return an error? Am I referencing the model incorrectly?
I found a similar problem here but wasn't able to adapt it to my situation. Passing a parameter to the uploader / accessing a model's attribute from within the uploader / letting the user pick the thumbnail size

Well you are right model method wont be available because version is a
class method and whereas model is instance method of uploader
but there are way to get them
if you check the link that I have attached in the mail the all that is define inside block are class_eval so taking that into account you can modify your code like this
version :title do
process :image_crop => [960, 384],:if => :has_title?
process :resize_to_fill => [960, 384] ,:if => :has_not_title?
def has_title?
model.dimensions_hash["title"].present?
end
def has_not_title?
not(has_title?)
end
end
Hope this help

Related

Extract and validate data from attachment

I'm using paperclip to manage my file uploads in rails.
From the attachments users give me, I'd like to extract some data to associate with the model the attachment is associated with.
has_attached_file :resume, #...
# ...
def extract_resume_summary
path_to_resume = self.resume.queued_for_write[:original].path
extracted = parse_resume_file(path_to_resume)
self.number_of_jobs = extracted.number_of_jobs
self.highest_level_of_education = extracted.highest_level_of_education
rescue ResumeParseError => e
#problem_with_resume = e.message
end
I'm having some trouble figuring out exactly when and where to do this.
I could use a custom Paperclip::Processor:
class ::Paperclip::Summary < ::Paperclip::Processor
def make
#attachment.instance.extract_resume_summary
Tempfile.new('unused')
end
end
# ...
has_attached_file :resume,
:styles => { :summary => {} },
:processors => [ :summary ] }, #...
But the fit isn't great. I think processors are intended to create new files (which I don't need, and thus the spurious Tempfile).
Also my extraction might fail, which means my user gave me bad data. I want that to be a validation-time problem, so I can report it along with other validation errors, and post-processing happens strictly AFTER validation.
I've tried hacking it in at initialization:
validate :successfully_parses_resume
def successfully_parses_resume
errors.add(:resume, #problem_with_resume) if #problem_with_resume
end
def initialize(attributes=nil, options={})
super
extract_resume_summary
end
But I'm not quite sure that's right either, as that's not only when the file is uploaded, but also when I read the model in later. To say nothing of the havoc that could happen if I assumed #resume= or #[:resume]= was auto updating the extracted data too.
I think in an ideal world, I'd just subclass Paperclip::Attachment and make my extracted data peers of resume_file_name, resume_file_size, resume_content_type, resume_created_at, extracting that at the same time the mime-type is calculated and the file size is calculated. But looking at the source, those are pretty hard-coded throughout.
Is there another way to do this that I'm overlooking?
The solution I figured out was to wrap the attachment's setter. That's what'll be called during initialize and will give me a chance to detect problems before validation.
The only trick is that since the attachment's setter is created by has_attached_file and not inherited from ActiveRecord::Base, I can't just use super, I need to get an explicit reference to the version defined by has_attached_file (either by alias or, my preference, by instance_method):
has_attached_file :resume
old_setter = instance_method :resume=
define_method :resume= do |file|
old_setter.bind(self).call(file)
begin
extracted = parse_resume_file(resume.path)
self.number_of_jobs = extracted.number_of_jobs
self.highest_level_of_education = extracted.highest_level_of_education
rescue ResumeParseError => e
#problem_with_resume = e.message
end
end

File uploading with Dragonfly, impossible to access images after upload (Rails 3)

I'm trying to make a multiple drag and drop upload file system with Rails 3 and Dragonfly (or anything that would work actually)
I'm at the point where my file comes in my controller through the params hash and I can retrieve it as an ActionDispatch::Http::UploadedFile so I thought all I would have to do then is push it in my model's attribute image but it doesn't work
This my Picture model :
class Picture < ActiveRecord::Base
image_accessor :image
attr_accessible :image_name, :image_uid, :title
end
I thought this would work in my controller :
def createImage
#new_picture = Picture.new
#new_picture.image = params[:pic]
if #new_picture.save
render :json => { :picture => #new_picture }
end
end
Ok, so this registers the record with image_name nil oddly, but with the image_uid set
However, when I try to access my image <%= image_tag #picture.image.url %> I get a not found error
For example :
Request URL:http://localhost:3000/media/BAhbBlsHOgZmSSIhMjAxMi8wOS8yMi8xOV8zMF8yOF83MzBfZmlsZQY6BkVU
Request Method:GET
Status Code:404 Not Found
I'm using ruby 1.9.3 and rails 3.2.8
Any ideas ? :D
Dragonfly needs to load it's rack adapter before it is able to take encoded url requests (like the one above). The best way to load it is to add this before config.encoding = "utf-8 in your /config/application.rb
config.middleware.insert 0, 'Rack::Cache', {:verbose => true,:metastore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"),:entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body")} unless Rails.env.production?
config.middleware.insert_after 'Rack::Cache', 'Dragonfly::Middleware', :images
Please note that you will need the Rack-Cache gem as well.
Hope this helps!

How can I reference images in the asset pipeline from a model?

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

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.

Unit testing paperclip uploads with Rspec (Rails)

Total Rspec noob here. Writing my first tests tonight.
I've got a model called Image. Using paperclip I attach a file called photo. Standard stuff. I've run the paperclip generator and everything works fine in production and test modes.
Now I have a spec file called image.rb and it looks like this (it was created by ryanb's nifty_scaffold generator):
require File.dirname(__FILE__) + '/../spec_helper'
describe Image do
it "should be valid" do
Image.new.should be_valid
end
end
This test fails and I realise that it's because of my model validations (i.e. validates_attachment_presence)
The error that I get is:
Errors: Photo file name must be set., Photo file size file size must be between 0 and 1048576 bytes., Photo content type is not included in the list
So how do I tell rspec to upload a photo when it runs my test?
I'm guessing that it's got somethign to do with fixtures.... maybe not though. I've tried playing around with them but not having any luck. For the record, I've created a folder called images inside my fixtures folder and the two files I want to use in my tests are called rails.png and grid.png)
I've tried doing the following:
it "should be valid" do
image = Image.new :photo => fixture_file_upload('images/rails.png', 'image/png').should be_valid
# I've also tried adding stuff like this
#image.stub!(:has_attached_file).with(:photo).and_return( true )
#image.stub!(:save_attached_files).and_return true
#image.save.should be_true
end
But rspec complains about "fixture_file_upload" not being recognised... I am planning to get that Rspec book. And I've trawled around the net for an answer but can't seem to find anything. My test database DOES get populated with some data when I remove the validations from my model so I know that some of it works ok.
Thanks in advance,
EDIT:
images.yml looks like this:
one:
name: MyString
description: MyString
two:
name: MyString
description: MyString
This should work with Rails 2.X:
Image.new :photo => File.new(RAILS_ROOT + '/spec/fixtures/images/rails.png')
As of Rails 3, RAILS_ROOT is no longer used, instead you should use Rails.root.
This should work with Rails 3:
Image.new :photo => File.new(Rails.root + 'spec/fixtures/images/rails.png')
Definitely get the RSpec book, it's fantastic.
Rails.root is a pathname object so you can use it like this:
Image.new :photo => Rails.root.join("spec/fixtures/images/rails.png").open
Edit - probably does not work in Rails 3...
see answer by #Paul Rosania
In case anyone else finds this via Google, RAILS_ROOT is no longer valid in Rails 3.0. That line should read:
Image.new :photo => File.new(Rails.root + 'spec/fixtures/images/rails.png')
(Note the lack of leading slash!)
I use the multipart_body gem in my integration tests. Its a bit truer to BDD than testing.
http://steve.dynedge.co.uk/2010/09/19/multipart-body-a-gem-for-working-with-multipart-data/
With respect to rspec and paperclip, the has_attached_file :photo directive creates a virtual attribute of sorts i.e. :photo ... when you assign a file or a path to photo, paperclip takes over, stores the file, optionally does processing on it e.g. auto-create thumbnails, import a spreadsheet, etc. You aren't telling rspec to test paperclip. You are invoking code and telling rspec what the results of that code -should- be.
In $GEM_HOME/gems/paperclip-2.3.8/README.rdoc, about 76% of the way through the file under ==Post Processing (specifically lines 147 and 148):
---[ BEGIN QUOTE ]---
NOTE: Because processors operate by turning the original attachment into the styles, no processors will be run if there are no styles defined.
---[ END QUOTE ]---
Reading the code, you'll see support :original ... does your has_attached_file define a style?
I use a generic ":styles => { :original => { :this_key_and => :this_value_do_not_do_anything_unless_a_lib_paperclip_processors__foo_dot_rb__does_something_with_them } }" ... just to get paperclip to move the file from some temp directory into my has_attached_file :path
One would think that would be default or more obvious in the docs.

Resources