0

My Rails application uses a custom FormBuilder and a Model not associated to a database table to deal with user comments. If I validate the form and it fails, I render the new view with errors, but the original input to the fields does not show. If I view the parameters, they are showing there.

Parameters:

Parameters: {
  "utf8"=>"✓",
  "authenticity_token"=>"ofJbUSioJ9w+c6EkPy993jtBskYMK/97gp667ACWZDI=",
  "message"=>{
    "name"=>"a",
    "email"=>"a",
    "comment"=>"a"
  },
  "commit"=>"Send"
}

Rendered view:

<form accept-charset="UTF-8" action="/contact_us" class="new_message" id="new_message" method="post">
  <div style="display:none">
    <input name="utf8" type="hidden" value="&#x2713;" />
    <input name="authenticity_token" type="hidden" value="ofJbUSioJ9w+c6EkPy993jtBskYMK/97gp667ACWZDI=" />
  </div>
  <label class="" for="message_name">
    Name
    <input class="" id="message_name" label="Name" name="message[name]" type="text" />
  </label> 
  <label class="error" for="message_email">
    Email
    <input class="error" id="message_email" label="Email" name="message[email]" type="text" />
  </label>
  <small class="error">Is invalid</small>
  <label class="" for="message_comment">
    Comment
    <textarea class="" id="message_comment" label="Comment" name="message[comment]" rows="10">
    </textarea>
  </label> 
  <input class="button round right" name="commit" type="submit" value="Send" />
</form>

Controller:

def create
    @message = Message.new(message_params)
    if @message.valid?
        flash[:success] = "Thank you for your message, we will be in touch shortly."
        redirect_to root_path
    else
        flash.now[:alert] = "Oops! You have not filled out the form correctly. Please try again."
        render 'new'
    end
end

private

    def message_params
        params.require(:message).permit(:name, :email, :comment)
    end

Model:

class Message
    include ActiveModel::Validations
  include ActiveModel::Conversion
  extend ActiveModel::Naming

  attr_accessor :name, :email, :comment

    validates :name, presence: true
    validates :email, presence: true, format: { with: /@/ }
    validates :comment, presence: true

    def initialize(attributes = {})
    attributes.each do |name, value|
      send("#{name}=", value)
    end
  end

  def persisted?
    false
  end
end

FormBuilder:

class FoundationFormBuilder < ActionView::Helpers::FormBuilder
    delegate :content_tag, to: :@template
    delegate :label_tag, to: :@template

    def text_field(method, options = {})
        options[:label] ||= "#{method.to_s}".humanize
        options[:class] ||= ""
        field_errors = object.errors[method].join(', ') unless object.errors[method].blank?
        options[:class] << "error" if field_errors
        options = objectify_options(options)
        options.delete(:object)

        label = lambda do
            label_tag("#{@object_name}[#{method}]", "#{options[:label]}", class: "#{'error' if field_errors}") do
                label = "#{options[:label]}"
                options.delete(:label)
                label << @template.send('text_field_tag', "#{@object_name}[#{method}]", nil, options)

                label.html_safe
            end
        end

        error_messages = lambda do
            content_tag(:small, field_errors.humanize, class: "error") if field_errors
        end

        "#{label.call} #{error_messages.call}".html_safe
    end

    def text_area(method, options = {})
        options[:label] ||= "#{method.to_s}".humanize
        options[:class] ||= ""
        field_errors = object.errors[method].join(', ') unless object.errors[method].blank?
        options[:class] << "error" if field_errors
        options = objectify_options(options)
        options.delete(:object)

        label = lambda do
            label_tag("#{@object_name}[#{method}]", "#{options[:label]}", class: "#{'error' if field_errors}") do
                label = "#{options[:label]}"
                options.delete(:label)
                label << @template.send('text_area_tag', "#{@object_name}[#{method}]", nil, options)

                label.html_safe
            end
        end

        error_messages = lambda do
            content_tag(:small, field_errors.humanize, class: "error") if field_errors
        end

        "#{label.call} #{error_messages.call}".html_safe
    end
end

Why are the fields not picking up the values in the parameters hash? Is this a result of my custom FormBuilder or is there some other issue?

Ashley Bye
  • 1,752
  • 2
  • 23
  • 40
  • Without knowing anything about how you make the form it's impossible to help. – Dave Newton Jul 28 '14 at 13:55
  • Source code updated, should help. – Ashley Bye Jul 28 '14 at 14:01
  • Can you add your view template code as well please? – A Fader Darkly Jul 28 '14 at 14:19
  • As an aside, you've duplicated the error_messages and label lambdas. You could easily parameterise them and turn them into regular private methods, so they could be used across input types. That would reduce the LOC count and eliminate some apparent complexity. – A Fader Darkly Jul 28 '14 at 14:25
  • Yes, I need to refactor that bit still. Re the answer below - perfect, as is passing `method` in place of `nil`. How can I call `super` for the method I want from a method? I can't work out how to pass the calling method as a parameter. – Ashley Bye Jul 28 '14 at 15:12
  • http://stackoverflow.com/questions/1251178/calling-another-method-in-super-class-in-ruby – A Fader Darkly Jul 28 '14 at 15:19
  • I guess I'm looking at the second example and needing to extend Object? – Ashley Bye Jul 28 '14 at 15:36
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/58184/discussion-between-a-fader-darkly-and-churchill614). – A Fader Darkly Jul 28 '14 at 16:06

1 Answers1

1

Rather than

label << @template.send('text_area_tag', "#{@object_name}[#{method}]", nil, options)

you could try:

label << super(name, options)

This should delegate the generation of the actual input field to the parent class, which should pick up on the value that you appear to be missing.

I suspect that the value isn't making it to the text_area_tag method.

Edit to add:

Also, I would accept *args rather than options, to ensure the correct parameters are always passed.

def text_area(label, *args)
  # Add your decoration here
  super(label, *args))
  # More decoration if needed
end

An example is here:

http://www.likeawritingdesk.com/posts/very-custom-form-builders-in-rails

A Fader Darkly
  • 3,516
  • 1
  • 22
  • 28
  • I've just managed to drill down to the `nil` issue. Looking through the rails source code, they call `self` here, but they generate the tag in a different manner. `method` replacing nil works too. – Ashley Bye Jul 28 '14 at 15:13