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 ruby, english, daily_method | geen reacties
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 ruby, ruby on rails, english, daily_method | geen reacties
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 ruby, ruby on rails, dagmethode | 2 reacties
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 ruby, english | 1 reactie
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 ruby, english, daily_method | geen reacties
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 ruby, dagmethode | geen reacties
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 ruby, english, daily_method | 2 reacties
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 ruby, dagmethode | geen reacties
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 ruby, dagmethode | 1 reactie
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 ruby, english, daily_method | geen reacties