How to implement options hashes in Ruby? - ruby

How can I implement options hashes? How is the structure of a class that has option hashes in it? Say I have a person class. I want to implement a method such as my_age that when called upon will tell me my age using options hashes.

You could do something like this:
class Person
def initialize(opts = {})
#options = opts
end
def my_age
return #options[:age] if #options.has_key?(:age)
end
end
and now you're able to call to the age like this
p1 = Person.new(:age => 24)<br/>
p2 = Person.new
p1.my_age # => 24<br/>
p2.my_age # => nil

class Person
def birth_date
Time.parse('1776-07-04')
end
def my_age(opts=nil)
opts = {
as_of_date: Time.now,
birth_date: birth_date,
unit: :year
}.merge(opts || {})
(opts[:as_of_date] - opts[:birth_date]) / 1.send(opts[:unit])
end
end

Might be worth mentioning that Ruby 2.1 adds the ability to pass in keyword arguments that don't need to be in a particular order AND you can make them be required or have default values.
Ditching the options hash reduces the boilerplate code to extract hash options. Unnecessary boilerplate code increases the opportunity for typos and bugs.
Also with keyword arguments defined in the method signature itself, you can immediately discover the names of the arguments without having to read the body of the method.
Required arguments are followed by a colon while args with defaults are passed in the signature as you'd expect.
For example:
class Person
attr_accessor(:first_name, :last_name, :date_of_birth)
def initialize(first_name:, last_name:, date_of_birth: Time.now)
self.first_name = first_name
self.last_name = last_name
self.date_of_birth = date_of_birth
end
def my_age(as_of_date: Time.now, unit: :year)
(as_of_date - date_of_birth) / 1.send(unit)
end
end

In Ruby 2.x you can use ** operator:
class Some
def initialize(**options)
#options = options
end
def it_is?
return #options[:body] if #options.has_key?(:body)
end
end

Related

Based on user input I would like to find by name for a object within a list I have in my CLI

I am making a CLI it has 100 objects each has a name and I would like to create an option to search my list of 100 objects to find the object by name. What would be the best implementation to use here.
To start with I am assuming in Ruby I can use .find ? My current WIP is below. Any help is appreciated.
class PokeDEXCLI::Pokemon
attr_accessor :name, :id, :height, :weight
##all = []
def initialize(attr_hash)
attr_hash.each do |key, value|
self.send("#{key}=", value) if self.respond_to?("#{key}=")
end
self.save
end
def save
##all << self
end
def self.all
##all
end
end
My thought was I could search by input to find by doing something like this first?
def self.find_by_name(input)
puts " Would you like to search by pokemon name? Please type in your query."
input = gets.chomp
if ##all.include? input
(this is where I am unsure how to compare input to the :name attribute)
end
So I believe I will use the below snippet. My other question is can I add a default argument to name so it will return nil if there is no match?
class Pokemon
def self.find_by_name(name = nil)
##all.select { |pokemon| pokemon.name.include?(name) }
end
end
The Ruby approach here is to use a Hash:
OBJECTS = {
shoe: 'A shoe',
rose: 'A red flower',
dog: 'A yappy dog'
cat: 'Some orange blur'
}
This doesn't have to be a constant as it is here, you could easily have a variable, but if the data never changes constants are more efficient in terms of impact on performance.
Where you can reference it like this:
OBJECTS[:shoe]
Or based on input:
OBJECTS[gets.chomp.to_sym]
If collection accepts objects with already existing names, then use select method to return collection of matched names
class Pokemon
def self.find_by_name(name)
##all.select { |pokemon| pokemon.name == name }
end
end
If you want to find objects which name contains given string, use same approach with different condition
class Pokemon
def self.find_by_name(name)
##all.select { |pokemon| pokemon.name.include?(name) }
end
end
If collection accepts only unique names, then collection could be implemented by using Hash
class Pokemon
##all = {}
def self.find_by_name(name)
##all[name]
end
def save
##all[name] == self
end
end
this class method could help you if your 'Pokemon' class have attr_accessor 'name'
def self.find_by_name(search_string)
result = []
ObjectSpace.each_object(self) { |pokemon| result << pokemon if pokemon.name.include?(search_string) }
result
end
it will return an array of pokemons with name, including the search string

Adding to Ruby Objects

I have been doing a little reading up on Ruby. Like how simplistic the language is. I've been trying to look it up, and figure it out on my own. I'm looking for some help with Objects and how I add data to it. I want to make an Object called Athlete where I read it in from a .txt or .csv file their Jersey Number and Name.
class Athlete
def setNumber (jNum)
#mynum = jNum
end
def getNumber
return #mynum
end
def setName (jName)
#myname = jName
end
def getName
return #myname
end
end
Is that how I would set up the class?
Then I read in the file:
myAthlete = Athlete.new
fileObj = File.new(uInput, "r")
while (line = fileObj.gets)
jData = line.split(" ")
myAthlete.setNumber(jData.at(0))
myAthlete.setName(jData.at(1))
end
fileObj.close
this is where I start to get a bit lost. I know it splits the data perfectly, because I've already tried this with just Array.new -- That being said, I'm trying to make the array inside of the Athlete class. Can someone assist me with this?
So if my input file is:
52 Sabathia
19 Tanaka
17 Holliday
24 Sanchez
I would like for it to split and then if I call lets say uhhh myAthlete(1) it'd print out Tanaka's stuff
The thing about Ruby to embrace out of the gate is how clean the syntax is and how a lot of Ruby style conventions are driven by that. For example, Ruby advises against methods with get and set in them, instead preferring things like name and name= for accessor and mutator methods.
Yes, you can have = at the end of a method name, just like you can have ? or !, each of which have taken to mean particular things.
It's also a given that the last operation you perform in a method is implicitly the return value, so there's no need for return.
Here's a simple refactoring of your code with that in mind:
class Athlete
def number
#number
end
def number=(value)
#number = value
end
def name
#name
end
def name=(value)
#name = value
end
end
You can reduce this even further since Ruby has a method that will generate these for you automatically called attr_accessor. You can also make your life a little easier by making an initialize method to populate these:
class Athlete
attr_accessor :number
attr_accessor :name
def initialize(number, name)
#number = number
#name = name
end
end
So to put this all together:
athletes = [ ]
File.readlines(input) do |line|
number, name = line.chomp.split(/\s+/)
athletes << Athlete.new(number, name)
end
A lot of Ruby code uses blocks to define operations that should happen. In this case readlines calls that block for each line read from the file. These lines include the \n newline at the end which chomp removes. << is a quick way to append something to an array.
Try to keep your variable and method names all lower-case as well. Capitals have significant meaning in Ruby, so jData should be jdata or j_data.
Update: To make this more debugger-friendly:
class Athlete
def inspect
"Athlete [%s] %s" % [ #number, #name ]
end
end
First off, you don't need to define explicit getters/setters. Something like this will do
class Athlete
attr_accessor :name, :number
def initialize(name, number)
self.name = name
self.number = number
end
end
Or even shorter:
Athlete = Struct.new(:name, :number)
Then to create athletes:
athletes = File.foreach(file_path).map do |line|
number, name = line.chomp.split(' ')
Athlete.new(name, number)
end
You will now have an array full of Athletes.

Create hash of instance methods references

I have a class that can parse different types of messages and what I want to do is to create a hash that will use the msg type id as the keys and different instance methods as the values.
Something like this:
class Parser
def initialize(msg_id)
#my_methods = {1 => method_1, 2 => method_2, 3 => method_3}
#my_methods[msg_id]()
end
def method_1
end
def method_2
end
def method_3
end end
I know it's possible, but I am not sure how to do it. I tried using the self.method(:method_1) as a value but I got an error saying that method_1 is not defined.
Thank you
The simplest possible changes to fix your code are like this:
class Parser
def initialize(msg_id)
#my_methods = { 1 => method(:method_1), 2 => method(:method_2), 3 => method(:method_3) }
#my_methods[msg_id].()
end
def method_1; end
def method_2; end
def method_3; end
end
I.e. use the Object#method method to get a Method object, and use the Method#call method to execute it.
However, there are a few improvements we could make. For one, your Hash associates Integers with values. But there is a better data structure which already does that: an Array. (Note: if your message IDs are not assigned sequentially, then a Hash is probably the right choice, but from the looks of your example, they are just Integers counting up from 1.)
And secondly, hardcoding the methods inside the Parser#initialize method is probably not a good idea. There should be a declarative description of the protocol, i.e. the message IDs and their corresponding method names somewhere.
class Parser
# this will make your message IDs start at 0, though
PROTOCOL_MAPPING = [:method_1, :method_2, :method_3].freeze
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map(&method(:method))
#my_methods[msg_id].()
end
def method_1; end
def method_2; end
def method_3; end
end
Another possibility would be something like this:
class Parser
PROTOCOL_MAPPING = []
private_class_method def self.parser(name)
PROTOCOL_MAPPING << name
end
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map(&method(:method))
#my_methods[msg_id].()
end
parser def method_1; end
parser def method_2; end
parser def method_3; end
end
Or maybe this:
class Parser
PROTOCOL_MAPPING = {}
private_class_method def self.parser(msg_id, name)
PROTOCOL_MAPPING[msg_id] = name
end
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map {|msg_id, name| [msg_id, method(name)] }.to_h.freeze
#my_methods[msg_id].()
end
parser 1, def method_1; end
parser 2, def method_2; end
parser 3, def method_3; end
end
While provided answer would work fine, there are few "minor" issues with it:
If there'd be tons of methods, hardcoding such hash would take time, and since it is not dynamic (because you have to update the hash manually each time new method is added to the class body) it is very error prone.
Even though you are within the class, and technically have access to all methods defined with any visibility scope with implicit receiver (including private and protected), it is still a good practice to only rely on public interface, thus, I'd recommend to use Object#public_send.
So here is what I would suggest (despite the fact I do not see how the idea of having such map would work in real life):
class Parser
def initialize(msg_id)
# generate a dynamic hash with keys starting with 1
# and ending with the size of the methods count
methods_map = Hash[(1..instance_methods.size).zip(instance_methods)]
# Use public_send to ensure, only public methods are accessed
public_send(methods_map[msg_id])
end
# create a method, which holds a list of all instance methods defined in the class
def instance_methods
self.class.instance_methods(false)
end
end
After a quick thought I refactored it a bit, so that we hide the implementation of the mapping to private methods:
class Parser
def initialize(msg_id)
public_send(methods_map[msg_id])
end
# methods omitted
private
def methods_map # not methods_hash, because what we do is mapping
Hash[(1..instance_methods.size).zip(instance_methods)]
# or
# Hash[instance_methods.each.with_index(1).map(&:reverse)]
end
def instance_methods
self.class.instance_methods(false)
end
end
The method you're looking for is send.
Note that the values in your hash need to be symbols to be passed to send.
class Parser
def initialize(msg_id)
#my_methods = {1 => :method_1, 2 => :method_2, 3 => :method_3}
send(#my_methods[msg_id])
end
def method_1
end
def method_2
end
def method_3
end
end
Documentation here

Reflection in ruby?

I am curious how this works. For example if I create a factory pattern based class where you can "register" classes for later use and then do something like
FactoryClass.register('YourClassName', [param, param, ...]);
FactoryClass.create('your_class_name').call_method_from_this_object
where 'class_name' is a key in a hash that maps to value: ClassName
is there anything like php reflection, where I can create an instance of a class based on a string name and pass in the arguments in? (in php the arguments would be an array of them that php then knows how what to do with)
So if we take a real world example:
class Foo
attr_reader :something
def initialize(input)
#something = input
end
def get_something
return #something
end
end
# In the factory class, foo is then placed in a hash: {'foo' => 'Foo'}
# This step might not be required??
FactoryClass.create('Foo', ['hello'])
# Some where in your code:
FactoryClass.create('foo').get_something # => hello
Is this possible to do in ruby? I know everything is essentially an object, but I haven't seen any API or docs on creating class instances from string names like this and also passing in objects.
As for the hash above, thinking about it now I would probably have to do something like:
{'foo' => {'class' => 'Foo', 'params' => [param, param, ...]}}
This way when you call .create on the FactoryClass it would know, ok I can instantiate Foo with the associated params.
If I am way off base, please feel free to educate me.
Check out Module#const_get (retrieving a constant from a String) and Object#send (calling a method from a String).
Here is an answer that doesn't use eval.
PHP's Reflection is called Metaprogramming in Ruby, but they are quite different. Everything in Ruby is open and could be accessed.
Consider the following code:
class Foo
attr_reader :something
def initialize(input)
#something = input
end
def get_something
return #something
end
end
#registered = { }
def register(reference_name, class_name, params=[])
#registered[reference_name] = { class_name: class_name, params: [params].flatten }
end
def create(reference_name)
h = #registered[reference_name]
Object.const_get(h[:class_name]).new(*(h[:params]))
end
register('foo', 'Foo', ['something'])
puts create('foo').get_something
You can use Object#const_get to get objects from strings. Object.const_get('Foo') will give you the object Foo.
However, you don't need to send class name as string. You can also pass around the class name as object and use that directly.
class Foo
attr_reader :something
def initialize(input)
#something = input
end
def get_something
return #something
end
end
#registered = { }
def register(reference_name, class_name, params=[])
#registered[reference_name] = { class_name: class_name, params: [params].flatten }
end
def create(reference_name)
h = #registered[reference_name]
h[:class_name].new(*(h[:params]))
end
register('foo', Foo, ['something else'])
puts create('foo').get_something
Actually one of the strong points in ruby is meta-programming. So this is really easy to do in ruby.
I am going to skip the registering part, and jump straight to the creation
A simple implementation would be this
class FactoryClass
def self.create(class_name, params)
klass = Object.const_get(class_name)
klass.new(*params)
end
end
and then you can just do:
FactoryClass.create('YourClassName', [param, param, ...]);
and this would be equivalent to calling
YourClassName.new(param, param, ...)

Ruby create methods from a hash

I have the following code I am using to turn a hash collection into methods on my classes (somewhat like active record). The problem I am having is that my setter is not working. I am still quite new to Ruby and believe I've gotten myself turned around a bit.
class TheClass
def initialize
#properties = {"my hash"}
self.extend #properties.to_methods
end
end
class Hash
def to_methods
hash = self
Module.new do
hash.each_pair do |key, value|
define_method key do
value
end
define_method("#{key}=") do |val|
instance_variable_set("##{key}", val)
end
end
end
end
end
The methods are created and I can read them on my class but setting them does not work.
myClass = TheClass.new
item = myClass.property # will work.
myClass.property = item # this is what is currently not working.
If your goal is to set dynamic properties then you could use OpenStruct.
require 'ostruct'
person = OpenStruct.new
person.name = "Jennifer Tilly"
person.age = 52
puts person.name
# => "Jennifer Tilly"
puts person.phone_number
# => nil
It even has built-in support to create them from a hash
hash = { :name => "Earth", :population => 6_902_312_042 }
planet = OpenStruct.new(hash)
Your getter method always returns the value in the original hash. Setting the instance variable won't change that; you need to make the getter refer to the instance variable. Something like:
hash.each_pair do |key, value|
define_method key do
instance_variable_get("##{key}")
end
# ... define the setter as before
end
And you also need to set the instance variables at the start, say by putting
#properties.each_pair do |key,val|
instance_variable_set("##{key}",val)
end
in the initialize method.
Note: I do not guarantee that this is the best way to do it; I am not a Ruby expert. But it does work.
It works just fine for me (after fixing the obvious syntax errors in your code, of course):
myClass.instance_variable_get(:#property) # => nil
myClass.property = 42
myClass.instance_variable_get(:#property) # => 42
Note that in Ruby instance variables are always private and you never define a getter for them, so you cannot actually look at them from the outside (other than via reflection), but that doesn't mean that your code doesn't work, it only means that you cannot see that it works.
This is essentially what I was suggesting with method_missing. I'm not familiar enough with either route to say why or why not to use it which is why I asked above. Essentially this will auto-generate properties for you:
def method_missing sym, *args
name = sym.to_s
aname = name.sub("=","")
self.class.module_eval do
attr_accessor aname
end
send name, args.first unless aname == name
end

Resources