2012-04-27 24 views
8

Próbuję ustawić funkcję ustawiającą atrybuty w modelu ActiveRecord, zawijając jej wartość w funkcji postgres text2ltree(), zanim szyny wygenerują zapytanie sql.Jak ustawić przelicznik wartości wysyłania za pomocą funkcji SQL

Na przykład

post.path = "1.2.3" 
post.save 

powinien generować coś

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

Jaki jest najlepszy sposób to zrobić?

+0

Możesz napisać funkcję PostgreS (procedura składowana), a następnie po prostu wywołać 'SELECT myfunc ('1.2.3')'. Mogę podać przykład, jeśli jesteś zainteresowany tą trasą. –

Odpowiedz

2

EDIT: Aby dokładnie to, czego szukasz powyżej osiągnąć, można to wykorzystać, aby zastąpić domyślny setter w pliku modelu:

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

Następnie kod masz powyżej działa.

Chciałbym dowiedzieć się więcej o wewnętrznych elementach ActiveRecord i ich nieprzenikalnych podstawach metaprogramowania, więc jako ćwiczenie próbowałem osiągnąć to, co opisałeś w swoich komentarzach poniżej. Oto przykład, który pracował dla mnie (to wszystko w post.rb): wyjście

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 

konsoli:

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> 

W prawdziwym życiu przypuszczam że chcesz include moduł w ActiveRecord: : Base, w pliku gdzieś wcześniej w ścieżce ładowania. Musiałbyś również poprawnie obsługiwać typ argumentu, który przekazujesz do funkcji bazy danych. Na koniec dowiedziałem się, że connection.execute jest implementowany przez każdą kartę baz danych, więc sposób uzyskiwania dostępu do wyniku może być inny w PostgreSQL (ten przykład to SQLite3, gdzie zestaw wyników jest zwracany jako tablica skrótów i klucz do pierwszego rekordu danych . jest 0]

Ten blog post był niezwykle pomocny:

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

jak było prowadnicami dla plugin-authoring:

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

Ponadto, w moim przekonaniu, w Postgres nadal robiłbym to za pomocą migracji, aby utworzyć regułę przepisywania zapytań, ale dzięki temu można się było nauczyć. Mam nadzieję, że to działa i mogę przestać myśleć o tym, jak to zrobić teraz.

+1

Zupełnie innym sposobem na zrobienie tego, o którym pomyślałem, byłoby użycie silnika do przeszukiwania zapytań PostgreSQL. Trochę jak za pomocą spustu. Właśnie skonfigurowałeś regułę do przepisywania aktualizacji tak, aby wartość została przekazana przez funkcję. Plusem jest to, że twój model jest czysty i wykonujesz tylko jedno połączenie z bazą danych. Minusem jest to, że nie jest to agnostyka bazy danych, co oczywiście nie powinno stanowić problemu w twoim przypadku. Nie jestem wystarczająco zaznajomiony ze składnią, aby opublikować przykład, ale tutaj jest link do dokumentów: http://www.postgresql.org/docs/8.4/static/sql-createrule.html –

+0

Czuję, że tutaj powinien być bardziej efektywny sposób robienia tego, który obejmuje łatanie małpy ActiveRecord/Arel –

+0

Jeśli przez "wydajne" masz na myśli wydajność, metoda przepisywania zapytania jest lepsza; Nie widzę sposobu, w jaki można zaimplementować w Railsach coś, co pozwoli uniknąć dwóch wywołań do bazy danych. Jeśli przez "efektywny" rozumie się DRYer/bardziej ekspresyjny (zakładając, że nie jest to jedyne miejsce, w którym kiedykolwiek chcesz to zrobić), to zgadzam się, że to opcja. –

Powiązane problemy