What are the alternatives to eval in Ruby? - ruby

I wrote the following recursive function in order to parse some settings and correctly populate some log file objects I have.
When I run this code against Code Climate it tells me that (of course) the use of eval is not encouraged. Is there a way I can rewrite this method so I do not need eval and also do not need a case statement? Nothing comes to mind.
def parse(settings, logfile = nil)
settings.each do |key, value|
if value.is_a?(Hash)
logfile = Logmsg::LogFile.new
parse(value, logfile)
else
eval("logfile.#{key} = value")
end
end
end
Any ideas?
I am trying the instance_variable_set meta-programming method however I am still running into some issues with it. For some reason my tests are all failing now due to
Argument Error: comparison of Fixnum with String failed
I am still trying to figure that out.

Since assignment to an object's attribute is just syntactic sugar for a method call (i.e obj.foo = bar is the same as calling the foo= method on obj with the argument bar), use Object#public_send:
logfile.public_send(:"#{key}=", value)
In older code you often see send instead of public_send, but you should use the latter since it will raise an error if you try to call a private method.

Related

In Ruby, how do I assign a value to a class instance

I want to create a class instance that has a value such that I can do something like puts a = Example.new(1) where a's value is specified in initialize.
I expect that this is a simple problem since all predefined Ruby classes allow this, but I'm unable to figure out how to do it for my classes.
Class#new and Return Values
Your example doesn't quite work because Ruby treats Class#new as a special case, and is expected to invoke the #initialize method and return an object. If it didn't, calling #new on a class would surprise a lot of people by returning the last evaluation of the initializer from your class, or from Object#new if it's otherwise undefined for your class. In either case, this would violate the principle of least surprise.
However, you can do what you want pretty easily by simply creating an accessor method and then chaining off of Example#new. For example, in Ruby 3.1.0:
class Example
attr_reader :int
def initialize(int) = (#int = int)
end
# prints `1` to STDOUT and assigns the value to *a*,
# but returns nil because you're using Kernel#puts
# which always returns nil
puts a = Example.new(1).int
# shows that the local variable *a* is set to the value
# returned by the Example#int accessor for the class'
# #int instance variable
a
#=> 1
To avoid the confusion of having a nil return value (even though this is expected with Kernel#puts, just change your puts statement to use Kernel#p instead:
p a = Example.new(2).int
#=> 2
Refactoring the Example Class for Older Rubies
If you're using an older Ruby than 3.0, you can't use an endless method or the improved handling for them in Ruby 3.1. The only difference is that rather than an inline method, you need to specify it with the standard def...end syntax, e.g.:
class Example
attr_reader :int
def initialize(int)
#int = int
end
end
Otherwise, the points above are valid as far back as any currently-supported Ruby version.

Accessing functions inside a Ruby block

I'm working inside a one-off Ruby script (so not inside an explicitly defined module or class) and I'm having a hard time accessing a function I've defined earlier in the script, from within a .each block.
def is_post?(hash)
if hash["data"]["post"] == "true" #yes, actually a string
true
else
false
end
end
#further down
threads["data"]["children"].each do |item|
puts item["data"]["title"] unless item.is_post?
end
Result:
in 'block in <top (required)>': private method `is_post?' called for #<Hash:0x007f9388008cf0\> (NoMethodError)
threads is a very, very nested hash. A hash, contaning a hash of arrays, the arrays contain a hash with header data, which contains another hash with the rest of the details. A bit messy, but I didn't write the module that generates that :P
The idea is to iterate through the arrays and retrieve the data from each one.
My questions are:
What manner of shenaniganery do I need to do to access my is_post? function from within the block?
Why is it coming up as a private method when I don't have any private declarations anywhere in my script?
Kernel vs instance method, self vs argument
def is_post?(hash)
...
end
By defining the methods in that way, you are defining a method for Kernel. You have the choice of either calling this method through Kernel.is_post?(hash), or is_post?(arg). Unless item is the Kernel object, you wont have defined the method is_post? for it.
Your method takes exactly one argument. In case item has a is_post? method, by doing item.is_post?, you are not providing an argument but only self to the method.
The solution
You probably should replace
item.is_post?
by
is_post?(item)
You don't want to call is_post? on the item (it's a Hash like the error message says).
What you want is the following:
threads["data"]["children"].each do |item|
puts item["data"]["title"] unless is_post?(item)
end

Equivalent of “pass” in Ruby

In python there is a pass keyword for defining an empty function, condition, loop, ...
Is there something similar for Ruby?
Python Example:
def some_function():
# do nothing
pass
No, there is no such thing in Ruby. If you want an empty block, method, module, class etc., just write an empty block:
def some_method
end
That's it.
In Python, every block is required to contain at least one statement, that's why you need a "fake" no-op statement. Ruby doesn't have statements, it only has expressions, and it is perfectly legal for a block to contain zero expressions.
nil is probably the equivalent of it:
def some_function
nil
end
It's basically helpful when ignoring exceptions using a simple one-line statement:
Process.kill('CONT', pid) rescue nil
Instead of using a block:
begin
Process.kill('CONT')
rescue
end
And dropping nil would cause syntax error:
> throw :x rescue
SyntaxError: (irb):19: syntax error, unexpected end-of-input
from /usr/bin/irb:11:in `<main>'
Notes:
def some_function; end; some_function returns nil.
def a; :b; begin; throw :x; rescue; end; end; a; also returns nil.
You always have end statements, so pass is not needed.
Ruby example:
def some_function()
# do nothing
end
Ruby 3.0
As of Ruby 3.0, so-called "endless" method definitions are now supported -- we no longer require end statements with every single method definition. This means the most concise way of expressing an empty method like the example above is now arguably something like this:
def some_function = nil
Alternatively, there has always been an uglier one-line option using the much-hated semicolon:
def some_function; end
Note that this doesn't really change anything about the first solution except how the code can be written.
Single line functions and classes
def name ; end
class Name ; end
works fine for pseudocode.
As answered before everything in ruby is an expression so it is fine to leave it blank.
def name
end
class Name
end
A ruby alternative for python programmers who love the pass keyword
def pass
end
# OR
def pass; end
Note that it is useless to do this in Ruby since it allows empty methods but if you're that keen on pass, this is the simplest and cleanest alternative.
and now you can use this function inside any block and it will work the same.
def name
pass
end
# OR
class Name
pass
end
Keep in mind that pass is a function that returns, so it is up to you how you can use it.
If you want to be able to use it freely with any number of arguments, you have to have a small trick on the arguments:
def gobble *args, &pr; end
As others have said, in Ruby you can just leave a method body empty. However, this could prove a bit different than what Python accomplishes with pass.
In Ruby, everything is an object. The absence of value, which some programming languages indicate with null or nil is actually an object of NilClass in Ruby.
Consider the following example (in irb):
class A
def no_op
end
end
A.new.no_op
# => nil
A.new.no_op.class
# => NilClass
A.new.no_op.nil?
# => true
Here's Ruby's NilClass documentation for reference.
I believe Python's pass is used mainly to overcome the syntactic limitations of the language (indentation), although I'm not that experienced in Python.
Ruby's equivalent to pass can be ().
if 1 == 1
()
else
puts "Hello"
end
=> nil
lambda do
()
end.call
=> nil
You can also use it as part of condition ? true-expr : false-expr ternary operator.

Ruby: Automatically set instance variable as method argument?

Are there any plans to implement ruby behavior similar to the CoffeeScript feature of specifying an instance variable name in a method argument list?
Like
class User
def initialize(#name, age)
# #name is set implicitly, but #age isn't.
# the local variable "age" will be set, just like it currently works.
end
end
I'm aware of this question: in Ruby can I automatically populate instance variables somehow in the initialize method? , but all the solutions (including my own) don't seem to fit the ruby simplicity philosophy.
And, would there be any downsides for having this behavior?
UPDATE
One of the reasons for this is the DRY (don't repeat yourself) philosophy of the ruby community. I often find myself needing to repeat the name of an argument variable because I want it to be assigned to the instance variable of the same name.
def initialize(name)
# not DRY
#name = name
end
One downside I can think of is that it may look as though a method is doing nothing if it has no body. If you're scanning quickly, this may look like a no-op. But I think given time, we can adapt.
Another downside: if you're setting other instance variables in the body, and you try to be readable by putting all the assignments at the beginning, it can take more cognitive "power" to see that there assignments also happening in the argument list. But I don't think this is any harder than, say, seeing a constant or method call and having to jump to its definition.
# notice: instance var assignments are happening in 2 places!
def initialize(#name)
#errors = []
end
After some pondering, I wondered if it's possible to actually get the argument names from a ruby method. If so, I could use a special argument prefix like "iv_" to indicate which args should be set as instance variables.
And it is possible: How to get argument names using reflection.
Yes! So I can maybe write a module to handle this for me. Then I got stuck because if I call the module's helper method, it doesn't know the values of the arguments because they're local to the caller. Ah, but ruby has Binding objects.
Here's the module (ruby 1.9 only):
module InstanceVarsFromArgsSlurper
# arg_prefix must be a valid local variable name, and I strongly suggest
# ending it with an underscore for readability of the slurped args.
def self.enable_for(mod, arg_prefix)
raise ArgumentError, "invalid prefix name" if arg_prefix =~ /[^a-z0-9_]/i
mod.send(:include, self)
mod.instance_variable_set(:#instance_vars_from_args_slurper_prefix, arg_prefix.to_s)
end
def slurp_args(binding)
defined_prefix = self.class.instance_variable_get(:#instance_vars_from_args_slurper_prefix)
method_name = caller[0][/`.*?'/][1..-2]
param_names = method(method_name).parameters.map{|p| p.last.to_s }
param_names.each do |pname|
# starts with and longer than prefix
if pname.start_with?(defined_prefix) and (pname <=> defined_prefix) == 1
ivar_name = pname[defined_prefix.size .. -1]
eval "##{ivar_name} = #{pname}", binding
end
end
nil
end
end
And here's the usage:
class User
InstanceVarsFromArgsSlurper.enable_for(self, 'iv_')
def initialize(iv_name, age)
slurp_args(binding) # this line does all the heavy lifting
p [:iv_name, iv_name]
p [:age, age]
p [:#name, #name]
p [:#age, #age]
end
end
user = User.new("Methuselah", 969)
p user
Output:
[:iv_name, "Methuselah"]
[:age, 969]
[:#name, "Methuselah"]
[:#age, nil]
#<User:0x00000101089448 #name="Methuselah">
It doesn't let you have an empty method body, but it is DRY. I'm sure it can be enhanced further by merely specifying which methods should have this behavior (implemented via alias_method), rather than calling slurp_args in each method - the specification would have to be after all the methods are defined though.
Note that the module and helper method name could probably be improved. I just used the first thing that came to mind.
Well, actually...
class User
define_method(:initialize) { |#name| }
end
User.new(:name).instance_variable_get :#name
# => :name
Works in 1.8.7, but not in 1.9.3. Now, just where did I learn about this...
I think you answered your own question, it does not fit the ruby simplicity philosophy. It would add additional complexity for how parameters are handled in methods and moves the logic for managing variables up into the method parameters. I can see the argument that it makes the code less readable a toss up, but it does strike me as not very verbose.
Some scenarios the # param would have to contend with:
def initialize( first, last, #scope, #opts = {} )
def search( #query, condition )
def ratchet( #*arg )
Should all of these scenarios be valid? Just the initialize? The #*arg seems particularly dicey in my mind. All these rules and exclusions make the Ruby language more complicated. For the benefit of auto instance variables, I do not think it would be worth it.

Does Ruby have a method_missing equivalent for undefined instance variables?

When I invoke a method that doesn't exist, method_missing will tell me the name of the method. When I attempt to access a variable that hasn't been set, the value is simply nil.
I'm attempting to dynamically intercept access to nil instance variables and return a value based on the name of the variable being accessed. The closest equivalent would be PHP's __get. Is there any equivalent functionality in Ruby?
I do not believe this is possible in Ruby. The recommended way would be to use a ''user'' method rather than a ''#user'' instance var in your templates.
This is consistent with the way you deal with Ruby objects externally (''obj.user'' is a method which refers to ''#user'', but is actually not ''#user'' itself). If you need any kind of special logic with an attribute, your best bet is to use a method (or method_missing), regardless if you're accessing it from inside or outside the object.
See my answer to another similar question. But just because you can do it doesn't mean that it's a good idea. Sensible design can generally overcome the need for this kind of thing and allow you to produce more readable and hence maintainable code.
instance_variable_get seems to be the closest equivalent of PHP's __get from what I can see (although I'm not a PHP user).
Looking at the relevant Ruby source code, the only 'missing' method for variables is const_missing for constants, nothing for instance variables.
there isn't an instance_variable_missing (at least that I know of)
But why are you accessing randomly named instance variables anyway?
If your thread all the access to the object state through method calls (as you should anyway) then you wouldn't need this.
If you are looking for a way to define magic stuff without messing up with the method lookup, you may want to use const_missing.
A bit late but, instance_variable_missing is the same as method_missing to a point... Take the following class:
class Test
def method_missing(*args)
puts args.inspect
end
end
t = Test.new
Now let's get some instance variables:
t.pineapples #=> [:pineapples]
t.pineapples = 5 #=> [:pineapples=,5]
Not sure why the method is nil for you...
EDIT:
By the sounds of it you want to accomplish:
t = SomeClass.new
t.property.child = 1
So let's try returning a Test object from our previous example:
class Test
def method_missing(*args)
puts args.inspect
return Test.new
end
end
So what happens when we call:
t = Test.new
t.property.child = 1
#=>[:property]
#=>[:child=,1]
So this goes to show that this is indeed possible to do. OpenStruct uses this same technique to set instance variables dynamically. In the below example, I create EternalStruct which does exactly what you wanted:
require 'ostruct'
class EternalStruct < OpenStruct
def method_missing(*args)
ret = super(*args)
if !ret
newES = EternalStruct.new
self.__send__((args[0].to_s + "=").to_sym, newES)
return newES
end
end
end
Usage of EternalStruct:
t = EternalStruct.new
t.foo.bar.baz = "Store me!"
t.foo.bar.baz #=> "Store me!"
t.foo #=> #<EternalStruct bar=#<EternalStruct baz="Store me!">>
t.a = 1
t.a #=> 1
t.b #=> #<EternalStruct:...>
t.b = {}
t.b #=> {}
def t.c(arg)
puts arg
end
t.c("hi there") #=> "hi there"

Resources