Cannot upload images to the cloud using Cloudinary(paperclip) gem - Rails - ruby-on-rails

I got problem with cloudinary gem when uploading images, here is my image model:
class Image < ApplicationRecord
default_scope { where.not(photo_file_name: [nil, ""]).where.not(photo_content_type: [nil, ""]) }
belongs_to :article, optional: true
after_save :delete_invalid_image
has_attached_file :photo, :storage => :cloudinary, path: "/uploaded/:class/:attachment/:id/:style_:filename", styles: { thumb: "300x200#", large: "1024x768>"}
validates_attachment_content_type :photo, content_type: /\Aimage\/.*\Z/
#attr_accessor :photo
def original_photo_url
photo.path(:large, timestamp: false)
end
paginates_per 30
private
def delete_invalid_image
if !photo?
self.destroy
end
end
end
here is create method in image controller:
def create
if !params[:hint].nil?
#image = Image.new(photo: params[:file])
if #image.save
render json: {
image: {
url: #image.original_photo_url,
id: #image.id
}
}, content_type: "text/html"
else
render json: {
error: "Something is wrong"
}, content_type: "text/html"
end
else
image = Image.create!(image_params)
if params[:ajax_upload].present?
image = {
id: image.id,
title: image.title,
caption: image.caption,
description: image.description,
width: image.width,
height: image.height,
url: image.photo.path(:thumb)
}
respond_to do |format|
format.json { render json: {image: image}}
end
else
redirect_to admin_images_path
end
end
end
When I trying to create(upload) a new image, the log show:
SQL (7.6ms) INSERT INTO `images` (`caption`, `description`, `title`, `created_at`, `updated_at`) VALUES ('', '', '14696760_1230106580395129_29071409_n', '2017-01-06 00:43:51', '2017-01-06 00:43:51')
SQL (7.2ms) DELETE FROM `images` WHERE `images`.`id` = 22
you can notice the insert and delete commands were happened simultaneously. I guest the problem come from the create method, but I cannot point out exactly where is it. Pls show me where I was wrong.

You seem to be using Paperclip raw with Cloudinary. There is a gem to use Paperclip with Cloudinary. Try using that instead.
https://github.com/GoGoCarl/paperclip-cloudinary
That gem says you can't start :path with a forward slash.
You should specify the Paperclip path pattern that you would like to use to store and access your saved attachments. The value should be URL-friendly, should NOT begin with a forward slash, and, aside from forward slashes, can only contain alphanumeric characters, dashes (-), periods (.) and underscores (_). The path can be specified in your default Paperclip options or via has_attached_file.
Also don't use photo.path(:large, timestamp: false). Use photo.url instead.
https://github.com/thoughtbot/paperclip#view-helpers
Also, you seem to be missing fields. Paperclip will create *_file_name, *_file_size, etc. fields. I think your migrations are wrong.
https://github.com/thoughtbot/paperclip#usage

Related

How to display image returned from Mongoid::GridFs

I'm trying to display my image saved in Mongo in the record row with the rails_admin gem.
I have my model saving, and my image saving, and I'm saving the image ID in the model record.
Here's my model:
require 'mongoid/grid_fs'
class Asset
include Mongoid::Document
field :data_file_name, type: String
field :data_content_type, type: String
field :data_file_size, type: Integer
field :image_id, type: String
end
Here's what I'm trying to do in rails_admin.rb for my Asset model:
list do
field :id
field :data_file_name
field :data_content_type
field :data_file_size
field :image do
formatted_value do
grid_fs = Mongoid::GridFs
bindings[:view].tag(:img, { :src => grid_fs.get(bindings[:object].image_id)})
end
end
end
And here's the action responsible for saving the model and image:
register_instance_option :controller do
proc do
if request.get? # EDIT
respond_to do |format|
format.html { render #action.template_name }
format.js { render #action.template_name, layout: false }
end
elsif request.put? # UPDATE
tempFile = params[:picture][:asset].tempfile
file = File.open(tempFile)
grid_fs = Mongoid::GridFS
grid_file = grid_fs.put(file.path)
Asset.new.tap do |asset|
asset.data_file_name = params[:picture][:asset].original_filename
asset.data_content_type = params[:picture][:asset].content_type
asset.data_file_size = ::ApplicationController.helpers.number_to_human_size(File.size(tempFile))
asset.image_id = grid_file.id
asset.save
binding.pry
end
end
end
end
The model is saving, and I can see the file saving in fs.files and fs.chunks, but at the moment, I'm just getting the following in the record row:
Update:
I've now tried getting the file from mongo (Which seems to work) and then displaying the image by using the file's actual filename.
field :image do
formatted_value do
grid_fs = Mongoid::GridFs
f = grid_fs.get(bindings[:object].image_id)
bindings[:view].tag(:img, { :src => f.filename})
end
end
Unfortunately this hasn't changed anything. Trying to open the image in a new tab takes me to the following link: /admin/asset#<Mongoid::GridFs::Fs::File:0x981vj5ry>
Update 2:
Changed field :image_id, type: String to field :image_id, type: BSON::ObjectId
No change in result.
If you are saving image data in GridFS, you need to have an endpoint in your application to retrieve that image data from GridFS and serve it to the applications. See this answer for how to serve image data: Rails - How to send an image from a controller
Then, link to this endpoint instead of linking to "f.filename" as you have indicated in the last code snippet.
After a lot more research, it looks like you can encode the data returned from grid_fs to base64.
In turn, you can use this to display the image by specifying the source to be data:image/png;base64,+base64Image like so:
field :asset_thumbnail do
formatted_value do
grid_fs = Mongoid::GridFs
f = grid_fs.get(bindings[:object].thumb_image_id)
b64 = Base64.strict_encode64(f.data)
bindings[:view].tag(:img, { :src => "data:image/png;base64,"+b64})
end
end

carrierwave not saving image

i'm using Angularjs as front end,
i'm able to upload image along with some parameters .
i'm using this directive angular-file-upload to upload single image with parameters:
uploader = $scope.uploader = new FileUploader(
url: '/recipes',
alias: 'cover',
removeAfterUpload: true,
#transformRequest: angular.identity,
headers: {'X-CSRF-TOKEN': csrf_token,'accept': 'application/json'},
withCredentials: true
)
uploader.onBeforeUploadItem = (item)->
#data = angular.toJSON($scope.recipe)
item.formData.push("recipe": angular.toJson($scope.recipe))
#item.upload()
console.info('uploader', $scope.uploader);
uploader.uploadAll()
this is the create action:
def create
params[:recipe] = JSON.parse params[:recipe]
params[:recipe][:cover] = params[:cover]
#ingredients = Ingredient.where(:id => params[:recipe][:ingredients].map {|ingredient| ingredient[:id]})
#recipe = current_user.recipes.new(params.require(:recipe).permit(:name,:instructions))
#recipe.ingredients << #ingredients
#recipe.user_id = current_user.id
#recipe.save
render 'show', status: 201
end
recipe model :
class Recipe < ActiveRecord::Base
mount_uploader :cover, AvatarUploader
belongs_to :user
has_and_belongs_to_many :ingredients,:join_table => "ingredients_recipes"
accepts_nested_attributes_for :ingredients
attr_accessor :cover
end
this is the request :
-----------------------------100101598926016265538511946
Content-Disposition: form-data; name="recipe"
{"name":"amzpld","instructions":"mpzmdpzmez","ingredients":[{"id":3,"title":"oeuf"}]}
-----------------------------100101598926016265538511946
Content-Disposition: form-data; name="cover"; filename="10.jpg"
Content-Type: image/jpeg
ÿØÿà�JFIF������ÿÛ�� ( %"1"%),...383,7(-.+
0& $,,,2,,,,,,4,4,,,,,,,,,-,,,,,,,,,,,4,,,,,,,,,,,,,,,ÿÀ��¨,"�ÿÄ�������������
�ÿÄ�#�����!1AQa"q2¡±#BRÁáð3bÑr$ñCT¢ÿÄ��������������
ÿÄ�0�������!1A"Qaq¡2±#B3CÑáðÿÚ���?�öá]®Qâ1 Bs(À¡V_D$¯8þ&¡i6¨Û)Ò
´oóçÊ�dÌôªçñEÏêÚÙ]ëãzn£hÓÚêÄ6I½&&·¼ßËðȨ̈)L¤hGQÞ°Pf�MÔúSkÔBønàôÕjal3YðäKÇÖ¾Á¢#..........
the recipe is saved but with cover = nil
plz what i'm missing
You're making :cover part of your params[:recipe] hash:
params[:recipe][:cover] = params[:cover]
But you're not white-listing it, so instead of:
#recipe = current_user.recipes.new(params.require(:recipe).permit(:name,:instructions))
Try this:
#recipe = current_user.recipes.new(params.require(:recipe).permit(:name, :instructions, :cover))
update
You also have another issue in your Recipe model, you're using:
attr_accessor :cover
Remove that line, you don't need it because cover should be a real column in your recipes table, and if that column already exists this will override the method that assigns values to that column, thus causing your column to contain nil.
Now if you don't have that column in your recipes table, just add it using a migration, in your console type:
rails g migration add_cover_to_recipes cover:string
and then migrate:
rake db:migrate
Hope that solves your issue.

Using different style for paperclip image in Rails while rendering in json

I have a model which has an image field managed by paperclip:
class Meal < ActiveRecord::Base
has_attached_file :image, :default_url => "/images/normal/missing.png",
:styles => { :medium => "612x612", :small => "300x300" },
:path => ":rails_root/public/system/:attachment/:id/:style/:filename",
:url => "/system/:attachment/:id/:style/:filename"
I can access the different sizes like this:
meals.each do |n|
n.image.url(:small) # gives url for small images
puts n.image.url # returns url for original images, I want this to return small for this function
end
I am rendering the meals in JSON using render :json.
My question is, how can I pass the small image URLs into my meals variable (in my controller below)? I want to be able to return small image URLs as I tried doing above, except return it when my response renders (see below).
UPDATE:
In my controller:
def view_patient
response = Response.new
this_doctor = Doctor.find_by_remember_token(Doctor.digest(auth_params["remember_token"]))
if this_doctor
this_patient = this_doctor.users.find_by_id(params[:id])
if this_patient
meals = this_patient.meals
#
# Here should be code on how to set the meals.image.url to small
glucoses = this_patient.glucoses
response.data = { :patient => this_patient, :meals => meals }
response.code = true
else
response.error = "Could not find patient"
response.code = false
end
else
response.error = "Please Login"
response.code = false
end
render :json => response.json
end
TLDR
# inside meal.rb
def as_json(options=nil)
super( (options || {}).merge({
:methods => [:small_url]
}))
end
def small_url
self.image.url(:small)
end
You can then access the URL in your JSON structure
JSON.parse(meal.to_json)['small_url']
Explanation
When a ActiveModel is serialized through to_json the method as_json is first invoked on the object to separate the actual construction of the JSON data structure from the rendering. This data structure (a hash really) is then encoded as a JSON string through ActiveSupport.
So in orde to customize the object we wish to display as JSON we need to override the as_json method of that object - which is well documented. As per the documentation, the methods key for the options hash simply invokes the methods listed in the array passed as the value (in our case just small_url) and creates a key in the hash to be JSON encoded, with the value of the method invocation.
For an even more detailed explanation, please see this excellent answer.

Fancybox not showing uploaded pictures with CarrierWave

I am trying to use fancybox to display images. When I use static images, as the existing in the assets folder, all works fine. However, when I try to use uploaded images which were saved using carrierwave, it always puts the next message: "The requested content cannot be loaded.".
The code that I am using to display the picture is the next:
app/models/upload.rb
class Upload
include Mongoid::Document
include Mongoid::Timestamps
mount_uploader :file, UploadUploader
# Fields
field :file, type: String
field :filename, type: String
field :file64, type: String
field :size_file, type: Integer
field :file_extension, type: String
# Validations
validates_presence_of :file
# Hooks
before_validation do
if not self.file.url and self.filename and self.file64
sio = CarrierwaveStringIO.new( Base64.decode64( self.file64 ) )
sio.original_filename = self.filename
self.file = sio
self.filename = nil
self.file64 = nil
end
end
before_save do
self.size_file = self.size
self.file_extension = File.extname( self.file.to_s )[1..-1]
end
def base64
MIME::Types.type_of( self.file.url ).first.content_type
end
def size
self.file.size
end
end
app/views...
...
= link_to upload_path( upload.id ), class: 'fancybox' do
= image_tag upload_path( upload.id ), height: 174, width: 256
...
app/assets/javascript...
...
$( ".fancybox" ).fancybox()
...
I have tried lots of things to make the code works. For example, in the view, I change the "href" of the link_tag and the "src" of the image_tag, to the show path of the upload but it still does not work. Also, I tried to load the picture using AJAX and using the "content" property of fancybox but I obtained the same result. Finally, I created a show action for the upload controller in which I only display the picture and used the "content" and "type" properties of fancybox to use html, but I can not see the picture (no routes matches with the url of the picture, not the show action).
I have spent a lot of hours trying to make it works but I can not find anything. Thanks in advance.
EDIT:
app/controllers/upload_controller.rb
class UploadsController < ApplicationController
load_and_authorize_resource
respond_to :html, :json
def show
if request.format.html?
content_type = MIME::Types.type_for( #upload.file.url ).first.content_type
send_file #upload.file.url, content_type: content_type, disposition: 'inline', :x_sendfile => true
else
respond_with #upload, api_template: :general
end
end
...
app/uploaders/upload_...
class UploadUploader < CarrierWave::Uploader::Base
storage :file
def store_dir
"#{ Rails.root.to_s }/uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def cache_dir
"#{ Rails.root.to_s }/tmp/uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
def extension_white_list
%w(jpg jpeg png bmp tif)
end
end
app/views/uploads/show.html.haml
= image_tag #upload.file_url.to_s
Specifying type option, forces content type. it can be set to 'image', 'ajax', 'iframe', 'swf' or 'inline'
$( ".fancybox11" ).fancybox({
type: 'iframe', width: 380, height: 280
})
Refer this link fancybox api options.

Paperclip renaming files after they're saved

How do I rename a file after is has been uploaded and saved?
My problem is that I need to parse information about the files automatically in order to come up with the file name the file should be saved as with my application, but I can't access the information required to generate the file name till the record for the model has been saved.
If, for example, your model has attribute image:
has_attached_file :image, :styles => { ...... }
By default papepclip files are stored in /system/:attachment/:id/:style/:filename.
So, You can accomplish it by renaming every style and then changing image_file_name column in database.
(record.image.styles.keys+[:original]).each do |style|
path = record.image.path(style)
FileUtils.move(path, File.join(File.dirname(path), new_file_name))
end
record.image_file_name = new_file_name
record.save
Have you checked out paperclip interpolations?
If it is something that you can figure out in the controller (before it gets saved), you can use a combination of the controller, model, and interpolation to solve your problem.
I have this example where I want to name a file based on it's MD5 hash.
In my controller I have:
params[:upload][:md5] = Digest::MD5.file(file.path).hexdigest
I then have a config/initializers/paperclip.rb with:
Paperclip.interpolates :md5 do|attachment,style|
attachment.instance.md5
end
Finally, in my model I have:
validates_attachment_presence :upload
has_attached_file :upload,
:path => ':rails_root/public/files/:md5.:extension',
:url => '/files/:md5.:extension'
To add to #Voyta's answer, if you're using S3 with paperclip:
(record.image.styles.keys+[:original]).each do |style|
AWS::S3::S3Object.move_to record.image.path(style), new_file_path, record.image.bucket_name
end
record.update_attribute(:image_file_name, new_file_name)
My avatar images are named with the user slug, if they change their names I have to rename images too.
That's how I rename my avatar images using S3 and paperclip.
class User < ActiveRecord::Base
after_update :rename_attached_files_if_needed
has_attached_file :avatar_image,
:storage => :s3,
:s3_credentials => "#{Rails.root}/config/s3.yml",
:path => "/users/:id/:style/:slug.:extension",
:default_url => "/images/users_default.gif",
:styles => { mini: "50x50>", normal: "100x100>", bigger: "150x150>" }
def slug
return name.parameterize if name
"unknown"
end
def rename_attached_files_if_needed
return if !name_changed? || avatar_image_updated_at_changed?
(avatar_image.styles.keys+[:original]).each do |style|
extension = Paperclip::Interpolations.extension(self.avatar_image, style)
old_path = "users/#{id}/#{style}/#{name_was.parameterize}#{extension}"
new_path = "users/#{id}/#{style}/#{name.parameterize}#{extension}"
avatar_image.s3_bucket.objects[old_path].move_to new_path, acl: :public_read
end
end
end
And to add yet another answer, here is the full method I'm using for S3 renaming :
def rename(key, new_name)
file_name = (key.to_s+"_file_name").to_sym
old_name = self.send(file_name)
(self.send(key).styles.keys+[:original]).each do |style|
path = self.send(key).path(style)
self[file_name] = new_name
new_path = self.send(key).path(style)
new_path[0] = ""
self[file_name] = old_name
old_obj = self.send(key).s3_object(style.to_sym)
new_obj = old_obj.move_to(new_path)
end
self.update_attribute(file_name, new_name)
end
To use : Model.find(#).rename(:avatar, "test.jpg")
I'd like to donate my "safe move" solution that doesn't rely on any private API and protects against data loss due to network failure:
First, we get the old and new paths for every style:
styles = file.styles.keys+[:original]
old_style2key = Hash[ styles.collect{|s| [s,file.path(s).sub(%r{\A/},'')]} ]
self.file_file_name = new_filename
new_style2key = Hash[ styles.collect{|s| [s,file.path(s).sub(%r{\A/},'')]} ]
Then, we copy every file to it's new path. Since the default path includes both object ID and filename, this can never collide with the path for a different file. But this will fail if we try to rename without changing the name:
styles.each do |style|
raise "same key" if old_style2key[style] == new_style2key[style]
file.s3_bucket.objects[old_style2key[style]].copy_to(new_style2key[style])
end
Now we apply the updated model to the DB:
save!
It is important to do this after we create the new S3 objects but before we delete the old S3 objects. Most of the other solutions in this thread can lead to a loss of data if the database update fails (e.g. network split with bad timing), because then the file would be at a new S3 location but the DB still points to the old location. That's why my solution doesn't delete the old S3 objects until after the DB update succeeded:
styles.each do |style|
file.s3_bucket.objects[old_style2key[style]].delete
end
Just like with the copy, there's no chance that we accidentally delete another database object's data, because the object ID is included in the path. So unless you rename the same database object A->B and B->A at the same time (e.g. 2 threads), this delete will always be safe.
To add to #Fotios's answer:
its the best way I think to make custom file name, but in case you want file name based on md5 you can use fingerprint which is already available in Paperclip.
All you have to do is to put this to config/initializers/paperclip_defaults.rb
Paperclip::Attachment.default_options.update({
# :url=>"/system/:class/:attachment/:id_partition/:style/:filename"
:url=>"/system/:class/:attachment/:style/:fingerprint.:extension"
})
There's no need to set :path here as by default it's made that way:
:path=>":rails_root/public:url"
I didn't check if it's necessary but in case it doesn't work for you make sure your model is able to save fingerprints in the database -> here
One more tip which I find handy is to use rails console to check how it works:
$ rails c --sandbox
> Paperclip::Attachment.default_options
..
> s = User.create(:avatar => File.open('/foo/bar.jpg', 'rb'))
..
> s.avatar.path
=> "/home/groovy_user/rails_projectes/funky_app/public/system/users/avatars/original/49332b697a83d53d3f3b5bebce7548ea.jpg"
> s.avatar.url
=> "/system/users/avatars/original/49332b697a83d53d3f3b5bebce7548ea.jpg?1387099146"
The following migration solved the problem to me.
Renaming avatar to photo:
class RenamePhotoColumnFromUsers < ActiveRecord::Migration
def up
add_attachment :users, :photo
# Add `avatar` method (from Paperclip) temporarily, because it has been deleted from the model
User.has_attached_file :avatar, styles: { medium: '300x300#', thumb: '100x100#' }
User.validates_attachment_content_type :avatar, content_type: %r{\Aimage\/.*\Z}
# Copy `avatar` attachment to `photo` in S3, then delete `avatar`
User.where.not(avatar_file_name: nil).each do |user|
say "Updating #{user.email}..."
user.update photo: user.avatar
user.update avatar: nil
end
remove_attachment :users, :avatar
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
Hope it helps :)
Another option is set to default, work for all upload.
This example change name file to 'name default' for web, example: test áé.jpg to test_ae.jpg
helper/application_helper.rb
def sanitize_filename(filename)
fn = filename.split /(?<=.)\.(?=[^.])(?!.*\.[^.])/m
fn[0] = fn[0].parameterize
return fn.join '.'
end
Create config/initializers/paperclip_defaults.rb
include ApplicationHelper
Paperclip::Attachment.default_options.update({
:path => ":rails_root/public/system/:class/:attachment/:id/:style/:parameterize_file_name",
:url => "/system/:class/:attachment/:id/:style/:parameterize_file_name",
})
Paperclip.interpolates :parameterize_file_name do |attachment, style|
sanitize_filename(attachment.original_filename)
end
Need restart, after put this code

Resources