I have several methods that take many (keyword) arguments that end up passing the same set of arguments along to another method.
The following is normal.
def foo(a:, b:, c:, d:, e:)
bar('var', a:a, b:b, c:c, d:d, e:e)
end
# takes the same arguments as #foo + one more
def bar(var, a:, b:, c:, d:, e:)
...
end
This is just kind of tedious and annoying. I'm wondering if there is anything in the Ruby core that would make it easy to do the following...
def foo(a:, b:, c:, d:, e:)
bar('var', <something that automagically collects all of the keyword args>)
end
I know that you can parse over method(__method__).parameters, do some gymnastics, and package everything up into a hash that could be double-splatted and passed to bar. I'm just wondering if there is already something in the core that does that in a nice, neat way?
If there is something that applies in a more general way, i.e. not only to keyword args, then that's certainly interesting too.
Yes, **args will gather arbitrary keyword arguments as a Hash. Use ** again to flatten the Hash into keyword arguments for bar, Ruby 3 will no longer do this for you.
def foo(**bar_args)
# The ** is necessary in Ruby 3.
bar('var', **bar_args)
end
def bar(var, a:, b:, c:, d:, e:)
puts "#{var} #{a} #{b} #{c} #{d} #{e}"
end
This is appropriate if foo never uses those arguments, it just passes them along to bar. If foo were to use some arguments, those should be defined in foo.
def foo(a:, **bar_args)
puts "#{a} is for a"
bar('var', a: a, **bar_args)
end
def bar(var, a:, b:, c:, d:, e:)
puts "#{var} #{a} #{b} #{c} #{d} #{e}"
end
I got a nice way:
def foo(a:, b:, c:, d:, e:)
args = {a: a, b: b, c: c, d: d, e: e}
bar('var', **args)
end
def bar(var, a:, b:, c:, d:, e:)
...
end
Related
I want to know if a method on a class gets redefined.
use case: in version 1 of ruby-clock, defining a method on an object was part of the API. In version 2, doing so will break behavior, and a different API should be used.
what I ended up doing: https://github.com/jjb/ruby-clock/pull/28/files
The best would be if I could use some sort of metaprogramming to notice when it happens.:
# this is example code which does not work
class MyClass
def my_method
puts "hello"
end
# this is not real! it's an example of what i hope is somehow possible
def self.method_defined(m, *args, &block)
if :my_method == m
raise "you should not redefine my_method!"
end
end
end
Also acceptable would be if I could check back later and see if it has changed.
# this is example code which does not work
class MyClass
def my_method
puts "hello"
end
##my_method_object_id = get_method_object_id(:my_method)
end
# later, when app initialization is done, check if the user redefined the method
# this is not real! it's an example of what i hope is somehow possible
def self.method_defined(m, *args, &block)
if MyClass.get_method_object_id(:my_method) != MyClass.my_method_object_id
raise "you should not redefine my_method!"
end
end
n.b. that MyClass.method(:my_method) is a real thing in ruby, but it always produces a new object.
This sounds pretty much like XY problem. I don't think you should go this way - if you find yourself fighting with the Ruby object model, you most probably picked the wrong tool for the job.
But for the sake of completeness... There are a couple of callbacks in Module that can help with the ideas as crazy as this one. In particular, there is Module#method_added which is called each time some instance method is defined. So we can do smth. like this (very dirty example):
class C
#foo_counter = 0
def self.method_added(name)
if name == :foo
#foo_counter += 1
if #foo_counter > 1
raise "Don't redefine foo!"
end
end
end
def foo
"foo"
end
end
now if one tries to open C and redefine foo the following happens (pry session as an example):
pry(main)> class C
pry(main)* def foo
pry(main)* "bar"
pry(main)* end
pry(main)* end
RuntimeError: Don't redefine foo!
So you can do what you want to (to some extent - it's Ruby, so what stops one from redefining the annoying hook first? :)), but please don't. You'll get nothing but troubles...
You could use the callback BasicObject#singleton_method_added.
class C
#class_methods = []
def self.singleton_method_added(m)
puts "Method #{m} was just redefined" if #class_methods.include?(m)
#class_methods << m
end
def self.c
puts 'hi'
end
end
C.c
#=> "hi"
class C
def self.c
puts 'ho'
end
end
Method c was just redefined
C.c #=> "ho"
I want to call the constructors of two modules I have included in a class, but I don't know how to do it.
module GameObject
attr_reader :x, :y
def initialize(x, y)
#x, #y = x, y
end
end
module Attackable
attr_reader :health, :damage
def initialize(health, damage)
#health, #damage = health, damage
end
end
class SuperMario
include GameObject
include Attackable
def initialize(x, y, health, damage)
.. how to call the above constructors?
end
end
How do I call the constructor of Attackable and GameObject?
As the way you can call the module's methods explicitly in the class initializer as follows:
def initialize(x, y, health, damage)
m = GameObject.instance_method( :initialize )
m.bind(self).call( x, y )
m = Attackable.instance_method( :initialize )
m.bind(self).call( health, damage)
end
I imagine you come from the C++ world, where multiple inheritance is allowed... In short, Ruby only offers single inheritance, and doesn't allow to nitpick the parent method that you're calling. This makes what you're trying to achieve unweildy, to put it mildly. Modules aren't going to offer you any good workarounds -- even after accounting for prepend or self.included and the rest of the metaprogramming toolbox -- except in the specific set of cases where they have no name collisions.
What you could do is rename the initialize method so its logic in a function specific to its module, and call that. Aside: it's usually a code smell to define an initialize method in a module to begin with.
I learned something new today:
module B
def cat(a) puts "in B, a = #{a}" end
end
module C
def cat(a) puts "in C, a = #{a}"; super a+1; end
end
class A
include B
include C
def cat(a) puts "in A, a = #{a}"; super a+1; end
end
A.new.cat(2)
# => in A, a = 2
# => in C, a = 3
# => in B, a = 4
Your question is not restricted to initialize, which is why I illustrated this behavior with a generic method. I had expected the includes would just mix-in the other two cat methods, so the result would be the same as:
class D
def cat(a) puts "in 1st, a = #{a}" end
def cat(a) puts "in 2nd, a = #{a}"; super a+1; end
def cat(a) puts "in 3rd, a = #{a}"; super a+1; end
end
D.new.cat(2)
# => in 3rd, a = 2
# => module.rb:24:in `cat': super: no superclass method `cat' for
# #<D:0x007faadb99acb8> (NoMethodError) from module.rb:27:in `<main>'
I was puzzled by #sawa's comment about "super" and found I was wrong. (I hope not everyone reading this will be thinking, "so what else is new?".)
If I have methods:
def method_a(p1, p2)
# do some stuff
method_b(p1, p2)
end
def method_b(p1, p2)
# do other stuff
end
Is there a way to call method_b and automatically pass all parameters to it? (Sort like how you can call super and it automatically forwards all params)
I know one appriximate method:
def method_a *args
# do some stuff
method_b *args
end
def method_b *args
# do other stuff
end
or expanding arguments in the second method:
def method_a *args
# do some stuff
method_b *args
end
def method_b p1, p2
# do other stuff
end
Since super is key-work method, the ruby interperter can treat it as of the same argument list as in the specific method you've called. But default from to call a method without argument is the same as for super method, just method name:
method_a # calls to :method_a without arguments, not as the same argument list for the caller method.
So, it will be strong omonim for the call method syntax.
Considering arbitrary number of arguments and a possibility of a block, the most general format is:
def method_a(*args, &pr)
# do some stuff
method_b(*args, &pr)
end
Then, in the definition of method_b, you can set a specific number of arguments and whether or not it takes a block.
Use *args like this:
def method_a(*args)
...
method_b(*args)
end
def method_b(p1, p2)
...
end
You can process the arguments like an array in the method_a.
Wow, this is hacky. But it works
def fwd_call b, meth
send(meth, *b.eval('method(__method__).parameters.map { |p| eval(p.last.to_s) }'))
end
def method1 x, y
fwd_call(binding, :method2)
end
def method2 x, y
x+y
end
puts method1(3, 4)
# 7
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'.
I have a number of Ruby files, each of which declares a Class, but each of which could conceivably be run from the command line.
I'd like to put the following functionality at the bottom of each file with the least duplication possible:
if __FILE__ == $0
# instantiate the class and pass ARGV to instance.run
end
My first instinct was to do this:
# /lib/scriptize.rb:
Kernel.class_eval do
def scriptize(&block)
block.call(ARGV) if __FILE__ == $0
end
end
# /lib/some_other_file.rb:
include 'scriptize'
class Foo
# ...
end
scriptize { |args| Foo.new.run(args) }
But that doesn't work because __FILE__ is evaluated in scriptize.rb, so it's never Foo.
I imagine the solution is to literally inline the contents of scriptize.rb, but I don't know the syntax. I could use eval, but that's still quite a bit of duplication -- it can't really be reduced to a method I add to Kernel.
Try evaling it.
eval(IO.read(rubyfile), binding)
That's what Rails' initializer does when loading files in config/environments, because it needs to evaluate them within the Rails::Initializer.run block.
binding is a ruby method that'll return the current context, when passed to eval, causes it to evaluate the code within the calling environment.
Try this:
# my_class.rb
class MyClass
def run
puts 'hi'
end
end
eval(IO.read('whereami.rb'), binding)
# whereami.rb
puts __FILE__
$ ruby my_class.rb
my_class.rb
Use caller to determine how close you are to the top of the call stack:
---------------------------------------------------------- Kernel#caller
caller(start=1) => array
------------------------------------------------------------------------
Returns the current execution stack---an array containing strings
in the form ``_file:line_'' or ``_file:line: in `method'_''. The
optional _start_ parameter determines the number of initial stack
entries to omit from the result.
def a(skip)
caller(skip)
end
def b(skip)
a(skip)
end
def c(skip)
b(skip)
end
c(0) #=> ["prog:2:in `a'", "prog:5:in `b'", "prog:8:in `c'", "prog:10"]
c(1) #=> ["prog:5:in `b'", "prog:8:in `c'", "prog:11"]
c(2) #=> ["prog:8:in `c'", "prog:12"]
c(3) #=> ["prog:13"]
This gives this definition for scriptize
# scriptize.rb
def scriptize
yield ARGV if caller.size == 1
end
Now, as an example, we can use two libraries/executables that require each other
# libexA.rb
require 'scriptize'
require 'libexB'
puts "in A, caller = #{caller.inspect}"
if __FILE__ == $0
puts "A is the main script file"
end
scriptize { |args| puts "A was called with #{args.inspect}" }
# libexB.rb
require 'scriptize'
require 'libexA'
puts "in B, caller = #{caller.inspect}"
if __FILE__ == $0
puts "B is the main script file"
end
scriptize { |args| puts "B was called with #{args.inspect}" }
So when we run from the command line:
% ruby libexA.rb 1 2 3 4
in A, caller = ["./libexB.rb:2:in `require'", "./libexB.rb:2", "libexA.rb:2:in `require'", "libexA.rb:2"]
in B, caller = ["libexA.rb:2:in `require'", "libexA.rb:2"]
in A, caller = []
A is the main script file
A was called with ["1", "2", "3", "4"]
% ruby libexB.rb 4 3 2 1
in B, caller = ["./libexA.rb:2:in `require'", "./libexA.rb:2", "libexB.rb:2:in `require'", "libexB.rb:2"]
in A, caller = ["libexB.rb:2:in `require'", "libexB.rb:2"]
in B, caller = []
B is the main script file
B was called with ["4", "3", "2", "1"]
So this shows the equivalence of using scriptize and if $0 == __FILE__
However, consider that:
if $0 == __FILE__ ... end is a standard ruby idiom, easily recognized by others reading your code
require 'scriptize'; scriptize { |args| ... } is more typing for the same effect.
In order for this to really be worth it, you'd need to have more commonality in the body of scriptize - initializing some files, parsing arguments, etc. Once it gets complex enough, you might be better off with factoring out the changes in a different way - maybe passing scriptize your class, so it can instantiate them and do the main script body, or have a main script that dynamically requires one of your classes depending on what the name is.
Or, you could simply pass __FILE__ to scriptize
# /lib/scriptize.rb:
module Kernel
def scriptize(calling_file, &block)
block.call(ARGV) if calling_file == $0
end
end
# /lib/some_other_file.rb:
...
scriptize(__FILE__) { |args| Foo.new.run(args) }
I also took the time to do away with the class_eval thing. (and you might also do away with the whole module thing, since Kernel is your scope by default.
Another way to do it is how Test::Unit does it. A test case file only has a class definition in it (and a require 'test/unit').
The 'test/unit' library sets up an at_exit handler that automatically runs any test cases and suites. If your most common case is going to be running these class files, and occasionally using them as libraries, you could do something similar, and set a global to disable autorun when it was included as a library.
For example:
# tc_mytest.rb
require 'test/unit'
class TC_MyTest < Test::Unit::TestCase
def test_succeed
assert(true, 'Assertion was true.')
end
def test_fail
assert(false, 'Assertion was false.')
end
end
No boilerplater required to run:
% ruby tc_mytest.rb
Loaded suite tc_mytest
Started
F.
Finished in 0.007241 seconds.
1) Failure:
test_fail(TC_MyTest) [tc_mytest.rb:8]:
Assertion was false.
<false> is not true.
2 tests, 2 assertions, 1 failures, 0 errors
We can use eval(IO.read('filename.rb'), binding)
Example:-
setup.rb
def setup
#driver = Selenium::WebDriver.for :chrome
#base_url = "http://stage.checkinforgood.com/"
#driver.manage.timeouts.implicit_wait = 30
#verification_errors = []
end
def teardown
#driver.quit
assert_equal [], #verification_errors
end
c4g.rb
require "selenium-webdriver"
require "test/unit"
class C4g < Test::Unit::TestCase
eval(IO.read('setup.rb'), binding)
def test_login
#driver.get "http://stage.checkinforgood.com/"
#driver.find_element(:link, "Sign In").click
#driver.find_element(:id, "user_email").clear
#driver.find_element(:id, "user_email").send_keys "vtr#weboniselab.com"
#driver.find_element(:id, "user_password").clear
#driver.find_element(:id, "user_password").send_keys "test123"
#driver.find_element(:id, "user_submit").click
end
def element_present?(how, what)
#driver.find_element(how, what)
true
rescue Selenium::WebDriver::Error::NoSuchElementError
false
end
def verify(&blk)
yield
rescue Test::Unit::AssertionFailedError => ex
#verification_errors << ex
end
end
Now we can run,
$ruby c4g.rb
load 'somefile'