Our Daily Method #19: Time#method_missing Geplaatst door Michiel de Mare Our stock of cool tricks is depleted, but inspiration still strikes once in a while. Are you using strftime a lot? Do you like writing strfime ? Have you ever spelled it as strtime ? strf_time ? Thought it was frmtstr ? And if you spell it right, you still have to type those stupid %-signs. All in all, room for improvement. class Time def method_missing(name,*args) if name.to_s =~ /f_/ c = args[0] || ' ' x = name.to_s[2..-1].split(//). map {|f| f =~ /[a-z]/i ? "%"+f : c}. join strftime(x) else super end end end So, how does it work? It handles every method starting with f_ . What follows are the characters used within strftime : Y for 4-digit year, d for day of month, etc. Underscores are converted to spaces, or to the first argument, if you provide one. Examples: # standard lib Time.now.strftime("%Y%j") #=> "2008065" Time.now.strftime("s") #=> "1204725800" Time.now.strftime("%d %m %Y") #=> "05 03 2008" Time.now.strftime("%d-%m-%Y") #=> "05-03-2008" Time.now.strftime("%d %b %Y") #=> "05 Mar 2008" # new and improved Time.now.f_Yj #=> "2008065" Time.now.f_s #=> "1204725800" Time.now.f_d_m_Y #=> "05 03 2008" Time.now.f_d_m_Y('-') #=> "05-03-2008" Time.now.f_d_b_Y #=> "05 Mar 2008" Cool? Stupid? Why don’t you play with it ?

Our Daily Method #18: NilClass#method_missing Geplaatst door Remco van 't Veer Warning: only apply the following under parental guidance! We know the whiny nil, as applied in Rails, but what about an obedient nil: class NilClass def method_missing(*args) nil end end No more monkeying like: person && person.name && person.name.upcase Not DRY at all! Let’s use the obedient nil instead: person.name.upcase This concludes our last daily method. Ode to Michiel for providing a vast amount of nice tricks!

Our Daily Method #17: Enumerable#make_index Geplaatst door Michiel de Mare Do you know what I like so much about relational databases? And what I really miss in object-models? That databases make it so effortless to index data. Imagine, you’ve got a huge pile of data, unordered, continually changing, but because your database keeps a few indexes, you can access any item you wish within a few milliseconds. Wouldn’t it be fun if you could do that in Ruby? Especially the effortless part? It happens regularly that I’ve got an array with hundreds of items in which I have to search. That quicky gets slow, so I create a few hashes that I use as indexes. But what a difference with a real database! Let make a first step in the right direction, and implement the make_index method. module Enumerable def make_index(*names) Struct.new(*names).new(*names.map{|n| group_by(&n)}) end end index = User.find(:all).make_index(:login, :id, :nickname) index.login['mdemare'] => [<#User ...>] index.nickname['Michiel'] => [<#User ...>] I admit, not a huge improvement yet. But we’re getting there. For another time, indexes on more than one attribute, unique indexes and indexing attributes with a natural order. ( index.created_at > Time.now.at_midnight )

Our Daily Method #16: Array#bsearch Geplaatst door Michiel de Mare Searching in arrays is pretty slow. Better use a hash. But if the array is already sorted, you can use a binary search, which works by dividing the array in two and searching in the left orthe right part, until there’s only one element left. class Array def bsearch(k) x,y = -1,size while y != x + 1 do m = (x + y) / 2 if self[m] <= k x = m else y = m end end x >= 0 && self[x] == k && x end end

Our Daily Method #15: Kernel#levenshtein Geplaatst door Michiel de Mare Not in any way invented by us, but still extremely useful and not a part of the standard library: the Levenshtein Distance What does it calculate? The number of characters to add, delete or switch to turn the first string into the second. Ideal for detecting typos. # example: levenshtein('levenstine', 'levenshtein') # => 3 Or how about instead of a LIKE -clause? Not quite as fast (cough) but much more accurate. # In the old days: User.find(:all, :conditions => ['name like ?', params[:name] + '%']]) # Now: User.find(:all). sort_by {|u| levenshtein(params[:name], u.name)}. first def levenshtein(s1, s2) d = {} (0..s1.size).each { |row| d[[row, 0]] = row } (0..s2.size).each { |col| d[[0, col]] = col } (1..s1.size).each do |i| (1..s2.size).each do |j| i1,j1 = i-1,j-1 cost = s1[i1] != s2[j1] ? 1 : 0 d[[i, j]] = [d[[i1, j1]] + cost, d[[i1, j]] + 1, d[[i, j1]] + 1].min if i1*j1 > 0 and s1[i1] == s2[j-2] and s1[i-2] == s2[j1] d[[i, j]] = [d[[i,j]] , d[[i-2, j-2]] + cost].min end end end d[[s1.size, s2.size]] end Or how about instead of a-clause? Not quite as fast (cough) but much more accurate.

Our Daily Method #14: Time#warp Geplaatst door Remco van 't Veer Time travel can be very convenient but unfortunately it isn’t very easy. Reality won’t allow it but we can bend the Time class! class Time def self.now_with_warping @warptime || now_without_warping end class << self alias_method :now_without_warping, :now alias_method :now, :now_with_warping end def warp self.class.instance_variable_set('@warptime', self) yield ensure self.class.instance_variable_set('@warptime', nil) end end Now we can travel back to “Unix Epoch”: Time.at(0).warp do puts "The current time is: #{Time.now}" end Or just before the end of time as we know it: Time.at(2 ** 31 - 1).warp do Time.now + 1 end Now we can travel back to “Unix Epoch”:Or just before the end of time as we know it: What’s the use? It makes testing time dependent code very easy!

Our Daily Method #13: Object#mymethods Geplaatst door Michiel de Mare I always have an irb or rails console session open, and I love methods to see what an object is capable of, but it certainly returns an exceptional amount of unsorted crap, especially in Rails. Therefore, mymethods : class Object def mymethods (methods - Object.instance_methods).sort end end (1..2).methods.size # => 150 (1..2).mymethods.size # => 46

Our Daily Method #12: Comparable#at_least Geplaatst door Michiel de Mare It took me a while before I discovered the accepted Ruby idiom to find the bigger of two values. I expected a method in Kernel or perhaps in Comparable . Instead, the Ruby Way is to create an array and ask the maximum value in the array: [x,y].max Well, it’s certainly short, but I don’t like it, for three reasons: You create an array to compare two integers? That has to be inefficient, right? Of course, worrying about efficiency is not The Ruby Way.

I don’t like the name. When I say max it feels as if I’m declaring an upper bound, a maximum, when in fact, I’m declaring a lower bound, a minimum.

it feels as if I’m declaring an upper bound, a maximum, when in fact, I’m declaring a lower bound, a minimum. By listing both values in the array, you’re placing them on equal footing, when often that’s not really the case. Often it’s more an afterthought: you’re saying: I want value foo (and by the way, it must be at least 7.25). Hence, at_most and at_least : total_time / total_tries.at_least(1) Well, it’s certainly short, but I don’t like it, for three reasons:Hence,and Implementation too trivial to list.

Our Daily Method #11: Hash#inv_multi Geplaatst door Michiel de Mare Hash has an invert -method, but it often happens that you’re dealing with a hash with arrays as values. Naturally, you don’t want arrays as keys – you want every element in the array to turn into a key, with an array of all original keys with that element as the value. Simple example {1 => [3,4]}.inv_multi # => {3=>[1], 4=>[1]} Complex example {1 => [3,4,5,6], 2 => [3,4], 4 => 11}.inv_multi # => {5=>[1], 11=>[4], 6=>[1], 3=>[1, 2], 4=>[1, 2]} The method itself: class Hash def inv_multi # there's inject! again. daily method #5 inject!({}) do |h,(k,v)| # Is this obvious? If not, say so! # this lambda binds h and k. l = lambda {|x| (h[x] ||= []) << k} if Array === v v.each(&l) else # value doesn't have to be an array l[v] end end end end The method itself:

Our Daily Method #10: Object#r[rs][ds]s Geplaatst door Michiel de Mare Ruby 1.9 has Object#tap . Danny introduced Object#with a long time ago. Then there’s Object#instance_eval . Do these methods have anything in common, except for the inscrutability of their names? It turns out that they do. These methods fall in the family of methods that: Are defined for any Object Take a block and always execute it once Don’t do anything else It turns out that they do. These methods fall in the family of methods that: So it may seem amazing that there are as much as three methods in this category. Actually, there’s even a fourth! You see, even within these strict specs there are two decisions that we have to take. What do we return? The object itself? Or the result of the block? Within the block, what is self ? Is it the object? Or is it unchanged (so we need to pass on the object as an argument)? How do the methods we referred to earlier stack up? tap : returns result, same self in block

: returns result, same self in block with : return self, same self in block

: return self, same self in block instance_eval : returns result, different self in block. And the Missing Method: switch : returns self, different self in block. You see, even within these strict specs there are two decisions that we have to take.How do the methods we referred to earlier stack up?And the Missing Method: Let’s see these methods in action! Lees verder...

Oudere artikelen: 1 2