How to test file upload with factorygirl? - file-upload

I want to test a model with a basic file upload which does not use a plugin (I think it was a bad idea at the time but know I have to deal with tons of existing documents).
The thing is that I want to test the size of the uploaded file, so I need to access to the temfile attribute of file which is a ActionDispatch::Http::UploadedFile
It works quite well in my controllers but I can't find how to make my factory pass.
Attachment Model:
attr_accessor :file # file_field_tag name
attr_accessible :file, :filename # store the filename in the db
before_save :upload_file
def upload_file
if self.file && File.size(self.file.tempfile) < 10.megabytes
puts self.file.class.inspect # see below
File.open("#{Rails.root.to_s}/files/" + self.filename.to_s, 'wb') do |f|
f.write(self.file.read)
end
ensure_file_presence
end
end
FactoryGirl Attachment:
FactoryGirl.define do
factory :attachment do
filename 'anyfile.txt'
file ActionDispatch::Http::UploadedFile.new(:tempfile => "#{Rails.root}/spec/fixtures/anyfile.txt", :filename => "anyfile.txt")
end
end
When I run tests, a FactoryGirl.build :attachment will succeed but with a create will fail.
Strange observation: I've displayed the self.file class before the File.open and it is seen as a ActionDispatch::Http::UploadedFile, then I have an error on self.file.read: it has changed into a String.
puts self.file.class.inspect
=> ActionDispatch::Http::UploadedFile
f.write(self.file.read) => NoMethodError: undefined method `read' for #<String:0x007ff50f70f6a0>
I've read other posts that suggest I should use Rack::Test::UploadedFile or fixture_file_upload but I can't make it work, as the tempfile won't exists of any of these 2 solutions.

I see one issue. UpLoadedFile (:tempfile) is expecting a File not a String. For you observation, it never changed into a String you defined it as a String. You need to surround it with a File.new() as shown below. You need to put the file there before you try to run the test. Hope this helps!
FactoryGirl.define do
factory :attachment do
file ActionDispatch::Http::UploadedFile.new(:tempfile => File.new("#{Rails.root}/spec/fixtures/anyfile.txt"), :filename => "anyfile.txt")
end
end

Related

Delayed job and Mandrill: uninitialized constant Mandrill::API

I have mailer service where users can upload an .xls file with emails and some other user related data to send an email campaign.
I was having some timeout issues since it takes some seconds to process (as I do some validation and configuration for each email to be sent, eg: save records to database, check if an email was sent in the last 30 days, create personalised html code for each email (to create links that contain the email address as a parameter, etc).
After some research, moving this to a delayed job seemed reasonable, as suggested in this rails cast. The only problem is that I am having an error that says uninitialized constant Mandrill::API, it seems that when I run the job, the call require 'mandrill' doesn't work.
I created the task in my model file. Something like this
class Mailer < ActiveRecord::Base
attr_accessible :email, :lastname, :name
def self.send_mail(emails)
[...a lot of code here...]
require 'mandrill'
m = Mandrill::API.new ENV['MANDRILL_APIKEY']
message = {
:subject=> template.subject,
:from_name=> template.from_name,
:from_email=> from + "#" + email_domain,
:to=>mails,
:global_merge_vars=> [
{ :name => 'GREETING', :content => template.greeting},
{ :name => 'CONT1', :content => template.message},
{ :name => 'IMAGE', :content => image_url},
],
:html=>email_template,
:preserve_recipients => false,
:merge_vars => email_contents,
}
sending = m.messages.send message
end
end
from my controller I call Mailer.send_mails(emails) and it works just fine, but if I call Mailer.delay.send_mails(emails) I get the error. How can I fix this?
I have tried adding require 'mandrill' at the beginning of the class, before and after class Mailer < ActiveRecord::Base but nothing seems to work
Make sure to restart the delayed_job daemon to pick up any code changes. It does not auto-reload like the Rails development environment does.

how to pass a variable into a method call like #photo_main.file.url(:img_120x120)?

Using Carrierwave to upload Images,
I want to write 1 function to handle all my image includes in several sizes.
image_tag #photo_main.file.url(:img_122x145) rescue nil
The :img_120x120 is defined in Carrierwave uploader but why the :img_120x120 semicolon before its name? In what format is this?
Wanted outcome:
def get_avatar(size)
image_tag #photo_main.file.url(size) rescue nil
end
How could this be done?
UPDATE 1:
Fails with : ActionView::Template::Error (undefined method `file' for nil:NilClass):
1: .ruler
2:
3: //= show_avatar_profile(#profile.id)
4: = show_avatar_new(#profile.id, "96x96")
def show_avatar_new(id, size)
puts "size is"
size = size.to_sym
puts size
#photo_main = Photo.where(:attachable_id => id, :attachable_type => "Profile", :main => true, :moderated => true, :approved => true).first
#photo = Photo.where(:attachable_id => id, :attachable_type => "Profile", :moderated => true, :approved => true).first
if #photo_main
image_tag #photo_main.file.url(size)
else
image_tag #photo.file.url(size)
end
end
UPDATE 2:
class PhotoUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
version :img_48x48 do
process :resize_to_fill => [48, 48]
end
version :img_58x58 do
process :resize_to_fill => [58, 58]
end
version :img_75x75 do
process :resize_to_fill => [75, 75]
end
version :img_96x96 do
process :resize_to_fill => [96, 96]
end
# Used in search results,
version :img_122x145 do
process :resize_to_fill => [122, 145]
end
version :img_200x200 do
process :resize_to_fill => [200, 200]
end
protected
def secure_token(length=32)
var = :"##{mounted_as}_secure_token"
model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.hex(length/2))
end
def delete_empty_upstream_dirs
path = ::File.expand_path(store_dir, root)
Dir.delete(path) # fails if path not empty dir
path = ::File.expand_path(base_store_dir, root)
Dir.delete(path) # fails if path not empty dir
rescue SystemCallError
true # nothing, the dir is not empty
end
end
In Ruby, things beginning with colons : (not semicolons ;!) are symbols, which are essentially immutable strings.
"img_122x145".to_sym # => :img_122x145
It seems like what you've written there is exactly what you need. If you're wondering where to put it, you could put it in a helper
# app/helpers/avatar_helper.rb
def get_avatar(size)
image_tag #photo_main.file.url(size)
end
Please don't use rescue nil there, though. What error are you trying to catch? It would be much better to explicitly avoid it rather than using exceptions as flow control.
image_tag #photo_main.file.url(size) if #photo_main.file?
would be sufficient to avoid the problem of a #photo_main without a file, and is much more intention-revealing (and, in fact, more performant). Worst-case, you should still explicitly state what sort of error you're expecting to get
def get_avatar(size)
image_tag #photo_main.file.url(size)
rescue SomeSpecificErrorThatCantBeAvoided
nil
end
This short (<3min) screencast makes an excellent case for avoiding inline rescue.
Update
When you create versions in CarrierWave, it creates methods to access them - you don't pass an argument to url:
#photo.file.img_122x145.url
If you want to get a variable version, though, they are available through versions (a hash):
size = :img_122x145
#photo.file.versions[size].url
That won't solve your remaining problem, which is simply that your queries aren't finding anything.

Fog::Storage::Rackspace::NotFound error when using Carrierwave to upload to Rackspace

Everyone: I already searched the error before I posted this to Stackoverflow, so no need to point me to this: groups.google.com/forum/?fromgroups=#!topic/carrierwave/ It's not the same problem.
I'm using Carrierwave so users can upload files to my Rackspace container. But when I Submit from my site (on my local machine, still in test mode), I get a Fog::Storage::Rackspace::NotFound app/controllers/authors_controller.rb:8:in `update' error. My Rackspace container is called kontainer.ofstuff. Here's my code:
pic_uploader.rb:
class PicUploader < CarrierWave::Uploader::Base
include Rails.application.routes.url_helpers
storage :fog
def store_dir
"#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
model author.rb
class Author < ActiveRecord::Base
attr_accessible :stuff, :profilepic
mount_uploader :pic, PicUploader
def dostuff
end
end
carrierwave.rb is in the config/initializers directory
CarrierWave.configure do |config|
config.storage = :fog
config.fog_credentials = {
:provider => 'Rackspace',
:rackspace_username => 'myusername',
:rackspace_api_key => '98765asecretnumber3'
})
config.fog_directory = 'kontainer.ofstuff'
config.fog_host = 'https://34567secretnumberiiiii.ssl.cf2.rackcdn.com'
end
controller authors_controller.rb
class AuthorsController < ApplicationController
def update
#author = Author.find(params[:id])
#booklist = Book.where(:author_id => #author.id)
#line 7
if #author.update_attributes(params[:author])
sign_in #author
redirect_to #author
else
render 'profileinfo'
end
end
end
edit.html.erb:
<%= f.file_field :pic %>
<%= f.submit "Save Author Info" %>
When I had this code 'uploading'/storing to a file, this worked fine. Perhaps f.submit does not work with Carrierwave? If not...where do I find the correct code for submitting?
Any ideas what the trouble is?
I also got this error.
Solved by adding this to uploaded code:
class MyUploader < CarrierWave::Uploader::Base
....
def remove!
begin
super
rescue Fog::Storage::Rackspace::NotFound
end
end
end
I kind of had the same problem, but for me it turned out that I needed to make the container multiple times with the same name but for all the regions. I have no idea why it worked after that, but I guess that's something to try?
Update 11-7-2012
So Carrierwave had some updates since my answer. I was able to get a more stable upload through some trial and error. Here's what I did:
Updated carrierwave gem to 0.7.0
Logged into Rackspace and deleted containers for all the regions.
Added one single container. It doesn't matter which region, whichever is more applicable to you.
Made the container public (Enable CDN)
Copied the Public HTTP CDN link for the container
Updated my /config/initalizers/carrierwave.rb file:
CarrierWave.configure do |config|
config.fog_credentials = {
:provider => 'Rackspace',
:rackspace_username => '[Your Rackspace Username]',
:rackspace_api_key => '[Your Rackspace API key]'
}
config.fog_directory = '[The name of the container you created]'
if Rails.env.production? || Rails.env.staging?
config.asset_host = '[The Public HTTP CDN url for the container]'
end
end
As a note: I configured my uploader to use storage:fog when the environment is production or staging. Otherwise I use the default local file system.
The main thing to note is that carrierwave changed the config 'fog_host' to 'asset_host.'
For what it's worth: I was having this same problem after migrating from AWS to Rackspace. The error was being thrown because part of updating the file is deleting the old file. In my case, the old file was on S3, not on Rackspace, so carrierwave got upset.
This seemed to be a case of Wait A Few Months & install updated gem. The problem pretty much went away. I also got rid of Rackspace & went to Amazon S3, although I had tried S3 earlier with the same issues. I'm crediting the solution to the updated Carrierwave gem.

File Handling with Paperclip

I'm using Paperclip in a gem I built for a specific use case. My gem creates an interface for non-programmers to create and edit forms and then allows users to answer those forms.
I want to use Paperclip in order to provide a "File Upload" input type for questions, so my forms are more versatile. However, this means that I need to use the file_field_tag method to display the file input and I need to manually save whatever information is submitted through that input into the appropriate model object. Currently I'm sending the information through with the name question_1 and then trying to pull the uploaded data out with params["question_1"].
My code looks like this:
answer.update_attributes(upload: params["question_1"])
But I'm getting a No handler found for <image_name> error and I can't figure out what I'm doing wrong. I thought Paperclip handles everything after I pass it the data from a file_field?
Solution:
My form looked like this: <%= form_for #answer_set, multipart: true do %> when it should have looked like this: <%= form_for #answer_set, html: { multipart: true } do %>.
I use
has_attached_file :image
validates_attachment_presence :image
validates_attachment_content_type :image, :content_type => ['image/jpeg', 'image/png', 'image/jpg', 'image/pjeg']
and then:
#upload = Upload.find(params[:id])
#upload.update_attributes(params[:upload])
config/environment.rb
Rails::Initializer.run do |config|
config.gem "paperclip", version: "~> 2.7"
end
This thread also suggests checking the multi-part on the form
https://stackoverflow.com/a/10076046/1354978
answer_form_for #upload, :html => {:multipart => true} do |f|
There are also other possible solutions on that page.

how to tell Rails RSpec that spec is "type helper"

I wrote *simple_form* input extension that is located in app/inputs/something_input.rb
I'm trying to write RSpec for this. When I put this spec inside spec/helpers/application_helper_spec.rb everything was working without single problem.
# spec/helpers/application_helper_spec.rb
require 'spec_helper'
describe ApplicationHelper do
it do
helper.simple_form_for #foo,:method=>'get', :url=>helper.users_path do |f|
f.input :created_at, :as =>:custom_datepicker
end.should =~ /something/
end
end
Now I'm trying to move that spec to spec/inputs/something_input_spec.rb so it will be similar name path.
# spec/imputs/something_input_spec.rb
require 'spec_helper'
describe SomethingInput do
it do
helper.simple_form_for #foo,:method=>'get', :url=>helper.users_path do |f|
f.input :created_at, :as =>:custom_datepicker
end.should =~ /something/
end
end
#
#ERROR: undefined local variable or method `helper' for #<RSpec::Core::ExampleGroup
the thing I want to tell RSpec to threat this file as type helper spec, so I will have helper method availible with all the RSpec::Rails::HelperExampleGroup functionality
... how can I do that ??
I was trying to extend/include it with RSpec::Rails::HelperExampleGroup nothing seems to work
Start the describe block with the :helper type:
describe SomethingInput, :type => :helper do
... tests ...
end