【问题标题】:Render partial for model with nested attributes in another model在另一个模型中为具有嵌套属性的模型渲染部分
【发布时间】:2009-10-15 04:41:25
【问题描述】:

我有一个为房屋建模的 Rails 应用程序。 house 包含 rooms 并且房间具有 lightsmall_appliance 的嵌套属性。我有一个calculator 控制器,这是最终用户访问应用程序的方式。

我的问题是我无法从calculator 中获得添加rooms 的部分以正确呈现和提交。初始页面让用户输入house 信息,单击提交时使用save_house 保存。这也将用户重定向到add_rooms 页面,他们可以在该页面中为房屋添加房间。

add_rooms 显示正确,但是当我点击提交时,出现此错误:

RuntimeError in Calculator#add_room

Showing app/views/calculator/add_rooms.html.erb where line #2 raised:

Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id

Extracted source (around line #2):

1: <div id="addRooms">
2:   <p>House id is <%= @house.id %></p>
3:   
4:   <h3>Your rooms:</h3>
5:   <% if @house.rooms %>

RAILS_ROOT: C:/Users/ryan/Downloads/react
Application Trace | Framework Trace | Full Trace

C:/Users/ryan/Downloads/react/app/views/calculator/add_rooms.html.erb:2:in `_run_erb_app47views47calculator47add_rooms46html46erb'
C:/Users/ryan/Downloads/react/app/controllers/calculator_controller.rb:36:in `add_room'
C:/Users/ryan/Downloads/react/app/controllers/calculator_controller.rb:33:in `add_room'

这对我来说很奇怪,因为当add_rooms 第一次渲染时,它会显示house_id。不明白为什么表单提交后没有通过。

代码如下:

app/models/room.rb

class Room < ActiveRecord::Base
  # schema { name:string, house_id:integer }
  belongs_to :house
  has_many :lights, :dependent => :destroy
  has_many :small_appliances, :dependent => :destroy
  validates_presence_of :name
  accepts_nested_attributes_for :lights, :reject_if => lambda { |a| a.values.all?(&:blank?) }, :allow_destroy => true
  accepts_nested_attributes_for :small_appliances, :reject_if => lambda { |a| a.values.all?(&:blank?) }, :allow_destroy => true         
end

app/models/house.rb

class House < ActiveRecord::Base
  has_many :rooms

  # validation code not included

  def add_room(room)
    rooms << room
  end

end

app/controllers/calculator_controller.rb

class CalculatorController < ApplicationController
  def index
  end

  def save_house
    @house = House.new(params[:house])
    respond_to do |format|
      if @house.save
        format.html { render :action => 'add_rooms', :id => @house }
        format.xml { render :xml => @house, :status => :created, :location => @house }
      else
        format.html { render :action => 'index' }
        format.xml  { render :xml => @house.errors, :status => :unprocessable_entity }
      end
    end
  end

  def add_rooms
    @house = House.find(params[:id])
    @rooms = Room.find_by_house_id(@house.id)

  rescue ActiveRecord::RecordNotFound
    logger.error("Attempt to access invalid house #{params[:id]}")
    flash[:notice] = "You must create a house before adding rooms"
    redirect_to :action => 'index'
  end

  def add_room
    @room = Room.new(params[:room])
    @house = @room.house

    respond_to do |format|
      if @room.save
        flash[:notice] = "Room \"#...@room.name}\" was successfully added."
        format.html { render :action => 'add_rooms' }
        format.xml { render :xml => @room, :status => :created, :location => @room }
      else
        format.html { render :action => 'add_rooms' }
        format.xml  { render :xml => @room.errors, :status => :unprocessable_entity }
      end
    end
  rescue ActiveRecord::RecordNotFound
    logger.error("Attempt to access invalid house #{params[:id]}")
    flash[:notice] = "You must create a house before adding a room"
    redirect_to :action => 'index'
  end

  def report
    flash[:notice] = nil
    @house = House.find(params[:id])
    @rooms = Room.find_by_house_id(@house.id)
  rescue ActiveRecord::RecordNotFound
    logger.error("Attempt to access invalid house #{params[:id]}")
    flash[:notice] = "You must create a house before generating a report"
    redirect_to :action => 'index'
  end

end

app/views/calculator/add_rooms.html.erb

<div id="addRooms">
  <p>House id is <%= @house.id %></p>

  <h3>Your rooms:</h3>
  <% if @house.rooms %>
  <ul>
    <% for room in @house.rooms %>
    <li>
      <%= h room.name %> has <%= h room.number_of_bulbs %> 
      <%= h room.wattage_of_bulbs %> watt bulbs, in use for 
      <%= h room.usage_hours %> hours per day.
    </li> 
    <% end %>
  </ul>
  <% else %>
  <p>You have not added any rooms yet</p>
  <% end %>  

  <%= render :partial => 'rooms/room_form' %>

  <br />
</div>

<%= button_to "Continue to report", :action => "report", :id => @house %>

app/views/rooms/_room_form.html.erb

<% form_for :room, @house.rooms.build, :url => { :action => :add_room } do |form| %>
  <%= form.error_messages %>
  <p>
    <%= form.label :name %><br />
    <%= form.text_field :name %>
  </p>

  <h3>Lights</h3>
  <% form.object.lights.build if form.object.lights.empty? %>
  <% form.fields_for :lights do |light_form| %>
    <%= render :partial => "light", :locals => { :form => light_form } %>
  <% end %>
  <p class="addLink"><%= add_child_link "[+] Add new light", form, :lights %></p>

  <h3>Small Appliances</h3>
  <% form.object.small_appliances.build if form.object.small_appliances.empty? %>
  <% form.fields_for :small_appliances do |sm_appl_form| %>
    <%= render :partial => "small_appliance", :locals => { :form => sm_appl_form } %>
  <% end %>
  <p class="addLink"><%= add_child_link "[+] Add new small appliance", form, :small_appliances %></p>

  <p><%= form.submit "Submit" %></p>
<% end %>

application_helper.rb

module ApplicationHelper
  def remove_child_link(name, form)
    form.hidden_field(:_delete) + link_to_function(name, "remove_fields(this)")
  end

  def add_child_link(name, form, method)
    fields = new_child_fields(form, method)
    link_to_function(name, h("insert_fields(this, \"#{method}\", \"#{escape_javascript(fields)}\")"))
  end

  def new_child_fields(form_builder, method, options = {})
    options[:object] ||= form_builder.object.class.reflect_on_association(method).klass.new
    options[:partial] ||= method.to_s.singularize
    options[:form_builder_local] ||= :form
    form_builder.fields_for(method, options[:object], :child_index => "new_#{method}") do |form|
      render(:partial => options[:partial], :locals => { options[:form_builder_local] => form })
    end
  end
end

谢谢,
瑞恩

【问题讨论】:

    标签: ruby-on-rails partial renderpartial


    【解决方案1】:

    出于好奇,为什么不让 house 接受房间的嵌套属性。这将使您的控制器代码更简单,因为添加许多房间、灯和小家电就像 @house.update_attributes(params[:house]) 一样简单。但是,这不是一个有帮助的答案,因为如果您进行了更改,您仍然会遇到当前的问题。

    您的第一个错误来自 app/views/calculator/_room_form.html.erb

    的第一行

    &lt;% form_for :room, :url =&gt; { :action =&gt; :add_room, :id =&gt; @house } do |form| %&gt;

    你没有给 form_for 一个对象,所以 add_child 调用了 new_child_fields 方法 _link 正在尝试在 Nil 类上调用 reflect_on_association。

    解决办法是换行

    &lt;% form_for :room, @house.rooms.build, :url =&gt; { :action =&gt; :add_room } do |form| %&gt;

    这可以让您简化控制器,因为与房屋关联的房间已经被传递给它。

    def add_room
        @room = Room.new(params[:room])
        @house = @room.house
        respond_to do |format|
          if @room.save
            flash[:notice] = "Room \"#...@room.name}\" was successfully added."
            format.html { render :action => 'add_rooms' }
            format.xml { render :xml => @room, :status => :created, :location => @room }
          else
            format.html { render :action => 'add_rooms' }
            format.xml  { render :xml => @room.errors, :status => :unprocessable_entity }
          end
        end
      rescue ActiveRecord::RecordNotFound
        logger.error("Attempt to access invalid house #{params[:id]}")
        flash[:notice] = "You must create a house before adding a room"
        redirect_to :action => 'index'
      end
    

    我相信您的第二个错误是同样的问题。但是,因为您调用的是 has_many 访问器而不是生成 nil,所以您传递的是一个空数组,这解释了错误消息的差异。同样,解决方案是在渲染之前构建一个轻巧的设备(如果还没有的话)。

      <h3>Lights</h3>
      <% form.object.lights.build if form.object.lights.empty? %>
      <% form.fields_for :lights do |light_form| %>
        <%= render :partial => 'rooms/light', :locals => { :form => light_form } %>
      <% end %>
      <p class="addLink"><%= add_child_link "[+] Add new light", form, :lights %></p>
    
      <h3>Small Appliances</h3>
      <% form.object.small_appliances.build if form.object.small_appliances.empty? %>
      <% form.fields_for :small_appliances do |sm_appl_form| %>
        <%= render :partial => 'rooms/small_appliance', :locals => { :form => sm_appl_form } %>
      <% end %>
    

    您的新错误来自此:

     def new_child_fields(form_builder, method, options = {})
        options[:object] ||= form_builder.object.class.reflect_on_association(method).klass.new
    
        # specifically this line. 
        options[:partial] ||= method.to_s.singularize
    
        options[:form_builder_local] ||= :form
        form_builder.fields_for(method, options[:object], :child_index => "new_#{method}") do |form|
          render(:partial => options[:partial], :locals => { options[:form_builder_local] => form })
        end
      end
    

    new_child_fields 假设 _light 部分位于 app/views/calculators 文件夹中

    解决方案是将 light 和 small_appliances 部分移动到此文件夹或修改您的辅助方法以接受部分选项。

    【讨论】:

    • 感谢您的回复。我曾想过让room 成为house 的嵌套属性,但没有因为我希望在单独的页面上创建它们。我进行了这些更改,但现在我收到了一个新错误,我已将其添加到原始帖子中。
    • 解决了您的新错误。您收到的错误消息是您出现问题的最佳指示。如果不是语法错误,90% 的情况是 Rails 做出了不正确的假设。使用错误消息找出与您期望的工作方式不符的地方。
    • 谢谢。我移动了部分,现在一切都正确显示了。但是,一旦我单击 add_rooms 页面的提交,它就会给出一个新错误。 Showing app/views/calculator/add_rooms.html.erb where line #2 raised: Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id在线&lt;p&gt;House id is &lt;%= @house.id %&gt;&lt;/p&gt;。我不明白为什么它将 nil 作为house id 传递,因为它在第一次加载add_rooms 时显示house id。
    • 我已将原帖更新为当前问题和当前代码。
    • 解决了。感谢所有的帮助。
    猜你喜欢
    • 2010-12-06
    • 1970-01-01
    • 1970-01-01
    • 2019-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    相关资源
    最近更新 更多