I am using Paperclip (w/ Amazon s3) on Rails 3. I want to delete an existing attachment without replacing it using an update action.
I've only found one example of this here and could not get that to work, it just wouldn't delete and there was nothing in the logs to say why. I wanted to do something like this on the form:
<%- unless #page.new_record? || !#page.image? -%>
<%= f.check_box :image_delete, :label => 'Delete Image' %>
<%- end -%>
(page is the name of the model, image is the attribute name which holds the attachment)
But how do I detect that checkbox and more importantly, how do I delete the image? I appreciate any help!
First off, when you create a check_box in a form_for (which it looks like you are), then the form should by default send :image_delete as "1" if checked and "0" if unchecked. The method declaration looks like this:
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
Which shows that you can assign other values if you want to, but that is of course optional.
Secondly, the call to manually delete an attachment without deleting the model instance to which it is attached to is:
#page.image.destroy #Will remove the attachment and save the model
#page.image.clear #Will queue the attachment to be deleted
And to accomplish your way of deleting the images through a checkbox, perhaps add something like this to your Page model:
class Page < ActiveRecord::Base
has_attached_file :image
before_save :destroy_image?
def image_delete
#image_delete ||= "0"
end
def image_delete=(value)
#image_delete = value
end
private
def destroy_image?
self.image.clear if #image_delete == "1"
end
end
This way, when you create your form and add the :image_delete checkbox, it will load the default value "0" from the User instance. And if that field is checked then the controller will update the image_delete to "1" and when the User is saved, it will check if the image is to be deleted.
has_attached_file :asset
=>
attr_accessor :delete_asset
before_validation { asset.clear if delete_asset == '1' }
No need to destroy asset, Paperclip will do it.
In the form form.check_box(:delete_asset) will suffice.
This is Benoit's answer, but wrapped in a module, and covering the edge case of nested attribute models where the destroy tickbox is the only thing changed on the model.
It will apply to all attachments on the model.
# This needs to be included after all has_attached_file statements in a class
module DeletableAttachment
extend ActiveSupport::Concern
included do
attachment_definitions.keys.each do |name|
attr_accessor :"delete_#{name}"
before_validation { send(name).clear if send("delete_#{name}") == '1' }
define_method :"delete_#{name}=" do |value|
instance_variable_set :"#delete_#{name}", value
send("#{name}_file_name_will_change!")
end
end
end
end
remember to add this to your Page model too:
attr_accessible :image_delete
Modified version of Paul's solution, to support Rails 5 custom attributes. I just wish there were a way to include the module at the top of the file, before has_attached_file definitions.
module Mixins
module PaperclipRemover
extend ActiveSupport::Concern
included do
attachment_definitions.keys.each do |name|
attribute :"remove_#{name}", :boolean
before_validation do
self.send("#{name}=", nil) if send("remove_#{name}?")
end
end
end
end
end
Was able to achieve this with less code, by just implementing a delete_attachment on the model's side.:
class MyModel < ApplicationRecord
has_attached_file :image
def image_delete=(other)
self.image = nil if other == "1" or other == true
end
end
Related
I have a very simple action text model and form
class Course < ApplicationRecord
validates :title, presence: true
has_rich_text :content
end
<%= form_with model: #course do |f| %>
<%= f.text_field :title %>
<%= f.rich_text_area :content %>
<% end %>
It's all working great but since the content field is optional is it possible to create a course model without creating action_text_rich_texts entries that are empty/blank? Even if the user only enters the title without any content it's currently creating them and there's a lot of unnecessary and empty action_text_rich_texts rows in the database
The way I handled this in my application is with a before_save callback that removes the ActionText::RichText database record if the body is blank.
This avoids polluting the controller and works on both create and update actions. The body attribute of the action_text attribute is still accessible even without a corresponding database record, because ActionText will instantiate a new object if the record cannot be found (which allows you to test for blank? in either scenario).
Try this:
class Course < ApplicationRecord
validates :title, presence: true
has_rich_text :content
before_save :clean_up_content
private
def clean_up_content
self.content.destroy if self.content.body.blank?
end
end
I'm not sure about anything built into Actiontext for this, but I would imagine you could handle this at the controller level.
The first thing I would try is to see if not setting anything to content prevents Rails from creating an associated record:
class CourseController
def create
# remove course_params[:content] if it's blank
course_values = course_params[:content].blank? ? course_params.except(:content) : course_params
Course.create(course_values)
...
end
end
Extending Eric Powell's approach:
# app/models/concerns/do_not_save_blank_rich_text.rb
module DoNotSaveBlankRichText
extend ActiveSupport::Concern
included do
before_validation :do_not_save_blank_rich_text
end
private
def do_not_save_blank_rich_text
rich_text_attributes = self.class.reflections.values.select do |reflection|
reflection.options[:class_name] == "ActionText::RichText"
end.map(&:name)
rich_text_attributes.each do |rich_text_attribute|
if self.public_send(rich_text_attribute) && self.public_send(rich_text_attribute).body.blank?
self.public_send(rich_text_attribute).mark_for_destruction
end
end
end
end
first of all, i am using rails 3.1.3 and carrierwave from the master
branch of the github repo.
i use a after_init hook to determine fields based on an attribute of
the page model instance and define attribute accessors for these field
which store the values in a serialized hash (hope it's clear what i am
talking about). here is a stripped down version of what i am doing:
class Page < ActiveRecord::Base
serialize :fields, Hash
after_initialize :set_accessors
def set_accessors
case self.template
when 'standard'
class << self
define_method 'image' do
self.fields['image']
end
define_method 'image=' do |value|
self.fields['image'] = value
end
end
mount_uploader :image, PageImageUploader
end
end
end
end
leaving out the mount_uploader command gives me access to the
attribute as i want. but when i mount the uploader a get an error
message saying 'undefined method new for nil class'
i read in the source that there are the methods read_uploader and
write_uploader in the extensions module.
how do i have to override these to make the mount_uploader command
work with my 'virtual' attribute.
i hope somebody has an idea how i can solve this problem. thanks a lot
for your help.
best regard. dominik.
Same problem but solved in your model you should override read_uploader(column) and write_uploader(column, identifier) instance methods. I also have a problem with #{column}_will_change! and #{column}_changed? for a virtual column so I had to define them too:
class A < ActiveRecord::Base
serialize :meta, Hash
mount_uploader :image, ImageUploader
def image_will_change!
meta_will_change!
#image_changed = true
end
def image_changed?
#image_changed
end
def write_uploader(column, identifier)
self.meta[column.to_s] = identifier
end
def read_uploader(column)
self.meta[column.to_s]
end
end
Now there's also an add-on to carrierwave which provides the exact functionality as described by Antiarchitect:
https://github.com/timsly/carrierwave-serializable
I have two models, Page and PageContent.
class Page < ActiveRecord::Base
has_many :page_contents
end
class PageContent < ActiveRecord::Base
belongs_to :page
end
Page has a :css_design attribute, but I want to be able to edit that attribute from my PageContent form. Any ideas?
I see a lot of accepts_nested_attributes and fields_for advice, but they don't work, because they all seem to be for forms that are either A. Creating entire new instances of a different model from a form (for example, creating tasks from a project form), or B. Updating associated records thru the parent. I want to do the opposite- I want to update the parent's record thru associated records.
Any help is greatly appreciated! Many thanks in advance!
--Mark
UPDATE
I have added the following to my PageContent model:
def css_design
page ? page.css_design : nil
end
def css_design= (val)
if page
page.update_attribute 'css_design', val
else
#css_design = val
end
end
after_create :set_page_css_design_on_create
def set_page_css_design_on_create
self.css_design = #css_design if page && #css_design
end
And I have, in both my create and update actions:
#page_content.update_attributes params[:page_content]
But I'm getting:
NoMethodError (undefined method `css_design=' for #<Page:0x00000003a80338>):
app/models/page_content.rb:13:in `css_design='
app/controllers/page_contents_controller.rb:56:in `new'
app/controllers/page_contents_controller.rb:56:in `create'
When I go to create the page_content for the first time. I copied and pasted this stuff straight from my files, so if you see anything weird, please let me know!
Update your model with the following:
class PageContent < ActiveRecord::Base
belongs_to :page
def css_design
page ? page.css_design : nil
end
def css_design= (val)
if page
page.update_attribute 'css_design', val
else
#css_design = val
end
end
after_create :set_page_css_design_on_create
def set_page_css_design_on_create
self.css_design = #css_design if page && #css_design
end
end
Your form can now display and update the current Page's value, even though it looks like it's only handling PageContent:
<%= form_for #page_content do |f| %>
...
<%= f.text_field :css_design %>
...
<% end %>
In your controller, e.g.:
def update
...
#page_content.update_attributes params[:page_content]
...
end
If it's not too many fields, you can add attributes to the PageContent model via attr_accessor. Then update the parent after_save. Something like this:
class PageContent < ActiveRecord::Base
belongs_to :page
attr_accessor :css_design
after_save :update_parent_css
private
def update_parent_css
self.page.update_attribute(:css_design, self.css_design)
end
end
Then you can put a form field for it just like any other PageContent field. If it's more than one field, use update_attributes. Also, you probably want to make sure the attribute is set (using attribute_present?(attribute)) so it doesn't overwrite it with nil.
(Sorry I don't have time to test it right away. I'll try later. )
In a Ruby on Rails application I am trying to use information from fields that are not associated with the model in validation.
Here is part of the model as an example (the whole model has gotten kinda big):
class Scorecard < ActiveRecord::Base
belongs_to :course
belongs_to :user
validate :attributes_consistency
def attributes_consistency
# Executed for all scorecards. Checks if the user completed the hole attributes correctly
if ( params[:no_fairways] and any_fairways? and !only_nine? ) or ( params[:no_fairways] and !any_h1_to_h9_score_blank and any_h1_to_h9_fairway? and only_nine? ) or ( params[:no_fairways] and !any_h10_to_h18_score_blank and any_h10_to_h18_fairway? and only_nine? )
errors.add_to_base("You inidicated that you missed all the fairways, but you also marked one or more fairways in the scorecard. Either uncheck the fairways mistakenly marked or uncheck the 'No fairways' checkbox.")
end
if ( params[:no_girs] and any_girs? and !only_nine? ) or ( params[:no_girs] and !any_h1_to_h9_score_blank and any_h1_to_h9_gir? and only_nine? ) or ( params[:no_girs] and !any_h10_to_h18_score_blank and any_h10_to_h18_gir? and only_nine? )
errors.add_to_base("You inidicated that you missed all the greens, but you also marked one or more greens in the scorecard. Either uncheck the marked greens on the scorecard or uncheck the 'No GIRs' checkbox.")
end
end # attributes_consistency
def any_h1_to_h9_score_blank?
h1_score.blank? or h2_score.blank? or h3_score.blank? or h4_score.blank? or h5_score.blank? or h6_score.blank? or h7_score.blank? or h8_score.blank? or h9_score.blank?
end
def any_h10_to_h18_score_blank?
h10_score.blank? or h11_score.blank? or h12_score.blank? or h13_score.blank? or h14_score.blank? or h15_score.blank? or h16_score.blank? or h17_score.blank? or h18_score.blank?
end
def any_h1_to_h9_fairway?
h1_fairway? or h2_fairway? or h3_fairway? or h4_fairway? or h5_fairway? or h6_fairway? or h7_fairway? or h8_fairway? or h9_fairway?
end
def any_h10_to_h18_fairway?
h10_fairway? or h11_fairway? or h12_fairway? or h13_fairway? or h14_fairway? or h15_fairway? or h16_fairway? or h17_fairway? or h18_fairway?
end
def any_h1_to_h9_gir?
h1_gir? or h2_gir? or h3_gir? or h4_gir? or h5_gir? or h6_gir? or h7_gir? or h8_gir? or h9_gir?
end
def any_h10_to_h18_gir?
h10_gir? or h11_gir? or h12_gir? or h13_gir? or h14_gir? or h15_gir? or h16_gir? or h17_gir? or h18_gir?
end
So how can I access params from the model?
Don't let params sneak up to the model. There's no point of having a controller in that case. Instead, checkout this episode from Railscasts that talks about virtual attributes that do not go into the database but can still be used for validations.
You don't need a corresponding model attribute for the virtual attributes. Define attributes local to the class such as #no_fairways that hold the state.
class ScoreCard < ActiveRecord::Base
# define attributes and accessors for both fields
attr_accessor :no_fairways, :no_girs
..
end
Now inside you form, you could just write:
<% form_for #scorecard %>
<%= f.check_box :no_fairways %>
<% end %>
Found the solution, thanks for the lingo though, "virtual attribute" helped with the google searchin.
The cleanliest way to accomplish this is to create attributes that are not part of the database but still part of the model. In my case I put this into the model:
attr_accessor :no_fairways
attr_accessor :no_girs
That easy! Now #scorecard.no_fairways and #scorecard.no_girs act just like any other attribute but aren't part of the database.
I have the following models set up:
class Contact < ActiveRecord::Base
belongs_to :band
belongs_to :mode
validates_presence_of :call, :mode
validates_associated :mode, :band
validates_presence_of :band, :if => :no_freq?
validates_presence_of :freq, :if => :no_band?
protected
def no_freq?
freq.nil?
end
def no_band?
band.nil?
end
end
class Band < ActiveRecord::Base
has_many :logs
end
class Mode < ActiveRecord::Base
has_many :logs
end
When I enter a frequency on my new view it allows for no band to be specified if a freq is entered. This creates a problem in my other views though because band is now nil. How do I allow for band not to be specified and just show up as empty on my index and show views, and then in the edit view allow one to be specified at a later point in time.
I have been able to get my index to display a blank by doing:
contact.band && contact.band.name
But I'm not sure if this is a best approach, and I'm unsure of how to apply a similar solution to my other views.
Many thanks from a rails newb!
In my views, I use the following for potentially nil objects in my views:
<%= #contact.band.name unless #contact.band.blank? %>
if your object is an array or hash, you can use the empty? function instead.
<%= unless #contacts.empty? %>
..some code
<% end %>
Hope this helps!
D
A couple years old but still a top Google result for "rails view handle nil" so I'll add my suggestion for use with Rails 3.2.3 and Ruby 1.9.3p0.
In application_helper.rb, add this:
def blank_to_nbsp(value)
value.blank? ? " ".html_safe : value
end
Then to display a value in a view, write something like this:
<%= blank_to_nbsp contact.band %>
Benefits:
"blank" catches both nil values and empty strings (details).
Simply omitting a nil object, or using an empty string, may cause formatting issues. pushes a non-breaking space into the web page and preserves formatting.
With the "if" and "unless" suggestions in other answers, you have to type each object name twice. By using a helper, you only have to type each object name once.
<%= #contact.try(:band).try(:name) %>
This will return nil if band or name do not exist as methods on their respective objects.
You can use Object#andand for this:
<%= #contact.band.andand.name %>
<%= #contact.band if #contact.band %> also works