Rendering parziale per modello con attributi nidificati in un altro modello
-
21-09-2019 - |
Domanda
Ho un'applicazione rotaie che modelli una casa. house
contiene rooms
e le camere hanno nidificato attributi per light
e small_appliance
. Ho un regolatore di calculator
, che è come gli utenti finali potranno accedere all'applicazione.
Il mio problema è che non riesco a ottenere il parziale per l'aggiunta di rooms
per rendere e inviare correttamente da calculator
. La pagina iniziale consente all'utente di inserire informazioni house
, che viene salvato utilizzando save_house
quando si fa clic su submit. Questo reindirizza anche l'utente alla pagina add_rooms
, dove si possono aggiungere sale alla casa.
display add_rooms
correttamente, ma quando ho fare clic su Invia, ottengo questo errore:
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'
Questo è strano per me, perché quando add_rooms
rende prima, mostra il house_id
. Non capisco il motivo per cui non è passato dopo che il modulo viene inviato.
Ecco il codice:
app / modelli / 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 / modelli / 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 / calcolatore / 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 / camere / _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
Grazie,
Ryan
Soluzione
Per curiosità perché non hanno casa accetta attributi nidificati per le stanze. Ciò renderebbe il codice di controllo più semplice, come l'aggiunta di molte stanze, luci e piccoli elettrodomestici è così semplice come solo facendo @ house.update_attributes (params [: casa]). Tuttavia questo non è una risposta che aiuta, come si avrebbe ancora i vostri problemi attuali se hai fatto la modifica.
Il primo errore viene dalla prima linea di app / views / calcolatore / _room_form.html.erb
<% form_for :room, :url => { :action => :add_room, :id => @house } do |form| %>
Non stai dando form_for un oggetto in modo che il metodo di new_child_fields chiamato da add_child _link sta tentando di chiamare reflect_on_association sulla classe Nil.
La soluzione è cambiare la linea
<% form_for :room, @house.rooms.build, :url => { :action => :add_room } do |form| %>
Questo consente di semplificare il controller, in quanto una stanza associato ad una casa è già stata passata ad esso.
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
Credo che il vostro secondo errore è lo stesso problema. Tuttavia, poiché si sta chiamando una funzione di accesso has_many invece di ottenere un nullo che viene prodotto, si sta passando un array vuoto, il che spiega la differenza nei messaggi di errore. Anche in questo caso la soluzione è quella di costruire un leggero e piccolo elettrodomestico prima del rendering se non ne esistono ancora.
<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 %>
Il nuovo errore deriva da questo:
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 presuppone che il _light parziale è nella cartella app / views / calcolatori
La soluzione è quella di spostare sia la luce e small_appliances parziali a questa cartella o modificare i metodi di supporto ad accettare una soluzione parziale.