Mam aplikację, która działa na bazie Oracla, chciał bym się do niej podpiąć RoR, żeby sobie troche danych powyciągać i dorobić API. Bazy zmienić nie mogę.
Problem jaki mam to mapowanie kolumn w bazie, które przechowują wartości ‘T/F’ na boolean.
Działa to u mnie połowicznie dodałem do modelu
attribute :nieuzywana, :boolean
i Active record zwraca mi poprawnie
>> OplatyStaleDefinicje.first.nieuzywana
OplatyStaleDefinicje Load (9.5ms) SELECT * FROM (SELECT "OPLATY_STALE_DEF".* FROM "OPLATY_STALE_DEF" ORDER BY "OPLATY_STALE_DEF"."ID_OPLATY_STALE_DEF" ASC ) WHERE ROWNUM <= :a1 [["LIMIT", 1]]
=> false
Natomiast kiedy próbuje użyć tego w where to, true/false mapowane jest na wartości 0/1 zamiast używane w bazie ‘T/F’
>> OplatyStaleDefinicje.where nieuzywana: false
=> []
SELECT "OPLATY_STALE_DEF".* FROM "OPLATY_STALE_DEF" WHERE "OPLATY_STALE_DEF"."NIEUZYWANA" = :a1 [["nieuzywana", "0"]]
W tym momencie nawet podanie w where wartośći ‘F’ jest mapowane na 0
>> OplatyStaleDefinicje.where nieuzywana: 'F'
=> []
OplatyStaleDefinicje Load (15.0ms) SELECT "OPLATY_STALE_DEF".* FROM "OPLATY_STALE_DEF" WHERE "OPLATY_STALE_DEF"."NIEUZYWANA" = :a1 [["nieuzywana", "0"]]
VARCHAR ale przechowuje dane nie jako znaki ‘Y’ i ‘N’, a ‘T’ i ‘F’. Na razie zrobiłem to tak, że w inicializers dodałem
ActiveRecord::ConnectionAdapters::OracleEnhanced::Quoting.class_eval do
def quoted_true
return "'T'" if emulate_booleans_from_strings
"1"
end
def unquoted_true
return "T" if emulate_booleans_from_strings
"1"
end
def quoted_false
return "'F'" if emulate_booleans_from_strings
"0"
end
def unquoted_false
return "F" if emulate_booleans_from_strings
"0"
end
end
Domyślnie było tam właśnie Y/N i po tych zmianach nawet działa.
Zastanawiam się tylko, czy nie da się tego jakoś bardziej elegancko skonfigurować.
Pytanie czy jest jakiś nadrzędny powód, żeby w ogóle to mapowanie robić? Zwykle staram się unikać wartości boolean mapowanych z baz, jako że to często prowadzi do mało czytelnych błędów. Łatwo doprowadzić do sytuacji, gdzie NULL/nil będzie błędnie interpretowany jako false. Radziłbym dodać sobie po prostu enum do modelu, zmapować te wartości T/N na coś bardziej czytelnego/opisowego enabled/disabled (?) i nie używać nigdzie true/false. Tym bardziej unikałbym monkey patch w okolicach ConnectionAdapter…
Miałem na myśli zwykły, ActiveRecord::Enum w modelu, żeby móc używać scope w stylu Model.active zamiast where. Czyli coś ala:
class OplatyStaleDefinicje < ApplicationRecord
enum nieuzywana: {
active: "N",
disabled: "T",
}
end
Tu przy okazji wychodzi trochę drobiazgów, które mogą być mniejszym czy większym problemem. Ogólnie lepiej unikać polskich nazw klas, zmiennych. Nawet kiedy nie ma bezpośredniej kontroli nad danymi, to mimo wszystko użyłbym angielskich nazw. Tutaj i tak musisz podać nazwę tabeli w modelu, ze względu na pluralizację nazwy. Atrybut można zmienić na klika sposobów, np: alias_attribute.
Drugi problem to negacja flagi. Jak rozumiem TRUE w polu nieuzywana oznacza, ze ta definicja nie jest używana? O niebo lepsze byłoby było tam po prostu active: T/F. Musisz podjąć decyzję, czy mapować to tak jak jest i dalej męczyć się z tą formą, czy odwrócić i trzymać u siebie po prostu active/disabled.
Zastanawiałem się mocno nad tym, ale niewiem czy dobrym pomysłem jest wymyślanie nowej nomenklatury jeśli jest ona już w tej bazie i tak jest używana w innej aplikacji i raportach, które są z niej generowane. Samo używanie polskich nazw mi się też nie podoba.