0

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.

Iván Cortés
  • 581
  • 1
  • 9
  • 22

1 Answers1

1

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 
max
  • 96,212
  • 14
  • 104
  • 165