has_many - edit the association

Stephan,

  > I have a has_and_belongs_to_many relationship, let's say
  > class Student < ActiveRecord::Base
  > has_and_belongs_to_many :courses
  > end
  > class Course < ActiveRecord::Base
  > has_and_belongs_to_many :students
  > end

CRUDify/complexify your models, and your life/code will be simpler:
add 1 model - Enrolment - and use has_many :through instead of hbtm

Something like (untested) :

  class Student
      has_many :enrolments , :dependent => :destroy
      has_many :courses , :through => :enrolments
  end
  class Course
      has_many :enrolments , :dependent => :destroy
      has_many :studends , :through => :enrolments
  end
  class Enrolment
     belongs_to :course
     belongs_to :studen
  end

You would then create enrolments explicitely

    Enrolment.create(:student => a_student, :course => a_course)

(you'd place the validations rules in the enrolment model).

For more about this CRUDification, see slides 24 and following of
"World of resources", by DHH

   http://media.rubyonrails.org/presentations/worldofresources.pdf

Alain Ravet

Stephan

  > Thanks - sounds good. What would the controller's update method
  > look like then?

By creating a new model - Enrolment - you simplify your controller by
having the model do all the - model - validation.

  > Enrolment.create will manipulate the database, while I want to first
  > perform validations (must have at least one 4-credit course, for
  > example), before the database-saves.

Something like (untested) :

  class Enrolment < ActiveRecord::Base
     belongs_to :course
     belongs_to :student

  protected
     def validate
        errors.add("student", "does not have enough credits") unless
student.credits >= 4
     end
  end

API validations :
http://api.rubyonrails.com/classes/ActiveRecord/Validations.html#M000931

Alain Ravet

Stephan

My code was just an example; adapt for your needs.

   > The first "Enrolment.create" might fail, even if the parameters
   > submitted from the form contain a 4-credits course (unless the
   > controller does some sorting, which doesn't look proper either).

I don't understand :

  class Enrolment < ActiveRecord::Base
    ..
    def validate
      errors.add("student", "does not have enough credits") unless
student.credits >= 4
    end
  end

will only fail if student.credits < 4

In the controller you'd have something like

  def register
    student = Student.find(params[:student_id])
    course = Course .find(params[:course_id])

    if Enrolment.create(student, course)
       ..
    else
       flash[:notice] = "student cannot be enrolled for this course"
       ..
    end
  end

Alain Ravet

Stephan,

  > In the form there are checkboxes for a whole bunch of courses.
  > So there would be a whole sequence of
  >
  > Enrolment.create(student, course1) # course1 was checked off.
  > Enrolment.create(student, course2) # course2 was checked off.
  > Enrolment.create(student, course3) # course3 was checked off.
  >
  > But validation depends on the whole set (does one of them have 4
  > credits).

I guess you have two options :
- check the form values in the controller before creating any new
record (the validation logic could be moved to the model, in a class
method.),
or
- add a more complex (multiple) creator in the model.

Alain Ravet