2012-04-27 11 views
8

Sto provando a fare in modo che un setter di attributi in un modello ActiveRecord includa il suo valore nella funzione postgres text2ltree() prima che rails generi la sua query sql.Come rendere il valore di invio di un attributo tramite la funzione SQL

Per esempio,

post.path = "1.2.3" 
post.save 

dovrebbe generare qualcosa come

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

Qual è il modo migliore di fare questo?

+0

È possibile scrivere una funzione Postgres (stored procedure) e quindi chiamare semplicemente 'SELECT myfunc ('1.2.3')'. Posso fornire un esempio se sei interessato a quel percorso. –

risposta

2

EDIT: Per ottenere esattamente quello che stai cercando di cui sopra, devi usare questo per ignorare il setter di default nel file del modello:

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

Poi il codice che avete sopra funziona.

Sono interessato a saperne di più sugli interni di ActiveRecord e le sue impenetrabili basi metaprogrammanti, così come un esercizio ho cercato di realizzare ciò che hai descritto nei tuoi commenti qui sotto. Ecco un esempio che ha funzionato per me (questo è tutto in post.rb): uscita

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:

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> 

Nella vita reale Presumo che ci si vuole include il modulo in ActiveRecord: : Base, in un file da qualche parte prima nel percorso di caricamento. Dovresti anche gestire correttamente il tipo di argomento che stai passando alla funzione del database. Infine, ho appreso che connection.execute è implementato da ogni adattatore del database, quindi il modo in cui si accede al risultato potrebbe essere diverso in Postgres (questo esempio è SQLite3, dove il set di risultati viene restituito come una matrice di hash e la chiave del primo record di dati . è 0]

Questo post è stato incredibilmente utile:

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

come era le guide per il plugin-authoring:

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

Inoltre, per quello che vale, penso che in Postgres lo farei ancora usando una migrazione per creare una regola di riscrittura delle query, ma ciò è stato per un'ottima esperienza di apprendimento. Spero che funzioni e posso smettere di pensare a come farlo ora.

+1

Un modo completamente diverso di fare ciò che ho appena pensato sarebbe quello di utilizzare il motore di riscrittura delle query di Postgresql. Un po 'come usare un grilletto. Dovresti semplicemente impostare la regola per riscrivere gli aggiornamenti in modo tale che il valore sia passato attraverso la funzione. Il lato positivo è che il tuo modello è pulito e fai una sola chiamata al database. Il rovescio della medaglia è che non è indipendente dal database, il che ovviamente non è un problema nel tuo caso. Non ho familiarità con la sintassi per pubblicare un esempio, ma ecco un link ai documenti: http://www.postgresql.org/docs/8.4/static/sql-createrule.html –

+0

Mi sembra che qui dovrebbe essere un modo più efficiente di fare ciò che coinvolge la patch di scimmia ActiveRecord/Arel –

+0

Se per "efficiente" si intende la prestazione, il metodo di riscrittura delle query è superiore; Non vedo come si possa implementare qualcosa in Rails che eviterebbe due chiamate al db. Se per "efficiente" intendi DRYer/più espressivo (supponendo che questo non sia l'unico posto in cui tu voglia mai farlo), allora sono d'accordo che è un'opzione. –

Problemi correlati