Attachments saved using Paperclip (v4.2.0) are not being deleted from disk when the model is destroyed, is anybody else experiencing this issue? Everything is working as expected but the files just aren't getting deleted from disk. Any help or ideas would be super appreciated!
Models:
class Attachment < ActiveRecord::Base
belongs_to :article
has_attached_file :file, { :preserve_files => "false" }
do_not_validate_attachment_file_type :file
end
class Article < ActiveRecord::Base
belongs_to :topic
belongs_to :subtopic
belongs_to :author
has_many :attachments, :dependent => :destroy
accepts_nested_attributes_for :attachments, allow_destroy: true, reject_if: lambda { |a| a[:file].blank? }
validates :topic_id, presence: true
validates :title, presence: true, length: { maximum: 16 }
validates :subtitle, length: { maximum: 20 }
validates :content, presence: true
end
Destroy action in Articles controller:
def destroy
#article = Article.find(params[:id])
begin
# first delete the attachments from disk
#article.attachments.each do |a|
a.file.destroy
end
#article.destroy
rescue
flash[:danger] = "Unable to delete article"
else
flash[:success] = "Article deleted"
end
redirect_to admin_articles_url
end
You need to set the attachment 'file' attribute to nil before destroying it, in order to delete the uploaded file from the disk.
So your code should be like this
Destroy action in Articles controller:
def destroy
#article = Article.find(params[:id])
begin
# first delete the attachments from disk
#article.attachments.each do |a|
a.file = nil
a.save
end
#article.destroy
rescue
flash[:danger] = "Unable to delete article"
else
flash[:success] = "Article deleted"
end
redirect_to admin_articles_url
end
Try to add preserve_files option to "false" string
has_attached_file :file, :preserve_files => "false"
Come up with:
def destroy
self.transaction do
self.images.destroy_all
super
end
end
self.images is collection of records with attachments.
Most important there is self.transaction do ... because when for any reason super (original destroy) failed it wont remove files from hdd. It waits untils COMMIT
try something like this:
def destroy
#article = Article.find(params[:id])
#article.attachments.destroy
#article.destroy
respond_to do |format|
format.html { redirect_to admin_articles_url }
end
end
#article = Article.find(params[:id]) will find the article which you want to delete.
#article.attachments will collect all attachments related that particular article
Note: you must destroy #article after all attachments destroyed. If you write #article.destroy before #article.attachments.destroy then it will give an error as #article not found.
As you have mentioned in Article model has_many :attachments, :dependent => :destroy
then I think no need to write #article.attachments.destroy as destroying #article it will delete all attachments related to it.
You can also use a before_destroy callback
example
class User < ActiveRecord::Base
before_destroy :delete_image
has_attached_file :image,
path: "/attachments/:class/:attachment/:style/:filename"
validates_attachment_content_type :image, content_type: \Aimage\/.*\z/
private
def delete_image
self.image = nil
self.save
end
end
then any time an instance of the model class is destroyed, then the attachments will be deleted first.
Related
So I have these files
deal.rb
class Deal < ApplicationRecord
has_many :images, as: :imageable, dependent: :destroy
#there is more code after this
end
image.rb
class Image < ApplicationRecord
belongs_to :imageable, polymorphic: true
belongs_to :deal
has_attached_file :attachment, styles: { thumb: "100x100!", medium: "200x200!" }
validates_attachment_content_type :attachment, content_type: /\Aimage\/.*\z/
end
deals_controller.rb
module Admins
class DealsController < BaseController
before_action :find_deal, only: [:edit, :update, :destroy]
def index
#deals = Deal.includes(:images)
end
def new
#deal = Deal.new
end
def edit
end
def create
#deal = Deal.new(deal_params.merge(created_by: current_user.id))
if #deal.save
flash[:success] = t('.success')
redirect_to admins_deals_url
else
flash.now[:warning] = t('.failure')
render :new
end
end
def update
if #deal.update(deal_params)
flash[:success] = t('.success')
redirect_to admins_deals_url
else
flash.now[:warning] = #deal.errors[:base].to_sentence
render :edit
end
end
def destroy
if #deal.destroy
flash[:success] = t('.success')
redirect_to admins_deals_url
else
flash.now[:warning] = t('.failure')
render :index
end
end
private
def deal_params
params.require(:deal).permit(:title, :description, :price, :discounted_price, :quantity, :publish_date, images_attributes: [:id, :attachment, :_destroy])
end
def find_deal
#deal = Deal.find_by(id: params[:id])
unless #deal
flash[:warning] = t('deals.not_found')
redirect_to admins_deals_path
end
end
end
end
application_controller.rb
class ApplicationController < ActionController::Base
helper_method :current_user, :current_cart
def current_user
#current_user ||= User.find_by(id: current_user_id)
end
def current_user_id
cookies.signed[:user_id] || session[:user_id]
end
def current_cart
#current_cart ||= (current_user.addressed_cart || current_user.cart) if current_user
end
end
EDIT:
Although I don't think application_controller has anything to do with the error
I am creating a deal with nested image attributes. I am using paperclip to upload the images. But I am getting these errors. I don't have any idea what the errors even mean. Here is an image to show the errors.
Here is the pastebin link
errors on terminal on creating deal
This appears to be a validation error. Try this for your validation:
validates_attachment_content_type :attachment, :content_type => /image/
Or for other variations you can see Validate Attachment Content Type Paperclip
UPDATE after testing your code seems this was a validation error because Paperclip creates an image but doesn't know about the belongs_to association. You can make it optional because by default rails 5 requires the belongs_to id field.
class Image < ApplicationRecord
belongs_to :imageable, polymorphic: true
belongs_to :deal, optional: true
has_attached_file :attachment, styles: { thumb: "100x100!", medium: "200x200!" }
validates_attachment_content_type :attachment, content_type: /\Aimage\/.*\z/
end
I am trying to make it so that when I save an answer, I also save the prop_id that is associated with that answer.
I have a nested route relationship so that each prop (stands for proposition or bet) has a an associated answer like this: http://localhost:3000/props/1/answers/new.
Right now, when I save an answer, I save the answer choice and the user_id who created the answer. I need to save also the prop that is associated with the answer.
Answers Controller:
class AnswersController < ApplicationController
attr_accessor :user, :answer
def index
end
def new
#prop = Prop.find(params[:prop_id])
#user = User.find(session[:user_id])
#answer = Answer.new
end
def create
#prop = Prop.find(params[:prop_id])
#user = User.find(session[:user_id])
#answer = #user.answers.create(answer_params)
if #answer.save
redirect_to root_path
else
render 'new'
end
end
def show
#answer = Answer.find params[:id]
end
end
private
def answer_params
params.require(:answer).permit(:choice, :id, :prop_id)
end
Answer Model
class Answer < ActiveRecord::Base
belongs_to :prop
belongs_to :created_by, :class_name => "User", :foreign_key => "created_by"
has_many :users
end
Prop Model
class Prop < ActiveRecord::Base
belongs_to :user
has_many :comments
has_many :answers
end
User Model
class User < ActiveRecord::Base
has_many :props
has_many :answers
has_many :created_answers, :class_name => "Answer", :foreign_key => "created_by"
before_save { self.email = email.downcase }
validates :username, presence: true, uniqueness: {case_sensitive: false}, length: {minimum: 3, maximum: 25}
has_secure_password
end
Just modify your code a little bit, and it will work:
def create
#user = User.find(session[:user_id])
#prop = #user.props.find_by(id: params[:prop_id])
#answer = #user.answers.build(answer_params)
#answer.prop = #prop
# Modify #user, #prop or #answer here
# This will save #user, #prop & #answer
if #user.save
redirect_to root_path
else
render 'new'
end
end
When a category is deleted, contents/posts with same category_id also needs be destroyed.
Here is my categories controller.
#Categories Controller
def destroy
authorize #category
#category.destroy
respond_to do |format|
format.html { redirect_to categories_url, notice: 'Category was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_category
#category = Category.find(params[:id])
end
def category_params
params.require(:category).permit(:title, :user_id, :image)
end
Inside my Category model, I have added a after_destory.
#Category model
after_destroy :remove_content
private
def remove_content
if Content.exists?(:category_id => self.id)
Content.destroy(the_content)
end
end
def the_content
#the_content = Content.where(category_id: self.id)
end
I use self.id to grab category_id, but the category uses id and title as uri.
def to_param
"#{id} #{title}".parameterize
end
Content belongs to category.
#Content model
class Content < ActiveRecord::Base
has_attached_file :image, styles: { medium: "300x160#", thumb: "100x53#" }, default_url: "/images/:style/missing.png"
validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/
belongs_to :user
belongs_to :category
def to_param
"#{id} #{title}".parameterize
end
end
How can I grab category_id from CategoriesController without self.id ?
Just use association for this:
classs Category < ActiveReocrd::Base
has_many :contents, dependent: :destroy
dependend: :destroy does exactly what you are trying to achieve. It adds a hook destroying all the contents which belongs to deleted category. Note, do not use #category.delete as it is not triggering any hooks - always use #category.destroy
Like what BroiStaste said, you'd be best using the dependent: :destroy switch in the Category model:
#app/models/category.rb
class Category < ActiveRecord::Base
has_many :contents, dependent: :destroy
This is a very simple principle, in that you're telling ActiveRecord that if you destroy a Category object (which is what you're doing), it should destroy any of the dependent Content objects it has.
From the Docs:
If you set the :dependent option to:
:destroy, when the object is destroyed, destroy will be called on its associated objects.
:delete, when the object is destroyed, all its associated objects will be deleted directly from the database without calling their destroy method.
I'm a newbie in rails and trying to implement image uploading to ftp with 'carrierwave-ftp' gem. For image uploading, I have two controllers. First one is 'events_controller' while the second one is 'events_pictures_controller'.
Pictures are getting uploading to ftp. But the problem is that when I'm deleting a single picture, it is destroying the entire event. Please help!
Here is my Events Model:
class Event < ActiveRecord::Base
has_many :event_pictures, dependent: :destroy
accepts_nested_attributes_for :event_pictures, allow_destroy: true
validates_presence_of :name, :date
end
Here is my EventPictures Model:
class EventPicture < ActiveRecord::Base
mount_uploader :picture_title, EventPicturesUploader
validates_presence_of :picture_title
belongs_to :event, dependent: :destroy
end
Events Controller:
def index
#events = Event.all.order('date DESC')
end
def show
#event_pictures = #event.event_pictures.all
end
def new
#event = Event.new
#event_picture = #event.event_pictures.build
end
def edit
end
def create
#event = Event.new(event_params)
respond_to do |format|
if #event.save
params[:event_pictures]['picture_title'].each do |a|
#event_picture = #event.event_pictures.create!(:picture_title => a, :event_id => #event.id)
end
format.html { redirect_to #event, notice: 'Event was successfully created.' }
else
format.html { render :new }
end
end
end
def destroy
#event = Event.find params[:id]
#event.destroy
redirect_to events_url
end
private
def set_event
#event = Event.find(params[:id])
end
def event_params
params.require(:event).permit(:name, :date, event_pictures_attributes: [:id, :event_id, :picture_title])
end
This is the Destroy method in EventPictures Controller
def destroy
#event_picture = EventPicture.find params[:id]
#event_picture.destroy
redirect_to "events_url"
end
Meanwhile in the events.show.html.erb, I have this:
<% #event_pictures.each do |p| %>
<%= link_to image_tag(p.picture_title_url, :class => 'event-img'), image_path(p.picture_title_url) %>
<%= link_to 'Delete', p, method: :delete, data: { confirm: "Are you sure?" } %>
<% end %>
In your EventPicture model you have dependent: :destroy on the association which means that when the picture will deleted the corresponding events too. So just edit the association and make it:
belongs_to :event
And you have dependent destroy on the Event model so when a event will be deleted the corresponding pictures too will get deleted which is correct.
Hope this helps.
I believe your error lies with this line
belongs_to :event, dependent: :destroy
This is telling the EventPicture model to delete its parent model Event when it is deleted.
Replace with
belongs_to :event
I'm having a regular for with checkboxes for my has_many :through relation. My problem is that I can't create a new project and have checkboxes checked. I get a validation error "Users is invalid". This is really weird.
If I create a project with no user checked it works and I can check them when I'm editing the project.
- User.each do |user|
%label.checkbox{title: user.email}
= check_box_tag 'project[user_ids][]', user.id, #project.user_ids.include?(user.id)
= truncate(user.full_name, length: 16)
So in short: I can edit projects but not create new ones. Any ideas?
EDIT:
I have three models, User, Project, Projectship where the latest is the relation between the others. It's when I'm trying to create a project and pass user relations to it my problem occurs. When editing everything works like a charm.
User
id
email
has_many :projectships, dependent: :destroy
has_many :projects, through: :projectships
Project
id
name
has_many :projectships, dependent: :destroy
has_many :users, through: :projectships
Projectship
id
user_id
project_id
belongs_to :project
belongs_to :user
validates :project_id, presence: true
validates :user_id, presence: true
ProjectsController:
# GET /projects/new
def new
#project = Project.new
end
# POST /projects
def create
#project = Project.new(project_params)
if #project.save
redirect_to #project, notice: t('flash.project_created')
else
render :new
end
end
# GET /projects/:id/edit
def edit
#project = Project.includes(:users).find(params[:id])
end
# PUT /projects/:id
def update
#project = Project.find(params[:id])
if #project.update_attributes(project_params)
redirect_to :back, notice: t('flash.project_updated')
else
render :edit
end
end
private
def project_params
params.require(:project).permit(
:client_id, :currency, :description, :end_date, :estimated_hours,
:fixed_price, :hourly_rate, :name, :start_date, :status,
:billable_type, :user_ids
)
end
P.S I'm using 4.0.0.beta D.S
To store users_ids the model needs to be saved and that's way it didn't work. So now I store the ids in the create action and than add them directly after I save the project.