Has_many :through: zusätz licher Join für through-Model?

Um ein paar der neuen Rails-Features auszuprobieren, baue ich eine
kleine Filmdatenbank. Darin gibt es Filme, Personen, Rollen und
RollenTypen. RollenTypen gibt es “Schauspieler” und “Regisseur”. Ich
möchte nun für Personen eine Assoziation definieren, die die Person mit
allen Filmen verknüpft, in denen sie mitgespielt hat.

Das hatte ich mir zunächst so gedacht

class Person < ActiveRecord::Base
has_many :roles, :include => :role_type
has_many :acts_in, :through => :roles, :source => :movie,
:order => ‘release_date’,
:conditions => { :roles => { :role_type => { :name => ‘Actor’ } } }

Es funktioniert aber nicht, weil das :include von roles nicht an die
abgeleitete acts_in weitergegeben wird. Nächster Versuch

class Person < ActiveRecord::Base
has_many :roles
has_many :acts_in, :through => :roles, :source => :movie,
:order => ‘release_date’,
:include => :role_type,
:conditions => { :roles => { :role_type => { :name => ‘Actor’ } } }

Geht auch nicht, weil :include sich auf das Zielmodel, nämlich Movie,
bezieht.

Ist es ohne handgeschriebenes SQL möglich, eine Bedingung zu
formulieren, die sich auf ein weiteres, zu dem through-Objekt gejointes
Objekt bezieht?

Michael


Michael S.
mailto:[email protected]
http://www.schuerig.de/michael/

Hallo Michael,

hier mein Lösungsvorschlag:

class Person < ActiveRecord::Base
has_many :films
has_many :roles, :through => :films, :uniq => true
end

class Film < ActiveRecord::Base
belongs_to :person
belongs_to :role
end

class Role < ActiveRecord::Base
has_many :films
has_many :people, :through => :films
end

Das sind alle Modelle die ich benötige.
Role entspricht den von Dir definierten Rollentypen

p = Person.find_by_name(‘Clint Eastwood’)

Jetzt kannst ich alle Filme abfragen in welchen Clint Eastwood z.B.
als Schauspieler mitgespielt hat:

p.roles.find_by_name(‘Schauspieler’).films
oder
p.films.all(:conditions => [‘role_id = 1’]) falls die Rolle
‘Schauspieler’ die id = 1 hat.

Die zweite Abfrageform sieht nicht so elegant aus ist aber schneller
in der Ausführung.

Gruss,
Roman

Am 08.03.2009 um 01:11 schrieb Michael S.:

On Sunday 08 March 2009, Roman Sladeczek wrote:

belongs_to :person
belongs_to :role
end

class Role < ActiveRecord::Base
has_many :films
has_many :people, :through => :films
end

Ja, das geht, meine Modellierung ist aber anders, das Role-Model hat
keinen String, der die Art der Rolle bestimmt, sondern einen FK auf
RoleType. Das genau ist die Schwierigkeit.

class Role < ActiveRecord::Base
belongs_to :person
belongs_to :role_type
belongs_to :movie
validates_presence_of :person_id, :role_type_id, :movie_id
end

class RoleType < ActiveRecord::Base
validates_presence_of :name
end

Mir ist vollkommen klar, dass ich mir für diesen Beispielfall die
Modellierung so zurecht biegen könnte, dass es funktioniert. Das möchte
ich nicht, weil das Ergebnis, um das es mir geht, nicht ein fertiges
Produkt, sondern eine neue Erkenntnis ist.

Ich möchte redundante Typ-Strings im Role-Model vermeiden und diese in
ein weiteres Model, RoleType, herausziehen. Dadurch vermeide ich bei
der DB-Modellierung Update- und Delete-Anomalien. Außerdem will ich
keine IDs als Parameter für Bedingungen angeben. – Ja, ich weiß
natürlich, dass das starke Einschränkungen sind, ich will aber sehen,
ob ich trotzdem mit AR das Ziel erreichen kann.

Anders gesagt, ich möchte AR dazu bringen, SQL zu erzeugen, das etwa so
aussieht:

SELECT … FROM people
JOIN roles ON roles.person_id = people.id
JOIN movies ON roles.movie_id = movies.id
JOIN role_types ON roles.role_type_id = role_types.id
WHERE role_types.name = ‘Director’

Michael


Michael S.
mailto:[email protected]
http://www.schuerig.de/michael/

On Sunday 08 March 2009, Michael S. wrote:

Anders gesagt, ich möchte AR dazu bringen, SQL zu erzeugen, das etwa
so aussieht:

SELECT … FROM people
JOIN roles ON roles.person_id = people.id
JOIN movies ON roles.movie_id = movies.id
JOIN role_types ON roles.role_type_id = role_types.id
WHERE role_types.name = ‘Director’

So kommt zumindest im Ergebnis heraus, was ich haben möchte:

class Person < ActiveRecord::Base
has_many :roles, :include => :role_type

module RoleTypeExtensions
def as_actor
self.find(:all,
:joins => ‘JOIN role_types ON roles.role_type_id =
role_types.id’,
:conditions => { :role_types => { :name => name }})
end
end

has_many :movies, :through => :roles, :extend => RoleTypeExtensions

named_scope actors,
:order => ‘lastname, firstname’,
:joins => { :roles => :role_type },
:conditions => { :roles => { :role_types => { :name => ‘Actor’ }}}

named_scope :with_movie_in_year, lambda { |year|
{
:joins => { :roles => :movie },
:conditions => [“movies.release_date between date(‘:year-01-01’)
and date(‘:year-12-31’)”,
{ :year => year }]
}
}

end

@actor.movies.as_actor
Person.actors.with_movie_in_year(2004)

Michael


Michael S.
mailto:[email protected]
http://www.schuerig.de/michael/