user3684314
user3684314

Reputation: 771

Nested has_many attributes (Ruby on Rails) throwing errors

So this may be somewhat related to my question that I asked earlier, so here is a link to it, just in case.

So, currently I'm getting the error undefined method 'location' for #<Block:0x6503478>.

I've already gotten the has_one relation for cue working, but what seems to be the problem is the has_many relations with block_block and block_character.

The parameters being passed are:

{"utf8"=>"✓",
 "authenticity_token"=>"blahblahblah",
 "block"=>{"block_code"=>"1",
 "block_duration"=>"",
 "cue"=>"no",
 "cue_attributes"=>{"cue_code"=>"",
 "cue_type_code"=>"",
 "cue_description"=>"",
 "cue_method_code"=>""},
 "location_code"=>"1",
 "scene_code"=>"1",
 "block_description"=>"",
 "block_block"=>{"block_block_code"=>"",
 "primary_block_code"=>"",
 "secondary_block_code"=>"",
 "block_block_start"=>""},
 "block_character"=>{"block_character_code"=>"",
 "character_code"=>"",
 "block_code"=>"",
 "character_action"=>"",
 "character_motivation"=>""}},
 "blockblock"=>{"block"=>"no"},
 "blockblockdirect"=>{"blockdirect"=>"before"},
 "blockchar"=>{"character"=>"no"},
 "commit"=>"Create Block"}

Here is my form for block (views/block/_form.html.erb):

<%= form_for(@block) do |f| %>
  <% if @block.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@block.errors.count, "error") %> prohibited this block from being saved:</h2>

      <ul>
      <% @block.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field hidden">
    <%= f.label :block_code, class: "hidden" %><br>
    <%= f.text_field :block_code, class: "form-control hidden" %>
  </div>
  <div class="field">
    <%= f.label :block_duration %><br>
    <div class="input-group">
      <%= f.number_field :block_duration, class: 'text_field form-control', :step => 'any' %>
      <div class="input-group-addon">seconds</div>
    </div>
  </div>

  <div class="field">
    <%= label "cue", "Does this block have a cue associated with it?" %>
    <!-- Add whether it is specific/generic cue -->
    <%= radio_button_tag "block[cue]", "yes", false %> Yes
    <%= radio_button_tag "block[cue]", "no", true %> No
    <div class="field" id="cue_fields" style="display:none;">
      <%= f.fields_for :cue, @block.build_cue do |ff| %>
        <div class="field hidden">
          <%= ff.label :cue_code, class: "hidden" %><br>
          <%= ff.text_field :cue_code, class: "hidden" %>
        </div>
        <div class="field">
          <%= ff.label "Cue Type" %><br>
          <%= ff.collection_select(:cue_type_code, CueType.all, :cue_type_code, :cue_type_name, {prompt: "Select a cue type..."}, {class: "form-control"}) %>
        </div>
        <div class="field">
          <%= ff.label "Cue Description" %><br>
          <%= ff.text_area :cue_description, class: "form-control" %>
        </div>
        <div class="field">
          <%= ff.label "Cue Method" %><br>
          <%= ff.collection_select( :cue_method_code, CueMethod.all, :cue_method_code, :cue_method_name, {prompt: "Select a cue method..."}, {class: "form-control"}) %>
        </div>
      <% end %>
    </div>
  </div>


  <div class="field">
    <%= f.label "Location" %><br>
    <%= collection_select :block, :location_code, Location.all, :location_code, :location_name, {prompt: "Select a location..."}, {class: "form-control", required: true} %>
  </div>
  <div class="field">
    <%= f.label "Scene" %><br>
    <%= f.collection_select :scene_code, Scene.all, :scene_code, :actAndScene, {prompt: "Select a scene..."}, {class: "form-control", required: true} %>
  </div>
  <div class="field">
    <%= f.label "Block Description" %><br>
    <%= f.text_area :block_description, class: "form-control" %>
  </div>

  <!-- This needs work -->
  <div class="field">
    <%= label "blockblock", "Is this block associated with any other blocks?" %>
    <%= radio_button_tag "blockblock[block]", "yes", false %> Yes
    <%= radio_button_tag "blockblock[block]", "no", true %> No
    <div class="field" id="blockblock_fields" style="display:none;">
      <ol>
        <li>
          <%= label "blockdirect", "Does this block come directly before or after another block?" %>
          <%= radio_button_tag "blockblockdirect[blockdirect]", "before", true %> Before
          <%= radio_button_tag "blockblockdirect[blockdirect]", "after", false %> After
          <%= f.fields_for :block_block do |gg| %>
            <div class="field hidden">
              <%= gg.label :block_block_code, class: 'hidden' %><br>
              <%= gg.text_field :block_block_code, class: 'hidden' %>
            </div>
            <div class="field" id="blockblock_after_fields" style="display:none;">
              <%= gg.label "Primary Block Code" %><br>
              <%= gg.text_field :primary_block_code, class: 'form-control' %>
            </div>
            <div class="field" id="blockblock_before_fields">
              <%= gg.label "Secondary Block Code" %><br>
              <%= gg.text_field :secondary_block_code, class: 'form-control' %>
            </div>
            <div class="field">
              <%= gg.label "Block Start" %><br>
              <p><i>This field indicates the amount of time after the first block starts that the second block should begin, i.e. if you type "1" here, then the second block will start 1 second after the first block starts</i></p>
              <div class="input-group">
                <%= gg.number_field :block_block_start, class: 'text_field form-control', :step => 'any' %>
                <div class="input-group-addon">seconds</div>
              </div>
            </div>
          <% end %>
        </li>
      </ol>
    </div>
  </div>

  <!-- This needs work -->
  <div class="field">
    <%= label "character", "Are any characters associated with this block?" %>
    <%= radio_button_tag "blockchar[character]", "yes", false %> Yes
    <%= radio_button_tag "blockchar[character]", "no", true %> No
    <div class="field" id="character_fields" style="display:none;">
      <ol>
                <%= f.fields_for :block_character do |hh| %>
        <li>
          <div class="field hidden">
            <%= hh.label :block_character_code, class: 'hidden' %><br>
            <%= hh.text_field :block_character_code, class: 'hidden' %>
          </div>
          <div class="field">
            <%= hh.label "Character" %><br>
            <%= hh.collection_select :character_code, Character.all, :character_code, :character_name, {prompt: "Select a character..."}, {class: "form-control"} %>
          </div>
          <div class="field hidden">
            <%= hh.label :block_code, class: 'hidden' %><br>
            <%= hh.text_field :block_code, class: 'hidden' %>
          </div>
          <div class="field">
            <%= hh.label :character_action %><br>
            <%= hh.text_field :character_action, class: 'form-control' %>
          </div>
          <div class="field">
            <%= hh.label :character_motivation %><br>
            <%= hh.text_area :character_motivation, class: 'form-control' %>
          </div>
        </li>
      <% end %>
      </ol>
    </div>
  </div>

  <div class="actions">
    <%= f.submit "Create Block", class: "btn btn-primary" %>
  </div>
<% end %>

Additionally, here are the models associated with block, block_character, and block_block, respectively:

class Block < ActiveRecord::Base
    validates_presence_of :location
    validates_presence_of :scene

  has_one :cue
  has_many :block_blocks
  has_many :block_characters

  accepts_nested_attributes_for :cue, allow_destroy: true
  accepts_nested_attributes_for :block_blocks, allow_destroy: true
  accepts_nested_attributes_for :block_characters, allow_destroy: true

    attr_accessor :block_blocks
    attr_accessor :block_characters
end

class BlockBlock < ActiveRecord::Base
  belongs_to :block
end

class BlockCharacter < ActiveRecord::Base
  belongs_to :block
end

Here is the controller for block:

class BlocksController < ApplicationController
  before_action :set_block, only: [:show, :edit, :update, :destroy]

  # GET /blocks
  # GET /blocks.json
  def index
    @blocks = Block.all
  end

  # GET /blocks/1
  # GET /blocks/1.json
  def show
  end

  # GET /blocks/new
  def new
    @block = Block.new

    # Set block code as next integer after max block code.
    @block.block_code = (Block.maximum(:block_code).to_i.next).to_s(2)

  end

  # GET /blocks/1/edit
  def edit
  end

  # POST /blocks
  # POST /blocks.json
  def create
    @block = Block.new(block_params)

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

  # PATCH/PUT /blocks/1
  # PATCH/PUT /blocks/1.json
  def update
    respond_to do |format|
      if @block.update(block_params)
        format.html { redirect_to @block, notice: 'Block was successfully updated.' }
        format.json { render :show, status: :ok, location: @block }
      else
        format.html { render :edit }
        format.json { render json: @block.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /blocks/1
  # DELETE /blocks/1.json
  def destroy
    @block.destroy
    respond_to do |format|
      format.html { redirect_to blocks_url, notice: 'Block was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_block
      @block = Block.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def block_params
      params.require(:block).permit(:block_code, :block_duration, :cue_code, :location_code, :scene_code, :block_description, :cue_attributes => [:id, :cue_code, :cue_type_code, :cue_description, :cue_method_code], :block_block_attributes => [:id, :block_block_code, :primary_block_code, :secondary_block_code, :block_block_start], :block_character_attributes => [:id, :block_character_code, :character_code, :block_code, :character_action, :character_motivation])
    end
end

And finally, the migrations I added for id and foreign_key for both block_block and block_character:

class AddBelongsToToBlockBlocks < ActiveRecord::Migration
  def change
    add_reference :block_blocks, :block, index: true
    add_foreign_key :block_blocks, :blocks
  end
end

class AddBelongsToToBlockCharacters < ActiveRecord::Migration
  def change
    add_reference :block_characters, :block, index: true
    add_foreign_key :block_characters, :blocks
  end
end

Honestly, thank you all so much for your help through this, I am by no means an advanced Rails user and this has all been amazingly helpful. Please let me know if you need any more information!

UPDATE 1

So I changed the following in block.rb (the model file), as suggested by Vijay (thanks so much!!!):

class Block < ActiveRecord::Base
    validates_presence_of :location_code
    validates_presence_of :scene_code

  has_one :cue
  has_many :block_blocks
  has_many :block_characters

  accepts_nested_attributes_for :cue, allow_destroy: true
  accepts_nested_attributes_for :block_blocks, allow_destroy: true
  accepts_nested_attributes_for :block_characters, allow_destroy: true

    attr_accessor :block_blocks
    attr_accessor :block_characters
end

At this point, the block saves (and appears in the index view), but the block_block and block_character do not do so. There is no associated error however...

UPDATE 2

This is currently what the params submitted look like:

Params submitted

Which is odd because it says that cue, block_block, and block_character are unpermitted parameters, even though, in the controller they are:

def block_params
  params.require(:block).permit(:block_code, :block_duration, :cue_code, :location_code, :scene_code, :block_description, :cue_attributes => [:id, :cue_code, :cue_type_code, :cue_description, :cue_method_code], :block_block_attributes => [:id, :block_block_code, :primary_block_code, :secondary_block_code, :block_block_start], :block_character_attributes => [:id, :block_character_code, :character_code, :block_code, :character_action, :character_motivation])
end

Upvotes: 1

Views: 46

Answers (2)

Vijay
Vijay

Reputation: 219

Not sure why you've added these in Rails 4

    attr_accessor :block_blocks
    attr_accessor :block_characters

Try removing them and see if they are interfering with the controller (I'm guessing, but happy to play if you put up a jsfiddle)

Upvotes: 1

Vijay
Vijay

Reputation: 219

Are you triggering the error in the validation?

validates_presence_of :location

From your code it looks like it should be

validates_presence_of :location_code

Upvotes: 2

Related Questions