Search code examples
ruby-on-railssyntax-errorrenderpartial

Rendering partial results in syntax error


Currently working on nested forms with the following railscast http://railscasts.com/episodes/196-nested-model-form-revised

I have an Opportunity model,

class Opportunity < ActiveRecord::Base
    has_many :headings
    accepts_nested_attributes_for :headings
end

a Heading model

class Heading < ActiveRecord::Base
    belongs_to :opportunity
    has_many :subheadings
    accepts_nested_attributes_for :subheadings
end

and a Subheading model

class Subheading < ActiveRecord::Base
    belongs_to :heading
end

Right now I am just working on the view for the new action for the Opportunity model.

<h1>Add an opportunity</h1>
<%= form_for(@opportunity, :html => {:role => 'form'}) do |f| %>
    <%= f.fields_for :headings do |builder| %>
        <%= render 'heading_fields', f: builder %>
    <% end %>
    <%= link_to_add_fields "Add heading", f, :headings %>
    <%= f.submit %>
<% end %>

And the _heading_fields.html.erb partial:

<fieldset>
    <%= f.label :title, "Heading" %>
    <%= f.text_field :title %>
    <%= f.hidden_field :_destroy %>
    <%= link_to "Remove section", '#', class: "remove_fields" %>
    <%= link_to_add_fields "Add section", f, :subheadings %>
</fieldset>

And finally the link_to_add_fields method in the application helper:

def link_to_add_fields(name, f, association)
    new_object = f.object.send(association).klass.new
    id = new_object.object_id
    fields = f.fields_for(association, new_object, child_index: id) do |builder|
        render(association.to_s.singularize + "_fields", f: builder)
    end
    link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end

Now I am receiving the following error for the render call in the link_to_add_fields function:

syntax error, unexpected tIDENTIFIER, expecting keyword_end

It seems to have something to do with the underscore in the name of the partial being rendered in the link_to_add_fields method as it goes away when the underscore is not present.

Greatly appreciate any help at solving this headache!


Solution

  • Debug

    As mentioned in the comments, you're basically going to have an issue with the closing of your loops - Rails is expecting an end call, but you've set an identifier of sorts instead.

    Having looked through your code, I cannot see where the issue might be, so I'll give you some details on how I'd debug it:

    I would remove any elements which I would presume would be causing the issue, until I got it to work

    For example, I would begin by replacing the def link_to_add_fields(name, f, association) with a piece of demo content:

    def link_to_add_fields(name, f, association)
        "test"
    end
    

    If this outputs the correct string, it means the error is inside this method (which I'm guessing anyway). If this was the case, I would then try this:

    def link_to_add_fields(name, f, association)
        new_object = f.object.send(association).klass.new
        id = new_object.object_id
        fields = "test"
        link_to(name, '#', class: "add_fields", data: {id: id, fields: fields})
    end
    

    If this works, it means the problem is likely going to be with the f.fields_for loop which you're using. Although this is just my guess, it's basically how I'd debug - sequentially remove things until it actually works


    Fields

    If you're looking to add fields dynamically to your form, you may wish to use the cocoon gem. The Railscast you're using is firstly a little out of date, and only allows you to add single fields to your form

    The "right" way to do it is to use an ajax method, which I detail here

    There's a very good site I found which showed you how to do it, but the site has since gone offline. Anyway, I'll explain it for you here:

    #config/routes.rb
    resources :controller do
       collection do
          get :add_field
       end
    end
    

    This gives you a route to send ajax requests to:

    Controller

    #app/views/controller/form.html.erb
    <%= form_for @model do |f| %>
       <%= render partial: "fields", locals: { builder: f } %>
       <%= link_to "Add Field", controller_add_field_path, id: "add_field", remote: true %>
       <%= f.submit "test" %>
    <% end %>
    
    #app/views/controllers/your_controller.rb
    Class YourController < ActiveRecord::Base
       def add_field
          @model = Model.new
          @model.association.build
          render "add_field", layout: false
       end
    end
    

    Views

    Now you need to render the add_field view, with the fields_for partial

    #app/views/controller/_form.html.erb
    <%= form_for @model do |f| %>
        <%= render partial: "fields", locals: { builder: f } %>
    <% end %>
    
    
    #app/views/controller/_fields.html.erb
    <%= f.fields_for :association, child_index: Time.now.to_i do |a| %>
        <%= a.text_feld :your_fields %>
    <% end %>
    

    JQuery

    All you need to do then is create a way to fetch the new fields:

    #app/assets/javascripts/application.js.coffee
    $ ->
      $(document).on "ajax:success", "#add_field", (data) ->
           el_to_add = $(data).html()
           $('#new_form').append(el_to_add)