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
Related
I have the code below to save my local file to AWS S3. It uploads my file to S3 correctly, but then my local file is not deleted. Now I've filled disk space completely. How can I ask paperclip to remove local file after it has uploaded it?
class JobArtifact < ActiveRecord::Base
attr_reader :remote_url
has_attached_file :file, path: 'tmp/:id/:fingerprint.:extension'
do_not_validate_attachment_file_type :file
def remote_url=(url)
self.file = URI.parse(url_value)
#remote_url = url
end
end
It is called this way:
#filename = "#{Rails.root}/tmp/values.csv"
JobArtifact.create(file: File.open(#filename))
Since you know the path of the file you can just do something like this after it's uploaded to s3 -
def remove_file
File.delete(#filename) if File.exist?(#filename)
end
An Image model has a 1:1 association with an Organization model. In the organizations controller, the create method calls on an Image model method called upload_file.
def create
#organization = Organization.new(new_params)
if #organization.save
Image.upload_file(#organization.id)
end
end
The upload_file method uses a carrierwave uploader to store a standard file in an Amazon S3 bucket. To this end, the Image model includes mount_uploader :file_name, ImageUploader.
My question is how to create an Image instance for the uploaded file? The path to the stored file should be stored in the column file_name in the Image model. And the organization associated with the image should be stored in the column organization_id in the Image model. How can I do this? More specifically, what code should I add for this to the model method below? (also see the comments in the method below.
def self.upload_file(organization_id)
file = 'app/assets/emptyfile.xml'
uploader = ImageUploader.new
uploader.store!(file)
# Am I correct to assume that the previous line uploads the file using the uploader, but does not yet create an Image record?
# If so, then perhaps the next line should be as follows?:
# Image.create!(organization_id: organization_id, filename: file.public_url)
# I made up "file.public_url". What would be the correct code to include the path that the uploader stored the image at (in my case an Amazon S3 bucket)?
end
Currently in rails console I get the following error:
>> uploader = ImageUploader.new
=> #<ImageUploader:0x00000005d24f88 #model=nil, #mounted_as=nil, #fog_directory=nil>
>> file = 'app/assets/emptyfile.xml'
=> "app/assets/emptyfile.xml"
>> uploader.store!(file)
CarrierWave::FormNotMultipart: CarrierWave::FormNotMultipart
from /usr/local/rvm/gems/ruby-2.2.1/gems/carrierwave-0.10.0/lib/carrierwave/uploader/cache.rb:120:in `cache!'
etc.
You don't have to call the uploader yourself. Carrierwave comes with a mechanism to do the uploading and storing in AR models for you:
class Organization < ActiveRecord::Base
mount_uploader :image, ImageUploader
end
Then you can do
u = Organization.new
u.image = params[:file] # Assign a file like this, or
# like this
File.open('somewhere') do |f|
u.image = f
end
u.save!
u.image.url # => '/url/to/file.png'
u.image.current_path # => 'path/to/file.png'
u.image # => 'file.png'
Please visit the carrierwave README for more extensive examples.
i am trying to upload a file with carrierwave in my rails app and currently this is my code:
Controller:
def fileSave
#code.store!(code)
end
View:
= form_for #code = Code.new(params[:code]), :as => :code, :html => {:multipart => true} do |f|
div class="browse"
span
= f.file_field :code
= f.submit 'Upload'
Uploader:
# encoding: utf-8
class CodeUploader < CarrierWave::Uploader::Base
def pre_limit file
#require 'debugger'; debugger
if file && file.size > 100.megabytes
raise Exception.new('too large')
end
true
end
storage :file
def store_dir
"public/uploads"
end
def extension_white_list
%w(txt js ttf html)
end
def filename
"file.txt" if original_filename
end
end
Model:
require 'carrierwave/orm/activerecord'
class Code < ActiveRecord::Base
attr_accessor :code
mount_uploader :code, CodeUploader
end
And my problem is i can not store the uploaded file. x[ I am sure this is like 3 lines of code but i can not figure it out. Also the file to be uploaded is expected to be txt (probably figured that out looking the extension list).
Thanks to all readers and answerers. :}
P.S. I was wondering if i could create some kind of imaginary file, a file which is not really created. The is idea is if a take a text from a textarea and create a file (the imaginary one), store the text inside and then eventually save the whole file (maybe use carrierwave as and manually store it).
I've been following the instructions provided by Cloudinary, but have not been able to get direct uploading working. To further complicate things my image upload is a polymorphic class and is usually in a nested form.
I'm using both the Cloudinary and Carrierwave gems. In the non-direct setup everything works properly however unicorn times out if there are too many images being uploaded at once (which may frequently be the case)
Below is the partial that adds a file upload. It is nested in multiple different forms and the user can add and remove fields dynamically. Per the instructions, I tried to replace = f.file_field :asset and = f.hidden_field :asset_cache with = cl_image_upload :asset, however, this throws an error of: wrong number of arguments (1 for 2..3). When adding a second parameter it is appended to data-cloudinary-field in the generated HTML. Additionally no upload takes place when an image is added and no reference is attached to the record.
_image_fields.html.haml
.image-field-group
.field
= f.label :asset, "Image"
= cl_image_upload :asset
/ = f.file_field :asset
/ = f.hidden_field :asset_cache
- if f.object && f.object.asset && f.object.asset.filename
.image-box
= cl_image_tag(f.object.asset.filename.to_s, :transformation => 'hint', alt: f.object.asset.filename.to_s)
.remove-fields
= link_to_remove_fields f
Here are the associated files:
image.rb
class Image < ActiveRecord::Base
default_scope order('images.id ASC')
attr_accessible :asset,
:asset_cache
belongs_to :imageable, polymorphic: true
mount_uploader :asset, ImageUploader
end
image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
include Cloudinary::CarrierWave
def extension_white_list
%w(jpg jpeg gif png)
end
end
EDIT: Added Images Controller
images_controller.rb
class ImagesController < ApplicationController
before_filter :load_imageable
def index
#images = #imageable.images
end
def new
#image = #imageable.images.new
end
def create
#image = #imageable.images.new(params[:image])
if #image.save
redirect_to #imageable, notice: "Image created."
else
render :new
end
end
private
def load_imageable
resource, id = request.path.split('/')[1, 2]
#imageable = resource.singularize.classify.constantize.find(id)
end
end
The following documentation section describes how to use direct image uploading from the browser in your Ruby on Rails application. Make sure to include jQuery and the required plugins in the correct order and to add the required Javascript configuration.
http://cloudinary.com/documentation/rails_image_upload#direct_uploading_from_the_browser
Embedding a file input field that performs direct image uploading is done using the cl_image_upload_tag helper method that accepts the name of the input field.
When using CarrierWave, you can use the cl_image_upload helper method. When calling this method directly, you need to pass the object name and the attribute them (as you do with other Rails view helper methods such as text_field_tag and text_field). Alternatively, you can use it with Rails' standard form builders.
Assuming your model is called entity and your attribute with a mounted CarrierWaver uploader is called asset, the following view code should embed a signed file input field for direct uploading from the browser:
= form_for(:entity) do |f|
= f.cl_image_upload(:asset)
In addition, as you can see below you can use present? to check if the asset exists and pass the CarrierWave attribute directly to cl_image_tag. Alternatively, you can use CarrierWave standard versions instead of dynamically building image URLs using cl_image_tag.
- if f.object && f.object.asset.present?
.image-box
= cl_image_tag(f.object.asset, :transformation => 'hint', alt: f.object.asset.filename.to_s)
If the file input field was added successfully to your view but no direct uploading is performed, you should verify that there are no Javascript errors in the console and that all jQuery plugins were included correctly.
I cant figure out how to validate that carrierwave has uploaded a document to my mongoid object.
i have a Document Class
class Content::Document < Content
mount_uploader :attachment, DocumentUploader
field :attachable_id
field :attachable_type
end
and an uploader:
require 'carrierwave/orm/mongoid'
class DocumentUploader < CarrierWave::Uploader::Base
storage = :filesystem
include CarrierWave::RMagick
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def extension_white_list
%w(doc docx xls xlsx ppt pptx pdf )
end
i would like to validate that the upload exists and that it matches the white list else through a standard validation error
this is on Rails 2.3.8
While it's true that Carrierwave does have extensive tests, you could test for validity with something like this:
it "is valid with valid attributes" do
file_bytes = File.open("spec/binary/avatar.png")
valid_attrs = {:name => "foo", :description => "bar", :avatar => file_bytes}
user = User.new(valid_attrs)
user.should be_valid
end
Hope that helps!
In general you don't need to do that since this behaviour is already tested in carrierwave specs itself.
You can test your uploaders in isolation, using Carrierwave test helpers. E.g. I would just write a spec like
attachment_uploader.extension_white_list.should =~ %w(doc docx xls xlsx ppt pptx pdf)
But if you insist on testing that I would suggest using FakeFS to stub filesystem and then check with
File.exists? document.attachment.current_path
whether attachment has been created.