1

I'm struggling to get nested attributes down. Working off of Railscast 196, I tried to setup my own app that does basic nesting. Users can create scavenger hunts. Each hunt consists of a series of tasks (that can belong to any hunt, not just one). I got a little help here and tried to learn from a post with a similar issue, but I'm still stuck. I've been hacking around for hours and I've hit a brick wall.

class HuntsController < ApplicationController

  def index
     @title = "All Hunts"
     @hunts = Hunt.paginate(:page => params[:page])
  end

  def show
    @hunt = Hunt.find(params[:id])
    @title = @hunt.name 
    @tasks = @hunst.tasks.paginate(:page => params[:page])
  end

  def new
    if current_user?(nil) then    
      redirect_to signin_path
    else
      @hunt = Hunt.new
      @title = "New Hunt"
      3.times do
        #hunt =  @hunt.tasks.build 
        #hunt = @hunt.hunt_tasks.build  
        hunt = @hunt.hunt_tasks.build.build_task
      end
    end
  end

  def create
    @hunt = Hunt.new(params[:hunt])
    if @hunt.save
      flash[:success] = "Hunt created!"
      redirect_to hunts_path
    else
      @title = "New Hunt"
      render 'new'     
    end
  end
....
 end

With this code, when I try and create a new hunt, I'm told that there's no method "build_task" (it's undefined). So when I remove that line and use the second bit of code that's commented out above, I get the error below.

    NoMethodError in Hunts#new

    Showing /Users/bendowney/Sites/MyChi/app/views/shared/_error_messages.html.erb where line #1 raised:

    You have a nil object when you didn't expect it!
    You might have expected an instance of ActiveRecord::Base.
    The error occurred while evaluating nil.errors
    Extracted source (around line #1):

    1: <% if object.errors.any? %>
    2:   <div id="error_explanation">
    3:     <h2><%= pluralize(object.errors.count, "error") %> 
    4:         prohibited this <%= object.class.to_s.underscore.humanize.downcase %> 

    Trace of template inclusion: app/views/tasks/_fields.html.erb, app/views/hunts/_fields.html.erb, app/views/hunts/new.html.erb

And when I use the first bit of code that's commented out in the hunt controller, then I get an error telling me that my 'new' method has an unintialized constant:

    NameError in HuntsController#new
uninitialized constant Hunt::Tasks

I'm at my wit's end. Any suggestions on what exactly I'm doing wrong? Or a strategy Here are my models:

    class Hunt < ActiveRecord::Base
      has_many :hunt_tasks
      has_many :tasks, :through => :hunt_tasks #, :foreign_key => :hunt_id

      attr_accessible :name
      validates :name,  :presence => true,
                        :length   => { :maximum => 50 } ,
                        :uniqueness => { :case_sensitive => false }
    end

    class Task < ActiveRecord::Base

      has_many :hunt_tasks
      has_many :hunts, :through => :hunt_tasks#, :foreign_key => :hunt_id

      attr_accessible :name 
      validates :name,  :presence => true,
                        :length   => { :maximum => 50 } ,
                        :uniqueness => { :case_sensitive => false }

    end

    class HuntTask < ActiveRecord::Base
      belongs_to :hunts # the id for the association is in this table
      belongs_to :tasks
    end
Community
  • 1
  • 1
Ben Downey
  • 2,575
  • 4
  • 37
  • 57
  • 1
    Nested attributes have a way of biting you down the road, explore options to use a form class before going down that road (see point #3 in http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ for one possible approach to this) – bbozo Jan 02 '14 at 07:05

3 Answers3

1

have you tried hunttask = @hunt.build_hunt_task in the HuntsController new action?

http://guides.rubyonrails.org/association_basics.html#detailed-association-reference

mober
  • 361
  • 1
  • 7
  • I have that a try, but I got another no method error. `NoMethodError in HuntsController#new undefined method `build_hunt_task' for []:ActiveRecord::Relation` – Ben Downey Apr 01 '12 at 23:29
  • I am sorry, I suggested the builder method that comes with a has_one relation... What you want to use here is a has_many association which accordingly to the docs -> http://guides.rubyonrails.org/association_basics.html#has_many-association-reference provides a `@hunt.hunt_tasks.build` method. that you already tried to use but commented out. What was your reason doing so? – mober Apr 01 '12 at 23:51
  • My reason to commenting out `@hunt.hunt_tasks.build` was that it returns the error message you mention in your last comment. I'm not sure exactly what you mean in that last comment. Would you clarify? – Ben Downey Apr 02 '12 at 00:29
1

When you create an association between 2 of your models, you add functionality to them, depending on how you define your relationship. Each type kinda adds different functions to your model.

I really recommend reading this guide -> http://guides.rubyonrails.org/association_basics.html

Here you can see which functions get added by each different type of association. http://guides.rubyonrails.org/association_basics.html#detailed-association-reference

If I do a small sample program like...

class HuntsController < ApplicationController
  # GET /hunts
  # GET /hunts.json
  def index
    @hunts = Hunt.all

    respond_to do |format|
      format.html # index.html.erb
      format.json { render json: @hunts }
    end
  end

  # GET /hunts/1
  # GET /hunts/1.json
  def show
    @hunt = Hunt.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.json { render json: @hunt }
    end
  end

  # GET /hunts/new
  # GET /hunts/new.json
  def new
    @hunt = Hunt.new
    3.times do |i|
        t = @hunt.hunt_tasks.build
        t.name = "task-#{i}"
    end

    respond_to do |format|
      format.html # new.html.erb
      format.json { render json: @hunt }
    end
  end

  # GET /hunts/1/edit
  def edit
    @hunt = Hunt.find(params[:id])
  end

  # POST /hunts
  # POST /hunts.json
  def create
    @hunt = Hunt.new(params[:hunt])

    respond_to do |format|
      if @hunt.save
        format.html { redirect_to @hunt, notice: 'Hunt was successfully created.' }
        format.json { render json: @hunt, status: :created, location: @hunt }
      else
        format.html { render action: "new" }
        format.json { render json: @hunt.errors, status: :unprocessable_entity }
      end
    end
  end

  # PUT /hunts/1
  # PUT /hunts/1.json
  def update
    @hunt = Hunt.find(params[:id])

    respond_to do |format|
      if @hunt.update_attributes(params[:hunt])
        format.html { redirect_to @hunt, notice: 'Hunt was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: "edit" }
        format.json { render json: @hunt.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /hunts/1
  # DELETE /hunts/1.json
  def destroy
    @hunt = Hunt.find(params[:id])
    @hunt.destroy

    respond_to do |format|
      format.html { redirect_to hunts_url }
      format.json { head :no_content }
    end
  end
end

and this model-relation

class Hunt < ActiveRecord::Base
    has_many :hunt_tasks
end

class HuntTask < ActiveRecord::Base
  belongs_to :hunt
end

and add this snippet somewhere in views/hunts/_form.html

<% @hunt.hunt_tasks.each do |t| %>
    <li><%= t.name %></li>
<% end %>

I get regular output, seeing that the 3 tasks were created.

mober
  • 361
  • 1
  • 7
0

The immediate error you are seeing is in app/views/shared/_error_messages.html.erb. object is not defined, You probably need to find where that partial is called. Find:

render :partial=>"/shared/error"

replace it with

render :partial=>"/shared/error", :locals=>{:object=>@hunt}

If you find it in app/views/hunts somewhere, if you find in in app/views/tasks, replace @hunt with @task

That will at least show you what the real error is.

RadBrad
  • 7,234
  • 2
  • 24
  • 17
  • When I change the new.html.erb in the hunt views to this `<%= render 'shared/error_messages', :locals=>{:object=>@hunt} %>`, I still end up with the same error as before. – Ben Downey Apr 02 '12 at 00:42