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.
Related
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
Is it possible to add a validation to accept only .pdf and .doc files using Active Storage?
Currently you have to write your own validator which looks at the attachment's MIME type:
class Item
has_one_attached :document
validate :correct_document_mime_type
private
def correct_document_mime_type
if document.attached? && !document.content_type.in?(%w(application/msword application/pdf))
document.purge # delete the uploaded file
errors.add(:document, 'Must be a PDF or a DOC file')
end
end
end
Also, there are some useful shortcut methods image?, audio?, video? and text? that check against multiple mime types.
there is a gem which provides validation for active storage
gem 'activestorage-validator'
https://github.com/aki77/activestorage-validator
validates :avatar, presence: true, blob: { content_type: :image }
validates :photos, presence: true, blob: { content_type: ['image/png', 'image/jpg', 'image/jpeg'], size_range: 1..5.megabytes }
Since ActiveStorage doesn't have validations yet, I found the following helps in my forms.
<div class="field">
<%= f.label :deliverable %>
<%= f.file_field :deliverable, direct_upload: true,
accept: 'application/pdf,
application/zip,application/vnd.openxmlformats-officedocument.wordprocessingml.document' %>
</div>
I was looking at https://gist.github.com/lorenadl/a1eb26efdf545b4b2b9448086de3961d
and it looks like on your view you have to do something like this
<div class="field">
<%= f.label :deliverable %>
<%= f.file_field :deliverable, direct_upload: true,
accept: 'application/pdf,
application/zip,application/vnd.openxmlformats-officedocument.wordprocessingml.document' %>
</div>
Now on your model, you can do something like this
class User < ApplicationRecord
has_one_attached :document
validate :check_file_type
private
def check_file_type
if document.attached? && !document.content_type.in?(%w(application/msword application/pdf))
document.purge # delete the uploaded file
errors.add(:document, 'Must be a PDF or a DOC file')
end
end
end
I hope that this helps
class Book < ApplicationRecord
has_one_attached :image
has_many_attached :documents
validate :image_validation
validate :documents_validation
def documents_validation
error_message = ''
documents_valid = true
if documents.attached?
documents.each do |document|
if !document.blob.content_type.in?(%w(application/xls application/odt application/ods pdf application/tar application/tar.gz application/docx application/doc application/rtf application/txt application/rar application/zip application/pdf image/jpeg image/jpg image/png))
documents_valid = false
error_message = 'The document wrong format'
elsif document.blob.byte_size > (100 * 1024 * 1024) && document.blob.content_type.in?(%w(application/xls application/odt application/ods pdf application/tar application/tar.gz application/docx application/doc application/rtf application/txt application/rar application/zip application/pdf image/jpeg image/jpg image/png))
documents_valid = false
error_message = 'The document oversize limited (100MB)'
end
end
end
unless documents_valid
errors.add(:documents, error_message)
self.documents.purge
DestroyInvalidationRecordsJob.perform_later('documents', 'Book', self.id)
end
end
def image_validation
if image.attached?
if !image.blob.content_type.in?(%w(image/jpeg image/jpg image/png))
image.purge_later
errors.add(:image, 'The image wrong format')
elsif image.blob.content_type.in?(%w(image/jpeg image/jpg image/png)) && image.blob.byte_size > (5 * 1024 * 1024) # Limit size 5MB
image.purge_later
errors.add(:image, 'The image oversize limited (5MB)')
end
elsif image.attached? == false
image.purge_later
errors.add(:image, 'The image required.')
end
end
end
And in job Destroy
class DestroyInvalidationRecordsJob < ApplicationJob
queue_as :default
def perform(record_name, record_type, record_id)
attachments = ActiveStorage::Attachment.where(name: record_name, record_type: record_type, record_id: record_id)
attachments.each do |attachment|
blob = ActiveStorage::Blob.find(attachment.blob_id)
attachment.destroy
blob.destroy if blob.present?
end
end
end
I was doing direct uploads with ActiveStorage. Since validators don't exist yet I simply overwrote the DirectUploadsController Create method:
# This is a kind of monkey patch which overrides the default create so I can do some validation.
# Active Storage validation wont be released until Rails 6.
class DirectUploadsController < ActiveStorage::DirectUploadsController
def create
puts "Do validation here"
super
end
end
also need to overwrite the route:
post '/rails/active_storage/direct_uploads', to: 'direct_uploads#create'
+1 for using gem 'activestorage-validator' with ActiveStorage
In your model you can validate doc, docx and pdf formats this way:
has_one_attached :cv
validates :cv, blob: { content_type: ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/pdf'], size_range: 0..5.megabytes }
Adding a gem for just a few lines of code is not always needed. Here's a working example based on a small alteration on the example above.
validate :logo_content_type, if: -> { logo.attached? }
def logo_content_type
allowed = %w[image/jpeg image/jpeg image/webp image/png]
if allowed.exclude?(logo.content_type)
errors.add(:logo, message: 'Logo must be a JPG, JPEG, WEBP or PNG')
end
end
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
I want to retrieve image upload dimensions before create as I attach the file. I got this via Extracting Image dimensions through model. But I want to dispatch through custom processor. What I tried is:
Player.rb
class Player < ActiveRecord::Base
has_attached_file :avatar, processors: [:custom], :style => {:original => {}}
....
end
/lib/paperclip_processors/custom.rb
module Paperclip
class Custom < Processor
def initialize file, options = {}, attachment = nil
super
#file = file
#attachment = attachment
#current_format = File.extname(#file.path)
#format = options[:format]
#basename = File.basename(#file.path, #current_format)
end
def make
temp_file = Tempfile.new([#basename, #format])
#geometry = Paperclip::Geometry.from_file(temp_file)
temp_file.binmode
if #is_polarized
run_string = "convert #{fromfile} -thumbnail 300x400 -bordercolor white -background white +polaroid #{tofile(temp_file)}"
Paperclip.run(run_string)
end
temp_file
end
def fromfile
File.expand_path(#file.path)
end
def tofile(destination)
File.expand_path(destination.path)
end
end
end
I got the above (custom.rb) code from here.
Is it possible to achieve is? How? Thanks in advance :)
I reproduced your case and I believe the cause is the typo here:
has_attached_file :avatar, processors: [:custom], :style => {:original => {}}
It should be :styles instead of :style. Without a :styles option, paperclip does not do any post-processing and ignores your :processors.
Also, here is a very simple implementation of a Paperclip::Processor - turns attachments into grayscale. Replace the command inside convert to perform your own post-processing:
# lib/paperclip_processors/custom.rb
module Paperclip
class Custom < Processor
def make
basename = File.basename(file.path, File.extname(file.path))
dst_format = options[:format] ? ".\#{options[:format]}" : ''
dst = Tempfile.new([basename, dst_format])
dst.binmode
convert(':src -type Grayscale :dst',
src: File.expand_path(file.path),
dst: File.expand_path(dst.path))
dst
end
end
end
I have a custom Paperclip processor which extracts a screenshot from an MP4 using FFMPEG. This is working very well but I would like for the user to be able to select the time at which the screenshot is taken in the video in the upload form before it is processed, which has so far eluded me.
upload.rb
class Upload < ActiveRecord::Base
has_attached_file :uploaded_file,
styles: {
screenshot: { :processors => [:screenshot], :format => 'png' }
},
default_url: "/images/:style/missing.png"
default_scope order('created_at DESC')
def paperclip_screenshot_time
# I'm attempting to pull in params[:screenshot_time] (set in the view)
self.screenshot_time.to_s
end
end
The above does not work, however if I change the paperclip_screenshot_time method to:
def paperclip_screenshot_time
'5'
end
It works fine.
Here is an excerpt from the processor, which as I say is also working fine:
screenshot.rb
module Paperclip
class Screenshot < Processor
def initialize(file, options = {}, attachment = nil)
super
#file = file
#options = options
#instance = attachment.instance
#current_format = '.*'
#basename = File.basename(#file.path, #current_format)
#whiny = options[:whiny].nil? ? true : options[:whiny]
end
def target
#attachment.instance
end
def make
# Removed for brevity
begin
system('ffmpeg -i ' + #file.path + ' -ss 00:00:0' + target.paperclip_screenshot_time.to_s + ' -f image2 -vframes 1 ' + tmp_dir + '/' + tmp_filename)
end
# Removed for brevity
end
end
end
...and the relevant field from the view.
upload.jst.erb
<input name="upload[screenshot_time]" type="number" min="0" max="9" value="4" class="js-form-input" />
The field is also permitted in the controller:
uploads_controller.rb
def upload_params
params[:upload].permit(:uploaded_file, :title, :screenshot_time)
end
So, to re-iterate the problem seems to be in setting paperclip_screenshot_time with the value from upload[screenshot_time] in the view. How should I go about doing this?
Any help gratefully received!
It turns out the issue was the order in which the upload params were set in the controller.
So, in order for title and screenshot_time to be accessible in the processor they had to come before uploaded_file. So by changing the upload_params method to this:
def upload_params
params[:upload].permit(:title, :screenshot_time, :uploaded_file)
end
It all works smoothly.