Rubocop gives me the following offence
lib/daru/vector.rb:1182:5: C: Style/MethodMissing: When using method_missing, define respond_to_missing? and fall back on super.
def method_missing(name, *args, &block) ...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The method missing is defined as:
def method_missing(name, *args, &block)
if name =~ /(.+)\=/
self[$1.to_sym] = args[0]
elsif has_index?(name)
self[name]
else
super(name, *args, &block)
end
end
I tried fixing it with the below code sighting an example from here
def respond_to_missing?(method_name, include_private=false)
(name =~ /(.+)\=/) || has_index?(name) || super
end
But now Rubocop give me the follow offence:
lib/daru/vector.rb:1182:5: C: Style/MethodMissing: When using method_missing, fall back on super.
def method_missing(name, *args, &block) ...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I can't seem to figure out what's wrong. As you can see I'm falling back on super in the else block.
Rubocop expects super to be called without arguments. As the arguments you are passing to super are the same as those you received, you can simply remove the arguments:
def method_missing(name, *args, &block)
if name =~ /(.+)\=/
self[$1.to_sym] = args[0]
elsif has_index?(name)
self[name]
else
super
end
end
Maybe you should try def respond_to_missing?(name, include_private=false) instead?
Related
I am new to ruby
Trying to write an around aspect. My code is as follows
My code looks as follows
module Utils
module Aspects
def self.included(base)
base.extend(self)
end
def around_aspect(method_name, before_proc, after_proc)
code = %Q[
def #{method_name} *args, &block
#{before_proc.call}
old_#{method_name} *args, &block
#{after_proc.call}
end
]
class_eval %Q[
alias_method :old_#{method_name}, :#{method_name}
]
class_eval code
end
# def before_aspect method_name, before_proc
# around_aspect method_name, before_proc, ->(){}
# end
#
# def after_aspect method_name, after_proc
# around_aspect method_name, ->(){}, after_proc
# end
end
end
class Test
include Utils::Aspects
def test
puts 'test'
end
before = ->(){puts 'before'}
after = ->(){puts 'after'}
around_aspect :test,before,after
end
Test.new.test
The problem is that when i do Test.new.test I expect it to print
before, test and after" in order. But right now it prints "before,after and test"
The problem is that when i do Test.new.test I expect it to print
before, test and after" in order. But right now it prints "before,after and test"
No, it doesn't. When calling Test.new.test it only prints test. before and after are printed when defining the wrapped method, i.e. when calling around_advice.
Try to put a puts in between the call to around_advice and the call to Test.new.test (and try to call test several times) to observe this:
puts '______________________'
Test.new.test
Test.new.test
# before
# after
# ______________________
# test
# test
You are calling the lambdas only once, when defining the method:
code = %Q[
def #{method_name} *args, &block
#{before_proc.call}
# ^^^^^^^^^^^^^^^^^^^
old_#{method_name} *args, &block
#{after_proc.call}
# ^^^^^^^^^^^^^^^^^^
end
]
You need to call them every time when calling the method:
code = %Q[
def #{method_name} *args, &block
before_proc.call
old_#{method_name} *args, &block
after_proc.call
end
]
However, it would be much easier to just use Module#prepend, after all, that's what it's there for:
module Aspects
refine Module do
def around_aspect(method_name, before_proc, after_proc)
prepend(Module.new do
define_method(method_name) do |*args, &block|
before_proc.()
super(*args, &block)
after_proc.()
end
end)
end
end
end
class Test
using Aspects
def test
puts 'test'
end
before = -> {puts 'before'}
after = -> {puts 'after'}
around_aspect :test, before, after
end
Just putting my code up here. This is how i ended up achieving what i was trying to do, module.prepend as suggested above is another way
module Utils
module Aspects
def self.included(base)
base.extend(self)
end
def around_aspect(method_name, before_proc, after_proc)
new_method_name = Random.new_seed.to_s
alias_method :"#{new_method_name}", :"#{method_name}"
define_method "#{method_name}" do |*args, &block|
before_proc.call
send(:"#{new_method_name}", *args, &block)
after_proc.call
end
end
def before_aspect method_name, before_proc
around_aspect method_name, before_proc, ->(){}
end
def after_aspect method_name, after_proc
around_aspect method_name, ->(){}, after_proc
end
end
end
class Test
include Utils::Aspects
def test
puts 'test'
end
before = ->(){puts 'before'}
after = ->(){puts 'after'}
before_aspect :test, before
after_aspect :test, after
end
Test.new.test
I have a function which defines and returns a new class, with some pre-built methods. E.g.:
def define_class(name, options={}, &block)
klass = Class.new(Class) do
def say_hello
puts "Hello!"
end
def say_goodbye
puts "Adios!"
end
end
parent_class.const_set(form_class, klass)
klass
end
So, for example, this works:
define_class("testing").new.say_hello #=> "Hello!"
But I would like to be able to pass in custom methods through a block, which would then be added to my class, like so:
define_class "testing" do
# ... custom methods
end
Such that this would work:
klass = define_class "testing" do
def interject
puts "Excuse me?"
end
end
klass.new.interject #=> "Excuse me?"
I can't figure out how to make that work though; I've tried instance_eval, class_eval, and yield, and none are producing the desired result.
Try simply:
def define_class(name, options={}, &block)
klass = Class.new(&block)
parent_class.const_set(form_class, klass)
klass
end
If you want to call the block and your own block, you should use class_eval:
def define_class(name, options={}, &block)
klass = Class.new do
def say_hello
puts "Hello!"
end
def say_goodbye
puts "Adios!"
end
class_eval(&block)
end
parent_class.const_set(form_class, klass)
klass
end
I'm walking through the Ruby Koans and I have a trouble in about_proxy_object_project.rb
This is my solution
class Proxy
attr_reader :messages
def initialize(target_object)
#object = target_object
# ADD MORE CODE HERE
#messages = []
end
def number_of_times_called(method_name)
#messages.count method_name
end
def called?(method_name)
#messages.include? method_name
end
def method_missing(method_name, *args, &block)
if #object.respond_to? method_name
#object.send(method_name, *args)
#messages << method_name
else
super method_name, *args, &block
end
end
end
but when I typed rake I got this
The answers you seek...
Expected 10 to equal [:channel=, :power, :channel]
Please meditate on the following code:
/home/Shanicky/koans/about_proxy_object_project.rb:61:in `test_tv_methods_still_perform_their_function'
and in my about_proxy_object_project.rb
def test_tv_methods_still_perform_their_function
tv = Proxy.new(Television.new)
tv.channel = 10
tv.power
assert_equal 10, tv.channel # this is the 61st line
assert tv.on?
end
I am confused
Where i did do wrong?
Thanks all
and this is my Television class
class Television
attr_accessor :channel
def power
if #power == :on
#power = :off
else
#power = :on
end
end
def on?
#power == :on
end
end
In this if clause:
if #object.respond_to? method_name
#object.send(method_name, *args)
#messages << method_name # <-- this is the return value you get
else
super method_name, *args, &block
end
The check #object.respond_to? method_name always evaluates to true because the Television class defines all these methods you've called on its objects (channel=, power, channel). Therefore the first branch of the if runs and this code essentially adds to the #messages instance variable (which is an Array) the method names that you are calling.
So when you call tv.channel the return value of the method is that commented statement in the above code, which of course is not equal to 10. You essentially get the return value of #messages << method_name which is the new #messages array, which actually contains all the undefined method you've called until that time: [:channel=, :power, :channel].
You can just use 'send' without checking. There is no purpose of using 'else' because it always returns some Exception.
def method_missing(method_name, *args, &block)
#messages << method_name
#object.send(method_name, *args, &block)
end
if you swap the two statements:
#object.send(method_name, *args)
#messages << method_name # <-- this is the return value you get
so:
#messages << method_name # <-- this is the return value you get
#object.send(method_name, *args)
then the return value would be whatever is returned by the method_name, and so in the case of tv.channel it would return 10 as expected in your test.
I think this is just saying the same thing as in the answer, but thought it worth making it clearer
This is what I'm looking to do.
# DSL Commands
command :foo, :name, :age
command :bar, :name
# Defines methods
def foo(name, age)
# Do something
end
def bar(name)
# Do something
end
Basically, I need a way to handle arguments through define_method, but I want a defined number of arguments instead of an arg array (i.e. *args)
This is what I have so far
def command(method, *args)
define_method(method) do |*args|
# Do something
end
end
# Which would produce
def foo(*args)
# Do something
end
def bar(*args)
# Do something
end
Thoughts?
I think the best workaround for this would be do to something like the following:
def command(method, *names)
count = names.length
define_method(method) do |*args|
raise ArgumentError.new(
"wrong number of arguments (#{args.length} for #{count})"
) unless args.length == count
# Do something
end
end
It's a little weird, but you can use some type of eval. instance_eval, module_eval or class_eval could be used for that purpose, depending on context. Something like that:
def command(method, *args)
instance_eval <<-EOS, __FILE__, __LINE__ + 1
def #{method}(#{args.join(', ')})
# method body
end
EOS
end
This way you'll get exact number of arguments for each method. And yes, it may be a bit weirder than 'a little'.
Can someone help me modify the answer provided for intercepting instance method calls so that it works with either class method calls, or both class and instance method calls? From my limited knowledge of metaprogramming with Ruby, I'd imagine it would have something to do with opening up the singleton class some place using class << self, but I've tried doing that in various places with this code and I can't seem to figure it out. Instead of a direct answer, though, could you provide me with a push in the right direction? I'm a big fan of figuring things out for myself unless I'm completely out of my depth. Thanks!
Here is my solution modified from the answer in the link you provided. I moved the hook logic from super class to a separate module so that when ever a class needs the hook, it just include or extend that module and call the hook method.
before_each_method type, &block - type can be :class or :instance, and the block is the code to be executed before each method. The block will be evaluated under certain environments, that is, for instance methods, self in the block is the instance; for class methods, self in the block is the class.
before_class_method &block - alias for before_each_method :class, &block
before_instance_method &block - alias for before_each_method :instance, &block
module MethodHooker
def self.included(base)
base.extend(ClassMethods)
end
def self.extended(base)
base.extend(ClassMethods)
end
module ClassMethods
def before_each_method type, &block
singleton = class << self; self; end
case type
when :instance
this = self
singleton.instance_eval do
define_method :method_added do |name|
last = instance_variable_get(:#__last_methods_added)
return if last and last.include?(name)
with = :"#{name}_with_before_each_method"
without = :"#{name}_without_before_each_method"
instance_variable_set(:#__last_methods_added, [name, with, without])
this.class_eval do
define_method with do |*args, &blk|
instance_exec(name, args, blk, &block)
send without, *args, &blk
end
alias_method without, name
alias_method name, with
end
instance_variable_set(:#__last_methods_added, nil)
end
end
when :class
this = self
singleton.instance_eval do
define_method :singleton_method_added do |name|
return if name == :singleton_method_added
last = instance_variable_get(:#__last_singleton_methods_added)
return if last and last.include?(name)
with = :"#{name}_with_before_each_method"
without = :"#{name}_without_before_each_method"
instance_variable_set(:#__last_singleton_methods_added, [name, with, without])
singleton.class_eval do
define_method with do |*args, &blk|
instance_exec(name, args, blk, &block)
send without, *args, &blk
end
alias_method without, name
alias_method name, with
end
instance_variable_set(:#__last_singleton_methods_added, nil)
end
end
end
end
def before_class_method &block
before_each_method :class, &block
end
def before_instance_method &block
before_each_method :instance, &block
end
end
end
class Test
extend MethodHooker
before_each_method :instance do |method, args, block|
p [method, args, block]
puts "before instance method(#{method}) #{#var}"
end
before_class_method do |method, args, block|
puts "before class method(#{method}) #{#class_instance_var}"
end
#class_instance_var = 'stackoverflow'
def initialize
#var = 1
end
def test(a, b, c)
puts "instance method test"
end
def self.test1
puts "class method test"
end
end
Test.new.test(1, "arg2", [3]) {|t| t}
Test.test1
The output will be something like:
[:initialize, [], nil]
before instance method(initialize)
[:test, [1, "arg2", [3]], #<Proc:0x00000001017d5eb8#/Users/test/before_method.rb:88>]
before instance method(test) 1
instance method test
before class method(test1) stackoverflow
class method test