In Ruby, Why Is a Method Invocation Not Able to Be Treated as a Unit When "Do" and "End" Is Used

In Ruby, why is a method invocation not able to be treated as a unit when do and end is used?

From the (unofficial) ruby grammar, we see that the contents of (...) in puts (...) must be CALL_ARGS, which don't directly reduce to STMT. However, they can reduce to '(' COMPSTMT ')'. By including an extra set of parentheses, you can use do ... end.

a = [1,2,3,4]

puts ((a.inject do |sum, x|
sum + x
end))

Ruby Print Inject Do Syntax

The block written using the curly braces binds to the inject method, which is what your intention is, and it will work fine.

However, the block that is encapsulated in the do/end block, will bind to the p-method. Because of this, the inject call does not have an associated block. In this case, inject will interpret the argument, in this case 0, as a method name to call on every object. Bacuase 0 is not a symbol that can be converted into a method call, this will yield a warning.

Rails - Why can't I use a method I created in a module in my tests?

There are a couple of different errors here.
First, when you create a module and you mix the content of the module into a class, the methods within the module become part of the class itself.

That said, the following line doesn't make sense

assert_redirected_to SpreedlyTools.spreedly_signup_url(user)

Assuming you mixed the module into a User class, you should call the method as

assert_redirected_to User.new.spreedly_signup_url(user)

Also note the new statement. Because you include the module into the class and you don't extend the class, the method become an instance method not a class method.

assert_redirected_to User.new.spreedly_signup_url(user) # valid
assert_redirected_to User.spreedly_signup_url(user) # invalid

For this reason, the following line doesn't make sense.

class ActiveSupport::TestCase
include SpreedlyTools
....
end

Why can't protected methods be called with symbol to proc?

Let's start by noting that in Ruby, as you probably know, in a method a declared on a class Foo, I can invoke protected methods on any instance of Foo.

How does Ruby determine whether we're in a method declared on class Foo though? To understand this, we'll have to dig into the internals of method invocation. I'll be using examples from version 2.2 of MRI, but presumably the behavior and implementation are the same in other versions (I'd love to see results from testing this on JRuby or Rubinious, though).

Ruby does this in rb_call0. As the comment suggests, self is used to determine whether we can call protected methods. self is retrieved in rb_call from the current thread's call frame info. Then, in rb_method_call_status, we check that this value of self is of the same class on which a protected method is defined.

Blocks confuse the issue somewhat. Remember that local variables in a Ruby method are captured by any block declared in that method. This means that in the block, self is the same self on which the method was invoked. Let's look at an example:

class Foo
def give_me_a_block!
puts "making a block, self is #{self}"
Proc.new do
puts "self in block0 is #{self}"
end
end
end

proc = Foo.new.give_me_a_block!

proc.call

Running this, we see the same instance of Foo is the same at all levels, even though we called the proc from a completely different object.

So, now we understand why it's possible to call a protected method on another instance of the same class from within a block in a method.

Now let's look at why a proc created with &:bar can't do this. When we place an & sign before a method argument we do two things: instruct ruby to pass this argument as a block, and instruct it to call to_proc on it.

This means invoking the method Symbol#to_proc. This method is implemented in C, but when we invoke a C method, the pointer to self on the current frame becomes the receiver of that C method -- in this case, it becomes the symbol :bar. So we're looking at the instance of foo we got as though we are in a method of class Symbol, and we can't invoke a protected method.

That's a mouthful, but hopefully it makes enough sense. Let me know if you have any suggestions as to how I might improve it!

syntax error, unexpected ',', expecting ')' with Rails4

It's no longer valid (since 1.8.7 I believe) to call a multi-arg method with a space between the method name and any parenthesis:

~ >rvm 1.8.7
~ >ruby -ve'def bs(a,b) end; bs (1,2)'
ruby 1.8.7 (2013-06-27 patchlevel 374) [i686-darwin12.5.0]
-e:1: warning: don't put space before argument parentheses
~ >rvm 2.1.1
~ >ruby -ve'def bs(a,b) end; bs (1,2)'
ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-darwin12.0]
-e:1: syntax error, unexpected ',', expecting ')'
def bs(a,b); end; bs (1,2)
^
-e:1: warning: possibly useless use of a literal in void context
~ >ruby -ve'def bs(a,b) end; bs(1,2)'
ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-darwin12.0]

Here's another answer from StackOverflow that touches on the ruby grammer behind this.

When to use undef_method, and when to use remove_method?

From the fine manual:

undef_method(symbol) → self

Prevents the current class from responding to calls to the named method. Contrast this with remove_method, which deletes the method from the particular class; Ruby will still search superclasses and mixed-in modules for a possible receiver.

So a remove_method like this:

class CC < C
remove_method :m
end

is essentially the opposite of this:

class CC < C
def m
end
end

Where def m adds the method m to the class, remove_method :m removes m. But, if the super class has an m method, then that will still be used.

undef_method, OTOH, is more like this:

class CC < C
def m
raise 'No, you cannot do that.'
end
end

So undef_method doesn't actually remove the method, it replaces the method with a special internal flag that causes Ruby to complain if you try to call that method.

Sounds like you're trying to replace an existing method and replace is semantically the same as a remove followed by an add so remove_method is probably more appropriate. However, if you want to be paranoid and make sure the replacement method is in place, then undef_method would be useful; or, if for some reason you need to remove the method in one place and add it in another, undef_method would at least tell you that you only did half the job whereas remove_method would either leave you with the super class's implementation (and possible strange bugs) or a rather confusing NoMethodError.

How to restrict users from calling specific methods

Your unit tests can use Object#send(...) to invoke private methods:

class Foo
private
def bar
"ok"
end
end

f = Foo.new
f.bar # => NoMethodError: private method `bar' called...
f.send(:bar) # => "ok"

So your "encrypt" method could be the only public one but it can still use private methods which are not exposed to polite users of your library (they can, of course, use send also if they want).

Note also that a peculiar aspect of "private" methods is that they cannot be invoked with an explicit receiver, even in the same class. So you could not call self.my_private_method, instead you must simply call my_private_method. For example:

class Foo
def foo
self.bar # => NoMethodError: private method `bar' called...
bar # => "ok"
end
private
def bar; "ok"; end
end


Related Topics



Leave a reply



Submit