# Has\_many - edit the association

**URL:** <https://www.ruby-forum.com/t/has-many-edit-the-association/103979>\
**Category:** Rails\
**Created:** [May 12, 2007, 6:57am UTC](https://www.ruby-forum.com/t/has-many-edit-the-association/103979 "2007-05-12T06:57:13Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![stephanwehner](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/0ea827/32.png) [@stephanwehner](https://www.ruby-forum.com/u/stephanwehner)\
**Post date:** [May 12, 2007, 6:57am UTC](https://www.ruby-forum.com/t/has-many-edit-the-association/103979/1 "2007-05-12T06:57:13Z")

</div>

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

Now I have a form for a student, with a checkbox for applicable courses.

How do I validate the courses checked against some conditions, so that  
if validation fails, the new associations are not saved?

I try, for example, in the student controller’s update method

@student.attributes = params[:student]

# gather the course assignment from other parameters…

# updated\_courses = …

# 

@student.courses = updated\_courses.collect { |u| @student.courses.build  
( u.attributes ) }  
@successful = @student.save

But the courses for the student are then actually wiped out.

What is an easy way?

Thanks a lot,

Stephan

---

<div class="post-metadata">

**Author:** ![stephanwehner](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/0ea827/32.png) [@stephanwehner](https://www.ruby-forum.com/u/stephanwehner)\
**Post date:** [May 12, 2007, 12:58pm UTC](https://www.ruby-forum.com/t/has-many-edit-the-association/103979/2 "2007-05-12T12:58:21Z")

</div>

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](http://media.rubyonrails.org/presentations/worldofresources.pdf)

## Alain R.

[blog.ravet.com/](http://blog.ravet.com/)

---

<div class="post-metadata">

**Author:** ![stephanwehner](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/0ea827/32.png) [@stephanwehner](https://www.ruby-forum.com/u/stephanwehner)\
**Post date:** [May 12, 2007, 4:27pm UTC](https://www.ruby-forum.com/t/has-many-edit-the-association/103979/3 "2007-05-12T16:27:13Z")

</div>

> 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)
> 
> ```

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

More specific, how would the snippet I mentioned earlier look with this  
new Enrolment class:

@student.attributes = params[:student]

# gather the course assignment from other parameters…

# updated\_courses = …

# 

@student.courses = updated\_courses.collect { |u| @student.courses.build  
( u.attributes ) }  
@successful = @student.save

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.

Stephan

> ## Alain R.
> 
> [blog.ravet.com/](http://blog.ravet.com/)

---

<div class="post-metadata">

**Author:** ![stephanwehner](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/0ea827/32.png) [@stephanwehner](https://www.ruby-forum.com/u/stephanwehner)\
**Post date:** [May 12, 2007, 9:14pm UTC](https://www.ruby-forum.com/t/has-many-edit-the-association/103979/4 "2007-05-12T21:14:19Z")

</div>

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](http://api.rubyonrails.com/classes/ActiveRecord/Validations.html#M000931)

## Alain R.

[http://blog.ravet.com](http://blog.ravet.com)

---

<div class="post-metadata">

**Author:** ![stephanwehner](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/0ea827/32.png) [@stephanwehner](https://www.ruby-forum.com/u/stephanwehner)\
**Post date:** [May 13, 2007, 12:46am UTC](https://www.ruby-forum.com/t/has-many-edit-the-association/103979/5 "2007-05-13T00:46:18Z")

</div>

Alain R. wrote:

> 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

Thanks a lot.

This looks to me like it might not allow adding the first course to the  
student set of courses, if it doesn’t satisfy the “student.credits\>=4”  
condition.

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).

Also, the Enrolment.create would ensue a database save independently of  
other form-value validations of the student’s fields…

Am I missing something? I looked through the validations section of  
ActiveRecord , but couldn’t find anything applicable.

Thanks again.

Stephan

> API validations :  
> [Peak Obsession](http://api.rubyonrails.com/classes/ActiveRecord/Validations.html#M000931)
> 
> ## Alain R.
> 
> [http://blog.ravet.com](http://blog.ravet.com)

---

<div class="post-metadata">

**Author:** ![stephanwehner](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/0ea827/32.png) [@stephanwehner](https://www.ruby-forum.com/u/stephanwehner)\
**Post date:** [May 13, 2007, 7:04am UTC](https://www.ruby-forum.com/t/has-many-edit-the-association/103979/6 "2007-05-13T07:04:19Z")

</div>

Alain R. wrote:

> 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 R.
> 
> [http://blog.ravet.com](http://blog.ravet.com)

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).

Plus, if the last Enrolment.create fails, one would like the first one  
not to commit.

I’m getting the impression ActiveRecord doesn’t quite go as far as what  
I’m looking for.

I don’t quite understand why one can perform

student.courses = [… an array of courses …]

and this gets saved right away, without a chance to apply other changes  
to the student record for one-step-validation.

Stephan

---

<div class="post-metadata">

**Author:** ![stephanwehner](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/0ea827/32.png) [@stephanwehner](https://www.ruby-forum.com/u/stephanwehner)\
**Post date:** [May 13, 2007, 1:21am UTC](https://www.ruby-forum.com/t/has-many-edit-the-association/103979/7 "2007-05-13T01:21:35Z")

</div>

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 R.

[http://blog.ravet.com](http://blog.ravet.com)

---

<div class="post-metadata">

**Author:** ![stephanwehner](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/0ea827/32.png) [@stephanwehner](https://www.ruby-forum.com/u/stephanwehner)\
**Post date:** [May 16, 2007, 2:23am UTC](https://www.ruby-forum.com/t/has-many-edit-the-association/103979/8 "2007-05-16T02:23:09Z")

</div>

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 R.

[http://blog.ravet.com](http://blog.ravet.com)
