Proper Usage of Ruby Statement Modifiers

Proper usage of Ruby statement modifiers

I find that I usually have no trouble reading those trailing conditionals (as they are also sometimes called), provided that other code readability guidelines are still followed. Putting a 60 character expression and a 40 character condition on the same line, you are going to end up with a 100 character gob of text, which is surely going to be unreadable, completely independent of the issue of trailing conditionals.

In the specific code sample you are showing, it is pretty much obvious that there must be a conditional following. Who would want to raise an ArgumentError without even taking a look at the arguments first?

Also, trailing conditionals are similar to guard clauses in math and functional languages, which also tend to be written after the expression they are guarding.

Last but not least, putting a couple of raise Bar if foo and return nil if quux expressions at the beginning of methods, as kind of guards, is actually considered good style, to simplify the control flow of the method. Again: since these come at the beginning of the method, it is kind of obvious that there has to be a condition, otherwise returning from the beginning of the method wouldn't make sense.


PS: I would actually use unless there, to get rid of the negation. With more complicated conditions, I find that unless can sometimes be hard to parse, but in this case, it's more obvious, at least IMHO.

Why should we avoid using rescue in its modifier form?

Why should we avoid using rescue in its modifier form in rails?

Firstly, because it hides all errors, including the ones you expect and the ones you don't, and a blanket rescue doesn't make it clear to future readers of your code which errors were expected or unexpected. This might not be a problem now, with a simple foo[:a][:b][:c], but at any given point in time somebody might modify that statement to read foo[:a][:b][some_method] and suddenly any errors that should bubble out of some_method are also swallowed.

Secondly, there is usually a better less all-encompassing solution that is more explicitly designed to handle only the error you intend to ignore: A missing index or a nil return value.

In your case, the alternative is not the massive if && && && you're suggesting. For a hash, you can use either dig, which has all the benefits of rescue without swallowing every type of exception that could be raised:

foo = bar.dig(:a, :b, :c)

Similarly, for chained method invocations, you can use try (in Rails) or the safe navigation operator (in Ruby 2.3):

foo = bar.try(:a).try(:b).try(:c)
# or
foo = bar&.a&.b&.c

Ruby 2.2.4 lexing an assignment in a conditional

Both lines won't work. And both lines will work. It is schrödinger expression :).

You can run it twice in a new repl:

a =  b if b = "test"
#=> NameError: undefined local variable or method `b' for main:Object
a = b if b = "test"
#=> "test"

Let's look deeper, open a new repl:

defined(b)
#=> nil
a = b if b = "test"
#=> NameError: undefined local variable or method `b' for main:Object
defined(b)
#=> local-variable
b
#=> "test"
a = b if b = "test"
#=> "test"

So actually Ruby has evaluated b = "test" part and defined this variable in current scope. Both expressions a = b and if b = "test" were executed. More than it, if statement is executed before assignment statement:

c = p("assignment") && b if b = p("if") && "test"
#=> "if"
#=> "assignment"
#=> NameError: undefined local variable or method `b' for main:Object

But b variable was not defined in scope of assignment statement when it was evaluated first time. And on the second approach it was already defined so you received correct result.

So, Never do assignments in this way

Proper usage of 'end_with?'?

As @razvans points out, the error message here is great.

The Error

You're initializing your object with input_file_path: nil, and then inside of call you're checking if @input_file_path (set to input_file_path in the initializer, AKA nil) ends with a string.

There is no .end_with? method on nil. That's causing the NoMethodError error.

The Solution

You could guard against nil in your end_with? call, or give it a default value in the initializer.

Can I put an if/unless clause on the next line in Ruby?

Can I put an if/unless clause on the next line in Ruby?

You can't. From page 107 (PDF page 127) of the final draft of ISO Ruby which usually isn't relevant, but basic things like this are and it also spares us from having to read parse.y:

unless-modifier-statement ::
statement [no line-terminator here] unless expression

This is pretty clear. It just doesn't get more similar to your Perl example than:

raise ArgumentError, "incompatible object given: #{object.inspect}" unless
object.is_a?(ExampleObject) and object.respond_to?(:foo)`

or:

raise ArgumentError, "incompatible object given: #{object.inspect}" \
unless object.is_a?(ExampleObject) and object.respond_to?(:foo)


Related Topics



Leave a reply



Submit