2013-10-12 21 views
15

Chciałbym dodać metodę do wszystkich kolekcji dla konkretnego modelu. Powiedzmy, że chcę dodać metodę my_complicated_averaging_method do zbiorów WeatherData:Jak dodać metodę do kolekcji activerecord?

WeatherData.all.limit(3).my_complicated_averaging_method() 
Station.first.weatherdata.my_complicated_averaging_method() 

Jaki jest najlepszy sposób to zrobić? W tej chwili jedyny sposób, jaki znalazłem, jest następujący:

class WeatherData < ActiveRecord::Base 
    def self.my_complicated_averaging_method 
    weighted_average = 0 
    @relation.each do |post| 
     # do something complicated 
     # weighted_average = 
    end 
    return weighted_average 
    end 
end 

Czy to dobry sposób na dodanie metody do kolekcji? Czy istnieje lepszy/obsługiwany sposób na zrobienie tego?

Odpowiedz

12

Istnieje wiele sposobów, jak to zrobić, a Twoja jest całkowicie ważna (choć ja osobiście wolę owijać metody klasy w oddzielne sprawdzanie bloku this), ale jako ludzie dodają do swoich modeli więcej logiki biznesowej i ślepo podążają za "chudymi kontrolerami, grubymi modelami", modele zamieniają się w kompletny bałagan.

Aby uniknąć tego bałaganu, że to dobry pomysł, aby wprowadzić obiektów usługowych, w danym przypadku byłoby tak:

class AverageWeatherData 
    class << self 
    def data(collection) 
     new(collection).data 
    end 
    end 

    def initialize(collection) 
    @collection = collection 
    end 

    def data 
    @collection.reduce do |avg, post| 
     # reduce goes through every post, each next iteration receives in avg a value of the last line of iteration 
     # do something with avg and post 
    end 
    # no need for explicit return, every line of Ruby code returns it's value 
    # so this method would return result of the reduce 
    # more on reduce: http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-reduce 
    end 
end 

Teraz można wywołać tej klasy bezpośrednio przekazując swoją kolekcję do niego. Ale można też proxy wywołanie tak:

def self.my_complicated_averaging_method 
    AverageWeatherData.data(@relation) 
end 

Zachęcam do Lear więcej tego podejścia czytając ten blog: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

UPD

masz rację używając zmiennej instancji jest możliwym sposobem zepsucia elementów wewnętrznych (dodatkowo nie jest to interfejs publiczny i może się zmienić w przyszłości). Moja propozycja tutaj jest użycie metody scoped. Zasadniczo zamień @relation na scoped.

Sprawdź ten przykład.Użyłem model z własnego projektu, aby pokazać, że to rzeczywiście działa

2.0.0p247 :001 > Tracking # just asking console to load this class before modifying it 
# => Tracking(id: integer, action: string, cookie_id: string, ext_object_id: integer, created_at: datetime, updated_at: datetime) 
2.0.0p247 :002 > class Tracking 
2.0.0p247 :003?>  def self.fetch_ids 
2.0.0p247 :004?>   scoped.map(&:id) 
2.0.0p247 :005?>  end 
2.0.0p247 :006?> end 
# => nil 
2.0.0p247 :007 > 
2.0.0p247 :008 > Tracking.where(id: (1..100)).fetch_ids 
# Tracking Load (2.0ms) SELECT "trackings".* FROM "trackings" WHERE ("trackings"."id" BETWEEN 1 AND 100) 
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] 

UPD

W Rails 4 scoped jest przestarzała, więc jest to poprawne użycie all.

all.map(&:id) 
+0

Moje pytanie było złe :(Byłem rea Lly sprawdzanie, czy "@relation" był dobry w użyciu lub czy istnieje lepszy sposób dodawania metod do kolekcji. Bardzo podoba mi się twój post. – Leon

+0

Zrozumiałem teraz, sprawdź moją aktualizację. –

+0

Daje mi błąd z szynami 4, ale znalazłem problem: https://github.com/voxdolo/decent_exposure/pull/73 Dzięki za pomoc i link do świetnego posta na blogu z kodeksu! – Leon

0

Dobrze wygląda, że ​​wszystko działa, ale dla większej finezji jestem pewien, że jest coś o wiele lepszego. Co prawda, nie były zbyt szczegółowe opisując to, co chciał osiągnąć z tego, więc mogę tylko daje to szerokie sugestię

można zajrzeć do „Observer Classes


I wypowiedział się o nich here

obserwatora Klasy zasadzie monitorowania danej funkcji modelu & przedłużyć go. Myślę, że są tylko dla before_filter itd funkcje, ale nie rozumiem, dlaczego nie można przedłużyć indvidual funkcje, które tworzą

Będziesz musiał użyć the rails-observers gem w szynach 4.0+, aby je działa, ponieważ zostały zdeprecjonowane z rdzenia rdzeń

2

on Rails> = 4 można używać where(nil) inplace z scoped

class Foo < ActiveRecord::Base 
    def self.bar 
    where(nil).pluck(:id) 
    end 
end 

Foo.where(id: [1, 2, 3]).order(:id).bar 

I dalej, można użyć #scope, na przykład:

class Foo < ActiveRecord::Base 
    scope :bar, -> {where(nil).pluck(:id)} 
end 

Wreszcie, można napisać kod taki jak Foo.all.bar

+1

działa w wersji 5.1 –

Powiązane problemy