Creating a Hash with a method - ruby

I am having trouble creating a method to establish a new hash. I know that it is definitely easier just to declare the hash, however I need to create a method. Here is what I have so far and it keeps generating an error message.
def create_new(hash_name)
hash_name = Hash.new
end
This should create and empty hash^
def add_item(hash_name, item_name, item_quantity)
hash_name[:item_name.to_sym] = item_quantity
end
I keep getting an error message on the above code^ I am trying to update this hash and add a new key value pair with a method
p create_new("grocery_list")
This creates a new empty hash^ however when I call it with the below code is says the hash is undefined
add_item(grocery_list, "pizza", "1")
p grocery_list

You could also turn it into a class if you fancy.
class MyHash
attr_reader :hash
def initialize
#hash = Hash.new
end
def [](key)
self.hash[key]
end
def []=(key, value)
self.hash[key.to_sym] = value
end
end
grocery_list = MyHash.new
grocery_list['pizza'] = 1
> grocery_list.hash
=> {:pizza=>1}

in your create_new method, you define a hash_name local variable. This variable does not exist anywhere but the body of your method. That's what seems to confuse you.
You could express better your intent with :
def create_new
Hash.new
end
def add_item(hash, key, value)
hash[key.to_sym] = value
end
In order to get to what you are trying to do, you will have to store the result of your method in some kind of variable in order to use it :
grocery_list = create_new # grocery_list is now a local variable
add_item(grocery_list, 'pizza', 1)

Related

How create Ruby Class with same object id

I need to create a class where if the attribute value is the same it does not generate a new object id, example:
result:
described_class.new('01201201202')
<PixKey:0x00007eff5eab1ff8 #key="01201201202">
if i run it again with the same value it should keep the same object id
0x00007eff5eab1ff8
is similar behavior with the symbol
test:
describe '#==' do
let(:cpf) { described_class.new('01201201202') }
it 'verifies the key equality' do
expect(cpf).to eq described_class.new('01201201202')
end
end
Running the test shows an error, because the obejct id changes:
expected: #<PixKey:0x00007eff5eab1ff8 #key="01201201202">
got: #<PixKey:0x00007eff5eab2070 #key="01201201202">
Class:
class PixKey
def init(key)
#key = key
end
end
The other answers are fine, but they are a little more verbose than needed and they use class variables, which I find to be a confusing concept because of how they are shared among various classes.
class PixKey
#instances = {}
def self.new(id)
#instances[id] ||= super(id)
end
def initialize(id)
#key = id
end
end
p PixKey.new(1)
p PixKey.new(2)
p PixKey.new(2)
p PixKey.new(1)
Running the test shows an error, because the object id changes
Not quite. It shows an error because the objects are not equal. And the error message prints both objects including their id. But the object id is not what's causing the test to fail.
I need to create a class where if the attribute value is the same it does not generate a new object id
That would probably work, but you're likely approaching the problem from the wrong side. In Ruby, equality doesn't mean object identity. Two objects can be equal without being the same object, e.g.
a = 'foo'
b = 'foo'
a.object_id == b.object_id
#=> false
a == b
#=> true
There's no need to tinker with object ids to get your test passing. You just have to implement a custom == method, e.g.:
class PixKey
attr_reader :key
def initialize(key) # <- not "init"
#key = key
end
def ==(other)
self.class == other.class && self.key == other.key
end
end
The == method checks if both objects have the same class (i.e. if both are PixKey instances) and if their key's are equal.
This gives:
a = PixKey.new('01201201202')
b = PixKey.new('01201201202')
a == b
#=> true
Create a class method to create instances and have it look up a hash.
class PixKey
##instances = {}
def PixKey.create(id)
if not ##instances.has_key?(id)
##instances[id] = PixKey.new(id)
end
return ##instances[id]
end
def initialize(id)
#key = id
end
end
a = PixKey.new(123)
b = PixKey.new(123)
c = PixKey.create(123)
d = PixKey.create(123)
puts a
puts b
puts c
puts d
Output:
#<PixKey:0x000000010bc39900>
#<PixKey:0x000000010bc38078>
#<PixKey:0x000000010bc33eb0>
#<PixKey:0x000000010bc33eb0>
Notice the last two instances created with the PixKey.create(id) method return the same instance.
Note that Ruby's new method is just a method on Class and can be overridden like any other. The docs describe the default implementation.
Calls allocate to create a new object of class's class, then invokes that object's initialize method, passing it args. This is the method that ends up getting called whenever an object is constructed using .new.
So, if you want to keep the .new syntax and still get the same objects back, we can override new on the class and call super. This is exactly what OscarRyz' answer does, just with .new and super rather than a separate helper function.
class PixKey
##instances = {}
def PixKey.new(id)
if not ##instances.has_key?(id)
##instances[id] = super(id)
end
return ##instances[id]
end
def initialize(id)
#key = id
end
end
a = PixKey.new(123)
b = PixKey.new(123)
puts a
puts b

Method_missing not running when it should

I have a Team class in my program and I am trying to use method_missing
but instead of running the function when the method doesn't exist, it gives me an error:"undefined method `hawks' for Team:Class (NoMethodError)"
My code is as follows:
class Team
attr_accessor :cust_roster, :cust_total_per, :cust_name, :cust_best_player
##teams = []
def initialize(stats = {})
#cust_roster = stats.fetch(:roster) || []
#cust_total_per = stats.fetch(:per)
#cust_name = stats.fetch(:name)
#cust_best_player = stats.fetch(:best)
##teams << self
end
def method_missing(methId)
str = methID.id2name
Team.new(roster:[], per: 0, name: str.uppercase, best: 0)
end
class <<self
def all_teams
##teams
end
end
end
hawks = Team.hawks
There are a number of problems with your code. Let's go through one by one.
From the documentation,
method_missing(*args) private
Invoked by Ruby when obj is sent a message it cannot handle.
Here message refers to the method. In ruby, whenever you're calling a method on an object, you're actually sending a message to the object
To better understand this, try this in the irb shell.
1+2
=> 3
1.send(:+,2)
=> 3
Here 1 and 2 are objects of Fixnum class. You can confirm that by using 1.class. Ok, back to your question. So, a method_missing method should be called on an instance.
team = Team.new
team.hawks
If you try the above piece of code, you'll get an error saying 'fetch': key not found: :roster (KeyError)
You can get around this by passing a default value as the second parameter to fetch method. Replace your initialize method with
def initialize(stats = {})
#cust_roster = stats.fetch(:roster, [])
#cust_total_per = stats.fetch(:per, 0)
#cust_name = stats.fetch(:name, "anon")
#cust_best_player = stats.fetch(:best, "anon")
##teams << self
end
If you execute the script, you'll get a stack level too deep (SystemStackError) because of a small typo in this line.
str = methID.id2name
In the method definition, you're receiving an argument with the name of methId but inside you're trying to call methID. Fix it with
str = methId.id2name
If you execute your script, you'll again get an error saying undefined method uppercase for "hawks":String (NoMethodError)
This is because there is no uppercase method on strings. You should instead use the upcase method.
Team.new(roster:[], per: 0, name: str.upcase, best: 0)
and you should be good to go.
For more, see http://apidock.com/ruby/BasicObject/method_missing
Hope this helps!
class Team
attr_accessor :cust_roster, :cust_total_per, :cust_name, :cust_best_player
##teams = []
def initialize(stats = {roster: [], per: 0, name: "", best: 0}) # I added the default values here.
#cust_roster = stats.fetch(:roster)
#cust_total_per = stats.fetch(:per)
#cust_name = stats.fetch(:name)
#cust_best_player = stats.fetch(:best)
##teams << self
end
def method_missing(name, *args)
self.cust_name = name.to_s.upcase
end
class << self
def all_teams
##teams
end
end
end
team_hawks = Team.new #=> create an instance of Team class, I renamed the object to avoid confusions.
team_hawks.hawks #=> called method_missing which assigned the cust_name variable to "HAWKS"
team_hawks.cust_name #=> HAWKS, so cust_name is assigned to be hawks. This is to check if the assignment worked.
Hope this is what you are looking for.

Ruby: what is wrong with this change_keys functions?

class Klass
attr_accessor :keys
def change_keys(opt)
if opt == 1
keys = [keys[0], keys[keys.length - 1]]
else
tmp = keys[0]
keys[0] = keys[keys.length-1]
keys[keys.length-1] = tmp
end
keys
end
end
klass = Klass.new
klass.keys = [1,2,3,4,5]
# puts klass.change_keys(1)
# puts klass.change_keys(2)
This is not working at all, the error says: undefined method '[]' method for nil:NilClass
Ruby interprets keys[0] and the others in line 5 as local variables because it has seen a keys = ... . There is an ambiguity in Ruby grammar when it comes to differentiate local variables from method calls without arguments that gets disambiguated by that heuristic. that is, if the parser sees an assignment to that identifier then is a local variable if not is a method call.
You can solve this by referring to self.keys instead. to make clear that you want to use the accessor method.
class Klass
attr_accessor :keys
def change_keys(opt)
if opt == 1
self.keys = [keys[0], keys[keys.length - 1]]
else
tmp = keys[0]
keys[0] = keys[keys.length-1]
keys[keys.length-1] = tmp
end
keys
end
end
klass = Klass.new
klass.keys = [1,2,3,4,5]
puts klass.change_keys(1)
puts klass.change_keys(2)
It looks like you're expecting keys, within your change_keys method, to refer to an instance variable (the same one as you set explicitly by writing klass.keys = [1,2,3,4,5]), but it doesn't. You want #keys instead.
(There are programming languages, such as C++ and Smalltalk and Java, in which unadorned variable names are automatically taken to refer to instance variables. Ruby isn't one of them.)
You did not define an instance variable named keys to reference it. I would add the initialization and use the #keys instance variable:
class Klass
#attr_accessor :keys
def initialize(keys)
#keys = keys
end
def change_keys(opt)
if opt == 1
#keys = [#keys[0], #keys[#keys.length - 1]]
else
tmp = #keys[0]
#keys[0] = #keys[#keys.length-1]
#keys[#keys.length-1] = tmp
end
#keys
end
end
klass = Klass.new([1,2,3,4,5])
# puts keys.change_keys(1)
# puts keys.change_keys(2)
Try it out and let me know.
Attributes are instance variables, so you should be referring to keys as #keys everywhere within your method.

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

How does this ruby custom accessor work

So the method below in class_eval dynamically creates accessors for attributes defined at runtime. It can be used, for example, to create configuration objects with attributes read from a config file (and unknown until runtime). I understanding all of it except for the else branch. If I am correct the else branch returns the attribute value (val[0]) if there is one value passed in *val. However the way its written I would expect it to return an array (val) if there is more then one value passed in *var. In particular, if I have something like the following:
value = 5
then from reading the code I would expect #value to be [=,5]. However #value returns 5 and not the array [=,5]. How is this possible?
class Module
def dsl_accessor(*symbols)
symbols.each do |sym|
class_eval %{
def #{sym}(*val)
if val.empty?
##{sym}
else
##{sym} = val.size == 1 ? val[0] : val
end
end
}
end
end
end
An equals sign is not an argument for the method, it's a part of the method name. Actually you can call an assignment like this:
value=(5)
So only the integer 5 is an argument for the function.
class Module
def dsl_accessor(*symbols)
symbols.each do |sym|
class_eval %{
def #{sym}
##{sym}
end
def #{sym}=(val)
##{sym} = val
end
}
end
end
end

Resources