Accessing variables using overloading brackets [] in Ruby - ruby

Hi i want to do the following. I simply want to overload the [] method in order to access the instance variables... I know, it doesn't make great sense at all, but i want to do this for some strange reason :P
It will be something like this...
class Wata
attr_accessor :nombre, :edad
def initialize(n,e)
#nombre = n
#edad = e
end
def [](iv)
self.iv
end
end
juan = Wata.new('juan',123)
puts juan['nombre']
But this throw the following error:
overload.rb:11:in `[]': undefined method 'iv' for # (NoMethodError)
How can i do that?
EDIT
I have found also this solution:
def [](iv)
eval("self."+iv)
end

Variables and messages live in a different namespace. In order to send the variable as a message, you'd need to define it as either:
def [](iv)
send iv
end
(if you want to get it through an accessor)
or
def [](iv)
instance_variable_get "##{iv}"
end
(if you want to access the ivar directly)

try instance_variable_get instead:
def [](iv)
instance_variable_get("##{iv}")
end

Related

caller_method returns not the value, that i expected

I want to know, what method calls another method (I'm just trying to create simple expect("string").to eq("string") model (just like in RSpect, but more easier).
But i get "main", what is that? (I see that "main" for first time)
public
def expect(message)
message.to_s
end
def to
caller_method = caller_locations.first.label
puts caller_method
end
expect("test").to #=> <main>
#what output i expected:
expect("test").to #=> expect
My goal:
#first i need to do something like that:
expect("test").to eq("test") #=> true
#final must look like this:
expect(expect("test").to eq("test")).to eq(true) #=> true
I would recommend against using caller_method in this case. Rather, make a class whose methods return self - that way they will be chainable:
module Expectation
attr_accessor :caller_method
def expect(arg)
self.caller_method = "expect"
self
end
def to
caller_method
end
end
include Expectation
expect("foo").to
# => "expect"
Obviously this is only a starting point, and this doesn't actually do any comparisons / validations yet. But hopefully you can understand this pattern. The key thing is returning self to make a chainable API, and storing internal state using something like attr_accessor

define_method: How to dynamically create methods with arguments

I want to create a bunch of methods for a find_by feature. I don't want to write the same thing over and over again so I want to use metaprogramming.
Say I want to create a method for finding by name, accepting the name as an argument. How would I do it? I've used define_method in the past but I didn't have any arguments for the method to take.
Here's my (bad) approach
["name", "brand"].each do |attribute|
define_method("self.find_by_#{attribute}") do |attr_|
all.each do |prod|
return prod if prod.attr_ == attr_
end
end
end
Any thoughts? Thanks in advance.
If I understand your question correctly, you want something like this:
class Product
class << self
[:name, :brand].each do |attribute|
define_method :"find_by_#{attribute}" do |value|
all.find {|prod| prod.public_send(attribute) == value }
end
end
end
end
(I'm assuming that the all method returns an Enumerable.)
The above is more-or-less equivalent to defining two class methods like this:
class Product
def self.find_by_name(value)
all.find {|prod| prod.name == value }
end
def self.find_by_brand(value)
all.find {|prod| prod.brand == value }
end
end
It if you read the examples here http://apidock.com/ruby/Module/define_method you will find this one:
define_method(:my_method) do |foo, bar| # or even |*args|
# do something
end
is the same as
def my_method(foo, bar)
# do something
end
When you do this: define_method("self.find_by_#{attribute}")
that is incorrect. The argument to define_method is a symbol with a single word.
Let me show you some correct code, hopefully this will be clear:
class MyClass < ActiveRecord::Base
["name", "brand"].each do |attribute|
define_method(:"find_by_#{attribute}") do |attr_|
first(attribute.to_sym => attr_)
end
end
end
This will produce class methods for find_by_brand and find_by_name.
Note that if you're looking into metaprogramming, this is a good use-case for method_missing. here's a tutorial to use method_missing to implement the same functionality you're going for (find_by_<x>)

Confused about send method to set instance variables

I have the following code:
class Dog
def initialize(attributes = {})
attributes.each do |attr, value|
send("#{attr}", value)
end
end
end
dog = Dog.new({:talk => 'bruuuf'})
And I get an block in initialize': undefined method `talk' for # (NoMethodError)
Isn't send supposed to be doing something like:
dog.talk = bruuuf Why is not letting me do so? Also, this would not set an instance variable, but it would create an instance method. Right?
First off: you cannot set instance variables with Object#send. Object#send sends messages, it doesn't set instance variables. Of course, you can send a message which may or may not then in turn invoke a method which may or may not in turn then set an instance variable, but that's not the doing of Object#send, it's the doing of whatever method was invoked in response to the message you sent.
If you want to dynamically set instance variables, use Object#instance_variable_set:
class Dog
def initialize(**attrs)
attrs.each do |attr, value|
instance_variable_set(:"##{attr}", value)
end
end
end
dog = Dog.new(talk: 'bruuuf')
In this case, however, it looks like you don't actually want to set an instance variable but rather call a setter method. Setter methods have names that end with an = sign, e.g. Dog#talk=:
class Dog
def initialize(**attrs)
attrs.each do |attr, value|
send(:"#{attr}=", value)
end
end
end
dog = Dog.new(talk: 'bruuuf')
Note, of course, that this assumes that the method Dog#talk= actually exists. If it doesn't, you will get a NoMethodError.
Provided that method talk is defined, you can do send(:talk), but since talk is not defined, you cannot do that. Furthermore, dog.send(:talk) will not give you dog.talk = bruuuf. Provided that talk= is defined, dog.send(:talk=, ...) will do it.

Ruby: Is there a way to get the enclosing Module const of a Class?

I'm doing some metaprogramming in Ruby, and I need to dynamically generate a sibling class inside of a module. In doing so, I want to call const_set on the module, but I don't know which Module constant to call that on until runtime. An example:
Given classes
Foo::Bar::Baz
Foo::Quox::Quack
I want to be able to call a function like this (oversimplified here):
def generate_from klass
mod = klass.enclosing_module # <- THIS LINE is the one I need to figure out
mod.const_set("GeneratedClassName", Class.new)
end
and what I want to end up with, when calling with Baz, is a new class defined as
Foo::Bar::GeneratedClassName
and with a Quack, I want
Foo::Quox::GeneratedClassName
The only way I know of is to split up klass.name, then repeatedly call const_get on those strings, constantized. Does anyone know of a more elegant way?
This should get you on track:
module Foo
module Bar
class Baz
def initialize
#nesting = Module.nesting
end
def enclosing_module
#nesting.last
end
end
end
end
puts Foo::Bar::Baz.new.enclosing_module #=> Foo
Relevant documentation:
http://ruby-doc.org/core/classes/Module.html#M000441
Got it.
ActiveSupport has this Ruby extension, Module#parent. It's good enough for my use.
In Rails you can use a combination of deconstantize and constantize.
'Foo::Bar::Baz'.deconstantize.constantize # => Foo::Bar
so in a method of the class it can be used like this:
self.class.name.deconstantize.constantize
In case anyone is looking for a pure ruby version:
def get_parent_type
#Note: This will break for base types (lacking '::' in the name)
parent_type=self.class.name.split('::')[0...-1]
begin
Object.const_get(parent_type.join('::'))
rescue NameError => e
nil
end
end

ruby debugging, attempting to loop through an objects instance variables

I am curious if as to whether or not it would be possible to loop through the instance variables of an object and dump out some basic debug information.
I know you can get a list of instance variables by doing object.instance_variables which returns an array of symbolized variables like [:#var1, :#var2, :#etc] My first guess at how to do this was:
obj.instance_variables.each do
obj.instance_variable_get(var).to_yaml
end
but i am getting the following error: "can't dump anonymous class Class". What might a better approach be?
The problem is you have some anonymous proc or function in your instance variables that doesn't respond to to_yaml. Because it can't be converted to yaml you are getting this error. Try using inspect instead, all objects should respond to inspect:
obj.instance_variables.each do |var|
p obj.instance_variable_get(var).inspect
end
You have to take into account that in ruby just declaring the attr_accessor will not create the variable, you need to assign it:
class A
attr_accessor :x, :y
def initialize(z)
#x=z
end
end
def inspect_object(o)
o.instance_variables.each do |var|
var.slice!(0)
p var
p o.send(var)
end
end
a = A.new(5)
inspect_object(a)
This outputs
"x"
5

Resources