tags:

views:

62

answers:

2

I have an array of tags per item like so:

item1 = ['new', 'expensive']
item2 = ['expensive', 'lame']

I also have a boolean expression as a string based on possible tags:

buy_it = "(new || expensive) && !lame"

How can I determine if an item matches the buying criteria based on the tags associated with it? My original thought was to do a gsub on all words in buy_it to become 'true' or 'false' based on them existing in the itemx tags array and then exec the resulting string to get a boolean result.

But since the Ruby community is usually more creative than me, is there a better solution?

EDIT:

Just to clarify, buy_it in my example is dynamic, users can change the criteria for buying something at run-time.

A: 

A hash is a good candidate here.

items = {'ipad' => ['new', 'expensive'], 'kindle' => ['expensive', 'lame']}
items.each do |name,tags|
  if tags.include?('new' || 'expensive') && !tags.include?('lame')
    puts "buy #{name}."
  else
    puts "#{name} isn't worth it."
  end
end
Ben
That would work for this particular situation, but buy_it is actually a user specified string, so the logic has to be dynamic.
cgyDeveloper
+1  A: 

Along the lines of your gsub idea, instead of substituting each word for true/false every time, why not substitute each "query" into an expression that can be re-used, e.g. the example buy_it:

buy_it = "(new || expensive) && !lame"
buy_it_expr = buy_it.gsub(/(\w+)/, 'tags.include?("\1")')

puts buy_it_expr
=> (tags.include?("new") || tags.include?("expensive")) && !tags.include?("lame")

It could be evaluated into a Proc and used like this:

buy_it_proc = eval "Proc.new { |tags| #{buy_it_expr} }"

buy_it_proc.call(item1)
=> true
buy_it_proc.call(item2)
=> false

Of course, care must be taken that the expression does not contain malicious code. (One solution might be to strip all but the allowed operator characters from the string and of course be wary of exceptions during eval.)

Arkku
For further speed-ups, one might consider switching the look-up mechanism from `Array.include?` and/or the tag storage format from `String` to something that's faster to compare, e.g. `:symbols`. Automating this conversion could even use a similar `eval` trick.
Arkku
Yeah, this is along the lines of what I was thinking. I didn't know I could get the gsub down to one line though (thought I would have to do a scan first), that makes it pretty slick. The only thing this solution can't handle are tags that are substrings of other tags such as buy_it = "cool || cooler". (I suppose I could just make a rule that valid tags can not be substrings of other tags.)
cgyDeveloper
It handles substrings just fine: `"cool || cooler".gsub(/(\w+)/, 'tags.include?("\1")')` produces `tags.include?("cool") || tags.include?("cooler")`.
Arkku
Yeah, that was my mistake... I still had my gsub stuck in my head. Yours is the same as mine, but.... better. Exactly what I wanted, thanks :)
cgyDeveloper