5

probably asked already but I couldn't find it.. here are 2 common situation (for me while programming rails..) that are frustrating to write in ruby:

"a string".match(/abc(.+)abc/)[1]

in this case I get an error because the string doesn't match, therefore the [] operator is called upon nil. What I'd like to find is a nicer alternative to the following:

temp="a string".match(/abc(.+)abc/); temp.nil? ? nil : temp[1]

in brief, if it didn't match simply return nil without the error

The second situation is this one:

var = something.very.long.and.tedious.to.write
var = something.other if var.nil?

In this case I want to assign something to var only if it's not nil, in case it's nil I'll assign something.other..

Any suggestion? Thanks!

Bob Aman
  • 32,839
  • 9
  • 71
  • 95
luca
  • 12,311
  • 15
  • 70
  • 103
  • Ok I'm very tired I apologize for the second question it was stupid (yes it's a simple OR..), the first one remains interesting though.. – luca Dec 30 '10 at 19:25
  • Actually, the second one was interesting too. I briefly got it wrong, because I forgot about the `or` operator's precedence. Misuse of `or` is a pretty common faux pas and it's worth highlighting. – Bob Aman Dec 31 '10 at 18:17
  • This is very similar to this question: http://stackoverflow.com/questions/4371716/looking-for-a-good-way-to-avoid-hash-conditionals-in-ruby/ – mpd Dec 31 '10 at 21:22

6 Answers6

3

In Ruby on Rails you have the try method available on any Object. According to the API:

Invokes the method identified by the symbol method, passing it any arguments and/or the block specified, just like the regular Ruby Object#send does.

Unlike that method however, a NoMethodError exception will not be raised and nil will be returned instead, if the receiving object is a nil object or NilClass.

So for the first question you can do this:

"a string".match(/abc(.+)abc/).try(:[], 1)

And it will either give you [1] or nil without error.

icecream
  • 1,435
  • 12
  • 20
  • This code is hard to read and only works if ActiveSupport is present. – Bob Aman Dec 30 '10 at 20:09
  • The question was originally tagged with ruby-on-rails before you modified it, so I presented the cleanest solution built into Ruby on Rails. And hard to read? Most Ruby programmers are used to how the "send" method works, and this is barely less readable than the accepted answer, using the external Ick library. You might as well downvote that too, because it "only works if Ick is present." Overall, an unwarranted downvote. – icecream Dec 30 '10 at 20:41
  • Considering the second half of tokland's answer is 100% correct, that *would* be an unwarranted downvote. While the original question was tagged ruby-on-rails, nothing about the question has anything to do with rails. Unnecessary dependencies are among the most obnoxious mistakes that Ruby programmers make. But in your case, you didn't just add an unnecessary dependency. Your implementation also introduces an unnecessary and slow send-dispatch, and as stated before, it's hard to read. I would never let code like this live through code review. So yes, down-vote. – Bob Aman Dec 31 '10 at 18:18
3
"a string"[/abc(.+)abc/, 1]
# => nil
"abc123abc"[/abc(.+)abc/, 1]
# => "123"

And:

var = something.very.long.and.tedious.to.write || something.other

Please note that or has a different operator precedence than || and || should be preferred for this kind of usage. The or operator is for flow control usage, such as ARGV[0] or abort('Missing parameter').

Bob Aman
  • 32,839
  • 9
  • 71
  • 95
3

Forget that Python atavism!

"a string"[/abc(.+)abc/,1] # => nil
Nakilon
  • 34,866
  • 14
  • 107
  • 142
  • 2
    +1 for using the word "atavism" in a programming-related context. – zetetic Dec 30 '10 at 21:27
  • The only purpose of `match` I know is http://stackoverflow.com/questions/4472848/is-there-a-shorthand-for-assigning-from-1-n-after-matching-in-ruby/4472857#4472857 – Nakilon Dec 30 '10 at 22:50
  • Actually, awhile ago I wrote a routing mechanism that duck-typed on a `match`/`captures` method pair. That allowed me to trivially introduce URI template matching to the routing system. I've actually found many advantages to having a less idiomatic way of executing a regular expression. They just tend to be things that you write in the context of meta-programming or when creating DSLs. – Bob Aman Dec 31 '10 at 18:13
0

For the first I'd recommend ick's maybe (equivalent to andand)

"a string".match(/abc(.+)abc/).maybe[1]

I am not sure I understand the second one, you want this?

var = something.very.long.and.tedious.to.write || something.other
tokland
  • 66,169
  • 13
  • 144
  • 170
0
"a string".match(/foo(bar)/).to_a[1]

NilClass#to_a returns an empty array, and indexing outside of it gives you nil values.

Alternatively (what I do) you can splat the matches:

_, some, more = "a string".match(/foo(bar)(jim)/).to_a
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • @BobAman Would you care to elaborate? To which part do you object? The use of the underscore variable to silently ignore the full match in the cases where all you care about are the sub-matches? It helps no one to downvote, say "Ewww", and walk away. – Phrogz Dec 30 '10 at 19:44
  • 1
    I'm referring to the usage of the `NilClass#to_a` method plus the out-of-range indexing. As fun as duck typing is, you're being way too clever here. Clever is bad. And splatting the matches is more readable anyhow. I'd only ever do this, however, if I was assigning multiple capture groups at once. Otherwise you should use the `String#[]` method since that's what it's meant for. – Bob Aman Dec 30 '10 at 20:02
  • @BobAman I agree that `String#[]` is better when extracting a single match. I only do the above when splatting multiple captures. I also agree that being clever for clever sake is bad. IMO this is not clever, however. I feel that this is a common idiom I have seen in multiple spots; I first saw it used and advocated by Ara T. Howard. Thank you for your clarification. – Phrogz Dec 30 '10 at 21:03
0

For the first question, I think Bob's answer is good.

For the second question,

var = something.very.long.and.tedious.to.write.instance_eval{nil? ? something.other : self}
sawa
  • 165,429
  • 45
  • 277
  • 381