1

I cleaned up my code, it looks much nicer now, but still doesn’t work. It starts to be a pain…

I just can’t save a parent with an existing child in nested form with parent has_many childs :through joinmodel.

In my case a Project has_many contributing Teachers and many contributing Pupils, both are Join-Models to Users. A Project has_many Schools as well.

(May be I should better name the models Teacherize and Pupilize or ProjectTeacher and ProjectPupil.)

As long as all records are new it all works fine. As soon as I want to connect an existing User as new Teacher of a new Project I get the following error:

Couldn't find User with ID=1 for Teacher with ID= (1 is the correct user ID)

The problem should be somewhere her in my helper to setup empty form fields:

At least I guess so...

module ProjectsHelper
  def setup_project(project)
    if project.teachers.length <= 0  # usually there is just one teacher, so add one if there isn't one
      teacher = project.teachers.build(:role_in_project => 'master')
      if user_signed_in?
        #teacher = project.teachers.new(:role_in_project => 'master', :user => current_user)
        teacher.user = current_user # associate first teacher with current_user
      else
        #teacher = project.teachers.build
        teacher.user = User.new   # associate first teacher with a new user instance
      end
    end
    if project.project_schools.length <= 0   # usually there is just one school, so add one if there isn't one
      project_school = project.project_schools.build
      project_school.school = School.new
    end
    if project.pupils.length < 3  # There can be up to 3 people, so add a blank fieldset as long as there are less than 3
      pupil = project.pupils.build
      pupil.user = User.new
    end
    project
  end
end

These are my params received:

    {"utf8"=>"✓", "authenticity_token"=>"uCCMk/s3SpDfR7+fXcsCOHPvfvivBQv8pVFVhdh6iro=", 
     "project"=>{
       "teachers_attributes"=>{
           "0"=>{
                 "id"=>"",
                 "user_attributes"=>{
                      "id"=>"1",
                      "gender"=>"male",
                      "title"=>"",
                      "firstname"=>"Firstname1",
                      "name"=>"Lastname1",
                      "faculty"=>"",
                      "fon"=>"",
                      "fax"=>""}
                 }
           }, 
           "id"=>"",
           "title"=>"First Project",
           "description"=>"This is a foo bar project!",
           "presentation_type"=>"experimentell",
           "note"=>""
      }, 
      "commit"=>"Register Project",
      "action"=>"create",
      "controller"=>"projects"
   }

The case isn’t too abstract; it has to be possible to achieve it. It’s just to connect a new parent record with an existing child record!

In this article, which is very good, exactly the case is explaint: http://rubysource.com/complex-rails-forms-with-nested-attributes

# app/helpers/form_helper
module FormHelper
  def setup_user(user)
    user.address ||= Address.new
    (Interest.all - user.interests).each do |interest|
      user.interest_users.build(:interest => interest)
    end
    user.interest_users.sort_by! {|x| x.interest.name }
    user/tmp/clean-controllers.md.html
  end
end

There the interest is existing and gets connected through a new record in interest_uesers.

Why do I get the error when trying to do the same thing?

project.teachers.build(:user => current_user)

I studied several articles and casts, but none of them connect existing childs. http://railscasts.com/episodes/196-nested-model-form-revised http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html Rails 3.1+ Nested Forms Issue: Can't mass-assign protected attributes

Trying to use accepts_nested_attributes_for and has_and_belongs_to_many but the join table is not being populated

Quote: “accepts_nested_fields_for is used to create and modify related objects in a form. It can be used to populate join table, which is kind of what you're trying to do. However, using accepts_nested_fields_for to populate the join table is impossible with a HABTM relationship.”

That’s what I wanna do! Populate the join table! It starts to be frustrating and I’d be glad to get some help!


My Models

class Project < ActiveRecord::Base
  attr_accessible :title, :description, :presentation_type, :note,
                  :project_schools_attributes, :schools_attributes, :teachers_attributes, :pupils_attributes,
                  :users_attributes


  validates_presence_of :title
  validates_presence_of :description
  validates_presence_of :presentation_type


  has_many :project_schools,                :dependent => :destroy
  accepts_nested_attributes_for :project_schools
  has_many :schools,                        :through => :project_schools
  #accepts_nested_attributes_for :schools,   :reject_if => :all_blank

  has_many :pupils,                         :dependent => :destroy
  accepts_nested_attributes_for :pupils,    :reject_if => :all_blank
  has_many :users,                          :through => :pupils

  has_many :teachers,                       :dependent => :destroy
  accepts_nested_attributes_for :teachers,  :reject_if => :all_blank

  has_many :users,                          :through => :teachers
  #accepts_nested_attributes_for :users,    :reject_if => :all_blank
end

class ProjectSchool < ActiveRecord::Base
  attr_accessible :role_in_project, :comment,
                  :school_attributes, :school_id, :project_id

  belongs_to :school
  accepts_nested_attributes_for :school
  belongs_to :project
end

class School < ActiveRecord::Base
  attr_accessible :email, :fax, :fon, :name, :place, :street, :type_of_school, :www, :zip

  has_many :project_schools
  has_many :projects, :through => :project_schools
  has_many :users # in real live they are named teachers and pupils but in this case the association goes directly to a user_id, not to teacher/pupil model


  validates_presence_of :name, :type_of_school, :street, :place, :zip, :fon
  validates :email, :format => { :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create }, :allow_blank => true
end

class Teacher < ActiveRecord::Base
  attr_accessible :role_in_project, :user, :user_attributes, :project_id, :user_id

  belongs_to :project
  belongs_to :user
  accepts_nested_attributes_for :user

  serialize :role_in_project
end

class Pupil < ActiveRecord::Base
  attr_accessible :classname, :user_attributes #, :project_id, :user_id

  belongs_to :project
  belongs_to :user
  accepts_nested_attributes_for :user
end

class User < ActiveRecord::Base
  serialize :roles

  belongs_to :school
  has_many :teachers
  has_many :pupils
  has_many :projects, :through => :teachers
  has_many :projects, :through => :pupils

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :gender, :firstname, :name, :street, :place, :title, :faculty, :assignment,
                  :classname, :zip, :fon, :fax, :school_id, :roles,
                  :password, :added_by_user_id, :password_confirmation, :remember_me

  # Include default devise modules. Others available are:
  # :token_authenticatable, :confirmable,
  # :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable#, :validatable

  after_initialize :init
  def init
    # the default guest user
    self.roles  ||= ['default']           #will set the default value only if it's nil
  end
end

My controller

class ProjectsController < ApplicationController
  require 'axlsx'
  before_filter :load_page, only: [:show, :index, :destroy]

  def new
    @project = Project.new
  end

  def create
    @project = Project.new(params[:project])

    respond_to do |format|
      if @project.save
        sign_in(:user, @project.teachers[0].user) unless user_signed_in?

        # TODO: send mail

        # save as excel file in dropbox
        save_in_dropbox(@project)

        format.html { redirect_to @project, notice: t('project.was_created') }
      else
        logger.debug @project.errors.inspect
        format.html { render action: "new" }
      end
    end
  end
end

projects/_form.html.haml

%h1
  = t('project.register_headline')
= simple_form_for( setup_project(@project), :html => {:class => 'form-horizontal'} )do |f|
  = f.error_notification
  #teachers-wrapper.well
    %span.jumpanchor#lehrkraft_anchor
    %fieldset.form-inputs
      = f.simple_fields_for :teachers do |teacher|
        = render "teacher_fields", :f => teacher


  .school-wrapper.well
    %span.jumpanchor#schule_anchor
    %h2
      Informationen zur Schule
    %fieldset.form-inputs
      = f.simple_fields_for :project_schools do |project_school|
        = render "school_fields", :f => project_school
  .project-wrapper.well
    %span.jumpanchor#projekt_anchor
    %h2
      Informationen zum Projekt der Schüler
    %fieldset.form-inputs
      = f.hidden_field :id
      = f.input :title, :input_html => { :class => 'span6' }
      = f.input :description, :input_html => { :class => 'span6', rows: 5 }
      = f.input :presentation_type, collection: ['theoretisch', 'experimentell'], as: :radio_buttons, :class => 'controls-row', :input_html => { :class => 'inline' }
      .clearfix
      = f.input :note, :input_html => { :class => 'span6', rows: 3 }
  .pupils-wrapper.well
    %span.jumpanchor#schuler_anchor
    %fieldset.form-inputs
      = f.simple_fields_for :pupils do |pupil|
        = render "pupil_fields", :f => pupil

projects/_teacher_fields.html.haml
= f.simple_fields_for :user do |user|
  =# render "teacher_user_fields", :f => user
  %h2
    Betreuende Lehrkraft
  - if user_signed_in?
    = user.input :email, :disabled => true, :input_html => {:class => 'email_validation'}
  - else
    = user.input :email, :autofocus => true, :input_html => {:class => 'email_validation'}, :hint => 'Dies muß Ihre eigene E-Mailadresse sein!'
  .details
    =# user.hidden_field :id
    = user.input :id
    = user.input :gender, collection: [:female, :male]
    = user.input :title
    = user.input :firstname
    = user.input :name
    = user.input :faculty
    = user.input :fon
    = user.input :fax

It is Rails 3.2 with Ruby 1.9.3

Community
  • 1
  • 1
BBQ Chef
  • 696
  • 4
  • 11
  • 21
  • i believe the HABTM relationship is the culprit here. The behavior you look for is standard for other relationships, but wouldn't work with HABTM since you need to have an intermediate record in the (hidden) join table between your pupils and projects. Personnaly, i always avoid HABTM for such kind of problems, and prefer to have an explicit model / table with explicit belongs_to / has_many :through. – m_x Mar 03 '13 at 10:34
  • 1
    Some people already experienced the same problem as you : http://stackoverflow.com/questions/1847641/trying-to-use-accepts-nested-attributes-for-and-has-and-belongs-to-many-but-the , http://stackoverflow.com/questions/4924156/habtm-and-accepts-nested-attributes-for. The former would be the approach i'd use. – m_x Mar 03 '13 at 10:35
  • Thanks for your comments/links. It was helpful! Even if the key message "using accepts_nested_fields_for to populate the join table is impossible" isn't what I wanted to read... I updated my controller code above. – BBQ Chef Mar 03 '13 at 20:22
  • i would not say for sure it's impossible (did not try myself). Now that i remember, i once saw a question like this where the guy tried to chain nested attributes (nested attributes on association, then nested attributes on association's association). I can't find it now, neither remember if he was successful. – m_x Mar 03 '13 at 21:41
  • 1
    One advice, though : you should push all this controller code down to the model, and create your own custom `my_custom_save` method for this kind of case. Your controllers will be thinner, your logic more readable and the whole thing more maintainable (you can even wrap the logic in a transaction, and perform your save_in_dropbox in the process) – m_x Mar 03 '13 at 21:44
  • hey, found a more or less similar question to the one i was talking about : http://stackoverflow.com/questions/5932934/rails-3-nested-model-form-2-levels-deep-using-accepts-nested-attributes-for . Seems it works. Problem is, you will certainly get caught in a mess with presence validations - to save your time, know that using `inverse_of` on `has_many` solves "unexistent parent presence validation" chicken-and-egg problems. – m_x Mar 03 '13 at 21:50

0 Answers0