More than one has_many :through association between the same

I wonder if you can have more than one has_many :through association
between
2 models.

For example…

I have a model Teacher and a model Class

Now, 1 Teacher works in many Classes, right?. So I need a join model
like

class Work < ActiveRecord::Base

belongs_to :teacher

belongs_to :class

end

But I also would like to know if a teacher CAN teach a class before I
assign
him or her to one class. So I would like to have a new join model like
“CanTeach” :

class CanTeach< ActiveRecord::Base

belongs_to :teacher

belongs_to :class

end

The question is… can I do that? Because now the Teacher and Class
Models
would look like…

class Teacher< ActiveRecord::Base

has_many :works

has_many :classes, :through => :works

has_many :works

has_many :classes, :through => :can_teach

end

class Class < ActiveRecord::Base

has_many :works

has_many :teachers, :through => :works

has_many :works

has_many :teachers, :through => :can_teach

end

but this doesn’t work…

Anyone knows how to do this?

I would be very interested in an answer, too…

But I suggest you read this:
http://rubyonrails.org/api/classes/ActiveRecord/Associations/ClassMethods.html#M000530

And try to work around your problem with the the following option:

:source: Specifies the source association name used by has_many :through
queries. Only use it if the name cannot be inferred from the
association. has_many :subscribers, :through => :subscriptions will look
for either +:subscribers+ or +:subscriber+ on Subscription, unless a
+:source+ is given.

So you can have aclass.capable_teachers and
aclass.actually_teaching_teachers or something like that. Can you tell
me if it works?

Nauhaie

I first suggest you find a better model name thatn class, that could
muck things up, alot. Perhaps Lesson.

j`ey
http://www.eachmapinject.com
Federico Fernádez wrote:

I have a model Teacher and a model Class

Nauhaie wrote:

I would be very interested in an answer, too…

But I suggest you read this:
http://rubyonrails.org/api/classes/ActiveRecord/Associations/ClassMethods.html#M000530

And try to work around your problem with the the following option:

:source: Specifies the source association name used by has_many :through
queries. Only use it if the name cannot be inferred from the
association. has_many :subscribers, :through => :subscriptions will look
for either +:subscribers+ or +:subscriber+ on Subscription, unless a
+:source+ is given.

So you can have aclass.capable_teachers and
aclass.actually_teaching_teachers or something like that. Can you tell
me if it works?

Nauhaie

If I understood the documentation right, I could do this:

class Teacher< ActiveRecord::Base

has_many :lessons, :through => :works, :source => :teacher

has_many :classes, :through => :can_teach, :source => teacher

end

class Lesson< ActiveRecord::Base

has_many :teachers, :through => :works, :source => :lesson

has_many :teachers, :through => :can_teach, , :source => :lesson

end

Keeping the classes “CanTeach and Work” without changes.

I will try this and let you know.

Federico F. wrote:

Nauhaie wrote:

I would be very interested in an answer, too…

But I suggest you read this:
http://rubyonrails.org/api/classes/ActiveRecord/Associations/ClassMethods.html#M000530

And try to work around your problem with the the following option:

:source: Specifies the source association name used by has_many :through
queries. Only use it if the name cannot be inferred from the
association. has_many :subscribers, :through => :subscriptions will look
for either +:subscribers+ or +:subscriber+ on Subscription, unless a
+:source+ is given.

So you can have aclass.capable_teachers and
aclass.actually_teaching_teachers or something like that. Can you tell
me if it works?

Nauhaie

If I understood the documentation right, I could do this:

class Teacher< ActiveRecord::Base
has_many :lessons, :through => :works, :source => :teacher
has_many :classes, :through => :can_teach, :source => teacher
end

class Lesson< ActiveRecord::Base
has_many :teachers, :through => :works, :source => :lesson
has_many :teachers, :through => :can_teach, , :source => :lesson
end

Keeping the classes “CanTeach and Work” without changes.

I will try this and let you know.

I responded to your email about this yesterday but I guess my response
was lost. You must have unique names for associations within a class. If
you reuse the same name, only the last has_many will be available. Just
name them differently. And DON’T use :class or :classes - that will just
bork everything up as “class” is both a keyword and a method on Object
in ruby. You can spell it klass or clazz if you want.

You can also take the approach of having only one join model with an
attribute that indicates the state of the relationship, such as whether
the teacher is qualified to teach the class vs if he is currently
teaching it. I like this way best.

class Teaching < ActiveRecord::Base
belongs_to :teachers
belongs_to :courses
end

class Course < ActiveRecord::Base
has_many :teachings
has_many :teachers, :through => :teachings
end

class Teacher < ActiveRecord::Base
has_many :teachings
has_many :courses, :through => :teachings
has_many :courses_can_teach, :through => :teachings,
:class_name => “Course”, :conditions => “status = ‘can’”
has_many :courses_does_teach, :through => :teachings,
:class_name => “Course”, :conditions => “status = ‘does’”
end


Josh S.
http://blog.hasmanythrough.com

Uhh… I’m not sure I get the difference between :source and
:class_name…

:class_name - specify the class name of the association. Use it only if
that name canâ??t be inferred from the association name. So has_many
:products will by default be linked to the Product class, but if the
real class name is SpecialProduct, youâ??ll have to specify it with this
option.

:source: Specifies the source association name used by has_many :through
queries. Only use it if the name cannot be inferred from the
association. has_many :subscribers, :through => :subscriptions will look
for either +:subscribers+ or +:subscriber+ on Subscription, unless a
+:source+ is given.

Huh?

In fact, Rails Recipes says:

class Magazine < ActiveRecord::Base
has_many :subscriptions
has_many :readers, :through => :subscriptions
has_many :semiannual_subscribers,
:through => :subscriptions,
:source => :reader,
:conditions => [‘length_in_issues = 6’]
end

They say that :source should be used instead of :class_name…
Frederico, can you make :source work in your case?

Federico F. wrote:

Nauhaie wrote:

I would be very interested in an answer, too…

But I suggest you read this:
http://rubyonrails.org/api/classes/ActiveRecord/Associations/ClassMethods.html#M000530

And try to work around your problem with the the following option:

:source: Specifies the source association name used by has_many :through
queries. Only use it if the name cannot be inferred from the
association. has_many :subscribers, :through => :subscriptions will look
for either +:subscribers+ or +:subscriber+ on Subscription, unless a
+:source+ is given.

So you can have aclass.capable_teachers and
aclass.actually_teaching_teachers or something like that. Can you tell
me if it works?

Nauhaie

If I understood the documentation right, I could do this:

class Teacher< ActiveRecord::Base

has_many :lessons, :through => :works, :source => :teacher

has_many :classes, :through => :can_teach, :source => teacher

end

class Lesson< ActiveRecord::Base

has_many :teachers, :through => :works, :source => :lesson

has_many :teachers, :through => :can_teach, , :source => :lesson

end

Keeping the classes “CanTeach and Work” without changes.

I will try this and let you know.

It didn’t work, at all. Now when I do:

t = Teacher.find(:first).contracts I get a
HasManyThroughAssociationNotFoundError

and if I do:

l = Lesson.find(:all) I get another error.

In summary, that’s not the way to do it (you need the “has many”
:JoinTable statement, and/or I didn’t understand the “:source” option.

Josh S. wrote:

Federico F. wrote:

Nauhaie wrote:

I would be very interested in an answer, too…

But I suggest you read this:
http://rubyonrails.org/api/classes/ActiveRecord/Associations/ClassMethods.html#M000530

And try to work around your problem with the the following option:

:source: Specifies the source association name used by has_many :through
queries. Only use it if the name cannot be inferred from the
association. has_many :subscribers, :through => :subscriptions will look
for either +:subscribers+ or +:subscriber+ on Subscription, unless a
+:source+ is given.

So you can have aclass.capable_teachers and
aclass.actually_teaching_teachers or something like that. Can you tell
me if it works?

Nauhaie

If I understood the documentation right, I could do this:

class Teacher< ActiveRecord::Base
has_many :lessons, :through => :works, :source => :teacher
has_many :classes, :through => :can_teach, :source => teacher
end

class Lesson< ActiveRecord::Base
has_many :teachers, :through => :works, :source => :lesson
has_many :teachers, :through => :can_teach, , :source => :lesson
end

Keeping the classes “CanTeach and Work” without changes.

I will try this and let you know.

I responded to your email about this yesterday but I guess my response
was lost. You must have unique names for associations within a class. If
you reuse the same name, only the last has_many will be available. Just
name them differently. And DON’T use :class or :classes - that will just
bork everything up as “class” is both a keyword and a method on Object
in ruby. You can spell it klass or clazz if you want.

You can also take the approach of having only one join model with an
attribute that indicates the state of the relationship, such as whether
the teacher is qualified to teach the class vs if he is currently
teaching it. I like this way best.

class Teaching < ActiveRecord::Base
belongs_to :teachers
belongs_to :courses
end

class Course < ActiveRecord::Base
has_many :teachings
has_many :teachers, :through => :teachings
end

class Teacher < ActiveRecord::Base
has_many :teachings
has_many :courses, :through => :teachings
has_many :courses_can_teach, :through => :teachings,
:class_name => “Course”, :conditions => “status = ‘can’”
has_many :courses_does_teach, :through => :teachings,
:class_name => “Course”, :conditions => “status = ‘does’”
end


Josh S.
http://blog.hasmanythrough.com

Hello Josh! wow… thanks for the help, but let me see if I got it:

has_many :courses_can_teach, :through => :teachings,
:class_name => “Course”, :conditions => “status = ‘can’”

This means that I need a table called “courses_can_teach” with the same
structure of the table “courses”, right? The database will have 2
different tables, but as you use :class_name => “Course” they will be
represented with only one model class called “Course”, am I right?

Then you use :conditions => “status = …” so you can use 1 Join Table
instead of two.

However, even though your suggestion is good, I don’t want one join
table, but two, becase I want the Join Table “teachings” to use a
different set of attributes than the Join Table “capabilities”, and it
would be strange to mix both sets in one Join Table. If I want to use
more than one Join Table I don’t know what to do.

In addition, there is something strange in your solution, because I’ll
be able to do this:

t1 = Teacher.find(:first).courses_can_teach
t2 = Teacher.find(:first).courses_does_teach

and t1 << course, etc…

But what if, given a course, I want to do operations over the teachers
associated to it.

If I do this:

c = Course.find(:first).teacher

what am I going to get? A teacher who can teach that course, or a
teacher who does teach that course?

I will probably have to make the searching methods by hand. Then, I
would say that in rails you have to decide between freedom to do your
database models pretty and efficient, and the benefits of using
ActiveRecords Associations.

Nauhaie wrote:

In fact, Rails Recipes says:

class Magazine < ActiveRecord::Base
has_many :subscriptions
has_many :readers, :through => :subscriptions
has_many :semiannual_subscribers,
:through => :subscriptions,
:source => :reader,
:conditions => [‘length_in_issues = 6’]
end

They say that :source should be used instead of :class_name…
Frederico, can you make :source work in your case?

I haven’t tried Josh’s reply yet, because my real model is a bit more
complicated than the example. I was doing the migrations now, I’m late
for college, so I will complete it tomorrow, and let you know if it
worked with “source” and/or “class_name”.

Federico F. wrote:

Nauhaie wrote:

In fact, Rails Recipes says:

class Magazine < ActiveRecord::Base
has_many :subscriptions
has_many :readers, :through => :subscriptions
has_many :semiannual_subscribers,
:through => :subscriptions,
:source => :reader,
:conditions => [‘length_in_issues = 6’]
end

They say that :source should be used instead of :class_name…
Frederico, can you make :source work in your case?

I haven’t tried Josh’s reply yet, because my real model is a bit more
complicated than the example. I was doing the migrations now, I’m late
for college, so I will complete it tomorrow, and let you know if it
worked with “source” and/or “class_name”.

Ok… I could make it work with 2 join tables. It was a little pain on
the ass to get it set, but then I felt good… I have what I wanted.
Below is the explanation of what I did. Notice that I changed some class
names for the sake of understandability. Also please note that my real
application is more complex, which is the reason why using 2 join tables
may look like a strange decision in this example, but it is not (or not
that much) in my application.

class Scheduling< ActiveRecord::Base

the teachers work in some courses through schedulings

belongs_to :teachers
belongs_to :courses
end

class Screening< ActiveRecord::Base

the teachers can work in some courses through screenings

belongs_to :teachers
belongs_to :courses

end

class Course < ActiveRecord::Base
has_many :schedulings
has_many :screenings

has_many :teachers_does_teach, :through => :schedulings, :source => :teacher
has_many :teachers_can_teach, :through => :screenings, :source => :teacher
end

class Teacher < ActiveRecord::Base
has_many :schedulings
has_many :screenings

has_many :courses_does_teach, :through => :schedulings, :source => :course
has_many :courses_can_teach, :through => :screenings, :source => :course
end

And I have the following tables:

  • teachers, courses, screenings, scheduling AND…
  • teachers_can_teach, teachers_does_teach with the same structure as
    teachers
  • courses_can_teach, courses_does_teach with the same structure as
    courses

So now I can do this:

screen = Screening.new
t = Tutor.find(11)
c = Course.find(3)
t.screenings << screen
c.screenings << screen
s.year = 1974
s.college = “University of Minesota”
s.comments = “Graduated with honors”
s.save

sched = Scheduling.new
t = Tutor.find(11)
c = Course.find(3)
t.schedulings << sched
c.schedulings << sched
s.day = “monday”
s.save

Then I can do things like:

t.courses_can_teach.find(:first).name
t.courses_does_teach_count

AND I don’t have the following methods:

t.courses
c.teachers

which is fine, since that wouldn’t make sense in my example.

Now… I don’t know if using two has-many-through associations is better
than using one (as Josh suggested) but I’m happy to know that you can do
it in rails. I really don’t like having to change my ER diagrams because
of framework restrictions, do you?

Federico F. wrote:

Ok… I could make it work with 2 join tables. It was a little pain on
the ass to get it set, but then I felt good… I have what I wanted.
Below is the explanation of what I did. Notice that I changed some class
names for the sake of understandability. Also please note that my real
application is more complex, which is the reason why using 2 join tables
may look like a strange decision in this example, but it is not (or not
that much) in my application.

Then I can do things like:

t.courses_can_teach.find(:first).name
t.courses_does_teach_count

AND I don’t have the following methods:

t.courses
c.teachers

which is fine, since that wouldn’t make sense in my example.

Now… I don’t know if using two has-many-through associations is better
than using one (as Josh suggested) but I’m happy to know that you can do
it in rails. I really don’t like having to change my ER diagrams because
of framework restrictions, do you?

There’s no problem with having multiple has_many :through associations
in a model. If that’s the best fit to model your problem, then that’s
what you should do. Looks like you’ve worked out a good solution. Nicely
done.


Josh S.
http://blog.hasmanythrough.com

Josh S. wrote:

Federico F. wrote:

Ok… I could make it work with 2 join tables. It was a little pain on
the ass to get it set, but then I felt good… I have what I wanted.
Below is the explanation of what I did. Notice that I changed some class
names for the sake of understandability. Also please note that my real
application is more complex, which is the reason why using 2 join tables
may look like a strange decision in this example, but it is not (or not
that much) in my application.

Then I can do things like:

t.courses_can_teach.find(:first).name
t.courses_does_teach_count

AND I don’t have the following methods:

t.courses
c.teachers

which is fine, since that wouldn’t make sense in my example.

Now… I don’t know if using two has-many-through associations is better
than using one (as Josh suggested) but I’m happy to know that you can do
it in rails. I really don’t like having to change my ER diagrams because
of framework restrictions, do you?

There’s no problem with having multiple has_many :through associations
in a model. If that’s the best fit to model your problem, then that’s
what you should do. Looks like you’ve worked out a good solution. Nicely
done.


Josh S.
http://blog.hasmanythrough.com

Yes, that’s what I think too… and this issue made me think about the
philosophy behing ActiveRecords Associations. In an initial stage you
have 1 Model - 1 Table, but then, as the ER Diagramm gets more complex
you will end with more tables than models, if you want Rails to generate
methods for you. I’m not sure, but perhaps this is something to improve
in the framework (or maybe it is something normal and good… I’m just
thinking loud)

Thanks for your help. I’m a newbie in ror and I’m starting to love it in
part due to the nice community.