2
votes

In Ruby on Rails, how can I render a partial from another controller's namespace and pass an object as a local variable?

For example, in a view template in the views/posts directory (managed by the PostsController), I need render a partial from the views/polls directory.

I've tried the following:

.item_index  
  = f.fields_for :poll_items do |poll|
    = render :partial => 'polls/poll_item_fields', f: poll
  .links
    = link_to_add_association 'Add more', f, :poll_items, render_options: {class: 'links'}

and I've got error:

Showing /home/ubuntu/workspace/app/views/polls/_poll_item_fields.html.haml where line #3 raised:
undefined local variable or method `f' for #<#<Class:0x0000000c01ec38>:0x0000000f56b170>**

EDIT

All complete views:

posts/new.html.haml

%h1= title "New post"
= simple_form_for [@group, @post] do |f|
  = f.error_messages header_message: nil
  = render 'form', f: f

  .form-actions
    %p= attach_links
    = f.button :submit, 'Опубликовать пост', class: 'btn-bg'
    - if paid?
      = f.button :submit, 'сохранить черновик', class: 'btn-inverse', name: 'draft'
    = button_tag 'Добавить опрос', type: 'button', id: 'poll-btn', class: 'btn btn-bg'
    %p
      Вернуться в группу:
      = link_to @group, @group

posts/_form.html.haml

- cat = Post::CATEGORIES.map { |c| [t(c, scope: :post_categories), c] }
= f.input :category, collection: cat
= f.input :subject, input_html: { class: 'input-block-level' }
= f.input :body, input_html: { rows: 5, class: 'ctrlenter input-block-level expanding' }
= link_to "#", class: 'smiley', role: 'add_smiley', tabidex: 4 do
  %i.icon.icon-smile
  смайлы
= smiles_helper '#post_body'

.form-horizontal
  = f.input :tag_list, input_html: { class: 'input-block-level' }
  - if (current_user.paid? && current_user.moderated_group_ids.include?(@group.id)) || moderator?
    = f.input :comments_disabled, inline_label: true, label: false
.attachments
  = f.simple_fields_for :attachments do |af|
    = render "attachments/#{af.object.asset_type.underscore}", :f => af
.poll{style: "display: none;"}
  %h1 "Новый опрос"
  = f.simple_fields_for :poll do |poll|
    = render "polls/poll_fields", f: poll

polls/_poll_fields.html.haml

= f.error_messages header_message: nil
= f.input :question, input_html: { class: 'input-block-level' }
= f.input :results_hidden, as: :boolean, inline_label: 'Скрыть результаты до окончания опроса', label: false
= f.input :from_date, as: :datetime, input_html: { class: 'poll_date' }
= f.input :to_date, as: :datetime, input_html: { class: 'poll_date' }
%h3#poll-items Варианты ответа (не больше пяти)
.item_index  
  = f.fields_for :poll_items do |poll_item|
    = render partial: 'polls/poll_item_fields', locals: {f: poll_item}
  .links
    = link_to_add_association 'Добавить еще вариант', f, :poll_items, render_options: {class: 'links'}

polls/poll_items_fields

.poll_row
  .poll_item
    = f.input :answer, input_html: { class: 'ctrlenter expanding' }, label: false, placeholder: 'Введите вариант ответа'
    = link_to_remove_association "удалить", f, { wrapper_class: 'poll_item' }

When use = render partial: 'polls/poll_item_fields', locals: { f: poll }, display error: howing /home/ubuntu/workspace/app/views/polls/_poll_fields.html.haml where line #11 raised: howing /home/ubuntu/workspace/app/views/polls/_poll_fields.html.haml where line #11 raised: Missing partial posts/poll_item_fields

Line 11 is = link_to_add_association 'Add more', f, :poll_items, render_options: {class: 'links'}

2

2 Answers

0
votes

When passing variables to a partial via render, you need to wrap them in a locals key:

= render partial: 'polls/poll_item_fields', locals: { f: poll }

For more detail, see the Layouts and Rendering guide.

0
votes

There are two forms of the render method, which let you pass locals in different ways.

The shortcut version will accept a Hash:

= render 'polls/poll_item_fields', f: poll

You, however, are using the long version. If you use the partial key, then you also need to use the locals key:

= render partial: 'polls/poll_item_fields', locals: { f: poll }

That's not just a matter of style, but it's because of how ruby treats method arguments.

In the shortcut version, the first argument is a String and the second a Hash. Rails will know that the Hash will contain locals.
In the second version, however, the first (and only) argument is a Hash. Without the :locals key, and thanks to Ruby's syntax that allows to omit parenthesis and curly braces, it would be translated to this:

render({ partial: 'polls/poll_item_fields', f: poll })

Which will leave Rails a bit confused as it won't be able to isolate the locals from the other keyed options passed to the method.

In the end it's still a matter of style: the methods signatures have been designed like that. I hope that my explanation makes things a bit clearer!