Dazt Dazt - 4 months ago 38
Ruby Question

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.

Answer

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

Comments