How create new instance of a Ruby Struct using named arguments (instead of assuming correct order of arguments) - ruby

Given: Customer = Struct.new(:name, :address, :zip)
Is there a way to name the arguments instead of presuming a certain order?
The docs say do it like this:
joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", "12345")
which IMO makes it too easy to switch two parameters accidentally.
I'd like to do something like this:
joe = Customer.new(name: "Joe Smith", address: "123 Maple, Anytown NC", zip: "12345")
so that the order is not important:
joe = Customer.new(zip: "12345", name: "Joe Smith", address: "123 Maple, Anytown NC")

Named parameters are not (yet) possible in Ruby's Struct class. You can create a subclass of your own in line with this Gist: https://gist.github.com/mjohnsullivan/951668
As you know, full-fledged Classes can have named parameters. I'd love to learn why they aren't possible with Structs... I surmise that someone on the Core team has thought of this and rejected it.

Related

Class instance with names defined by string

In 'pure ruby' (not rails), given a class:
class Person
end
...and an array of strings:
people_names = ['John', 'Jane', 'Barbara', 'Bob']
how can I instantiate the Person class, with each instance variable named one of the elements in my array?
John = Person.new
Jane = Person.new
Barbara = Person.new
Bob = Person.new
Its pretty unclear what you actually want here since an identifier in ruby starting with an uppercase letter in Ruby is a constant.
John = Person.new
Jane = Person.new
Barbara = Person.new
Bob = Person.new
You can dynamically assign constants with Module#const_set.
module MyModule
['John', 'Jane', 'Barbara', 'Bob'].each do |name|
const_set(name, Person.new)
end
end
# this imports the constants into Main which is the global scope
include MyModule
John
=> #<Person:0x007f973586a618>
Instance variables on the other hand use the # sigil. You can dynamically assign instance variables with instance_variable_set:
['John', 'Jane', 'Barbara', 'Bob'].map(&:downcase).each do |name|
instance_variable_set("##{name}", Person.new)
end
#john
# => #<Person:0x007f9734089530>
While you can declare an instance variable named #John it violates the conventions of the language.
Local variables cannot actually be defined dynamically. You can only change existing variables via eval and binding.local_variable_set.
def foo
a = 1
bind = binding
bind.local_variable_set(:a, 2) # set existing local variable `a'
bind.local_variable_set(:b, 3) # create new local variable `b'
# `b' exists only in binding
p bind.local_variable_get(:a) #=> 2
p bind.local_variable_get(:b) #=> 3
p a #=> 2
p b #=> NameError
end
I'm sure Ruby has some means for you into defining constants dynamically, but I'm not going to bother looking that up because this feels, almost 100%, like something you don't really want to do. It seems like you want some way to associate a "name" to a class instance. That is exactly what Hash is for.
people_names = ['John', 'Jane', 'Barbara', 'Bob']
people = people_names.each_with_object({}) do |name, ppl|
ppl[name] = Person.new(name)
end
people['John'].name # => 'John'
people['Jane'].name # => 'Jane'
Why do I say what you're asking for is probably not what you want? Because use meta programming to dynamically create and dynamically read from local variables/constants/instance variables is just generally frowned upon in professional development. For your own projects, for experimentation, sure maybe. For any project as part of a team though, when you start using meta-programming features to dynamically add these values and reference them (maybe directly, maybe indirectly later) is all well and good but when you try and figure out what's going on you will almost never be able to figure out where these things are defined/coming from unless the array with the dynamic names is hard coded. And if it's hard-coded why can't you just build the constants/variables/targets directly in the code? That's significantly better than dynamically doing it.
# this is a fake API to demonstrate
# let's assume this is what you want
PEOPLE_NAMES = ['John', 'Jane']
PEOPLE_NAMES.each do |name|
const_def(name, Person.new)
end
get_person('Jane').do_something # maps to const_get('Jane').do_something
get_person(PEOPLE_NAMES[0]).do_something
John.do_something
If you want the above, why can't you just do:
John = Person.new
Jane = Person.new
John.do_something
The latter is loads more clear, can still be dynamically looked up, but has a hardcoded definition that can easily be targeted when debugging.
That's my recommendation and answer. I'm pretty sure you don't want to do what you're asking to do. Hash totally fits the needs you desire, it's used heavily for purposes like this and closely related to it, I recommend you try and make that work for your needs then try and figure how to solve the problem you're specifically looking to get an answer too.
EDIT
As a really fun add-on, you can do some really cool dynamic stuff with Hash here that doesn't lead to tons of confusion unless you happen to hide where the hash is coming from. But you could do something like:
people = Hash.new { |h, k| h[k] = Person.new(k) }
# right now, people contains no actual people
people['John'].name # => 'John'
# now people contains one Person instance
This is cool for two reasons 1) You don't have to have a list to generate the hash, so if you get names after hash creation that's fine you can just add them by accessing that users name and 2) Being lazy, it will only use the memory you need. If you preload the hash with all four persons, and then access data from only two persons you wasted the space required for the unused 2 Person instances, so this let's you use only as much as you need and otherwise offers you all the same benefits.
You can certainly do it, although as Brandon says it probably isn't a good idea. Here's how to do it:
people_names.each { |name| eval("#{name} = Person.new")}
eval takes a string passed as an argument and executes it as a line of code. So, you use each to do that once for each member of your array.
You might want to google "eval" to see any number of articles about why it's evil. Many of these go off on metaprogramming (eval is an example of metaprogramming) in general, and make some good points. I have a bit more moderate approach to metaprogramming than that (attr_accessor, after all, is an example of metaprogramming, too, and people use it all the time), but you can certainly write some very tangled code using eval.
Note also, as several posters have observed, that by capitalizing your strings you are defining them as constants. You can change a constant's value in Ruby, but you will get a warning every time you do so.

Improved Way Make sentence using Ruby Hashes

I am creating a hash that contain data related to Name, Age, Hometown, and Favorite Food. I am taking that information and then making a sentence out of it. I have successfully got my code to work, yet I wonder if their is a way to do it that is shorter.
Here is my code:
person = {"Name" => "Randy", "Age" => 28, "Hometown" => "Jamesville",
"Favorite Food" => "Chicken"}
puts "I am #{person.fetch("Name")}. I am #{person.fetch("Age")}-years-old.
I'm from #{person.fetch("Hometown")} and my favorite food is #
{person.fetch("Favorite Food")}"
Which will print
I am Randy. I am 28-years-old. I'm from Jamesville and my favorite food is
Chicken.
If you make the Hash keys symbols e.g.
person = {name: "Randy",
age: 28,
hometown: "Jamesville",
favorite_food: "Chicken"}
You could use Kernel#sprintf instead e.g.
sentence = "I am %{name}. I am %{age}-years-old.
I'm from %{hometown} and my favorite food is %{favorite_food}"
sprintf sentence, person
#=> "I am Randy. I am 28-years-old.\nI'm from Jamesville and my favorite food is Chicken"
or you could use String#%
puts sentence % person
# I am Randy. I am 28-years-old.
# I'm from Jamesville and my favorite food is Chicken
#=> nil
both of these methods rely on symbolized keys though (which by the way is the preferred syntax for a Hash)

Can one uniquely initialize each object created by the create_list method in FactoryGirl?

In order to do this, I imagine passing an array of hashes to the create_list method. I am considering something that would look like this:
FactoryGirl.create_list(
:person, 3, [
{name: 'Rebekah', description: 'A woman with straight brown hair'},
{name: 'Day', description: 'A man with curly brown hair'},
{name: 'Ihsan', description: 'A boy with wavy blond hair'}
]
)
This would persist three objects initialized with the custom name and description values.
Is there any way to do this directly or ought I just loop through the array creating an individual instance with each set of unique values?
Do you really need them to be different? If you do, I see two options:
1- Set up the factory properties in a sequence. Like this:
FactoryGirl.define do
factory :person do
sequence(:name) { |n| "Person number #{n}" }
end
2- Create a list and set them up individually
list = FactoryGirl.create_list(:person, 3)
list.each do |person|
#setting up
end
There are other answers, but I would go with the first

How to use string interpolation

I have the following question:
person = "John", building = "Big Tower"
I want to use Ruby's string interpolation to produce the following:
"My friend John owns the building Big Tower"
Is this the correct answer:
puts "My friend #{person} owns the building #{building}"
??
If you intended the variable assignment to be Ruby code, then it is wrong. It should be
person = "John"; building = "Big Tower"
or
person, building = "John", "Big Tower"
And for the question, yes, except that interpolation is a feature of Ruby, not Rails. Please be respectful to plain Ruby and its developers, and don't confuse it with Rails. Rails is only a framework built on top of Ruby. Rails is not the only use of Ruby.
As I said in my comment, there's no syntax error in your declaration. It's a semantic error, because when you declare two variables or more separated by comma and you assign them values at the same time, the ones before the last will be considered as arrays by ruby.
The following declaration:
person = "John", building = "Big Tower"
With the following statement:
puts "person: #{person}", "building: #{building}"
Will output this:
person: ["John", "Big Tower"]
building: Big Tower
So, if by any constraint you cannot change your declaration to separate the two variables, the solution to your question would be to pick only the first element of your first variable like this:
puts "My friend #{person[0]} owns the building #{building}"
This will work. You can test it here http://ideone.com/ca2zw4
Consider this output of of IRB or the TryRuby interpreter:
> a=1,b=2
=> [1, 2]
> a
=> [1, 2]
> b
=> 2
This shows that #sawa's answer is right, that the problem is not in your interpolation, but in your compound assignment. Ruby interprets that as:
a = 1,(b=2)
Your problem is that you need to swap it to a,b=1,2 or, in your code,
person, building = "John", "Big Tower"
Please accept the answer by #sawa.
Alternatively, if you cannot change the initial, broken-code:
puts "My friend #{person.first} owns the building #{building}"
…or…
puts "My friend #{person.first} owns the building #{person.last}"
…or…
person = person.first # Fix the mistake
puts "My friend #{person} owns the building #{building}"

Ruby read variables from file

I am making a small text based game, and I want to save the game and load the data at a later date. I managed to have the game create a text file that stores all of the variables and their values in a format like this:
first_name = "Name"
last_name = "Last Name"
bank = 10000
...
When I select load, I want it to read the text and assign the appropriate variables. Can I do this?
One option: you could use IO.readlines, which would return an array of strings that you could parse. If you had a set of variables already in mind (and used by the code), you could just grab what's in quotes, for example. Take a look at what ruby can do with strings.
You cannot do that with local variables unless you use meta-programming techniques like evaluating or parsing the file, as proposed in other answers. If you want to do it without meta-programming hack, you need to use other types of variables or constants. Preparing a module (which is a special type of constant) for setting may help.
In the main file:
module Setting
def self.set h; #h = h end
def self.call #h end
end
load(path_to_setting_file)
In the setting file:
Setting.set(
first_name: "Name",
last_name: "Last Name",
bank: 10000,
)
To call it from the main file:
Setting.call
# => {
first_name: "Name",
last_name: "Last Name",
bank: 10000,
}
If you don't even like that, then you should use YAML.
Fastest and easiest way is to use Marshal.
# Save data
data = Marshal.dump(game_state)
# Load data
game_state = Marshal.load(data)
If you expect your game data schema to keep changing, you might want to consider storing your data in a hash or a Hashie::Mash instead of using instance variables.
Roughly:
variable_file = File.open("./myfile.txt")
variable_file.each do |variable_line|
variable_line.chomp!
#split on " = "
#gsub out the quotes
#add to hash where name is key and value is value
end
#your program uses the hash to access the values

Resources