Onze dagmethode #8: Object.alias_class_method

Geplaatst door Remco van 't Veer do, 14 feb 2008 08:00:00 GMT

In een eerdere dagmethode wordt een alias gemaakt voor een class methode. Dat is een beetje lastig omdat de Module#alias_method eigenlijk alleen opereert op instance methoden. Gelukkig is een class niet meer dan een instance van Class en kunnen we dus die instance gebruiken om alias_method op uit te voeren.

Een bekende methode om een object instance uit te breiden is met de class<< notatie. Hiermee kan je een object voorzien van een singleton/eigen/virtual/meta-class welke je kan voorzien van methoden voor die specifieke instancie. Omdat binnen een class definitie self verwijst naar de class instance van deze definitie kunnen we het volgende schrijven:


class Foo
  class << self
    alias_method :neu, :new
  end
end

Deze Foo class kunnen we nu instanceren met neu (merk op dat new gewoon een class methode is). Omdat de class<< notitie binnen een class een beetje een rommelige indruk wekt, gebruik ik liever de volgende uitbreiding op Object:


class Object
  def self.alias_class_method(new_name, old_name)
    meta = class << self; self; end
    meta.send :alias_method, new_name, old_name
  end
end

Hiermee kan je schrijven:


class Foo
  alias_class_method :neu, :new
end

Zie voor mee informatie over eigenclasses de discussie tussen Nutter en Lam op Ruby-core.

Geplaatst in ,  | geen reacties

Onze dagmethode #7: Enumerable#mjoin

Geplaatst door Michiel de Mare wo, 13 feb 2008 08:00:00 GMT

Jullie kennen natuurlijk flatten. flatten neemt een array en walst hem plat. Na afloop bevat de array alleen nog niet-arrays. Maar soms heb je een array van arrays van arrays en wil je hem in een array van arrays omtoveren. Wat we nodig hebben is flatten-light: mjoin

module Enumerable
  def mjoin
    inject!([]) {|memo,x| a.each {|x| memo << x}}
  end
end
(lees meer over inject!).

Wanneer heb je dit nodig? Altijd als je map wilt gebruiken maar je wilt niet elke keer precies één waarde teruggeven. en daarnaast sommige waardes in de array zelf arrays zijn, waardoor flatten afvalt.

Klinkt dat uitermate zeldzaam? In feite is dit anders een doodnormale datastructuur. Een tabel is een array van arrays, en een join is een operatie die per row nul of meer resultaten teruggeeft. En vandaar ook de naam! (join is natuurlijk al bezet – mjoin was geïnspireerd door dit artikel).

Binnenkort meer over tabellen als datastructuur in Ruby.

Recommend Michiel de Mare

Geplaatst in ,  | 1 reactie

Onze dagmethode #6: AR.find(..).each

Geplaatst door Remco van 't Veer di, 12 feb 2008 08:00:00 GMT

AR.find(..).each

Wat me vaak dwars zit als ik met ActiveRecord in de weer ben is dat ik na een find-all ook nog eens each moet schrijven. Sterker nog als ik each vergeet en er een block aanplak gebeurd daar helemaal niets mee, ook geen foutmelding of iets dergelijks.

Vandaag dus geen methode maar een methode minder!


class ActiveRecord::Base
  def self.find_with_block_sensitivity(*args)
    r = find_without_block_sensitivity(*args)
    [r].flatten.each {|v| yield v} if block_given?
    r
  end

  class << self
    alias_method :find_without_block_sensitivity, :find
    alias_method :find, :find_with_block_sensitivity
  end
end

Hiermee kunnen we ipv:


Article.find(:all).each {|article| puts article.title}

het volgende schrijven:


Article.find(:all) {|article| puts article.title}

Kijk mij! Zonder each! Werkt voor zowel enkele als meervoudige resultaten.

Geplaatst in , ,  | 2 reacties

Onze dagmethode #5: Enumerable#inject!

Geplaatst door Michiel de Mare ma, 11 feb 2008 06:40:00 GMT

Enumerable#inject is een geweldige en veelzijdige methode – zo veelzijdig dat ik hem regelmatig misbruik:

Neem bijvoorbeeld dit fragment:


ar_of_ar.inject(Hash.new(0)) do |h,a|
  a.each {|x| h[x] += 1 }
  h
end

# ik kon ook een lokale variabele gebruiken:
h = Hash.new(0)
ar_of_ar.each do |h,a|
  a.each {|x| h[x] += 1 }
end

# in ruby 1.9 heb je Object#tap
Hash.new(0).tap do |h|
  ar_of_ar.each do |a|
    a.each {|x| h[x] += 1 }
  end
end

Toch vind ik mijn inject-oplossing het mooist – geen lokale variabelen en ook geen overbodig block. Laten we inject aanpassen met een versie die niet steeds de return-value van het block teruggeeft, maar een die het argument in inject steeds aanpast. Als naam stel ik voor: inject!


module Enumerable
  def inject!(memo)
    each {|x| yield(memo,x) }
    memo
  end
end

ar_of_ar.inject!(Hash.new(0)){|h,a| a.each{|x| h[x] += 1}}

Recommend Michiel de Mare

Geplaatst in ,  | geen reacties

Onze dagmethode #4: Hash#excluding en Hash#only

Geplaatst door Remco van 't Veer vr, 08 feb 2008 08:00:00 GMT

Dit ziet er vast bekend uit:


u = params[:user]
attr = if u[:password].blank?
  u.reject do |k,_| 
    [:password, :password_confirmation].include?(k)
  end
end
User.update_attributes(attr || u)

Maar ruikt naar een herbruikbaar truukje, want willen we niet gewoon het volgende schrijven:


u = params[:user]
attr = if u[:password].blank?
  u.excluding(:password, :password_confirmation)
end
User.update_attributes(attr || u)

Simpel te implementeren, inclusief z’n only broertje, met:


class Hash
  def excluding(*keys)
    reject{|k,_| keys.include?(k)}
  end

  def only(*keys)
    reject{|k,_| !keys.include?(k)}
  end
end

Disclaimer: het bovenstaande voorbeeld werkt niet in Rails omdat params een “hash with indifferent access” is. Het maken van een onverschillige variant laat ik aan jou over! :)

Geplaatst in ,  | geen reacties

Onze dagmethode #3: Hash.multi

Geplaatst door Remco van 't Veer do, 07 feb 2008 08:00:00 GMT

Je hebt hem vast wel eens gebruikt Hash.new met een block om een twee-dimensionale hash te maken;


map = Hash.new{|h,k| h[k] = {}}
map[:dragon][:strength] = 9

Maar soms wil je een multi-dimensionale hash;


def Hash.multi
  Hash.new{|h,k| h[k] = Hash.multi}
end

Zelf gebruik ik hem in een applicatie als simpele cache waarvoor ik complexe sleutels heb;


CACHE = Hash.multi

def expensive_query(key)
  cache = CACHE[:query][auth_level][current_channel]
  unless cache.has_key?(key)
    cache[key] = Server.query(auth_level, current_channel, key) 
  else
    cache[key]
  end
end

Geplaatst in ,  | 1 reactie

Onze dagmethode #2: Numeric#in

Geplaatst door Michiel de Mare wo, 06 feb 2008 06:27:00 GMT

De meeste web-applicaties lijken niet veel met random getallen van doen te hebben, online-pokersites natuurlijk uitgezonderd (hoop ik), maar toch komen ze verbazend veel van pas. Daarom introduceer ik vandaag een nieuwe notatie voor kans.


# het kan natuurlijk zo:
redirect_to home_url if rand < 0.2222

# dit is al beter
redirect_to home_url if rand(9) < 2

# maar niet zo mooi als dit
redirect_to home_url if 2.in 9

# de code
class Numeric 
  def in(other)
    rand(other) < self
  end
end

Recommend Michiel de Mare

Geplaatst in ,  | 5 reacties

Onze dagmethode #1: Range#coerce

Geplaatst door Michiel de Mare di, 05 feb 2008 08:00:00 GMT

We introduceren een nieuwe feature: de methode van de dag. Hierin demonstreren we een korte, algemeen toepasbare methode. We doen aan verzoekjes!

Vandaag beginnen we met Range#coerce. Het probleem: je hebt een waarde, en je wilt garanderen dat deze binnen een bepaald interval ligt. Klinkt ideaal voor een Range. Een Range heeft wel een methode om te testen of een waarde erbinnen ligt (include?) maar niet om de waarde te forceren.

Vandaar het volgende:

class Range
  def coerce(x)
    x < first ? first : x > last ? last : x
  end
end

(1..12).coerce(999)   # => 12

Recommend Michiel de Mare

Geplaatst in ,  | Tags  | 5 reacties

Oudere artikelen: 1 2