2012-04-27 11 views
8

Ich versuche, einen Attribut-Setter in einem ActiveRecord-Modell seinen Wert in der postgres-Funktion text2ltree() wickeln, bevor Schienen seine SQL-Abfrage generiert.Wie man Attribut-Setzer sendet Wert durch SQL-Funktion

Zum Beispiel

post.path = "1.2.3" 
post.save 

erzeugen Sollte so etwas wie

UPDATE posts SET PATH=text2ltree('1.2.3') WHERE id = 123 # or whatever 

Was ist der beste Weg, dies zu tun?

+0

Sie könnten eine Postgres-Funktion (gespeicherte Prozedur) schreiben und dann einfach 'SELECT myfunc ('1.2.3')' aufrufen. Ich kann ein Beispiel geben, wenn Sie an dieser Route interessiert sind. –

Antwort

2

EDIT: genau zu erreichen, was Sie oben suchen, dann würden Sie diese verwenden, um die Standard-Setter in Ihrer Modelldatei außer Kraft zu setzen:

def path=(value) 
    self[:path] = connection.execute("SELECT text2ltree('#{value}');")[0][0] 
end 

Dann wird der Code, den Sie oben funktioniert haben.

Ich bin daran interessiert, mehr über ActiveRecords Interna und seine undurchdringlichen Metaprogrammierungsgrundlagen zu erfahren. Als Übung habe ich versucht, das zu erreichen, was Sie in Ihren Kommentaren unten beschrieben haben. Hier ist ein Beispiel, das für mich gearbeitet (das ist alles in post.rb):

module DatabaseTransformation 
    extend ActiveSupport::Concern 

    module ClassMethods 
    def transformed_by_database(transformed_attributes = {}) 

     transformed_attributes.each do |attr_name, transformation| 

     define_method("#{attr_name}=") do |argument| 
      transformed_value = connection.execute("SELECT #{transformation}('#{argument}');")[0][0] 
      write_attribute(attr_name, transformed_value) 
     end 
     end 
    end 
    end 
end 

class Post < ActiveRecord::Base 
    attr_accessible :name, :path, :version 
    include DatabaseTransformation 
    transformed_by_database :name => "length" 

end 

Console Ausgabe:

1.9.3p194 :001 > p = Post.new(:name => "foo") 
    (0.3ms) SELECT length('foo'); 
=> #<Post id: nil, name: 3, path: nil, version: nil, created_at: nil, updated_at: nil> 

Im wirklichen Leben Ich nehme an, Sie zu include das Modul in Active wollen würden: : Base, in einer Datei irgendwo früher im Ladepfad. Sie müssen auch den Typ des Arguments richtig behandeln, das Sie an die Datenbankfunktion übergeben. Schließlich habe ich gelernt, dass connection.execute von jedem Datenbankadapter implementiert wird, so dass die Zugriffe auf das Ergebnis in Postgres anders sein können (dieses Beispiel ist SQLite3, wobei die Ergebnismenge als Array von Hashwerten und der Schlüssel zum ersten Datensatz zurückgegeben wird) . 0]

Dieser Blog-Eintrag war unglaublich hilfreich:

http://www.fakingfantastic.com/2010/09/20/concerning-yourself-with-active-support-concern/

wie war die Rails Führung für Plugin-Authoring:

http://guides.rubyonrails.org/plugins.html

Auch, was es wert ist, denke ich in Postgres würde ich immer noch mit einer Migration eine Abfrage Rewrite-Regel erstellen, aber das war für eine großartige Lernerfahrung. Hoffentlich funktioniert es und ich kann aufhören darüber nachzudenken, wie es jetzt geht.

+1

Eine völlig andere Art, dies zu tun, die ich gerade gedacht habe, wäre die Verwendung der PostgreSQL-Engine für das Umschreiben von Anfragen. Ein bisschen wie mit einem Auslöser. Sie haben die Regel so eingerichtet, dass Aktualisierungen so umgeschrieben werden, dass der Wert über die Funktion übergeben wurde. Der Vorteil ist, dass Ihr Modell sauber ist und Sie nur einen Aufruf an die Datenbank machen. Der Nachteil ist, dass es nicht datenbankunabhängig ist, was natürlich in Ihrem Fall kein Problem sein kann. Ich bin nicht genug vertraut mit der Syntax, um ein Beispiel zu veröffentlichen, aber hier ist ein Link zu den Dokumenten: http://www.postgresql.org/docs/8.4/static/sql-createrule.html –

+0

Ich fühle mich wie hier sollte eine effizientere Möglichkeit, dies zu tun, die Affe Patching beteiligt ActiveRecord/Arel –

+0

Wenn durch "effizient" Sie Leistung meinen, ist die Abfrage neu schreiben Methode überlegen; Ich sehe nicht, wie Sie in Rails alles implementieren können, das zwei Aufrufe der DB vermeiden würde. Wenn Sie unter "effizient" DRYer/expressiver verstehen (vorausgesetzt, dies ist nicht der einzige Ort, an dem Sie das jemals tun möchten), stimme ich zu, dass dies eine Option ist. –

Verwandte Themen