Reputation: 253
I am trying to write a function that will solve a series of tests. It is an array of hashes. I got started and my solution solves two of the tests but i feel as though my solution might not be the best.
I tried to access the rows using the key
provided but ran into issues. So my next move was to loop through the fields in each row and then check it that matches the value passed in to the where
method.
class Array
def where(options)
result = []
keys = options.keys.to_s
values = options.values
puts options.keys
self.each do |row|
row.each do |k, v|
if values.include?(v)
result << row
end
end
end
result
end
end
The tests are below:
require 'minitest/autorun'
class WhereTest < Minitest::Test
def setup
@boris = {:name => 'Boris The Blade', :quote => "Heavy is good. Heavy is reliable. If it doesn't work you can always hit them.", :title => 'Snatch', :rank => 4}
@charles = {:name => 'Charles De Mar', :quote => 'Go that way, really fast. If something gets in your way, turn.', :title => 'Better Off Dead', :rank => 3}
@wolf = {:name => 'The Wolf', :quote => 'I think fast, I talk fast and I need you guys to act fast if you wanna get out of this', :title => 'Pulp Fiction', :rank => 4}
@glen = {:name => 'Glengarry Glen Ross', :quote => "Put. That coffee. Down. Coffee is for closers only.", :title => "Blake", :rank => 5}
@fixtures = [@boris, @charles, @wolf, @glen]
end
def test_where_with_exact_match
assert_equal [@wolf], @fixtures.where(:name => "The Wolf")
end
def test_where_with_partial_match
assert_equal [@charles, @glen], @fixtures.where(:title => /^B.*/)
end
#
def test_where_with_mutliple_exact_results
assert_equal [@boris, @wolf], @fixtures.where(:rank => 4)
end
#
def test_with_with_multiple_criteria
assert_equal [@wolf], @fixtures.where(:rank => 4, :quote => /get/)
end
def test_with_chain_calls
assert_equal [@charles], @fixtures.where(:quote => /if/i).where(:rank => 3)
end
end
Upvotes: 0
Views: 56
Reputation: 110725
You may wish to write your method Array#where
as follows:
Code
class Array
def where(select_hash)
select do |h|
select_hash.all? { |k,v| v === h[k] }
end
end
end
Examples
boris = {:name => 'Boris The Blade', :title => 'Snatch', :rank => 4,
:quote => "Heavy is good. If it doesn't work you can always hit them."}
charles = {:name => 'Charles De Mar', :title => 'Better Off Dead', :rank => 3,
:quote => 'Go that way. If something gets in your way, turn.' }
wolf = {:name => 'The Wolf', :title => 'Pulp Fiction', :rank => 4,
:quote => 'I need you guys to act fast if you wanna get out of this'}
glen = {:name => 'Glengarry Glen Ross', :title => "Blake", :rank => 5,
:quote => "Put. That coffee. Down. Coffee is for closers only."}
wanda = {:arr => [1, 2, 3] }
fixtures = [boris, charles, wolf, glen, wanda]
fixtures.where(:name => "The Wolf") # [wolf]
#=> [{:name=>"The Wolf", :title=>"Pulp Fiction", :rank=>4,
# :quote=>"I need you guys to act fast if you wanna get out of this"}]
fixtures.where(:title => /^B.*/) # [charles, glen]
#=> [{:name=>"Charles De Mar", :title=>"Better Off Dead", :rank=>3,
# :quote=>"Go that way. If something gets in your way, turn."},
# {:name=>"Glengarry Glen Ross", :title=>"Blake", :rank=>5,
# :quote=>"Put. That coffee. Down. Coffee is for closers only."}]
fixtures.where(:rank => 4) # [boris, wolf]
#=> [{:name=>"Boris The Blade", :title=>"Snatch", :rank=>4,
# :quote=>"Heavy is good. If it doesn't work you can always hit them."},
# {:name=>"The Wolf", :title=>"Pulp Fiction", :rank=>4,
# :quote=>"I need you guys to act fast if you wanna get out of this"}]
fixtures.where(:rank => 4, :quote => /get/) # [wolf]
#=> [{:name=>"The Wolf", :title=>"Pulp Fiction", :rank=>4,
# :quote=>"I need you guys to act fast if you wanna get out of this"}]
fixtures.where(:quote => /if/i).where(:rank => 3) # [charles]
#=> [{:name=>"Charles De Mar", :title=>"Better Off Dead", :rank=>3,
# :quote=>"Go that way. If something gets in your way, turn."}]
fixtures.where(:arr => Array) # [wanda]
#=> [{:arr=>[1, 2, 3]}]
@Amadan makes a good point that polluting the class Array
is not a good idea. Using refinements is a possibility, but I still find that distasteful, as the method requires a receiver that is a particular type of array, one whose elements are hashes. Array
methods, by contrast, operate on arbitrary arrays. Instead, I would suggest making the array a second argument (i.e., where(arr, select_hash)
).
Upvotes: 2
Reputation: 198436
First, I don't like direct modification of base classes, not now that we have refinements. You can just use the method definition if you don't want the refinement, it will work the same.
Second, you are replicating a lot of work that Ruby already knows how to do. In particular, keeping or removing elements from an array is the job of #select
, #select!
, #keep_if
, #delete_if
and the like; while it is certainly possible to not use them, you will get a performance hit (since those functions are written in C, and will be faster than anything you can do in Ruby itself), and a readability hit (as you'll need more lines for the same job if you're not using available tools but crafting your own). I start with the full array, then whittle it down to only the elements that match all of the conditions. Also, note that I use dup
to start with here - otherwise, keep_if
would be removing elements from the fixture itself!
Third, what you thought you could get with #include?
, you get from the subsumption operator ===
. It is always somewhat hard to explain what ===
actually does, but in general, it looks like condition === value
and you'll get true
(or truthy) if the value is described by the condition. It works for containers having an element or not, it works with regexps matching a string or not, it works with predicates describing a value, and also with simple values being equal to other simple values. See === vs. == in Ruby for better explanations than mine.
module ArrayWithWhere
refine Array do
def where(conditions)
dup.tap do |result|
conditions.each do |field, condition|
result.keep_if { |element| condition === element[field] }
end
end
end
end
end
class WhereTest < Minitest::Test
using ArrayWithWhere
# ...
end
Upvotes: 3