2011-07-12 14 views
5

Szukam rozwiązania następującego problemu: Mam obiekt ActiveRecord, który jest wspierany przez aktualizowalny widok bazy danych (w DB2 za pośrednictwem pliku activerecord-jdbc -klejnotu rozdziału). Ten widok zawiera jedną kolumnę, która jest obliczana z innych kolumn i jest "tylko do odczytu": nie można ustawić tej kolumny w jakikolwiek prawidłowy sposób. Po utworzeniu nowego rekordu dla tego obiektu należy ustawić to pole jako , a nie. Jednak domyślnie ActiveRecord ustawia go na 'default' (NULL), który jest odrzucany przez bazę danych.Ignoruj ​​kolumnę "tylko do odczytu" podczas tworzenia i aktualizowania w Ruby ActiveRecord

attr_readonly nie jest rozwiązaniem, ponieważ wyklucza tylko kolumnę z aktualizacji, a nie z plików create.

attr_ignore, taki jak zaimplementowany przez klejnot "lincoln", również nie jest rozwiązaniem, ponieważ wtedy pole jest całkowicie ignorowane. Jednak kolumna musi zostać przeczytana i być dostępna. Jest nawet używany jako część relacji.

Istnieją sposoby, aby uniemożliwić ustawienie pewien atrybut jednostki ActiveRecord, ale zwykle nie zapobiega to atrybut z wliczone jest w tworzenie lub oświadczenia aktualizacji

Czy ktoś wie, czy istnieje sposób, w ActiveRecord, aby określić kolumnę jako "nigdy nie ustawiaj tego pola"?

Aktualizacja, w odpowiedzi na Arsen7: Podjęto próbę użycia haka after_initialize w celu usunięcia atrybutu z nowo utworzonego obiektu, więc nie jest on zawarty w wbudowanym SQL. Problem polega na tym, że atrybut jest całkowicie usunięty i nie jest już dostępny, prawie identyczny z opisaną powyżej sytuacją "igonre_attr". Z powodu buforowania nie jest to łatwe do przejścia i wymagałoby dodatkowej logiki wymuszającej ponowne ładowanie jednostek tych tabel. Można to prawdopodobnie osiągnąć, przesyłając create, aby dodać "przeładowanie", oprócz użycia funkcji after_initialize.

(Jak podkreślił Arsen7, zapomniałem wspomnieć, że jestem w ActiveRecord 3.0.9)

Moje rozwiązanie

Ponieważ moje podmioty już dziedziczyć z podklasy ActiveRecord::Base, mam postanowił dodać haki before_create i after_create. W haku before_create usuwam kolumny "obliczone" z @attributes instancji. W haku after_create dodaję je ponownie i odczytuję wartości "wyliczonych" kolumn z bazy danych, aby ustawić je na wartości, które otrzymały.

Dodanie takich haczyków jest niemal identyczne jak nadpisanie, dlatego uważam odpowiedź Arsen7 za poprawną.

Odpowiedz

2

Obawiam się, że ActiveRecord nie jest przygotowany na potrzeby użycia. (Przy okazji: której wersji AR używasz?)

Sądzę jednak, że możesz zastosować dwa możliwe rozwiązania.

Pierwszym z nich jest nadpisanie metody "create" modelu, wykonanie innego SQL, przygotowanego ręcznie w najgorszym przypadku. Przypuszczam, że prawdziwa funkcja, która będzie musiała zostać zastąpiona, nie będzie sama "tworzyć", ale patrząc na źródła, którą można znaleźć.

Drugim rozwiązaniem, i uważam, że jest bardziej elegancka, byłoby utworzenie wyzwalacza w bazie danych.Jestem bardziej w świecie PostgreSQL, gdzie użyłbym 'CREATE RULE', ale patrząc na dokumentację DB2 widzę, że w DB2 są 'INSTEAD OF' triggers. Mam nadzieję, że to może być pomocne.

+0

Jeśli o przepisanie „tworzyć” Nie sugeruję usunięcie atrybutu z obiektu, po prostu sugerować tworzyć (i wykonanie) SQL bez określonej kolumnie. Ale co z wyzwalaczami? Wygląda jak najbardziej eleganckie rozwiązanie - twoje zapytanie zostanie przepisane przez bazę danych - tam możesz usunąć tę kolumnę tylko do odczytu z ostatecznego zapytania. – Arsen7

+0

Podoba mi się pomysł wyzwalaczy, ale wadą jest to, że pole obliczeniowe należy zaktualizować, gdy zmieni się jedno z pól "źródła". Zasadniczo obliczone pole działa tak samo, jak wiele wyzwalaczy na wielu innych polach. Jako taki jest nieco prostszy niż wiele wyzwalaczy. Jeśli chodzi o modyfikowanie "tworzenia" (i być może "aktualizowania"): wolę powyżej modyfikację metody, która buduje SQL, ponieważ ta ostatnia znajduje się głębiej w miskach ActiveRecord. Zmiany na wyższym poziomie są zwykle łatwiejsze do zrozumienia. Jednak nadal muszę to zrobić i może się okazać, że się mylę :) – Confusion

2

udało mi się osiągnąć ten sam rezultat nadrzędnymi ActiveRecord :: Base # arel_attributes w moim modelu

Class Model < ActiveRecord::Base 
    @@skip_attrs = [:attr1, :attr2]  

    def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys) 
    skip_attrs = @@skip_attrs.map { |attr| [self.class.arel_table[attr] } 
    attrs = super(include_primary_key, include_readonly_attributes, attribute_names) 
    attrs.delete_if {|key, value| skip_attrs.include?(key) }   
    end 
end 

atrybutów w @@ skip_attrs tablicy będą ignorowane przez ActiveRecord w obu sprawozdaniach włożyć i aktualizacji, jak obaj polegają na wartościach arel_attributes_values ​​w celu zwrócenia listy atrybutów modelu.

Lepszym rozwiązaniem byłoby: łatka na ActiveRecord :: Base # arel_attributes wraz z makrem "attr_ignore" podobnym do "attr_readonly".

okrzyki

+0

Rozważałem tę opcję i zgadzam się, że rozwiązuje to również problem. Główną rezerwą, którą mam na ten temat, jest to, że nie zdziwiłbym się, gdyby ta metoda została zmieniona w przyszłej wersji ActiveRecord, co może prowadzić do trudnego do znalezienia błędu. – Confusion

+0

Tak, masz rację. W połowie kadencji lepiej jest złożyć wniosek do autorów AR o rozważenie takiej funkcji. Ten powyżej jest po prostu "łatą", nie więcej niż. – evital

0

wiem, że to jest bardzo stary, ale mam zmaga się z tym samym problem. Mam bazę danych z wyzwalaczem, który oblicza wartość indeksu na podstawie maksymalnej wartości w kluczu. Ja również chcę uniemożliwić jakąkolwiek możliwość ustawienia wartości w AR, ponieważ może to spowodować odrzucenie indeksu zastosowanego po wstawieniu wierszy.

CREATE TRIGGER incr_col_idx 
AFTER INSERT ON fl_format_columns 
FOR EACH ROW 
BEGIN UPDATE fl_format_columns 
SET idx = (SELECT coalesce(max(idx),-1) + 1 
      FROM fl_format_columns 
      WHERE fl_file_format_id = new.fl_file_format_id) 
WHERE fl_file_format_id = new.fl_file_format_id AND name = new.name; 
END; 

Próbowałem różnych rzeczy, ale zawsze wróciłem do przesłonięcia settera bezpośrednio.

# @raise ArgumentError when an attempt is made to set a value that is calculated in db 
def idx=(o) 
    raise ArgumentError,'the value of idx is set by the db. attempts to set value is not allowed.' unless o.nil? 
end 

Będzie to wymagało przechwycenia wyjątku, a nie przesłuchiwania tablicy błędów, ale to właśnie zakończyłem. To nie przejść następujące kontrole:

context 'column index' do 
    it 'should prevent idx from being set' do 
    expect{FL_Format_Column.create(fl_file_format_id:-1,name:'test idx',idx:0)}.to raise_error(ArgumentError) 
    end 
    it 'should calculate idx relative to zero' do 
    x = FL_Format_Column.create(fl_file_format_id:-1,name:'test_idx_nil') 
    expect(x.errors[:idx].any?).to be false 
    expect(FL_Format_Column.last.idx).to be > -1 
    end 
end 
Powiązane problemy