Forum: Rails Germany Frage zu named scope in einer association

9e0ec2384dd80c7f2c190e02e596e15b?d=identicon&s=25 Michael Kastner (Guest)
on 2009-06-25 11:18
(Received via mailing list)
Hallo,

ich stehe gerade vor einem Verständnisproblem betreffend named scopes und
associations.

Ich habe zwei Models Publication und Price die durch eine
has_many-Association
verbunden sind:


class Publication < ActiveRecord::Base
   has_many :prices
end

class Price < ActiveRecord::Base
   belongs_to :publication

   named_scope :effective,
               :conditions => ['valid_from <= ? AND valid_through >= ?',
                Date.today, Date.today]
end



Jeder Price hat eine durch einen Anfangs- und ein Endzeitpunkt
vorgegebene
Gültikeitsdauer.

Price.effective gibt also die Preise aus, die zum Zeitpunkt der Abfrage
gültig sind.

So weit so gut.

Jetzt benötige ich allerdings auch eine Möglichkeit, daß eine Product-Methode
sämtliche Publications zu erhalten, die Preise haben, die gerade gültig sind.

Ich könnte jetzt für Publication noch einen scope anlegen, der über include dann
den entsprechenden scope erzeugt:

class Publication < ActiveRecord::Base
   has_many :prices
   named_scope :effective,
               :include => :prices,
               :conditions => ['prices.valid_from <= ? AND
prices.valid_through
 >= ?', Date.today, Date.today]
end

Aber das erscheint mir nicht wirklich DRY. Gibt es denn nicht eine
Möglichkeit,
mir die gültigen Produkte unter Verwendung des unter Price bereits angelegten
scope zu erhalten?


Viele
Grüße
Michael Kastner
B2721cc23cfb835d957b886c7885abac?d=identicon&s=25 Mike Zaschka (mike_za)
on 2009-06-25 12:08
Michael Kastner wrote:

> Aber das erscheint mir nicht wirklich DRY. Gibt es denn nicht eine
> Möglichkeit,
> mir die gültigen Produkte unter Verwendung des unter Price bereits angelegten
> scope zu erhalten?

Genau das geht!

Mit dem definierten named_scope im Price-Model kannst du folgendes
machen:

some_publication.prices.effective

Damit bekommt Du alle Preise, die dem scope entsprechen und zu einer
Publikation gehören.

named_scopes 4tw!
B2721cc23cfb835d957b886c7885abac?d=identicon&s=25 Mike Zaschka (mike_za)
on 2009-06-25 12:12
Mike Zaschka wrote:
> Michael Kastner wrote:
>
>> Aber das erscheint mir nicht wirklich DRY. Gibt es denn nicht eine
>> Möglichkeit,
>> mir die gültigen Produkte unter Verwendung des unter Price bereits angelegten
>> scope zu erhalten?
>
> Genau das geht!
>
> Mit dem definierten named_scope im Price-Model kannst du folgendes
> machen:
>
> some_publication.prices.effective
>
> Damit bekommt Du alle Preise, die dem scope entsprechen und zu einer
> Publikation gehören.
>
> named_scopes 4tw!

Wohl zu schnell geschossen... Du wolltest ja die Publikationen haben. Da
musst Du wohl tatsächlich über den Scope im Publication-Model gehen!
9e0ec2384dd80c7f2c190e02e596e15b?d=identicon&s=25 Michael Kastner (Guest)
on 2009-06-25 12:13
(Received via mailing list)
Ich will aber nicht die Preise, sondern die Produkte:

alle Produkte, die Preise haben, die derzeit gültig sind

Mike Zaschka schrieb:
9e0ec2384dd80c7f2c190e02e596e15b?d=identicon&s=25 Michael Kastner (Guest)
on 2009-06-25 12:19
(Received via mailing list)
Kein Problem! Trotzdem vielen Dank für die Antwort.

Viele
Grüße
Michael Kastner

Mike Zaschka schrieb:
59528506e6297141161afcde91d677c9?d=identicon&s=25 Nicolai Reuschling (codeblogger)
on 2009-06-25 12:37
(Received via mailing list)
Hallo Michael.

Immer dann, wenn man merkt, es geht in der aktuellen Modellierung
nicht DRY, sollte man über sein Design nachdenken. Ich denke, Dir
fehlt einfach eine (weitere) Abstraktion. Schon mal über ein
zusätzliches (datenbankloses) Modell nachgedacht?

Viele
GrüßeNicolai
9e0ec2384dd80c7f2c190e02e596e15b?d=identicon&s=25 Michael Kastner (Guest)
on 2009-06-25 12:52
(Received via mailing list)
Hi,

ok, das Designproblem sehe ich ein, aber wo sollte das datenbanklose
Modell
weiterhelfen?

Viele
Grüße
Michael Kastner

Codeblogger schrieb:
Bce1d1b7c3ec7b577dcb42e254899e6b?d=identicon&s=25 Michael Schuerig (Guest)
on 2009-06-25 19:03
(Received via mailing list)
On Thursday 25 June 2009, Michael Kastner wrote:
>    has_many :prices
>                 Date.today, Date.today]
> So weit so gut.
>
> Jetzt benötige ich allerdings auch eine Möglichkeit, daß eine
> Product-Methode sämtliche Publications zu erhalten, die Preise haben,
> die gerade gültig sind.

Den letzten Satz kann ich nicht fehlerfrei parsen, vielleicht verstehe
ich dich deshalb nicht richtig.

>  >= ?', Date.today, Date.today]
>
> end

Macht

class Publication ...
  ...
  def current
    Prices.effective(:include => :publications).map(&:publication)
  end
end

was du haben willst?

Nachteilig daran ist, dass du Publication#current nicht noch weiter
durch Scoping einschränken kannst. Schau zur Inspiration vielleicht mal
in diese Präsentation

http://www.schuerig.de/michael/pres/kreative-assoz...

Michael

--
Michael Schuerig
mailto:michael@schuerig.de
http://www.schuerig.de/michael/
9e0ec2384dd80c7f2c190e02e596e15b?d=identicon&s=25 Michael Kastner (Guest)
on 2009-06-25 19:32
(Received via mailing list)
Hallo Michael,

Michael Schuerig schrieb:
>> Jetzt benötige ich allerdings auch eine Möglichkeit, daß eine
>> Product-Methode sämtliche Publications zu erhalten, die Preise haben,
>> die gerade gültig sind.
>
> Den letzten Satz kann ich nicht fehlerfrei parsen, vielleicht verstehe
> ich dich deshalb nicht richtig.

Mein Verbalgenerator war auf random gestellt ;-) Was ich sagen wollte,
war:

Jetzt suche ich eine Möglichkeit, über eine Publication-Methode sämtliche
Publications zu erhalten, die Preise haben, die gerade gültich sind.

> class Publication ...
>   ...
>   def current
>     Prices.effective(:include => :publications).map(&:publication)
>   end
> end
>
> was du haben willst?

Super, ja genau das tut es. Muß allerdings dann so heißen

class Publication ...
   ...
   def self.current
     Price.effective(:include => :publications).map(&:publication)
   end
end

>
> Nachteilig daran ist, dass du Publication#current nicht noch weiter
> durch Scoping einschränken kannst.

Das macht im Moment nichts. Vielleicht brauche ich garkein weiteres
Scoping.

> Schau zur Inspiration vielleicht mal
> in diese Präsentation
>
> http://www.schuerig.de/michael/pres/kreative-assoz...

Die Präsentation kenne ich. Die ist klasse. Ich schau eh ab und zu auf
Deinem
Blog vorbei ;-)

Vielen Dank für Deinen Tip. Hat mir gerade den Abend gerettet.

Viele
Grüße
Michael Kastner
59528506e6297141161afcde91d677c9?d=identicon&s=25 Nicolai Reuschling (codeblogger)
on 2009-06-25 19:37
(Received via mailing list)
Hallo,

die Lösung mag pragmatisch sein, aber unter OO-Gesichtspunkten ist sie
definitiv suboptimal.
Jetzt ist auf einmal Price von Publication ohne guten Grund abhängig.
Viel Spaß beim Testen, sag ich mal...

Nicolai
Bce1d1b7c3ec7b577dcb42e254899e6b?d=identicon&s=25 Michael Schuerig (Guest)
on 2009-06-26 00:25
(Received via mailing list)
On Thursday 25 June 2009, Codeblogger wrote:
> Hallo,
>
> die Lösung mag pragmatisch sein, aber unter OO-Gesichtspunkten ist
> sie definitiv suboptimal.

Fällt dir eine bessere Lösung ein?

> Jetzt ist auf einmal Price von Publication ohne guten Grund abhängig.

Das stimmt nicht, Price ist nicht von Publication abhängig, nur
Publication von Price, wofür es aber einen Grund gibt.

Es gibt zwar durchaus

class Price < ActiveRecord::Base
  belongs_to :publication
end

Das führt aber zu keiner Abhängigkeit. Die Model-Klasse Price ist
problemlos nutzbar, auch wenn keine Model-Klasse Publication existiert.
Falls dich die Assoziation ernsthaft stören sollte, könntest du sie auch
getrennt von der Basisfunktionalität von Price an geeigneter Stelle
definieren. Etwa mit Price.class_eval { belongs_to :publication } in
einem Initializer.

> Viel Spaß beim Testen, sag ich mal...

Wo siehst du da eine Schwierigkeit?

Vielleicht täusche ich mich oder sehe einfach das, was ich von mir
selbst kenne: ich habe den Eindruck, dass du mit Intuitionen auf Ruby-
Code reagierst, die in einer anderen Umgebung (Java?) entstanden sind,
und auf Ruby nicht zwangsläufig passen. Was meinst du?

Michael

--
Michael Schuerig
mailto:michael@schuerig.de
http://www.schuerig.de/michael/
59528506e6297141161afcde91d677c9?d=identicon&s=25 Nicolai Reuschling (codeblogger)
on 2009-06-26 09:15
(Received via mailing list)
Hi!

>> Jetzt ist auf einmal Price von Publication ohne guten Grund abhängig.
>
> Das stimmt nicht, Price ist nicht von Publication abhängig, nur
> Publication von Price [...]
>

Stimmt, genau das habe ich auch gemeint. Harmlos ist die Abhängigkeit
nicht.
Die Suche nach einer Publikation mit einem gültigen Preis könnte in
einer neuen Klasse abgebildet werden. Deren Verantwortung wäre es
dann, die Dependency zu verwalten.
Bce1d1b7c3ec7b577dcb42e254899e6b?d=identicon&s=25 Michael Schuerig (Guest)
on 2009-06-26 09:43
(Received via mailing list)
On Friday 26 June 2009, Codeblogger wrote:
> Hi!
>
> >> Jetzt ist auf einmal Price von Publication ohne guten Grund
> >> abhängig.
> >
> > Das stimmt nicht, Price ist nicht von Publication abhängig, nur
> > Publication von Price [...]
>
> Stimmt, genau das habe ich auch gemeint. Harmlos ist die Abhängigkeit
> nicht.

Warum nicht? Dass eine Publikation einen Preis hat, ist fachlich
vorgegeben.

> Die Suche nach einer Publikation mit einem gültigen Preis
> könnte in einer neuen Klasse abgebildet werden. Deren Verantwortung
> wäre es dann, die Dependency zu verwalten.

Das würde ich gerne sehen.

Wenn es eine Anforderung gäbe, gemeinsame Publikationen einer Menge von
Autoren zu finden, würdest du dafür auch eine eigene Hilfsklasse
anlegen? Oder würdest du alle derartigen Funktionen zu einer Klasse
(Repository?) zusammenfassen? Würdest du die Suchmethoden als Instanz-
oder Klassenmethoden implementieren?

Ich neige dazu, Repositories als nicht Rails-gemäß anzusehen. Der Rails-
Code selbst, wie auch eine Vielzahl an Erweiterungen, haben die
Erwartung etabliert, dass Methoden, die sich auf alle Objekte einer
Klasse beziehen, als Klassenmethoden implementiert sind. #find und named
scopes sind klare Beispiele
dafür.
Eine physikalische Aufteilung in verschiedene Belange ist einfach mit
concerned_with möglich

http://m.onkey.org/2008/9/15/active-record-tips-and-tricks

Michael

--
Michael Schuerig
mailto:michael@schuerig.de
http://www.schuerig.de/michael/
B2e6324065cefccbfa39426cc05765ce?d=identicon&s=25 Christoph Petschnig (christophpetschnig)
on 2009-06-26 11:30
Michael Kastner wrote:
> class Price < ActiveRecord::Base
>    named_scope :effective,
>                :conditions => ['valid_from <= ? AND valid_through >= ?',
>                 Date.today, Date.today]
> end

Ich wollte noch anmerken, dass obiger Code sehr heimtückisch ist. In der
Development-Umgebung läuft nämlich alles prima!

Wenn aber in Production `config.cache_classes = true` ist, wird das
Macro named_scope nur noch beim Laden der Anwendung ausgeführt. D.h. ich
übergebe hier dem Model nur noch die Startzeit des Servers, spätestens
nach 24 Stunden liefert die Abfrage aber veraltete Daten.

Prinzipiell könnte man dieses Problem umgehen, wenn man statt Date.today
ein Proc Object übergibt, im Fall der :conditions wird das aber nicht
funktionieren.

Nur meine 0,02 EUR. Beste Grüße

Christoph
9e0ec2384dd80c7f2c190e02e596e15b?d=identicon&s=25 Michael Kastner (Guest)
on 2009-06-26 11:39
(Received via mailing list)
Hi Christoph,

guter Hinweis für 0,02 EUR. Werd' ich gleich mal korrigieren ...

Vielen Dank

Michael Kastner

Christoph Petschnig schrieb:
Bce1d1b7c3ec7b577dcb42e254899e6b?d=identicon&s=25 Michael Schuerig (Guest)
on 2009-06-26 11:48
(Received via mailing list)
On Friday 26 June 2009, Christoph Petschnig wrote:
> Ich wollte noch anmerken, dass obiger Code sehr heimtückisch ist. In
> der Development-Umgebung läuft nämlich alles prima!
>
> Wenn aber in Production `config.cache_classes = true` ist, wird das
> Macro named_scope nur noch beim Laden der Anwendung ausgeführt. D.h.
> ich übergebe hier dem Model nur noch die Startzeit des Servers,
> spätestens nach 24 Stunden liefert die Abfrage aber veraltete Daten.
>
> Prinzipiell könnte man dieses Problem umgehen, wenn man statt
> Date.today ein Proc Object übergibt, im Fall der :conditions wird das
> aber nicht funktionieren.

Zwei Möglichkeiten:

named_scope :effective,
  :conditions => 'valid_from <= CURRENT_DATE AND valid_through >=
CURRENT_DATE'

Kennt MySQL CURRENT_DATE? Im SQL:1999-Standard steht es und PostgreSQL
kann es.

named_scope :effective, lambda { |*args|
  when = args.first || Date.today
  :conditions => ['valid_from <= :when AND valid_through >= :when',
                  :when => when]
}

Michael

--
Michael Schuerig
mailto:michael@schuerig.de
http://www.schuerig.de/michael/
9e0ec2384dd80c7f2c190e02e596e15b?d=identicon&s=25 Michael Kastner (Guest)
on 2009-06-26 13:04
(Received via mailing list)
Hi,

es genügt, wenn man den gesamten conditions-Hash ohne args in den
lambda-Block
setzt, damit jeweils das aktuelle Datum im SQL verwendet wird:

named_scope :effective,
             lambda{
               {
                 :conditions => ['valid_from <= ? AND valid_through >=
?',
                                  Date.today,
                                  Date.today]
               }
             }

Hab's mit gecachten Klassen probiert und hat funktioniert.

Viele Grüße und vielen Dank an die beteiligten Helfer

Michael Kastner


Michael Schuerig schrieb:
Bce1d1b7c3ec7b577dcb42e254899e6b?d=identicon&s=25 Michael Schuerig (Guest)
on 2009-06-26 13:27
(Received via mailing list)
On Friday 26 June 2009, Michael Kastner wrote:
> es genügt, wenn man den gesamten conditions-Hash ohne args in den
> lambda-Block setzt, damit jeweils das aktuelle Datum im SQL verwendet
> wird:

Ich weiß, ich wollte nur spielen. Mit der parametrisierten Variante kann
man nach historischen Daten suchen.

Michael

--
Michael Schuerig
mailto:michael@schuerig.de
http://www.schuerig.de/michael/
This topic is locked and can not be replied to.