Частичный рендеринг для модели с вложенными атрибутами в другой модели
-
21-09-2019 - |
Вопрос
У меня есть приложение rails, которое моделирует дом. house
содержит rooms
и комнаты имеют вложенные атрибуты для light
и small_appliance
.У меня есть calculator
контроллер, с помощью которого конечные пользователи будут получать доступ к приложению.
Моя проблема в том, что я не могу получить частичное для добавления rooms
для правильного рендеринга и отправки с calculator
.Начальная страница позволяет пользователю ввести 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
.Я не понимаю, почему это не передается после отправки формы.
Вот код:
приложение/модели/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
приложение/модели/house.rb
class House < ActiveRecord::Base
has_many :rooms
# validation code not included
def add_room(room)
rooms << room
end
end
приложение / контроллеры/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
приложение / просмотры/ калькулятор/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 %>
приложение / виды/номера/_room_
форма.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.рб
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
Спасибо,
Райан
Решение
Из любопытства, почему бы дому не принять вложенные атрибуты для комнат.Это упростило бы код вашего контроллера, поскольку добавить много комнат, источников света и мелкой бытовой техники так же просто, как просто выполнить @house.update_attributes(параметры[:house]).Однако это не тот ответ, который помогает, так как у вас все равно остались бы ваши текущие проблемы, если бы вы внесли изменения.
Ваша первая ошибка исходит из первой строки приложение / просмотры / калькулятор/_room_form.html.erb
<% form_for :room, :url => { :action => :add_room, :id => @house } do |form| %>
Вы не указываете form_for для объекта, поэтому метод new_child_fields, вызываемый add_child _link пытается вызвать reflect_on_association для класса Nil.
Решение состоит в том, чтобы изменить строку на
<% form_for :room, @house.rooms.build, :url => { :action => :add_room } do |form| %>
Это позволяет упростить работу вашего контроллера, поскольку ему уже передается комната, связанная с домом.
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 в эту папку, либо изменить ваши вспомогательные методы, чтобы принять частичный вариант.