Our Daily Method #7: Enumerable#mjoin

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

You all know flatten. Flatten takes an array and removes all structure from it. But sometimes you’ve got an array of arrays of arrays, and want to turn it into an array of arrays. What we need is a flatten-light: mjoin

module Enumerable
  def mjoin
    inject!([]) {|memo,x| a.each {|x| memo << x}}
  end
end
(read more about inject!).

When do you use this? Whenever you want to use map but you don’t always want to return exactly one value, and some or all of the values are arrays (which means you can’t use flatten).

Does that sound exceedlingly rare? In fact, it is a very common structure. A table is an array of arrays, and a join is an operation which returns zero or more results per row. And this also explains the name! (join, of course, is already taken – mjoin was inspired by this article).

I plan to talk more about using the table datastructure in the near future.

Geplaatst in , ,  | geen reacties

Our Daily Method #6: AR.find(..).each

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

AR.find(..).each

Working with ActiveRecord it often bothers me I need to write each after doing a find-all. I can give find a block but is just ignored without any warning.

So today no new method but the removal of a method!


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

Now, instead of:


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

we can write:


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

Look ma! No each! Also works for single object results.

Geplaatst in , , ,  | geen reacties

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

Ruby And The Seven Virtues

Geplaatst door Michiel de Mare ma, 11 feb 2008 08:52:00 GMT

This is a Dutch blog, and therefore we love to quote Dutch computer scientists:

Elegance is not a dispensable luxury but a factor that decides between success and failure.

Edsgar Dijkstra

Seven pieces of Java code and the alternative in Ruby:

1. Short circuit with nil

Java


if(foo != null) { 
  bar(foo); 
} else { 
  bar(""); 
}

Ruby


bar(foo || "")
Lees verder...

Geplaatst in ,  | 1 reactie

Our Daily Method #5: Enumerable#inject!

Geplaatst door Michiel de Mare ma, 11 feb 2008 07:20:00 GMT

Enumerable#inject is a terrific and flexible method – so flexible that I regularly abuse it:

Take for instance this fragment:


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

# I could simply use a local variable
h = Hash.new(0)
ar_of_ar.each do |h,a|
  a.each {|x| h[x] += 1 }
end

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

And yet I like my inject-solution better – no local variables or superfluous blocks. Why not change inject by not returning the value of the block and using it in the next iteration, why not each time pass in the argument to inject. Let’s name this function 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}}

Geplaatst in , ,  | geen 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}}

Geplaatst in ,  | geen reacties

Our daily Method #4: Hash#excluding en Hash#only

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

This will look familiar:


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

But it smells like something reusable. I would like to write:


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

Easily to implement, including its little brother only, with:


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

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

Disclaimer: the above example doesn’t really work in Rails because params is a hash with indifferent access. Making an indifferent variant is up you! :)

Geplaatst in , ,  | 2 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

Our Daily Method #3: Hash.multi

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

You have probably used it before; Hash.new with a block to make a two-dimensional hash;


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

But sometimes you need a multi-dimensional hash;


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

I use it in an application to construct a simple cache with complex keys;


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 , ,  | geen reacties

Oudere artikelen: 1 ... 3 4 5 6 7 ... 12