Problem z własną metodą find

Chcę zdefiniować własną metodę find dla danego obiektu, jednak wewnątrz tej metody chcę się odwołać do find z ActiveRecord::Base, jak się za to zabrać?

def self.find(*args) joins = Array.new self.reflect_on_all_associations.each { |a| joins << a.name } ???(args, :joins => joins) #To powinien być find z ActiveRecord::Base, aktualnie się zapętla w nieskończoność i po chwili wywala błąd. end
Tak, wiem że jeszcze muszę dorzucić sprawdzenie czy w args nie ma już :joins.

Musisz skorzystać z alias_method: http://apidock.com/rails/ActiveSupport/CoreExtensions/Module/alias_method
Tylko czy na pewno potrzebujesz tego własnego finda?

Ło matko boska i wszyscy święci. :wink: Ja raczej zawsze odradzam modyfikowanie ActiveRecord::Base.find

super(args, :joins => joins)

nie działa?

@tjeden Tak mi się wydaje. Spójrz http://rubyonrails.pl/forum/t2419 staram się problem poruszony w tamtym temacie naprawić w ten sposób.
@seban Dlaczego?

EDIT:

@hubertlepicki Nie, przy Obiekt.all czy Obiekt.find(:all) wywala

ActiveRecord::RecordNotFound: Couldn't find Nurse with ID=all

Tak samo z całą resztą, co ciekawe przy Obiekt.find(100) wywala także że nie ma obiektu o ID=100, a jest taki w bazie.

EDIT2:

@tjeden
Staram się skorzystać z tego co podpowiedziałeś jednak mam problem, mianowicie:
Sprawdzam czy rozumiem na czym to polega:

irb(main):001:0> def sth irb(main):002:1> puts "sth" irb(main):003:1> end => nil irb(main):004:0> def nowe_sth irb(main):005:1> puts "nowe_sth" irb(main):006:1> end => nil irb(main):007:0> sth sth => nil irb(main):008:0> nowe_sth nowe_sth => nil irb(main):009:0> alias :stare_sth :sth => nil irb(main):010:0> alias :sth nowe_sth => nil irb(main):011:0> sth nowe_sth => nil irb(main):012:0> stare_sth sth => nil
Ok, wiem o co chodzi.

A teraz w moim obiekcie:

[code=ruby] alias :stare_find :find
alias :find :my_find

def self.my_find(*args)
  joins = Array.new
  self.reflect_on_all_associations.each { |a| joins << a.name }
  self.stare_find(args, :joins => joins)
end[/code]

Jednak wywala błąd:

NameError: undefined method `find' for class `Nurse' from /home/blazeroot/Projekty/pielegniarki/system/app/models/nurse.rb:26
Przy czym linia 26 to:

    alias :stare_find :find

Chyba powinienem się odwołać do tego find z ActiveRecord::Base w jakiś sposób?

Nienienie :slight_smile:

W tym wypadku dużo czyściej jest tak jak zaczął, czyli wykorzystanie dziedziczenia.

weisu:
Jeżeli już naprawdę musisz nadpisywać find, to zrób do tego trochę testów, które sprawdzą czy find działa tak jak powinno.

Jeżeli chodzi o to co tam ma być, to musisz pamiętać, że jeżeli railsy obsługują wiele różnych formatów find, to Ty też musisz:

.find(1) .find([1, 2, 3]) .find(:first) .find(:all, :conditions => {:published = false})
Mógłbyś przykładowo zrobić coś takiego:

[code]def self.find(*args)
joins = Array.new
self.reflect_on_all_associations.each { |a| joins << a.name }
unless args.last.is_a?(Hash)
args << {}
end
args.last.merge!({:joins => joins})

super(*args)
end[/code]
Tylko to jest kompletnie bez sensu moim zdaniem, bo zawsze ci będzie pobierało wszystkie asocjacje. W ten sposób nawet robiąc Model.find(:first) będziesz zajeżdżał sobie bazę.

Już lepiej zrobić coś takiego:

[code]def self.find_with_associations(*args)
joins = Array.new
self.reflect_on_all_associations.each { |a| joins << a.name }
unless args.last.is_a?(Hash)
args << {}
end
args.last.merge!({:joins => joins})

find(*args)
end

#i wtedy tam gdzie musisz mieć asocjacje używasz np:
Model.find_with_associations(:first)[/code]
Nie sprawdzałem powyższego kodu, ale na ok o powinien działać :wink:

Tylko pamiętaj o jednej rzeczy:
domyślnie stosowany jest w railsach left inner join, czyli jeżeli nie ma rekordów powiązanych z tym, który chcesz wyciągnąć, to nie dostaniesz tego rekordu.

Np. jak masz User has_many :messages i zrobisz User.find(10, :joins => :messages), a user o id == 10 nie ma żadnych wiadomości, to dostaniesz wyjątek ActiveRecord::RecordNotFound chociaż user o id == 10 istnieje. Jeżeli w Twoim przypadku tak jest to użyj koniecnzie :include zamiast join.

UPDATE:

Podkreślę może jeszcze raz to co napisałem na początku:
Jeżeli ktoś chce nadpisać metodę z klasy nadrzędnej to nie ma najmniejszego sensu używać alias_method lub alias_method_chain. Wystarczy poprawnie wykorzystać dziedziczenie.

UPDATE2:
Uaktualniłem kod, żeby obsługiwał format Model.find(1, 2, 3)

To raz.

Dwa, default_scope to już niemodne się zdążyło zrobić, że takie potworki polecacie (Drogomir)? :stuck_out_tongue:

Przecież kolega chce mieć po prostu joinowane asocjacje przy find, to wszystko. Oczywiście pomysł jest zły (nie zawsze będą mu one potrzebne, więc czasem niepotrzebnie będzie zajeżdżał bazę złączeniami), ale to nie znaczy że trzeba polecać jeszcze gorsze rozwiązania :wink:

To raz.

Dwa, default_scope to już niemodne się zdążyło zrobić, że takie potworki polecacie (Drogomir)? :P[/quote]
Ja tylko poprawiłem to co było powyżej czyli jak to zrobić używając find. :wink:

Ale przyznaję, przez to co powyżej ludzie zaczęli robić nie pomyślałem zupełnie o named_scope - jak zobaczyłem, że ktoś chce w takiej sytuacji używać alias, to od razu mi się włączył tryb "trzeba to sprostować i podszedłem do rozwiązania tak jak reszta, tylko podając działający sposób.

Ok wycofuję się z aliasów. To po prostu pierwsze co mi przyszło do głowy, żeby tego finda ugryźć.

@drogus, dzięki za przykłady! Już łapię na czym to polega. Pytanie tylko, może głupie, dlaczego jest super(*args) a nie super(args)?

@Tomash, rozumiem że te rozwiązanie nie jest dobre ale nie mam czasu modyfikować reszty kodu, jak będę miał więcej czasu to poprawię pewnie to wszystko ale aktualnie czas mnie goni niestety, dlatego taka oporna metoda.

@tjeden, ale przynajmniej poznałem dzięki temu te aliasy bo ich nie znałem.

EDIT: Tak jak się martwiłem, to i tak nie pomogło temu problemowi, coś czuję że będę musiał się przegryzać przez ActiveScaffold.

find pobiera argumenty jako tablicę dzięki zapisowi def self.find(*args). Dlatego możliwe są zapisy:

[code]find(1, 2, 3, 4, :order => “id DESC”) # args => [1, 2, 3, 4, {:order => “id DESC”}
find(:all, :conditions => {:id => 1}) # args => [:all, {:conditions => {:id => 1}}]
find(:first) # args => [:first]

itd.[/code]

W moim kodzie dodaję tego ostatniego hasha, o ile go tam nie ma. Następnie dodaję do ostatniego argumentu (który teraz na pewno już jest hashem) :joins.

I teraz trzeba tą tablicę wrzucić z powrotem. Jeżeli zrobiłbyś super(args), to będzie to równoważne napisaniu np. find([:all]) - to, że to jest tablica trochę przeszkadza. Dlatego trzeba ją rozbić na argumenty, co można zrobić dodajć * przed argumentem. Wtedy argumenty przekażą się prawidłowo tak jakbyś je tam włożył pojedynczo, a nie jako tablicę.

Tomashowi chodziło o bardzo proste rozwiązanie, coś w stylu:

[code]named_scope :with_all_associations, Proc.new do
joins = Array.new
self.reflect_on_all_associations.each { |a| joins << a.name }
{ :joins => joins }
end

i wtedy możesz napisać

Model.with_all_associations.find(:all)[/code]
Można też to wrzucić do default_scope tak jak napisał Tomash, zeby było aplikowane do wszystkich zapytań (nie polecam).

A co konkretnie nie działa? Sprawdziłem ten kod i u mnie ładuje wszystkie asocjacje.

@drogus Kod działa cały bardzo dobrze, ale ActiveScaffold nie pobiera wszystkiego przy listowaniu przez find(:all) tak jak myślałem na początku (nie wiedzieć dlaczego). Przede wszystkim zależało mi na tym żeby mi szybciej pobierało dane z bazy danych, tak jak to napisałem w temacie “Problem z obsługą dużej bazy” (http://rubyonrails.pl/forum/t2419). Jak testowałem to z palca w konsoli railsów to wynik był świetny ponieważ dostawałem wynik po parunastu sekundach a nie rezygnowałem z czekania po 20minutach. Jednak w wylistowaniu przy ActiveScaffold trwało to nadal tak samo długo. Miałem dwie opcje, przedzierać się przez kod ActiveScaffolda i to modyfikować, albo napisać całe zarządzanie samemu, no i się zabrałem za pisanie tego samemu. Nie chciałem znów ryzykować że stracę czas na grzebanie w kodzie jak nie uda mi się dojść do tego co trzeba pozmieniać. Tak więc piszę tą obsługę już ręcznie, ActiveScaffold nie jest mi potrzebny.

EDIT: Jednak gugluję trochę i znalazłem tylko tyle że jest coś takiego jak :active_scaffold_joins i :joins_for_collection w ActiveScaffold ale ludzie mają z tym problemy.

Ok, nie załapałem, że używasz ActiveScaffold - przeczytałem to jako ActiveSupport i się zastanawiałem o co Ci w ogóle chodzi :wink: