Forum: Rails Germany has_many :through: zusätz licher Join für through-Model?

Bce1d1b7c3ec7b577dcb42e254899e6b?d=identicon&s=25 Michael Schuerig (Guest)
on 2009-03-08 01:12
(Received via mailing list)
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 Schuerig
mailto:michael@schuerig.de
http://www.schuerig.de/michael/
A28d1a1f0f6150c0513c807ed51eec9b?d=identicon&s=25 Roman Sladeczek (wowo08)
on 2009-03-08 09:39
(Received via mailing list)
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 Schuerig:
Bce1d1b7c3ec7b577dcb42e254899e6b?d=identicon&s=25 Michael Schuerig (Guest)
on 2009-03-08 11:03
(Received via mailing list)
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 Schuerig
mailto:michael@schuerig.de
http://www.schuerig.de/michael/
Bce1d1b7c3ec7b577dcb42e254899e6b?d=identicon&s=25 Michael Schuerig (Guest)
on 2009-03-09 00:54
(Received via mailing list)
On Sunday 08 March 2009, Michael Schuerig 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 Schuerig
mailto:michael@schuerig.de
http://www.schuerig.de/michael/
This topic is locked and can not be replied to.