Frage zu named scope in einer association

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

Michael K. 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!

Mike Z. wrote:

Michael K. 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!

Ich will aber nicht die Preise, sondern die Produkte:

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

Mike Z. schrieb:

Kein Problem! Trotzdem vielen Dank für die Antwort.

Viele
Grüße
Michael K.

Mike Z. schrieb:

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

Hi,

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

Viele
Grüße
Michael K.

Codeblogger schrieb:

On Thursday 25 June 2009, Michael K. 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-assoziationen/index.html

Michael


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

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

Hallo Michael,

Michael S. 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 :wink: 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

Kreative Assoziationen

Die Präsentation kenne ich. Die ist klasse. Ich schau eh ab und zu auf
Deinem
Blog vorbei :wink:

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

Viele
Grüße
Michael K.

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.

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 S.
mailto:[email protected]
http://www.schuerig.de/michael/

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 S.
mailto:[email protected]
http://www.schuerig.de/michael/

Hi Christoph,

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

Vielen Dank

Michael K.

Christoph Petschnig schrieb:

Michael K. 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

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 S.
mailto:[email protected]
http://www.schuerig.de/michael/

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

Michael S. schrieb:

On Friday 26 June 2009, Michael K. 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 S.
mailto:[email protected]
http://www.schuerig.de/michael/