Running a passed proc with params inside a class instance in ruby - ruby

This example is a bit contrived but it's from a test so it sort of just works this way and will help me figure out how to use this in the actual library. I have a lambda, defined like this:
l = lambda { |v| process(v) }
I want to pass this to an object and have the proc run in the context of that object. The object might look like this:
class LambdaRunner
def process(v)
puts v
end
def run(proc)
# code to run proc in the context of the object
end
end
I am familiar with the solution of running the proc using instance_eval.
instance_eval(&proc)
But this won't work because I need to pass a value to the proc. I've managed something like a working solution, but it feels hacky and error prone.
self.class.class_eval do
define_method(:runner, &proc)
end
runner('hi from lambda runner')
All other method have been met with undefined method process

See the documentation for instance_exec (this is backported to 1.8.7). Assuming your proc takes a single argument |v|, you will probably end up with something like this in LambdaRunner.
def run(proc)
instance_exec(v_value, &proc)
end
By the way, in version of Ruby that don't have #instance_exec, your hack is basically the only way to implement it. This was how I implemented it for older Rubies which didn't have the backported method.

Related

Understanding Ruby define_method with initialize

So, I'm currently learning about metaprogramming in Ruby and I want to fully understand what is happening behind the scenes.
I followed a tutorial where I included some of the methods in my own small project, an importer for CSV files and I have difficulties to wrap my hand around one of the methods used.
I know that the define_method method in Ruby exists to create methods "on the fly", which is great. Now, in the tutorial the method initialize to instantiate an object from a class is defined with this method, so basically it looks like this:
class Foo
def self.define_initialize(attributes)
define_method(:initialize) do |*args|
attributes.zip(args) do |attribute, value|
instance_variable_set("##{attribute}", value)
end
end
end
end
Next, in an initializer of the other class first this method is called with Foo.define_initialize(attributes), where attributes are the header row from the CSV file like ["attr_1", "attr_2", ...], so the *args are not provided yet.
Then in the next step a loop loops over the the data:
#foos = data[1..-1].map do |d|
Foo.new(*d)
end
So here the *d get passed as the *args to the initialize method respectively to the block.
So, is it right that when Foo.define_initialize gets called, the method is just "built" for later calls to the class?
So I theoretically get a class which now has this method like:
def initialize(*args)
... do stuff
end
Because otherwise, it had to throw an exception like "missing arguments" or something - so, in other words, it just defines the method like the name implies.
I hope that I made my question clear enough, cause as a Rails developer coming from the "Rails magic" I would really like to understand what is happening behind the scenes in some cases :).
Thanks for any helpful reply!
Short answer, yes, long answer:
First, let's start explaining in a really (REALLY) simple way, how metaprogramming works on Ruby. In Ruby, the definition of anything is never close, that means that you can add, update, or delete the behavior of anything (really, almost anything) at any moment. So, if you want to add a method to Object class, you are allowed, same for delete or update.
In your example, you are doing nothing more than update or create the initialize method of a given class. Note that initialize is not mandatory, because ruby builds a default "blank" one for you if you didn't create one. You may think, "what happens if the initialize method already exist?" and the answer is "nothing". I mean, ruby is going to rewrite the initialize method again, and new Foo.new calls are going to call the new initialize.

How do I define an instance method in Ruby? [duplicate]

This question already has answers here:
What does a Java static method look like in Ruby?
(2 answers)
Closed 8 years ago.
I'm trying to define a method called function. It works when I do this:
a = A.new
def a.function
puts 100
end
But I want the method to work for any instance variable, not just a. For example, when I call function on an instance of A other than a, nothing happens. So I used A::f to define the function rather than a.function.
class A
attr_accessor :function
attr_accessor :var
end
def A::function
self.var = 0x123
end
a = A.new
a.function
puts a.var
This compiles fine but when I try to call the function I get an error. Why is it not working and how can I do what I'm attempting?
You're really tangled up here. I suggest you check out _why's poignant guide to ruby and get a handle on what is going on here.
As an attempt to steer you right, though…
a isn't an instance variable. It's a local variable that you're using to reference an instance of A.
def a.foo defines a method on the eigenclass of a.
attr_accessor :function already defined A#function (that is, an instance method on A called function) that essentially looks like this: def function; #function; end
def A::function defines a class method on A that you could access via A.function (not an instance of A as in a.function.
MRI doesn't really compile ruby like you might anticipate. It runs it, dynamically interpreting statements in realtime.
You probably want to stick with defining standard instance methods in the traditional manner, and avoid using “function” as a placeholder name since it is a reserved word and has special meaning in other languages. I'll use “foo” here:
class A
def foo
'Awww foo.'
end
end
That's it, you can now create an instance of A (a = A.new) and call foo on it (a.foo) and you'll get back 'Aww foo.'.
class A
def function
puts 100
end
end
a = A.new
a.function #=> "100"
That's a classic instance method. Is that what you're looking for or am I missing something?
If you're trying to define methods dynamically, you could use Class#define_method. Otherwise, if you are just wanting to define the method for the class, defining it in the scope of class A will suffice.
Anyway, could you be more specific on what are you trying to accomplish and what kind of error you're having, please?

Blocks and objects

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.

referring to module level variables from within module

I'm having some difficulty with referring to module-level variables in ruby. Say I have a situation like this, where I'm referring to M.a internally:
module M
##a=1
def self.a
##a
end
class A
def x
M.a
end
end
end
Now, this example works fine for me but it is failing in a slightly more complicated context (where the module is spread over a number of files installed in a local gem - but my understanding is that that should not effect the way the code is executed) with an error like this: undefined method `a' for M::M (NoMethodError).
So, is this the correct way to refer to module level variables in context? is there a simpler/more idiomatic way?
If the module is spread out over other files, you need to ensure that your initialization is run before the method is called. If they are in the same file, this should be as much as guaranteed, but if you somehow split them there could be trouble.
I've found you can usually get away with this:
module M
def self.a
#a ||= 1
end
end
If this variable is subject to change, you will need a mutator method. Rails provides mattr_accessor that basically does what you want, part of ActiveSupport.

How do you modify the scope passed to a block?

I've been working with Ruby for a little under a year, and I still don't fully understand "what makes blocks tick". In particular I'm curious how much control one has over the scope of a block. For example, say I have this code:
class Blob
attr_accessor :viscosity
def configure(&:block)
block.call self
end
end
blob = Blob.new
blob.configure do |b|
b.viscosity 0.5
end
A bit of a contrived example there, obviously.
Now, one thing I've noticed while migrating from Rails 2 to Rails 3 is that a lot of their configuration methods that take blocks no longer take a non-block argument.
For example, in routes.rb, it used to be ActionController::Routing::Routes.draw do |map| ... end, and now it's just ActionController::Routing::Routes.draw do ... end. But the methods that are called inside the block still have the appropriate context, without the need to repeat the name of the block's argument over and over.
In my example above, then, I want to be able to do:
blob.configure do
viscosity 0.5
end
so that I can tell people how easy it is to write a DSL in Ruby. :)
This uses instance_eval to do the magic. See http://apidock.com/ruby/Object/instance_eval/ for some documentation. instance_eval evaluates a block (or a string) in the context of it's receiver.
def configure(&block)
self.instance_eval &block
end
You'd still have to use the accessor method viscosity= in your example block or you'd have to define
def viscosity(value)
#viscosity = value
end
in your class.

Resources