The project which I am working in, is developed on Rails using haml markup to views. There is a view with a simple form like this:
= simple_form_for @message, url: [:admin, @request, @message], html: { class: 'form vertical-form} do |f|
= f.input :text, as: :text, input_html: { class: 'form-control', rows: 5 }
%br
= f.input :link_url, input_html: { class: 'form-control'}
%br
- if @message.has_picture_image?
= f.label :image
=link_to @message.picture_image, target: "_blank" do
= image_tag @message.picture_image(:thumb)
= f.file_field :image, class:'imagen-button'
= f.input_field :remove_picture, as: :boolean, inline_label: 'Remove'
%br
.form-actions
= f.submit(t('accept'), class: 'btn btn-large btn-primary')
= link_to(t('cancel'), [:admin, @message.request, @message], class: 'btn btn-large btn-danger')
and in Message model there is the bellow method:
def remove_picture
self.picture.destroy
end
The input_field
is used to check if I want to remove the message image if it exists. I understood that input_filed
gives me the option to check it so that when I click on accept
button, it call the method remove_picture in the Message model. But, before the browser deploys the form, it rise the next error:
undefined method `to_i' for #<Picture:0x007f7675162b58>
Extracted source (around line #39):
37: = image_tag @message.picture_image(:thumb)
38: = f.file_field :image, class:'imagen-button'
39: = f.input_field :remove_picture, as: :boolean, inline_label: 'Remove'
40: %br
41: .form-actions
42: = f.submit(t('accept'), class: 'btn btn-large btn-primary')
and if I reload the page, this time the form is deployed. I guess this is because in the first time, as the picture exists then immediatly the remove_picture
is called and the picture removed, and when I reload the form, as the picture already does not exist, the form is shown.
Obviously I am undestanding wrongly the input_field usage.
SimpleForms input_field
is a helper which binds an input to a model attribute. It does not create a box which calls your method when the box is ticked! But rather it will call your remove_picture
method when it rendering the form.
In some cases like checkboxes you will want to bind inputs to attributes that are not saved in the database. We call these virtual attributes. They are just like any old Ruby attributes:
class Message < ActiveRecord::Base
attr_accessor :remove_picture
# since this method is destructive it should have a bang (!)
def remove_picture!
self.picture.destroy
end
end
You could use it like this:
class MessagesController < ApplicationController
def update
@message.update(update_params)
@message.remove_picture! if message.remove_picture
# ...
end
def update_params
params.require(:message).allow(:remove_picture)
end
end
But there is a better way:
class Message < ActiveRecord::Base
has_one :picture_image
accepts_nested_attributes_for :picture_image, allow_destroy: true
end
accepts_nested_attributes_for
lets us create an image with picture_image_attributes
and destroy an image with:
@picture.update(picture_image_attributes: { _destroy: true })
This is how we would set up the form:
= simple_form_for @message, url: [:admin, @request, @message], html: { class: 'form vertical-form} do |f|
= f.input :text, as: :text, input_html: { class: 'form-control', rows: 5 }
%br
= f.input :link_url, input_html: { class: 'form-control'}
%br
- if @message.has_picture_image?
f.simple_fields_for :picture_image do |pif|
= pif.label :image
= link_to @message.picture_image, target: "_blank" do
= image_tag @message.picture_image(:thumb)
= pif.file_field :image, class:'imagen-button'
= pif.input_field :_destroy, as: :boolean, inline_label: 'Remove'
%br
.form-actions
= f.submit(t('accept'), class: 'btn btn-large btn-primary')
= link_to(t('cancel'), [:admin, @message.request, @message], class: 'btn btn-large btn-danger')
And your strong parameters whitelist:
def update_attributes
params.require(:message).allow(
:text,
:link_url,
picture_image_attributes: [:image, :_destroy]
)
end