I have a form which allows a user to invite multiple people via adding emails in a comma separated list. In my "Participant" model, I have a call to validate the uniqueness of the email entered (scoped by "project_id"). In the model validation, it gives a place to explain the error (message), but I can't get that error to show up on my form if the validation fails.
If a user enters the email of a person that has already been added, how can I get the errors message to render?
participant.rb
class Participant < ActiveRecord::Base
validates :email, uniqueness: {case_sensitive: false, scope: :project_id, message: "Looks like you\'ve already added this person."}
end
participant_controller.rb
def new_participant
#new_participants = Participant.new
#participants = Participant.where(project_id: #project.id).includes(:user)
#template = Template.find(#project.template_id)
#address = Address.where(project_id: #project.id).first
#food = ProjectRestriction.where(project_id: #project.id)
end
def add_participant
#added_by = User.find(current_user.id)
#new_participants = params[:new_participants][:email].split(/,\s*/)
#new_participants.each do |t|
newpart = Participant.new(:email => t, :project_id => #project.id, :level => 4,
:participant_cat_id => 2, :last_updated_by => current_user.id, :added_by => current_user.id, :status => 'unseen')
respond_to do |format|
if newpart.save
ProjectMailer.notify_recipient(newpart, #project, #added_by, #participant_invite ).deliver_later
self.response_body = nil
redirect_to participants_path(p: #project.id, w: 'recipient')
else
format.html { redirect_to new_participant_path(p: #project.id)}
format.json { render json: #new_participants.errors, status: :unprocessable_entity }
end
end
end
end
form
<%= form_for :new_participants, url: add_participant_path( :p => #project.id), html: { :multipart => true, :class=> "form-horizontal", id: "basicForm" } do |f| %>
<% if #new_participants.errors.any? %>
<h2>OOPS!</h2>
<ul>
<% #new_participants.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul></div>
<% end %>
<div class="form-group ">
<label class="form-label dk-aqua"> Email: <span class="asterisk">*</span></label>
<%= f.text_field :email, :autofocus => true, :required => true, :maxlength => 55, :placeholder => 'Email(s)', :class => 'form-control' %>
</div>
<%= f.submit 'INVITE', :class => 'btn btn-aqua btn-lg btn-block',
:style => 'margin-bottom:-5px' %>
<% end %>
Your main issues are:
you are creating a respond block for each email in the request. 1 request = 1 response.
The objects in stored in memory in #new_participants are not actually saved.
In your views your are treating #new_participants as if it where a single resource.
Pay attention to pluralization when naming routes, variables and actions.
def add_participants
#added_by = User.find(current_user.id)
#new_participants = params[:new_participants][:email].split(/,\s*/)
#new_participants.map do |email|
newpart = Participant.new(
:email => email,
:project_id => #project.id,
:level => 4,
:participant_cat_id => 2,
:last_updated_by => current_user.id,
:added_by => current_user.id,
:status => 'unseen'
)
if newpart.save
ProjectMailer.notify_recipient(newpart, #project, #added_by, #participant_invite ).deliver_later
end
new_part
end
#invalid = #new_participants.reject(&:valid?)
if #invalid.any?
respond_to do |format|
format.html { redirect_to new_participant_path(p: #project.id)}
format.json { render json: #new_participants.map(&:errors), status: :unprocessable_entity }
end
else
respond_to do |format|
redirect_to participants_path(p: #project.id, w: 'recipient')
end
end
end
<ul>
<% #new_participants.each |p| %>
<% p.errors.messages.each do |msg| %>
<li><%= msg %></li>
<% end if p.errors.any? %>
<% end %>
</ul>
Related
I have a rails app with events and event_registrants, someone can essentially register for an event.
Here's my event model:
class Event < ActiveRecord::Base
has_many :event_registrants, :dependent => :destroy
accepts_nested_attributes_for :event_registrants, :reject_if => lambda { |a| a[:first_name].blank? }, :allow_destroy => true
Here's my event_registrant model:
class EventRegistrant < ActiveRecord::Base
belongs_to :event
attr_accessible :comment, :company, :email, :first_name, :last_name, :phone, :event_id
validates_presence_of :company, :email, :first_name, :last_name, :phone
end
On the show template, I'd like to include a form to allow someone to register directly from there.
<%= form_for #event do |f| %>
<%= f.fields_for :event_registrants do |builder| %>
<%= render "registration_form", :f => builder %>
<% end %>
<%= f.submit 'Send it', :class => "blue-btn", :id => "registrant_submit" %>
<% end %>
Partial:
<ul class="form" id="register_form">
<li><%= f.text_field :first_name, :placeholder => "First name *" %> <%= f.text_field :last_name, :placeholder => "Last name *" %></li>
<li><%= f.text_field :email, :placeholder => "Email address *", :class => 'wide' %></li>
<li><%= f.text_field :company, :placeholder => "Company name *", :class => 'wide' %></li>
<li><%= f.text_field :phone, :placeholder => "Phone number *", :maxlength => '14' %></li>
<li><%= f.text_area :comment, :placeholder => "Additional info", :class => 'wide', :rows => 4, :cols => 68 %></li>
</ul>
Finally my events controller'
def show
#event = Event.where(:event_hosting_type => "eureka_event").find_by_permalink(params[:id])
1.times {#event.event_registrants.build}
respond_to do |format|
format.html # show.html.erb
format.json { render json: #event }
end
end
def update
#event = Event.find_by_permalink(params[:id])
respond_to do |format|
if #event.update_attributes(params[:event])
format.html { redirect_to #event, notice: "#{#event.title} updated." }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #event.errors, status: :unprocessable_entity }
end
end
end
def create
#event = Event.new(params[:event])
#event_registrant = #event.build_event_registrant
respond_to do |format|
if #event.save
format.html { redirect_to #event, notice: 'New event created.'}
format.json { render json: #event, status: :created, location: #event }
else
format.html { render action: "new" }
format.json { render json: #event.errors, status: :unprocessable_entity }
end
end
end
The issue I'm having is that the current form will display but will only update the current record, having 1.times {#event.event_registrants.build} will add a new, empty form but the previous entry will be there. I'd like someone to submit their info, have the form reset and allow someone else to submit new info.
You can update your code to say
1.times {#event.event_registrants.build} if #event.event_registrants.empty?
Your form will properly show the value of event_registrants if it exists, or build it if not there.
However, if you're looking to dynamically add or remove form fields, you may find the cocoon gem an easy way to handle the javascript/ruby involved with that.
In my app a user can upload a picture(with paperclip) or a link from youtube. The link is able to be submitted but now when I try to upload a picture I get a validation error saying the link is invalid. I have been going after this for like an hour now and I'm just going in circles at this point, any help would be awesome.
post.rb
class Post< ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
YT_LINK_FORMAT = /\A.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*\z/i
validates :link, allow_nil: true, format: YT_LINK_FORMAT
before_create :set_content_type
has_attached_file :pic, :styles => { :thumb => "600x600#", :medium => "300x300#", :small => "160x160#"}
validates_attachment_content_type :pic, :content_type => ["image/jpg", "image/jpeg", "image/png" ]
def set_content_type
self.is_pic = !self.pic_file_name.nil?
self.is_link = !self.link.nil?
# parse link
if self.is_link
uid = link.match(YT_LINK_FORMAT)
self.uid = uid[2] if uid && uid[2]
if self.uid.to_s.length != 11
self.errors.add(:link, 'is invalid.')
false
elsif Post.where(uid: self.uid).any?
self.errors.add(:link, 'is not unique.')
false
else
get_additional_info
end
end
end
Post_controller.rb
def create
#post= Post.create(post_params) do |post|
post.is_pic = true if params[:post][:pic]
post.is_link = true if params[:post][:link]
end
#post.user = current_user
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'post was successfully created.' }
format.json { render json: #post, status: :created, location: #post}
else
format.html { render action: "new" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
The form
<%= form_for #post, :html => { :multipart => true } do |f| %>
<% if #post.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#post.errors.count, "error") %> prohibited this post from being saved:</h2>
<ul>
<% #post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="uploads">
<h3>Upload Photo</h3>
<div class="visible-xs">
<%= f.file_field :pic %>
<h3>YouTube Link</h3>
<div class="visible-xs">
<%= f.label :link %>
<%= f.text_field :link, required: false %>
</div>
<div class="center">
<%= f.submit :class => "btn btn-info" %>
</div>
</div>
<% end %>
i'm making a Rails app that requires the use of a search form.
In my app, a user can create a post, 'Ad', and then specify if he wants it to be a small, medium or featured sized post.
This was working fine until I attempted to incorporate Sunspot Solr Search into my app.
No when ever I create a post, be it small, medium or featured, it creates on of every size whenever it should only create the selected one.
Ad Controller
class AdsController < ApplicationController
before_action :set_ad, only: [:show, :edit, :update, :destroy]
# GET /ads
# GET /ads.json
def index
#search_medium = Ad.where(:size => "medium").search do
fulltext params[:search]
end
#search_featured = Ad.where(:size => "featured").search do
fulltext params[:search]
end
#search_small = Ad.where(:size => "small").search do
fulltext params[:search]
end
#ads = Ad.all
#ads_small = #search_small.results
#ads_medium = #search_medium.results
#ads_featured = #search_featured.results
end
# GET /ads/1
# GET /ads/1.json
def show
end
# GET /ads/new
def new
#ad = Ad.new
end
# GET /ads/1/edit
def edit
end
# POST /ads
# POST /ads.json
def create
#ad = Ad.new(ad_params)
#ad.user_id = current_user.id
respond_to do |format|
if #ad.save
format.html { redirect_to #ad, notice: 'Ad was successfully created.' }
format.json { render action: 'show', status: :created, location: #ad }
else
format.html { render action: 'new' }
format.json { render json: #ad.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /ads/1
# PATCH/PUT /ads/1.json
def update
respond_to do |format|
if #ad.update(ad_params)
format.html { redirect_to #ad, notice: 'Ad was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #ad.errors, status: :unprocessable_entity }
end
end
end
# DELETE /ads/1
# DELETE /ads/1.json
def destroy
#ad.destroy
respond_to do |format|
format.html { redirect_to ads_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_ad
#ad = Ad.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def ad_params
params.require(:ad).permit(:title, :url, :preview, :location, :size, :info)
end
end
My Index View
<style type="text/css">
.ads_link {
font-family: NexaBold, Helvetica, Arial, sans-serif;
color: #e74c3c!important;
}
</style>
<%= form_tag ads_path, :method => :get do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
<% end %>
<% #ads_featured.each do |ad| %>
<div class="adspace_grid_f">
<div class="pic_sec_f">
<%= image_tag ad.preview.url(:featured) %>
</div>
<div class="text_info_f">
<span class="bold"><%= link_to ad.title, ad, :class => "title" %></span></br>
<p><%= truncate(ad.info, length: 90) %></p>
<span class="bold"><%= ad.location %></span></br>
<span class="bold"><%= ad.url %></span>
</div>
<% if can? :destroy, ad %>
<%= link_to 'Delete', ad, :method => :delete, :class => "delete" %>
<% end %>
</div>
<% end %>
<% #ads_medium.each do |ad| %>
<div class="adspace_grid">
<div class="pic_sec">
<%= image_tag ad.preview.url(:medium) %>
</div>
<div class="text_info">
<span class="bold"><%= link_to ad.title, ad, :class => "title" %></span></br>
<p><%= truncate(ad.info, length: 60) %></p>
<span class="bold"><%= ad.location %></span></br>
<span class="bold"><%= ad.url %></span>
</div>
<% if can? :destroy, ad %>
<%= link_to 'Delete', ad, :method => :delete, :class => "delete" %>
<% end %>
</div>
<% end %>
<% #ads_small.each do |ad| %>
<div class="adspace_grid">
<div class="pic_sec">
<%= image_tag ad.preview.url(:medium) %>
</div>
<div class="text_info">
<span class="bold"><%= ad.title %></span></br>
<p><%= truncate(ad.info, length: 30) %></p>
<div class="right-text">
<span class="bold"><%= ad.location %></span></br>
<span class="bold"><%= ad.url %></span>
</div>
</div>
<% if can? :destroy, ad %>
<%= link_to 'Delete', ad, :method => :delete %>
<% end %>
</div>
<% end %>
My Model File
class Ad < ActiveRecord::Base
attr_accessible :title, :url, :preview, :size, :location, :info
belongs_to :user
has_attached_file :preview, :default_url => "missing.jpg", :styles => { :medium => "125x125^", :featured => "250x250^", :showpg => "400x400^" }, :convert_options => {:medium => "-gravity center -extent 125x125", :featured => "-gravity center -extent 250x250", :showpg => "-gravity center -extent 400x400"}
validates :title, length: { maximum: 35 }
validates :url, length: { maximum: 40 }
searchable do
text :title, :info
end
end
the local sunspot server is running on the port 8983 by default, and it provide a web admin there. You can do a search by entering the query as ":", and this will return all the indexed record inside sunspot.
I suggest you add the size field into the searchable definition and do the search without the where method call.
I have an app that allows users to enter a project into a database.
They get the option to either enter new data via a textbox for some field, or can select data via a drop down menu, that has been entered before for that field.
If the user fills out the form, then clicks submit, but there is a problem, like they have missed out one of the fields, the page flags up an error saying which fields are missing, which is fine.
However, if the user had entered new data in the text boxes, that gets deleted, and the first option in the drop down is selected instead.
Here is my project controller:
class ProjectsController < ApplicationController
before_filter :authenticate_user!
#:except => [:show, :index]
def index
#projects = Project.all
respond_to do |format|
format.html # index.html.erb
format.json { render :json => #projects }
end
end
# GET /projects/1
# GET /projects/1.json
def show
#project = Project.find(params[:id])
#project_project_id = params[:id]
respond_to do |format|
format.html # show.html.erb
format.json { render json: #project }
end
end
# GET /projects/new
# GET /projects/new.json
def new
#project = Project.new
#technol = Technol.new(params[:tech])
#all_technols = Technol.all
tech_ids = params[:technols][:id].reject(&:blank?) unless params[:technols].nil?
#project_technol = #project.projecttechnols.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #project }
end
end
# GET /projects/1/edit
def edit
#project = Project.find(params[:id])
#project_technol = #project.projecttechnols.build
puts #project.inspect
puts #project.technols.inspect
end
# POST /projects
# POST /projects.json
def create
#project = Project.new(params[:project])
#project.client = params[:new_client] unless params[:new_client].blank?
#project.role = params[:new_role] unless params[:new_role].blank?
#project.industry = params[:new_industry] unless params[:new_industry].blank?
#project.business_div = params[:new_business_div] unless params[:new_business_div].blank?
if !params[:technols].nil?
params[:technols][:id].each do |tech|
if !tech.empty?
#project_technol = #project.projecttechnols.build(:technol_id => tech)
end
end
end
respond_to do |format|
if #project.save
format.html { redirect_to #project, notice: 'Project was successfully created.' }
format.json { render json: #project, status: :created, location: #project }
else
format.html { render action: "new" }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
# PUT /projects/1
# PUT /projects/1.json
# PUT /projects/1
# PUT /projects/1.json
def update
#project = Project.find(params[:id])
puts #project.inspect
puts #project.technols.inspect
#project.client = params[:new_client] unless params[:new_client].blank?
#project.role = params[:new_role] unless params[:new_role].blank?
#project.industry = params[:new_industry] unless params[:new_industry].blank?
#project.business_div = params[:new_business_div] unless params[:new_business_div].blank?
respond_to do |format|
if #project.update_attributes(params[:project])
format.html { redirect_to #project, notice: 'Project was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
# DELETE /projects/1
# DELETE /projects/1.json
def destroy
#project = Project.find(params[:id])
#project.destroy
respond_to do |format|
format.html { redirect_to projects_url }
format.json { head :no_content }
end
end
private
helper_method :sort_column, :sort_direction
def sort_column
Project.column_names.include?(params[:sort]) ? params[:sort] : "project_name"
end
def sort_direction
%w[asc desc].include?(params[:direction]) ? params[:direction] : "asc"
end
def per_page
params[:per_page] ||= 1
end
def page
params[:page] ||= 1
end
end
Here is some of my new project view
<%= stylesheet_link_tag "new" %>
<h1>Create New Project</h1>
<HTML>
<%= stylesheet_link_tag "form" %>
<%= form_for(#project) do |f| %>
<% if #project.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#project.errors.count, "error") %> prohibited this project from being saved:</h2>
<ul>
<% #project.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<CENTER>
<div id = "project_name">
<div class="project_name">
Project Name:
<%= f.text_field :project_name,:maxlength => 30 %>
</div>
</div>
<div id ="smallbox">
<div id = "status">
<div class="status">
<%= f.label :status %> :
<%#= f.select :status, [['Active'],['Completed']], {:include_blank => true} %>
<%= f.select :status, [['Active'],['Completed']] %>
</div></div>
<div class="client" STYLE="text-align: left;">
<%= label_tag :new_client, "Client" %><br/>
<%= text_field_tag :new_client, nil, :maxlength => 30 %>
Or
<%= f.select( :client, Project.all.map {|p| [p.client]}.uniq, :prompt => "Select previous..") %>
</div>
<div class="business_div" STYLE="text-align: left;">
<%= label_tag :new_business_div, "Business Division" %><br/>
<%= text_field_tag :new_business_div, nil, :maxlength => 30 %>
Or
<%= f.select( :business_div, Project.all.map {|p| [p.business_div]}.uniq, :prompt => "Select previous") %>
</div>
<div class="start_date" STYLE="text-align: left;">
<b>Start Date:</b>
<%= f.text_field :start_date, :class => 'datepicker', :style => 'width: 80px;' %>
</div>
</P>
<div class="create_button">
<div class="actions">
<%= f.submit "Save New Project", :class => "button", :confirm => "Are you sure you want to save the new project?" %>
</div>
</div>
</div> <%#= small div %>
<% end %>
<div class="back_button2">
<%= button_to "Back", projects_path , :class => "button", :method => "get" %>
</div>
Here is my project model
class Project < ActiveRecord::Base
attr_accessible :fullname, :edited_first_name, :edited_last_name, :first_name, :last_name, :business_div, :client, :customer_benefits, :edited_date, :end_date, :entry_date, :financials, :industry, :keywords, :lessons_learned, :project_name, :role, :start_date, :status, :summary, :technol_ids, :tech , :technols
validates_presence_of :business_div, :client, :customer_benefits, :end_date, :financials, :industry, :lessons_learned, :project_name, :role, :start_date, :status, :summary #, :keywords
validates_format_of :industry, :with => /\A[^\d]+\Z/, :message => "field should only have letters"
validates_format_of :business_div, :with => /\A[^\d]+\Z/, :message => "field should only have letters"
validates_format_of :client, :with => /\A[^\d]+\Z/, :message => "field should only have letters"
validates_format_of :exception_pm, :with => /\A[^\d]+\Z/, :message => "field should only have letters"
validates_format_of :project_owner, :with => /\A[^\d]+\Z/, :message => "field should only have letters"
validates_format_of :role, :with => /\A[^\d]+\Z/, :message => "field should only have letters"
has_many :projecttechnols
has_many :technols, :through => :projecttechnols
def set_fullname(a, b)
fullname = [a, b].join(' ')
end
accepts_nested_attributes_for(:technols)
end
If I need to include anything else please let me know. I have been stuck with this problem for some time now. Thank you in advance.
As railsdog suggests, use the data passed to Projects#create. Your form submits data and that data is held in a variable called params as key/value pairs. That variable should still be accessible from whichever view Projects#create renders.
What you need to do is to set some default content on your form elements. Here's an example for the text_field called "project_name"
<div class="project_name">
Project Name:
<%= f.text_field :project_name, params[:project_name],:maxlength => 30 %>
</div>
You should be able to do the same or something similar with any other fields.
I have a rails application that models a house. house contains rooms and rooms have nested attributes for light and small_appliance. I have a calculator controller, which is how end users will access the application.
My problem is that I can't get the partial for adding rooms to render and submit correctly from calculator. The initial page lets the user enter house information, which is saved using save_house when submit is clicked. This also redirects the user to the add_rooms page, where they can add rooms to the house.
add_rooms displays correctly, but when I click submit, I get this error:
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'
This is odd to me, because when add_rooms first renders, it shows the house_id. I don't understand why it isn't passed after the form is submitted.
Here's the code:
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
Thanks,
Ryan
Out of curiosity why not have house accept nested attributes for rooms. This would make your controller code simpler, as adding many rooms, lights and small appliances is as simple as just doing #house.update_attributes(params[:house]). However this is not an answer that helps, as you would still have your current problems if you made the change.
Your first error comes from the first line of app/views/calculator/_room_form.html.erb
<% form_for :room, :url => { :action => :add_room, :id => #house } do |form| %>
You're not giving form_for an object so the new_child_fields method called by add_child
_link is trying to call reflect_on_association on the Nil class.
The solution is change the line to
<% form_for :room, #house.rooms.build, :url => { :action => :add_room } do |form| %>
This lets you simplify your controller, because a room associated with a house is already being passed to it.
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
I believe your second error is the same problem. However because you're calling a has_many accessor instead of getting a nil being generated, you're passing an empty array, which explains the difference in error messages. Again the solution is to build a light and small appliance before rendering if none exist yet.
<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 %>
Your new error comes from this:
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 is assuming that the _light partial is in the app/views/calculators folder
The solution is to either move the light and small_appliances partials to this folder or modify your helper methods to accept a partial option.