So based on Ryan Bates rails cast (http://railscasts.com/episodes/196-nested-model-form-revised) I am creating a nested form. The part of the rails application that I am trying to get to work ideally does the following:
- Let a current user ask a question
- Then provide an answer for that question afterwards
I managed to get everything working, except that when I try to submit the form once everything is filled out, I keep getting the following error:
undefined method `meter_id' for nil:NilClass
app/models/answer.rb:13:in `associate_with_meter_id'
app/controllers/questions_controller.rb:13:in `create'
I believe I know what is wrong, but I am not sure how to fix it. The meter_id is returning an undefined value, because it is not being passed the correct value. Here is the method that associates the meter_id (of answers) with the meter_id (of users):
def associate_with_meter_id
self.meter_id = user.meter_id
end
Here is a partial of my user model
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me,
:home_size_sf, :meter_id, :avg_monthly_kwh, :discovery_score,
:questions_attributes, :answers_attributes
has_many :data_records, :foreign_key => :meter_id, :primary_key => :meter_id, :class_name => "DataRecord"
has_many :questions
has_many :answers
accepts_nested_attributes_for :questions, :answers
Here is the questions model
class Question < ActiveRecord::Base
attr_accessible :description, :taxonomy, :user_id, :answers_attributes
belongs_to :user
has_many :answers
accepts_nested_attributes_for :answers
validates :description, presence: { :on => :create }
validates :taxonomy, presence: { :on => :create }
def relevance_score
rand
end
end
Here is the questions controller
class QuestionsController < ApplicationController
respond_to :html, :json
def index
@question = current_user.questions.new
@questions = current_user.questions.all
end
def create
@question = current_user.questions.new(params[:question])
if !params[:update_button]
if @question.valid?
if params[:next_button] || !@question.save
render 'index'
elsif !params[:next_button] && params[:submit_button] && @question.save
flash[:success] = "Your question and answer have been saved."
respond_with @question, :location => questions_path
end
else
render 'index'
end
else
render 'index'
end
end
def next
@question = current_user.unanswered.first
@answer = Answer.new(:question => @question, :user => current_user)
respond_to do |format|
format.js
end
end
end
answers model
class Answer < ActiveRecord::Base
attr_accessible :value, :user_id, :meter_id, :question_id
belongs_to :user
belongs_to :question
validates :value, presence: true, :numericality => true
before_save :associate_with_meter_id
def associate_with_meter_id
self.meter_id = user.meter_id **(<-- line 13 from the error message)**
end
end
answers controller
class AnswersController < ApplicationController
respond_to :html, :json
def index
@answers = current_user.answers
end
def create
@answer = current_user.answers.create(params[:answer])
if @answer.save
flash[:notice] = "Thanks for for answer. Please continue with your input...."
respond_with @answer, :location => root_url
end
end
end
database schema
ActiveRecord::Schema.define(:version => 20120210184340) do
create_table "answers", :force => true do |t|
t.integer "meter_id"
t.integer "user_id"
t.integer "question_id"
t.float "value"
t.float "what_if_value"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "data_records", :force => true do |t|
t.datetime "timestamp"
t.float "value"
t.integer "meter_id"
t.string "status_code"
end
create_table "questions", :force => true do |t|
t.string "description"
t.string "taxonomy"
t.string "coeff"
t.float "rsquare", :default => 0.0
t.string "rank"
t.string "responses"
t.string "skips"
t.string "avganswer"
t.float "pval", :default => 0.0
t.float "quality", :default => 0.0
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "setup_constants", :force => true do |t|
t.float "exp_model", :default => 0.0
t.float "exp_pval_const", :default => 0.0
end
create_table "users", :force => true do |t|
t.integer "meter_id"
t.float "home_size_sf", :default => 1000.0
t.text "notifications"
t.float "avg_monthly_kwh"
t.float "ee_score"
t.string "email", :default => "", :null => false
t.string "encrypted_password", :default => "", :null => false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", :default => 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
end
Note:
In the answers model (above), the line:
self.meter_id = user.meter_id
Associates the meter_id of the answer model with the meter_id of the user model. I believe this is where the issue is. I tried changing the above line to:
self.meter_id = 2
And then everything worked fine, so it’s obvious that the user.meter_id is undefined, so I’m not sure how to pass that value through the nested form? I tried using a hidden field but with no luck (the following is a nested fields_for :answers, within a form_for @questions):
<fieldset>
<%= f.label "Yes" %>
<%= f.radio_button :value, 1 %>
<%= f.label "No" %>
<%= f.radio_button :value, 0 %>
<%= f.hidden_field :user_id %>
<%= f.hidden_field :question_id %>
<%= f.hidden_field :meter_id %>
</fieldset>
First of all, you don’t want to pass the current user in the view for security reasons. Instead, you want to do it from the controller.
A starting point (assuming you have current_user):
From there, it’s up to you how to pass the user to the answer model. You could, however, use this:
Assuming that’s appropriate.