When searching, keywords 'block' and ruby 'define_method' keep telling me to explicitly define + call the block. Tried a bunch of stuff, including looking at source code but just can't tell. All I'd like to know is if there is some way to have a block_given? call be called in the context of the method. I.e.
define_method(:example) do
raise TypeError if block_given?
nil
end
block_given? seems to get confused in this case.
You can try something like this instead -
define_method(:hello) do |*args, &block|
raise TypeError unless block
end
Related
I came across this solution for a Proxy class in the Ruby koans:
class Proxy
attr_accessor :messages
def initialize(target_object)
#object = target_object
#messages = []
end
def method_missing(method_name, *args, &block)
#messages << method_name
#object.send(method_name, *args, &block)
end
end
I can create an object from this proxy class by passing another class as an argument. For instance, the following code will result in "Do something", without having to type thing.method_missing(:do_thing):
class Thing
def do_thing
puts "Doing something."
end
end
thing = Proxy.new(Thing.new)
thing.do_thing
Why is the code in method_missing executed even without having to call said method?
There are methods that are called implicitly (i.e., called even when you don't write it in the code) when a certain event happens or a certain method is called. I call these methods hooks, borrowing the terminology of e-lisp. As far as I know, Ruby has the following hooks:
Ruby hooks
at_exit
set_trace_func
initialize
method_missing
singleton_method_added
singleton_method_removed
singleton_method_undefined
respond_to_missing?
extended
included
method_added
method_removed
method_undefined
const_missing
inherited
initialize_copy
initialize_clone
initialize_dup
prepend
append_features
extend_features
prepend_features
And method_missing is one of them. For this particular one, it is automatically called when Ruby cannot find a defined method. Or in other words, method_missing is the most default method that is called with the least priority, for any method call.
method_missing is one of the amazing aspects of metaprogramming in ruby. With proper use of this method, you can gracefully handle exceptions and whatnot. In your case it is called because the method you are calling on the object doesn't exist obviously.
But one should be careful of its use too. While you are at it do look at responds_to method too.
An example regarding ActiveRecord will make you understand better. When we write:
User.find_by_email_and_age('me#example.com', 20)
There isn't actually a method by that name. This call goes to the method_missing and then this fancyfindmethod is broken down into pieces and you are served what you asked for. I hope that helps.
I have this code:
class B
def self.definer(name, *args, &block)
define_method(name) { self.instance_exec(*args, &block) }
end
end
and when I try to use it, I get this error:
B.definer(:tst) { super }
# => :tst
B.new.tst
# => TypeError: self has wrong type to call super in this context: B (expected #<Class:#<Object:0x007fd3008123f8>>)
I understand that super has a special meaning, and works little different from calling a method. Can someone explain why and what is happening? It would also be great if someone suggests a solution for this.
I don't get the same error message as you did, but get an error anyway. super must be used within a method definition. But you are not using it in a method definition. That raises an error.
Regarding the solution, I cannot give you one since it is not clear at all what you are trying to do.
You definitely don't want instance_exec there.
If you didn't have the *args involved, I'd say you just wanted this:
def self.definer(name, &block)
define_method(name, &block)
end
But then your new definer method would do the exact same thing that define_method does in the first place, so there's be no reason to create it, instead of just using define_method in the first place.
What are you actually trying to do? Explain what you want to do, and maybe someone can help you.
But I think the instance_exec in your existing implementation isn't what you want -- it is immediately executing the block upon definer call, when calling define_method -- I think you want the block executed when the method you are defining is being called instead? But I'm not really sure, it depends on what you're trying to do, which is unclear. super doesn't really make any sense within an instance_exec -- super to what method did you think you'd be calling?
I was trying to write my first method_missing override when I kept running into (edited) stack level too deep errors. The main culprit seemed to be trying to utilize an instance attribute. For instance if 'self' was a instance of the User class then checking for something like:
def method_missing(name)
if self.name
# do stuff
end
end
Would seg fault. I spent a long time on this but ended up giving up. There must be something I'm not understanding about accessing it.
Edit
My apologies, Andrew is correct, I am getting Stack Level too deep errors. With this in mind, what is the appropriate (if any) way to access the instances attribute values?
You can potentially rectify this problem by ensuring that self.name actually exists:
def method_missing(name)
if self.respond_to?(:name) && self.name
# do stuff
end
end
Note this may not work if your class inherits from anything Railsy (e.g. ActiveRecord::Base), since it overrides respond_to?.
If you are in a Railsy class, your method missing should call super, lest you lose a lot of the "magic" ActiveRecord methods (including, probably, self.name itself):
def method_missing(name, *args, &block)
if name_is_something_i_should_handle_here
# do your stuff
else
super(name, *args, block) # call parent's method_missing
end
end
Obviously you should replace name_is_something_i_should_handle_here with the appropriate logic.
You may also wish to consider using dynamic method creation instead of method_missing.
I have an object like this
class SomeObject
def initialize &block
# do something
end
end
class AnotherObject < SomeObject
def initalize &block
super
# do something with block
end
end
When super is called in AnotherObject, the block seems to be passed to SomeObject. Is this the right behaviour and is there away round it?
According to rubyspec this is the correct behaviour, even if you pass explicit arguments to super (i.e. super('foo'))
If you don't want to pass that block, you could just pass a block that does nothing, although this isn't quite the same thing (e.g. if the method changes its behaviour based on block_given?)
It appears that
super(&nil)
is a way to pass no block at all to super, although I couldn't find this in ruby spec.
I don't want to raise a NoMethodError at a certain place but only for a certain class (ex. NilClass).
Ex.
begin
maybe_nil_maybe_not_nil = nil
maybe_nil_maybe_not_nil.x
rescure NoMethodError => ex
raise unless ex.class_which_raised == NilClass
end
I now do this based on the to_s of the exception but messages are different when you don't expect it for .id, .product and maybe others. It would be better to do this based on metadata/parameters. Is there a way to do this?
Using exceptions for control flow is usually a bad idea. What would happen if the method you called raised a NoMethodError itself? (It would get swallowed by your rescue block)
You're better off using respond_to?
raise 'this is bad' if object.nil?
if object.respond_to?(:x)
object.x
end
Well, depending on what you're doing and how you feel about re-opening classes, another option is to override method_missing on your class, e.g.:
class NilClass
alias method_missing old_method_missing
def method_missing(m_sym, *args, &block)
old_method_missing m_sym, *args, &block
log_that_you_had_a_missing_method_call
end
end
This is kind of dirty, but sometimes you don't have control over all of the callers of the method, if you're dealing with e.g. a library. I would make sure to add a lot of comments about why you're monkey patching your class, since the potential for unintended side effects is huge here.
I would recommend, barring any TOCTOU issues, if you possibly can, going with Matt von Rohr's solution of looking before you leap.