Jak zwrócić pustą relację ActiveRecord?

246

Jeśli mam zasięg z lambda i wymaga on argumentu, w zależności od wartości argumentu, mógłbym wiedzieć, że nie będzie żadnych dopasowań, ale nadal chcę zwrócić relację, a nie pustą tablicę:

scope :for_users, lambda { |users| users.any? ? where("user_id IN (?)", users.map(&:id).join(',')) : [] }

To, czego tak naprawdę chcę, to metoda „none”, przeciwieństwo „all”, która zwraca relację, która może być nadal powiązana, ale powoduje zwarcie zapytania.

dzajic
źródło
Jeśli po prostu pozwolisz na zapytanie, uruchom je, zwróci relację: User.where ('id in (?)', []). Class => ActiveRecord :: Relation. Czy starasz się całkowicie uniknąć zapytania?
Brian Deterling
1
Poprawny. Jeśli wiem, że nie może być żadnych dopasowań, najlepiej byłoby całkowicie uniknąć zapytania. Po prostu dodałem to do ActiveRecord :: Base: "def self.none; where (: id => 0); end" Wydaje się, że działa dobrze dla tego, czego potrzebuję.
dzajic
1
> Czy starasz się całkowicie uniknąć zapytania? byłoby całkowicie sensowne, rodzaj kulawy, że musimy w tym celu uderzyć DB
dolzenko

Odpowiedzi:

478

W Railsach 4 jest teraz „poprawny” mechanizm:

>> Model.none 
=> #<ActiveRecord::Relation []>
steveh7
źródło
14
Do tej pory nie został przeniesiony z powrotem do wersji 3.2 lub wcześniejszej. Tylko przewaga (4.0)
Chris Bloom
2
Właśnie wypróbowałem z Railsami 4.0.5 i działa. Ta funkcja trafiła do wersji Rails 4.0.
Evolve
3
@AugustinRiedinger Model.scopedrobi to, czego szukasz w szynach 3.
Tim Diggins
9
Od wersji Rails 4.0.5 Model.nonenie działa. Musisz użyć jednej ze swoich prawdziwych nazw modeli, np. User.noneLub co masz.
Grant Birchmeier,
77

Bardziej przenośne rozwiązanie, które nie wymaga kolumny „id” i nie zakłada, że ​​nie będzie wiersza o identyfikatorze 0:

scope :none, where("1 = 0")

Nadal szukam bardziej „poprawnego” sposobu.

steveh7
źródło
3
Tak, jestem naprawdę zaskoczony, że te odpowiedzi są najlepsze, jakie mamy. Myślę, że ActiveRecord / Arel wciąż musi być niedojrzały. Gdybym musiał przejść przez perambulacje, aby stworzyć pustą tablicę w Ruby, byłbym naprawdę zirytowany. Zasadniczo to samo tutaj.
Purplejacket
Mimo że jest nieco hackerski, jest to właściwy sposób dla Rails 3.2. W przypadku Rails 4 zobacz inną odpowiedź @ steveh7 tutaj: stackoverflow.com/a/10001043/307308
scarver2
scope :none, -> { where("false") }
nroose
43

Jadąc w szynach 4

W Railsach 4 łańcuch ActiveRecord::NullRelationbędzie zwracany z takich połączeńPost.none .

Ani to, ani powiązane metody nie będą generować zapytań do bazy danych.

Zgodnie z komentarzami:

Zwrócony ActiveRecord :: NullRelation dziedziczy z Relacji i implementuje wzorzec Null Object. Jest to obiekt o zdefiniowanym zachowaniu zerowym i zawsze zwraca pustą tablicę rekordów bez sprawdzania bazy danych.

Zobacz kod źródłowy .

Nathan Long
źródło
42

Możesz dodać zakres o nazwie „none”:

scope :none, where(:id => nil).where("id IS NOT ?", nil)

To da ci pusty ActiveRecord :: Relacja

Możesz także dodać go do ActiveRecord :: Base w inicjalizatorze (jeśli chcesz):

class ActiveRecord::Base
 def self.none
   where(arel_table[:id].eq(nil).and(arel_table[:id].not_eq(nil)))
 end
end

Wiele sposobów na uzyskanie czegoś takiego, ale na pewno nie najlepsza rzecz do przechowywania w bazie kodu. Użyłem zakresu: brak podczas refaktoryzacji i stwierdzenia, że ​​muszę zagwarantować pusty ActiveRecord :: Relacja przez krótki czas.

Brandon
źródło
14
where('1=2')może być nieco bardziej zwięzły
Marcin Raczkowski
10
W przypadku, gdy nie przewiniesz w dół do nowej „poprawnej” odpowiedzi: Model.nonejak to zrobić.
Joe Essey,
26
scope :none, limit(0)

Jest to niebezpieczne rozwiązanie, ponieważ Twój zasięg może być związany.

User.none.first

zwróci pierwszego użytkownika. Jest bezpieczniejszy w użyciu

scope :none, where('1 = 0')
bbrinck
źródło
2
Ten jest właściwy „zakres: brak, gdzie („ 1 = 0 ”)”. drugi zawiedzie, jeśli masz paginację
Federico
14

Myślę, że wolę sposób, w jaki to wygląda od innych opcji:

scope :none, limit(0)

Prowadząc do czegoś takiego:

scope :users, lambda { |ids| ids.present? ? where("user_id IN (?)", ids) : limit(0) }
Alex
źródło
Wolę tą. Nie jestem pewien, dlaczego where(false)nie miałby wykonywać tej pracy - pozostawia zakres bez zmian.
aceofspades
4
Strzeż się, że limit(0)zostanie zastąpione, jeśli zadzwonisz .firstlub .lastpóźniej w łańcuchu, ponieważ Railsy dołączą LIMIT 1do tego zapytania.
zykadelic
2
@aceofspades gdzie (fałsz) nie działa (Rails 3.0), ale gdzie (fałsz). Nie to, że prawdopodobnie obchodzi Cię teraz 2013)
Ritchie,
Dzięki @Ritchie, od tego czasu myślę, że mamy również nonerelację, jak wspomniano poniżej.
aceofspades
3

Użyj zakresu:

zakres: for_users, lambda {| users | users.any? ? gdzie ("user_id IN (?)", users.map (&: id) .join (',')): scoped}

Ale możesz także uprościć swój kod za pomocą:

zakres: for_users, lambda {| users | gdzie (: user_id => users.map (&: id)) if users.any? }

Jeśli chcesz uzyskać pusty wynik, użyj tego (usuń warunek if):

zakres: for_users, lambda {| users | gdzie (: user_id => users.map (&: id))}
Pan Thomakos
źródło
Zwracanie „z zakresem” lub zera nie osiąga tego, czego chcę, czyli ograniczenia wyników do zera. Zwrócenie „scoped” lub zero nie ma wpływu na zakres (co jest przydatne w niektórych przypadkach, ale nie w moim). Wymyśliłem własną odpowiedź (patrz komentarze powyżej).
dzajic
Dodałem również proste rozwiązanie, aby zwrócić pusty wynik :)
Pan Thomakos
1

Istnieją również warianty, ale wszystkie one przesyłają żądanie do db

where('false')
where('null')
fmnoise
źródło
2
BTW Pamiętaj, że muszą to być ciągi znaków. where(false)lub where(nil)jest po prostu ignorowany.
mahemoff,