Modify args parameter in resque gem in before_enqueue and pass the modified args to perform - resque

Is there any way to add some args in the *args parameter of resque enqueue method in the before_enqueue method and pass the added arguments to before_perform method.?
Or is there any way to send some data from before_enqueue method to before_perform method independently?
eg:
class Action
:queue queueName
def self.before_enqueue(*args)
param1 = 1
param2 = 2
args.push(param1, param2)
# I know this is not the correct way as args is a local variable here.
#But something like this
end
def self.before_perform(*args)
puts args.inspect # I need the added args here
end
def self.perform(params)
#some code here
end
end

Found that we can't add extra arguments in before_enqueue. But instead we can modify existing arguments. So, we can pass an empty hash as argument in the enqueue call and add arguments as key value pairs into this empty hash at before_enqueue. I'm talking everything with respect to Resque 1.20.0
Call to the enqueue method:
Resque.enqueue(class_name, {})
Inside the resque perform class:
self.before_enqueue(*args)
args[0][:param1] = 1
args[0][:param2] = 2
end
The above args will be available in before_perform as well.

Related

Ruby public_send method with hash params where applicable

Tldr: I am trying to use public_send to call methods but some of these methods require arguments as well. How to check which requires arguments and add those as arguments and include them in the call
I have an array of methods in a class like this
class_methods = [:first, :second, :third, :fourth] ...
I defined a can_execute method to check if the class has the method, if so it will execute.
def can_execute (class, method_name)
if class.respond_to?(method_name.to_sym) && class.class_methods.include?(method_name.to_sym)
class.public_send(method_name)
end
end
When user supplies any of those class_method as an argument, I can call them like this
can_execute(class, method_name)
Problem is some of those methods accepts hash as arguments, like
class.first(Value: true) or
class.second(Id: 0, Value: "string")
But some dont
class.third
I am not sure how to include the params hash some of these class_methods require?
I do have the option to check what arguments these methods require by calling, method_args on them, like
class.method_args(:first)
-> [:Value]
class.method_args(:second)
=> [:Id, :Value]
class.method_args(:third)
=> []
You'll need to pass the arguments to can_execute so it can pass them to public_send. The splat operator is your friend here. When used as parameter, it will wrap any number of arguments into an array with it's name. When used on an array, it breaks the array down into arguments to pass to a method.
In addition to that, class is a keyword. The convention is to call it klass, but it can be whatever you want (other than class)
With that in mind, our new method looks like this:
def can_execute (klass, method_name, *args)
if klass.respond_to?(method_name.to_sym) && klass.class_methods.include?(method_name.to_sym)
klass.public_send(method_name, *args)
end
end
Then to call, it can have parameters or not. No need to check first:
can_execute('teststring', :slice, 1, 5) # => "ests"
can_execute('teststring', :upcase) # => "TESTSTRING"
If you have another reason to want to check the params, you can use Method#arity or Method#parameters. Something like this with results for #slice
klass.method(method_name.to_sym).arity # => -1
klass.method(method_name.to_sym).parameters # => [[:rest]]

How to pass a function as method argument

I am trying to pass a function as an argument to a method of class. I know I need to use proc, but I am not sure I am using the right syntax. This is my attempt.
module MyApp;end
module MyApp::Stats
def self.sum(a)
a.inject(0){ |accum, i| accum + i }
end
def self.mean(a)
sum(a) / a.length.to_f
end
# Many more functions....
def self.sum_proc
Proc.new{|x| sum(x) }
end
def self.mean_proc
Proc.new{|x| mean(x)}
end
# And many more procs
end
class MyData
attr_reader :values
attr_reader :aggregates
def initialize(array)
#values = array
#aggregates = {}
end
def aggregate(aggregator)
puts aggregator.call(#values)
#I would also like to create a hash of aggregator. Something like:
#aggregates["aggregator"] = aggregator.call(#values)
end
end
I can then do
ar = [1,2,3,4,5,6,7,8,9]
data = MyData.new(ar)
And call the aggregate method in various ways:
aggregator = Proc.new{|x| MyApp::Stats.sum(x)}
data.aggregate(aggregator)
data.aggregate(Proc.new{|x| MyApp::Stats.mean(x)} )
data.aggregate(Proc.new{|x| x.count{|y| y > 3.0} })
data.aggregate(MyApp::Stats.sum_proc)
data.aggregate(MyApp::Stats.mean_proc)
I have two issues with this code. First it seems redundant as I have to define the aggregator first and then the associated proc, e.g. sum and sum_proc. Second, I wonder how I could pass any of the standard enumerator methods without defining a proc for it: say count or first.
Finally, I would like to create a hash for the aggregators so that I could do:
puts data.aggregates["sum"]
puts data.aggregates["mean"]
Methods aren't objects in Ruby. You can't pass a method as an argument because you can only pass objects as arguments.
However, you can get a Method proxy object representing a method by calling the method method and passing the name of the method as an argument. Method proxy objects duck-type Proc, so they respond to arity, parameters, and most importantly to_proc and call.
The idiomatic way of taking a single first-class procedure as an argument, is to take a block like this:
def aggregate
yield #values
end
and pass a method using the unary prefix & operator:
data.aggregate(&MyApp::Stats.:sum)

Binding method to instance

Is there a way to bind an existing method to an existing instance of an object if both the method and the instance are passed as symbols into a method that does that if the instance is not a symbol?
For example:
def some_method
#do something
end
some_instance = Klass.new(something)
def method_that_binds(:some_method, to: :some_instance)
#how do I do that?
end
Your requirements are a little unusual, but it is possible to do this mostly as you say:
class Person; end
harry = Person.new
barry = Person.new
def test
puts 'It works!'
end
define_method :method_that_binds do |a_method, to|
eval(to[:to].to_s).singleton_class.send(:define_method, a_method, &Object.new.method(a_method))
end
method_that_binds :test, to: :harry
harry.test
# It works! will be sent to STDOUT
barry.test
# undefined method 'test'
This doesn't actually use a named parameter, but accepts a hash with a to key, but you can see you can call it in the way you want. It also assumes that the methods you are defining are defined globally on Object.
The API you want doesn't easily work, because you have to know from which scope you want to access the local variable. It's not quite clear to me why you want to pass the name of the local variable instead of passing the content of the local variable … after all, the local variable is present at the call site.
Anyway, if you pass in the scope in addition to the name, this can be accomplished rather easily:
def some_method(*args)
puts args
puts "I can access some_instance's ivar: ##private_instance_var"
end
class Foo; def initialize; #private_instance_var = :foo end end
some_instance = Foo.new
def method_that_binds(meth, to:, within:, with: [])
self.class.instance_method(meth).bind(within.local_variable_get(to)).(*with)
end
method_that_binds(:some_method, to: :some_instance, within: binding, with: ['arg1', 'arg2'])
# arg1
# arg2
# I can access some_instance's ivar: foo
As you can see, I also added a way to pass arguments to the method. Without that extension, it becomes even simpler:
def method_that_binds(meth, to:, within:)
self.class.instance_method(meth).bind(within.local_variable_get(to)).()
end
But you have to pass the scope (Binding) into the method.
If you'd like to add a method just to some_instance i.e. it's not available on other instances of Klass then this can be done using define_singleton_method (documentation here.)
some_instance.define_singleton_method(:some_method, method(:some_method))
Here the first use of the symbol :some_method is the name you'd like the method to have on some_instance and the second use as a parameter to method is creating a Method object from your existing method.
If you'd like to use the same name as the existing method you could wrap this in your own method like:
def add_method(obj, name)
obj.define_singleton_method(name, method(name))
end
Let's say we have a class A with a method a and a local variable c.
class A
def a; 10 end
end
c = '5'
And we want to add the method A#a to c.
This is how it can be done
c.singleton_class.send :define_method, :b, &A.new.method(:a)
p c.b # => 10
Explanations.
One way to add a method to an object instance and not to its class is to define it in its singleton class (which every ruby object has).
We can get the c's singleton class by calling the corresponding method c.signleton_class.
Next we need to dynamically define a method in its class and this can usually be accomplished by using the define_method which takes a method name as its first argument (in our case :b) and a block. Now, converting the method into a block might look a bit tricky but the idea is relatively simple: we first transform the method into a Method instance by calling the Object#method and then by putting the & before A.new.method(:a) we tell the interpreter to call the to_proc method on our object (as our returned object is an instance of the Method, the Method#to_proc will be called) and after that the returned proc will be translated into a block that the define_method expects as its second argument.

ruby - how to pass on incoming named params to another method in one expression?

I have a method:
def move_knight(orig_x,orig_y,offset_x,offset_y)
if valid_knight_move(offset_x,offset_y)
move('knight',orig_x,orig_y,offset_x,offset_y)
end
end
It works. I would now like to refactor the call to move and pass on those last 4 arguments in one go in the call to move, as in
move('knight',*args)
but I get
<NameError: undefined local variable or method `args' for #<ChessGame:0x000...>>
All the examples I see have *args at in the calling methods params which I don't.
I would do as below :
def move_knight(*args)
if valid_knight_move(*args.last(2))
move('knight',*args)
end
end
Change the method definition to the following:
def move_knight(*args)
if valid_knight_move(args[2],args[3])
move('knight',*args)
end
end

Ruby Koans about message passing "send" block & arguments

I am working on Ruby Koans about_message_passing.rb and got the code working for method_missing as follows:
def method_missing(method_name, *args, &block)
#messages << method_name
#object.__send__(method_name, *args, &block)
end
This code seems to work, but I do not quite understand why the splat in needed in *args and the & is needed with the block.
If I were defining a method, I understand that the * and & are used to denote an array argument and block argument respectively, but what does it mean when they are used with the send method to invoke a method on an object?
I'll take these one at a time. take method_missing out of this completely, since it just makes what's going on confusing. It's actually completely unrelated to that.
The splat * does 2 things. In the arguments of a method definition, it soaks up multiple arguments into an array. When used in method invocation it splats out an array into individual arguments. Using both allows you to forward any number of arguments to another method.
def foo(*args)
bar(*args)
end
def bar(a, b, c)
puts a
puts b
puts c
end
foo(1,2,3) # prints 1, 2 and then 3
Since you are basically forwarding all arguments, this is the same pattern.
The & is for the block argument. There can be exactly one of these per method invocation, it's the block that hangs off the end. It's a special argument, in that it doesn't go in the arguments directly. You can capture the block to a variable by capturing add &someblock as the last argument in a method definition.
Then you can pass a block in a method invocation using the same syntax.
def foo(&block)
bar(&block)
end
def bar
yield
end
foo { puts 'hello' } # prints hello
This allows you pass the hanging block to another method, without invoking it. It's not always required because you usually just use yield to execute whatever block was passed. But if you want to do something besides just execute it, you need to capture a reference to the block itself.
So if you combine these 2 things, you get the ultimate method forwarder. You capture all of any number of arguments, and any block that was hanging off the end, and send those to another method.
# forwards everything to the method `bar`
def foo(*args, &block)
bar(*args, &block)
end
Lastly, send is just a method. It expects a name of a method, followed by any number of arguments (not an array), and can optionally handle a hanging block.
In other words:
foo.send methodName, *args, &block
The splat in the method definition means "take all unmatched arguments and put them in an array" (in ruby 1.8 this was always the last arguments, but in 1.9 splats can occur in the middle).
Using it in a method call is the reverse: it means take this array and use its contents as the arguments
foo(a,b) #call foo with 2 arguments: a and b
foo([a,b]) #call foo with a single array argument
foo(*[a,b]) # call foo with 2 arguments: a and b
& is similar: in a method definition it captures the block and turns it into a proc, but in a method call it turns a proc (or proc like object - anything responding to to_proc will do) into the block for that method
You need both of these for method_missing because (in general) you want to pass along all the arguments and the block from the original method call.
To my knowledge, anytime you pass a block directly, it is with the syntax &block_name.
Also, the method signature for Object#send takes endless arguments, not an array. So by passing the splatted values *args, it is the same as if you had passed the comma-delimited args.

Resources