Dazt
Dazt

Reputation: 113

has_many through access join table attribute in form

I have the following models:

class RandomExam < ActiveRecord::Base
  has_many :random_exam_sections
  has_many :sections, :through => :random_exam_sections
end

class Section < ActiveRecord::Base
  has_many :random_exam_sections
  has_many :random_exams, :through => :random_exam_sections

class RandomExamSection < ActiveRecord::Base
  belongs_to :random_exam
  belongs_to :section
end

The idea is to have certain configurations to create random exams, so this tables help to select which sections do you need and then also select the number of questions per section, here are the attributes of each table:

RandomExam: name(string), created_at(datetime), updated_at(datetime)

Section: name(string), created_at(datetime), updated_at(datetime)

RandomExamSection: random_exam_id(integer), section_id(integer), questions_number(integer)

As you can see the number of questions per section attribute is inside the RandomExamSections table and I want to access it in a form that is displayed from the RandomExam controller, here is my form:

<%= form_for (@random_exam) do |f|  %>
    <div class="row">

        <div class="input-field col s12">
            <%= f.label :name, 'Name' %>
            <%= f.text_field :name, placeholder: 'Enter the name of the       configuration' %>
        </div>

    </div>

    <% @sections.each do |section| %>

        <div class="row <%= dom_id(section) %>">
            <div class="col s4">
                <%= check_box_tag 'random_exam[section_ids][]',  section.id,
                @random_exam.section_ids.include?(section.id), id:     dom_id(section), class: "section-checkbox #{dom_id(section)}" %>
                <%= label_tag dom_id(section), (raw sanitize     section.name, tags: %w(h2 p strong em a br b i small u ul ol li     blockquote), attributes: %w(id class href)),
                class: "name #{dom_id(section)}" %>
            </div class="col s4">
            <div>
                <%= text_field_tag "random_exam[random_questions_numbers][#{section.id}][]", nil,
                                   :placeholder => 'Enter the number of questions' %>
            </div>

        </div>
    <% end %>

    <div class="form-group">
        <%= f.submit class: "btn waves-effect waves-light green"  %>
    </div>

<% end %>

My controller:

def create
  @random_exam = RandomExam.new(random_exam_params)
  if @random_exam.save
  assign_random_questions_number
  flash[:success] = 'Random configuration created successfully'
  redirect_to @random_exam
else
  flash.now[:danger] = @random_exam.errors.full_messages.to_sentence
  render 'new'
end

def assign_random_questions_number
  if params[:random_exam][:'random_questions_numbers'] == nil
    return
  end


params[:random_exam][:'section_ids'].each do |key, value|
  record = RandomExamSection.search_ids(@random_exam.id, key)

  record.each do |random_exam_section_record|
    number_of_questions = params[:random_exam][:'random_questions_numbers'][key].first.to_i
    random_exam_section_record.update(questions_number: number_of_questions)
  end
end

end

I'm getting a TypeError: TypeError: nil is not a symbol nor a string when I update the record in the method assign_random_questions_number

This error even appears when I run this on the console

random = RandomExamSection.first
random.update(questions_number: 10)

Or when I run:

random = RandomExamSection.first
random.questions_number = 10
random.save

EDIT

I ended up deleting the association in RandomExamSection and recreating it inside 'assign_random_questions_number' with the questions_number

Thanks.

Upvotes: 1

Views: 360

Answers (1)

Jorge Vargas
Jorge Vargas

Reputation: 650

If you use nested_attributes you can do something like this:

#form
<h4>Selected exams</h4>
<%= f.fields_for :random_exam_sections do |b| %>
  <%= b.hidden_field :section_id %>
  <%= b.label :selected, b.object.section.name %>
  <%= b.check_box :selected, { checked: !b.object.id.blank? } %>
  <br>
  <%= b.label :question_numbers %>
  <%= b.text_field :questions_number %>
 <% end %>

#RandomExamModel
class RandomExam < ApplicationRecord
  has_many :random_exam_sections, inverse_of: :random_exam
  has_many :sections, :through => :random_exam_sections

  accepts_nested_attributes_for :random_exam_sections, reject_if: :is_not_selected


  private
  def is_not_selected(attr)
    attr["selected"] == '0'
  end
end

# RandomExam
class RandomExamSection < ApplicationRecord
  belongs_to :random_exam
  belongs_to :section

  attr_accessor :selected
end

# Controller
# GET /random_exams/new
  def new
    @random_exam = RandomExam.new
    @random_exam.random_exam_sections.build(Section.all.map{|s| {section_id: s.id}})
  end

The idea basically is

- Build on controller the random_exam_sections to be selected
- Write a form that allows to you 'select' one option and assign the number
- Then, validate if the random_exam_section of a sections was selected (this why i made that `attr_accessor :selected`, i need a place to write if user select the exam_section)
- If was selected, save.

The trick here is build on the controller, then select on the view and validate the selected on the model. Here i made an example if you need help: https://github.com/afromankenobi/nested_attr_demo

To add sections when the random_exam_sections is already created you should probably use javascript. Maybe this railscasts can help you http://railscasts.com/episodes/196-nested-model-form-part-1

Upvotes: 1

Related Questions