Hi,
I’m implementing a plain simple search functionality over an array of
objects. One of the attributes that those objects expose is a creation
date (a Time object). The search functionality should work in 3 ways:
- starting from a date
- up to a date
- within a certain range
In the first implementation I had the same code repeated in different
search methods:
def search_from(start)
@list.select {|s| s.creation >= start}
end
def search_to(_end)
@list.select {|s| s.creation <= _end}
end
def search(start, _end)
@list.select {|s| s.creation >= start && s.creation <= _end}
end
So I wanted to remove the duplication and I thought about defining
search_from and search_to in terms of search. I tried using some sort
of block composition by means of lambda functions, but I didn’t really
like it:
def search_from(start)
search(start, nil)
end
def search_to(_end)
search(nil, _end)
end
def search(start, _end)
upper_limit = if start.nil?
lambda {|s| true}
else
lambda {|s| s.creation >= start}
end
lower_limit = if _end.nil?
lambda {|s| true}
else
lambda {|s| s.creation <= _end}
end
@list.select {|s| upper_limit.call(s) && lower_limit.call(s)}
end
In the end I’ve decided to override <=> on Time so that if you compare
with nil it returns 0 but:
- it’s really ugly
- as a side effect Time.mktime(2007) == nil evaluates to true (while
of course Time.mktime(2007).nil? is false) - your code may break in unexpected ways
Does anybody know a better approach towards block composition?
Thanks! AA
PS: here is the current code with the ugly, horrible, too bad Time.<=>
override.
class Time
alias_method :comp, “<=>”.intern
def <=>(other)
if other.nil?
0
else
comp(other)
end
end
end
class Stuff
attr_accessor :name
attr_accessor :creation
def initialize(name, creation)
@name = name
@creation = creation
end
end
class Repository
def initialize(list)
@list = list
end
def search_from(start)
search(start, nil)
end
def search_to(_end)
search(nil, _end)
end
def search(start, _end)
@list.select {|s| s.creation >= start && s.creation <= _end}
end
end
require ‘test/unit’
class TestRepository < Test::Unit::TestCase
def setup
@jan = Stuff.new(“jan”, Time.mktime(2007, 1))
@feb = Stuff.new(“feb”, Time.mktime(2007, 2))
@rep = Repository.new([@jan, @feb])
end
def test_search_returns_values_starting_from_creation_date
assert_equal([], @rep.search_from(Time.mktime(2008)))
assert_equal([@feb], @rep.search_from(Time.mktime(2007, 1, 15)))
assert_equal([@jan, @feb], @rep.search_from(Time.mktime(2006,
12)))
end
def test_search_returns_values_up_to_creation_date
assert_equal([], @rep.search_to(Time.mktime(2006)))
assert_equal([@jan], @rep.search_to(Time.mktime(2007, 1, 15)))
assert_equal([@jan, @feb], @rep.search_to(Time.mktime(2008)))
end
def test_search_returns_values_within_a_range_of_creation_dates
assert_equal([], @rep.search(Time.mktime(2005),
Time.mktime(2006)))
assert_equal([@jan], @rep.search(Time.mktime(2006),
Time.mktime(2007, 1, 15)))
assert_equal([@jan, @feb], @rep.search(Time.mktime(2006),
Time.mktime(2008)))
end
end