Quick tip #2: Memoizable 2

Posted by wijet
on Wednesday, April 22

Gdy jakaś metoda wykonuje kosztowne obliczenia, a jej wyniku używamy wielokrotnie, rozsądnym jest obliczenia wykonać raz, a wynik zapamietać, tzw. cachowanie za pomoca zmiennych instancji. W takim wypadku, najczęsciej używamy idiomu "||="

class Bar
  def foo
    @foo ||= lambda {
      # jakies kosztowne obliczenia
      ...
    }.call
  end
end

Od railsów 2.2 możemy korzystać z metody :memoize, należy dodać metody z modułu ActiveSupport::Memoizable jako metody klasy.

class Bar
  extend ActiveSupport::Memoizable

  def foo
    # jakies kosztowne obliczenia
    ...
  end

  memoize :foo
end

Spowoduje to że za pierwszym wywołaniem metody :foo, metoda ta faktycznie zostanie wywołana, a wartość przez nią zwrócona zapamiętana (dziala to tak jak cachowanie w zmiennej instancji, czyli tylko w obrebie danego obiektu).

Każde następne wywołanie metody :foo, na tym konkretnym obiekcie, spowoduje zwrócenie zapamiętanej wartośći.

Jeśli chcemy wymusić wywołanie metody (tzn. bez cachu), jako ostatni parametr należy podać wartość true.

Linki

Module ActiveSupport::Memoizable

Memo-what? - A Guide to Memoization

Railscasts: Memoization

What's New in Edge Rails: Easy Memoization

Quick tip #1: tap i returning 3

Posted by wijet
on Saturday, April 11

Tym postem chcę rozpocząć serię którkich wpisów, o konkretnych metodach/konstrukcjach dostępnych w railsach, lub w samym Ruby, Za pomocą których możemy zrobić coś krócej, ładniej, bardziej ruby way.

Czasami pojawia się potrzeba wykonania na obiekcie pewnych operacji, a następnie zwrócenie go.

def foo(name)
  product = Product.find_or_initialize_by_name(name)
  # tu cos mieszamy z produktem
  ...
  # zapisujemy
  product.save
  product
end

jak widać na końcu musi być 'product' aby metoda zwracała product, inaczej :foo zwrociła by nam prawde lub fałsz, czyli wynik product.save

Możemy to bardzo elegancko, zastapić, poniższa konstrukcją

returning

def foo(name)
  returning Product.find_or_initialize_by_name(name) do |product|
    # tu cos mieszamy z produktem
    ...
    # zapisujemy
    product.save
  end
end

:returning przyjmuje obiekt, przekazuje go do bloku który otrzymuje, a następnie zwraca ten obiekt

tap

Za pomocą :tap możemy zrobić to samo, w troche inny sposob:

def foo(name)
  Product.find_or_initialize_by_name(name).tap do |product|
    # tu cos mieszamy z produktem
    ...
    # zapisujemy
    product.save
  end
end

Metoda :tap przyjmuje blok do którego przekazuje self (obiekt na którym została wywołana), a następnie po wykonaniu bloku zwraca self.

Obydwie metody sa dostepne w railsach. Warto wiedzieć że metoda :tap została włączona do Ruby w wersji 1.9.

Za pomocą metody :tap mozemy nawet wiecej poczarować, polecam przejrzenie poniższych linków:

Linki

Eavesdropping on Expressions

Tapping method chains with Ruby 1.9

Rails ActiveSupport returning Method

Mining ActiveSupport: Object#returning

Łatwe pobieranie screencastow z Railscasts.com 3

Posted by wijet
on Wednesday, August 15

Na stronie Railscasts znajduje się masa screencastów o railsach, trochę irytowało mnie osobne ściąganie plików, sprawdzanie które już mam a których nie, wiec napisałem kawałek kodu. Skrypt pakujemy do katalogu w którym chcemy gromadzić filmiki, odpalamy, dostajemy listę dostępnych na stronie screencastow (pobrane są oznaczane na zielone, niepobrane na czerwono), następnie wpisujemy oddzielone spacjami numery screencastow do pobrania. Może komuś przypadkiem się przyda :)

#!/usr/bin/ruby
require 'open-uri'
require 'rss/2.0'
 
module RSS
  class Rss
    class Channel
      class Item
        def file_name
          @file_name ||= self.enclosure.url.split(/\//).last
        end
        def file_number
          file_name.split(/_/).first.to_i
        end
        def file_size
          self.enclosure.length.to_f / 1024 / 1024
        end
      end
    end
  end
end
 
rss_content = ""
open("http://feeds.feedburner.com/railscasts") do |f|
  rss_content = f.read
end
 
rss = RSS::Parser.parse(rss_content,false)
rss.items.reverse.each do |item|
  if File.exists?(item.file_name)
    printf "\e[32m%s (%.2f MB)\e[0m\n", item.title, item.file_size
  else
    printf "\e[31m%s (%.2f MB)\e[0m\n", item.title, item.file_size
  end
end
 
puts "Podaj numery screencastow do pobrania"
numbers = STDIN.gets
 
numbers.split(' ').each do |n|
  item = rss.items.detect{|i| i.file_number == n.to_i}
  unless item.nil? or File.exists?(item.file_name)
    system "wget -c #{item.enclosure.url}" 
  end
end