Carrierwave doesn't remove the deleted files from S3 - ruby-on-rails

I have 2 Carrierwave uploaders - ItemUploader and ImageUploader, and am using fog.
I can upload files to S3 just fine, but doing a destroy doesn't remove them from S3.
This is my destroy action:
def destroy
#item = Item.find(params[:id])
#item.destroy
respond_to do |format|
format.html { redirect_to items_url }
format.json { head :no_content }
end
end
When I do item.destroy, it deletes the record from my db but it doesn't remove the file from S3 and it doesn't remove the folders.
This is a brand new S3 bucket, with vanilla settings. Also a brand new Carrierwave install.
FYI: I have tried adding #item.remove_item! and #item.remove_image! to the destroy action of the controller but that hasn't done the trick either.
Edit 1
So it seems that what happens is that it deletes 1 of the attachments.
The model has this:
class Item < ActiveRecord::Base
# image :string(255)
# link :string(255)
mount_uploader :link, ItemUploader
mount_uploader :image, ImageUploader
end
So, when I delete an object in my console, it removes the object associated with ItemUploader and not the image related via the ImageUploader.
Why would it delete 1 and not the other?

It seems there is something wrong with my console - because once I delete the object via the web UI it deletes all the related objects in S3.
But if I do it via the console, it doesn't work.
I will be opening another SO question for that particular issue.

It deletes but take little time specially if you are referring file(image) from cdn.

Uses aws sdk - https://github.com/amazonwebservices/aws-sdk-for-ruby
You can build a facade to manage your objects on 3, for example:
require 'aws-sdk'
class Facades::AmazonFacade
attr_reader :s3
#
# Connection to Amazon S3
#
def initialize
#s3 = AWS::S3.new(
:access_key_id => config['access_key_id'],
:secret_access_key => config['secret_access_key']
)
#bucket = #s3.buckets[self.config['bucket']]
end
def config
##config ||= YAML::load(File.open("#{Rails.root}/config/amazon_s3.yml" ))[Rails.env]
end
####
def policy(bucket, options = {})
# Base 64 policy
end
def signature(bucket, options = {})
# Base64 signature
end
#
# Find object and get public urls
#
def url_link(obj, expires)
#bucket.objects[obj].url_for(:read, :secure => true, :expires => 10*60).to_s
end
def object_exists_on_amazon?(obj)
#bucket.objects[obj].exists?
end
def object_size(obj)
unless Rails.env.test?
#bucket.objects[obj].content_length
end
end
def object_upload_date(obj)
#bucket.objects[obj].last_modified
end
#
# create, delete objects
#
def store_object_on_amazon(obj, file, access)
#bucket.objects[obj].write(file, :acl => access)
end
def delete_object_on_amazon(obj)
#bucket.objects[obj].delete(:force => true)
end
end

Related

Paperclip custom path with Apartment Gem

Facing very wired issue while using Paperclip (With s3 storage) and Apartment gem.
Apartment gem is being used for multi-tenancy with Postgres DB. ( i.e Separate Schema for every tenant )
For Storage, I would like to create a separate folder for every tenant.
class Media < ApplicationRecord
belongs_to :user
has_attached_file :file,
:storage => :s3,
:s3_credentials => {
:access_key_id => "ACCESSKEY",
:secret_access_key => "SECRETE KEY"
},
:path => "#{Apartment::Tenant.current}/:class/:attachment/:id_partition/:style/:filename"
end
Above is my media folder, which belongs to User.
class User < ApplicationRecord
has_attached_file :avatar,
:storage => :s3,
:s3_credentials => {
:access_key_id => "ACCESSKEY",
:secret_access_key => "SECRETE KEY"
},
:path => "#{Apartment::Tenant.current}/:class/:attachment/:id_partition/:style/:filename"
end
Apartment.rb file looks like below
require 'apartment/elevators/subdomain'
require 'rescued_apartment_middleware'
Apartment.configure do |config|
config.excluded_models = %w{ Account }
config.tenant_names = lambda { Account.pluck :subdomain }
config.use_schemas = true
end
Rails.application.config.middleware.insert_before Warden::Manager, RescuedApartmentElevator
Apartment::Elevators::Subdomain.excluded_subdomains = ['www','admin']
rescued_apartment_elevator.rb file looks like below
class RescuedApartmentElevator < Apartment::Elevators::Subdomain
def call(env)
begin
super
rescue Apartment::TenantNotFound
env[:apartment_tenant_not_found] = true # to be later referenced in your ApplicationController
#app.call(env) # the middleware call method should return this, but it was probably short-circuited by the raise
end
end
end
In application_controller.rb I have handled the code something like below
class ApplicationController < ActionController::Base
before_action :load_schema, :authenticate_user!, :set_mailer_host
before_action :configure_permitted_parameters, if: :devise_controller?
private
def load_schema
Apartment::Tenant.switch!
return if request.subdomain == "www"
if request.subdomain == ""
redirect_to root_url(subdomain: 'www')
else
if current_account
Apartment::Tenant.switch!(current_account.subdomain)
else
if request.env[:apartment_tenant_not_found]
redirect_to root_url(subdomain: 'www'), notice: 'Account you are looking for do not exists'
end
end
end
end
end
I start my server, go to one of the tenant e.g http://apple.lvh.me:3000 (apple is tenant name here)
1. If I go to edit user profile and upload the file for user profile,
Apartment::Tenant.current is returning "public" in model and hence
the file is getting uploaded in public folder instead of "apple"
folder.
I am using devise gem (latest version) below is how my user_controller.rb looks like
class UsersController < ApplicationController
before_action : set_user, only: [:edit,:update]
def update
if #user.update(user_params)
if #user.pending_reconfirmation?
redirect_to edit_user_path(current_user), notice: "Your profile is successfully updated! But if you changed the email address, then please confirm it before!"
else
redirect_to edit_user_path(current_user), notice: "Your profile is successfully updated!"
end
else
render "edit"
end
end
end
Problem 2
**When I go to media and add some media, they would go properly to "apple" folder on s3 bucket. I would sign out, go to different tanent i.e http://google.lvh.me:3000/ (google is tenant name here). If I go to media listing, URL will continue to point to Apartment::Tenant.current -> apple only. This results in wrong URL for google tenant and images wont display.
If I shut down and restart the server, then rails will start returning google for Apartment::Tenant.current method and hence URL pointing correctly to correct folder. **
To Summerise, below are my problems.
Using Apartment::Tenant.current in model for paperclip custom path -
is it right way? Is there some other method that could return me
current tanent so I can use it for creating folder on s3 bucket.
Have I configured the middleware correctly in my apartment.rb file?
Is there different way of doing this?
After posting to StackOverflow, I was able to figureout the solution. Posting it here in case someone else stumbleupon this in future.
My Mistake was tryign to use #{Apartment::Tenant.current} into path directly. This is wrong. paperclip support Interpolates, I should be using that in order to generate dynamic paths.
so below are the changes required.
has_attached_file :file,
:storage => :s3,
:s3_credentials => {
:access_key_id => "AccessKey",
:secret_access_key => "SecreteKey"
},
:path => ":tenant/:class/:attachment/:id_partition/:style/:filename"
Notice the :tenant being introduced in the path.
Add following line into paperclip.rb
Paperclip.interpolates :tenant do |attachment, style|
Apartment::Tenant.current
end
This is it. Hope it helps someone!

How to upload image in ruby app folder and insert url in database column

Controller
Save the object
How to Use This Controller to insert image in any folder and image url store in database
def create_json
#user = User.new(userFirstName: params[:userFirstName], userLastName: params[:userLastName], userEmail: params[:userEmail], password: encrypted_password, userImage: params[:userImage])
if #user.save #if save succeeds, redirect to the index action
redirect_to(:action => 'show', id: User.last.id)
else
#if not succeeds, redirect to the index action
redirect_to(:action => 'new')
end end
User(Model) mount_uploader :userImage, AvatarUploader
UsersController -> #user = User.new(user_params)
if #user.save
redirect_to(:action => 'show', id: User.last.id)
else
render :json => data_hash2, :content_type => 'application/json'
end
class AvatarUploader < CarrierWave::Uploader::Base
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def extension_white_list
%w(jpg jpeg gif png)
end end
If You Using Web Service
<form action="http://ruby/controllername/create" lass="new_user" id="new_user" enctype="multipart/form-data" accept-charset="UTF-8" method="post">
View following method for uploading files to app/assets/images/people_profile_images folder
First upload Image like this:
#user =User.new(user_params)
#this will create user form paramaters given
uploaded_file = params[:user][:photo]
#this will add uploaded image or file in uplaoded_fie
if (uploaded_file)
File.open(Rails.root.join('app/assets', 'images/people_profile_images', uploaded_file.original_filename), 'wb') do |file|
file.write(uploaded_file.read)
#user.photo = "/assets/people_profile_images/"+uploaded_file.original_filename
end
else
#user.photo = "/assets/people_profile_images/default_avatar.png"
end
then save user
#user.save
I don't know of an approach that can cover both: image uploading and then saving the url on the database.
You can do one or the other:
1) Use a gem called paperclip that helps upload image files.
See this links:
https://github.com/thoughtbot/paperclip#ruby-and-rails
https://teamtreehouse.com/library/image-uploads-in-ruby-on-rails-41 (paperclip, carrierwave, and dragonfly)
I have used paperclip and it has worked well for me.
OR:
2) Include it as an additional field on your filename.html.erb for your image_url
One caveat, of using this though is that this can be changed and you have no control of the image that might show on your site. Thus, the image_url should be from a reputable CDN or at least from a reputable user uploading to your site.
Create a column url:string in your model. Add it as a hidden field in your form. Set its value to the same as your remote_url using .js
var urladdy = input.remoteurl.val()
input.url.val(urladdy)
This will upload image with carrier wave and also save its url as string.

Domain name missing in image paperclip in to_json rails 4

I have the model that uses paperclip like this
has_attached_file :image, styles: { :medium => "50x50>" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/
def image_url
image.url(:medium)
end
I need it Json, So in my controller,
respond_to do |format|
format.json { render json: #celebrity.to_json(:methods => [:image_url])}
end
And the result is
"image_url":"/system/celebrities/images/000/000/003/medium/Designed___developed_by_wd_ApS.png?1430926991"
but, I need to include the domain name, localhost:3000 ,
So what I have to do here
Try this.
Create module:
module WithDomain
def domain
#domain
end
def domain=(val)
#domain = val
end
def domain?
#domain.present?
end
end
Change you model accordingly:
class Celebtiry < ActiveRecord::Base
include WithDomain
# existing staff
def image_url
if domain?
URI.join(domain, image.url(:medium)).to_s
else
image.url(:medium)
end
end
end
and in your controller:
respond_to do |format|
format.json {
#celebrity.domain = request.base_url
render json: #celebrity.to_json(:methods => [:image_url])
}
end
Solution 1: (with existing code)
You can use asset_url from ActionView::Helpers::AssetUrlHelper module which will give you the absolute url of your image. Just include ActionView::Helpers::AssetUrlHelper this in your model so that asset_url becomes available inside your model.
So, your method inside the model would be:
include ActionView::Helpers::AssetUrlHelper
def image_url
asset_url(image.url(:medium))
end
This is the easiest solution for you with your current code.
Solution 2: (inside the controller)
In your controller request is available, so you can do:
URI.join(request.url, #celebrity.image.url(:medium))
which will give you the absolute url of the image. This will be an URI object, which can be converted to a String with .to_s method.
Here is the issue from paperclip from where this solution is derived. Hope this helps.

Rails - Limited Browsing of User Attachments using ckeditor Gem w/ CanCan & Paperclip

I have everything working correctly, but now I want to limit some user Abilities to perform some attachment actions.
Specifically, the ability to limit the viewing of all uploaded attachments, to those actually uploaded by the User.
Here is the applicable snippet from ability.rb I tried ...
if user.id
can :access, :ckeditor
can [:read, :create, :destroy], Ckeditor::Picture, assetable_id: user.id
can [:read, :create, :destroy], Ckeditor::AttachmentFile, assetable_id: user.id
end
The situation arises when I am using the CKeditor UI, click the Image button, and then click the Browse Server button to see the previously uploaded images -- right now the image browser shows the uploads of all users. I would like the viewed images to be limited to those of the current_user only.
Since the Ckeditor table saves the assetable_id of the attachment (i.e. the user.id), and the logic above does not work on its own, I'm guessing some custom Controller logic is also needed here.
Thanks.
I was able to solve this issue with custom Ckeditor controllers & some guidance from here:
https://github.com/galetahub/ckeditor/issues/246
First I needed to make copies of the Ckeditor controllers pictures_controller.rb & attachment_files_controller.rb and place them here:
/app/controllers/ckeditor/
Then a few updates to their suggestions to update index were necessary, particularly picture_model.find_all needed to be picture_adapter.find_all in pictures_controller.rb (and similarly attachment_file_adapter.find_all in attachment_files_controller.rb)
The key to it all is setting the proper scope with: ckeditor_pictures_scope(assetable_id: ckeditor_current_user) & ckeditor_attachment_files_scope(assetable_id: ckeditor_current_user)
Once these revisions are in place, the file browsers for pictures & attachments show only the appropriate files for that user.
Here are the revised files ... the changes are on line 4 of both.
/app/controllers/ckeditor/pictures_controller.rb
class Ckeditor::PicturesController < Ckeditor::ApplicationController
def index
#pictures = Ckeditor.picture_adapter.find_all(ckeditor_pictures_scope(assetable_id: ckeditor_current_user))
#pictures = Ckeditor::Paginatable.new(#pictures).page(params[:page])
respond_with(#pictures, :layout => #pictures.first_page?)
end
def create
#picture = Ckeditor.picture_model.new
respond_with_asset(#picture)
end
def destroy
#picture.destroy
respond_with(#picture, :location => pictures_path)
end
protected
def find_asset
#picture = Ckeditor.picture_adapter.get!(params[:id])
end
def authorize_resource
model = (#picture || Ckeditor.picture_model)
#authorization_adapter.try(:authorize, params[:action], model)
end
end
/app/controllers/ckeditor/attachment_files_controller.rb
class Ckeditor::AttachmentFilesController < Ckeditor::ApplicationController
def index
#attachments = Ckeditor.attachment_file_adapter.find_all(ckeditor_attachment_files_scope(assetable_id: ckeditor_current_user))
#attachments = Ckeditor::Paginatable.new(#attachments).page(params[:page])
respond_with(#attachments, :layout => #attachments.first_page?)
end
def create
#attachment = Ckeditor.attachment_file_model.new
respond_with_asset(#attachment)
end
def destroy
#attachment.destroy
respond_with(#attachment, :location => attachment_files_path)
end
protected
def find_asset
#attachment = Ckeditor.attachment_file_adapter.get!(params[:id])
end
def authorize_resource
model = (#attachment || Ckeditor.attachment_file_model)
#authorization_adapter.try(:authorize, params[:action], model)
end
end

Carrierwave, MiniMagick, and S3

In my Rails app, I enable users to upload images using Carrierwave and Amazon S3. I want to implement a feature that lets users edit existing images by rotating it 90 degrees.
I'm confused about where this code would go. Does it go in the image uploader file, or the image controller? And how is it called? I believe it should look something like this:
image = Image.find(params[:id])
image_obj = MiniMagick::Image.read(image.file)
image_obj.rotate(-90)
image_obj.write(image.file)
But I haven't been able to find examples to help me. If anyone can give me a pointer in the right direction, I would really appreciate it!
Edit
Thanks to deep for their thorough response! Here is what I ended up doing:
In my view:
# image.html.erb:
<%= link_to rotate_image_path(:id => image.id), :remote => true %>
In my controller:
# image_controller.rb:
def rotate
#image = Image.find(params[:id])
#image.rotated = true
#image.save
respond_to do |format|
format.js { render :nothing => true }
end
end
In my model:
# image.rb
attr_accessible :rotated
after_save :rotate_image, if: ->(obj){obj.rotated.present? && obj.rotated?}
def rotate_image
self.image_path.recreate_versions!
end
In my uploader:
# image_uploader.rb
process :rotate_img
def rotate_img
if model.rotated.present? && model.rotated?
manipulate! do |img|
img.rotate '-90'
img
end
end
end
The only real change I made was in the uploader, where I ran into errors trying to do a condition process. I put the conditional within the rotate_img method.
Here's my solution
First define a attribute accessor in your model and on update set it to true.
In your model
#image.rb
attr_accessor :rotate
In your controller
#images_controller.rb
def update
#image = Image.find(params[:id])
#image.rotate = true
#image.save
redirect_to root_path, :notice => "Bla bla bla"
end
Carrierwave provides a recreate_versions! method which will process and re-upload the image. In you case you can add a after_save callback that will trigger recreate_versions! method only if the rotate attribute is set to true.
In your model
#image.rb
after_save :rotate_image, if: ->(obj){ obj.rotate.present? and obj.rotate? }
def rotate_image
self.file.recreate_versions!
end
Now in your image uploader you can write the code to rotate a image.
#image_uploader.rb
.......
# It will replace the original image with rotated version
process :rotate_img, :if => model.rotate.present and model.rotate?
def rotate_img
manipulate! do |img|
img.rotate "90"
img #returns the manipulated image
end
end
If you don't want to replace the original image then all you have to do is to call process inside a version like
# Create different versions of your uploaded files:
version :rotated_img do
process :rotate_img
end

Resources