NoMethodError: undefined method `[]=' for nil:NilClass - ruby

Error in title. What's going wrong?
Attempting to initialize Temperature object with a hash. If I just do
puts Temperature.from_celsius(50).in_fahrenheit
then it works fine and returns 122.0
But
Temperature.new(:f => 50)
returns an error.
class Temperature
attr_accessor :f, :c
#temp = {:f => 32, :c => 0}
def initialize(params)
if params[:f] != nil
self.class.from_fahrenheit(params[:f])
else
self.class.from_celsius(params[:c])
end
end
def self.from_fahrenheit(temp)
#temp[:f] = temp
#temp[:c] = ((temp - 32.0)/1.8).round(1)
return #temp
end
def self.from_celsius(temp)
#temp[:c] = temp
#temp[:f] = (temp * 1.8 + 32).round(1)
return #temp
end
def in_fahrenheit
#temp[:f]
end
def in_celsius
#temp[:c]
end
end
class Hash
def in_fahrenheit
self[:f]
end
def in_celsius
self[:c]
end
end
puts Temperature.from_celsius(50).in_celsius
tempo = Temperature.new(:f => 50)
tempo.in_fahrenheit

Just as the error message says. You are calling []= on #temp in a Temperature instance, which is nil by default because you have not assigned anything to it anywhere.

You can't initialize instance variable in a class body, as you did. You should do it in a constructor, and because you have three constructors, your code should look like this:
class Temperature
def initialize(params)
#temp = {:f => 32, :c => 0}
if params[:f] != nil
self.class.from_fahrenheit(params[:f])
else
self.class.from_celsius(params[:c])
end
end
def self.from_fahrenheit(temp)
#temp = {}
#temp[:f] = temp
#temp[:c] = ((temp - 32.0)/1.8).round(1)
return #temp
end
def self.from_celsius(temp)
#temp = {}
#temp[:c] = temp
#temp[:f] = (temp * 1.8 + 32).round(1)
return #temp
end
def in_fahrenheit
#temp[:f]
end
def in_celsius
#temp[:c]
end
end

Related

Define a method within a class which creates an instance of the class

I'm writing a class MyHashSet that imitates the Set class. The elements of the set are contained as hash items reading {element => true}. Here is how it is defined:
class MyHashSet
attr_accessor :store
def initialize
#store = {}
end
def insert(el)
#store.merge!(el => true)
end
def include?(el)
#store[el]
end
def delete(el)
#store.select! {|key,value| key != el}
end
def to_a
#store.keys
end
def self.union(set)
result=MyHashSet.new
result.store=(self.store).merge(set.store)
result
end
end
The last method union should be such that if I type the commands:
set1=MyHashSet.new
set2=MyHashSet.new
set1.insert("Mark Hamill")
set1.insert("Harrison Ford")
set1.insert("Anthony Daniels")
set2.insert("Ewan McGregor")
set2.insert("Natalie Portman")
set2.insert("Anthony Daniels")
and then try to compute
set3=set1.union(set2)
I should get a set3 variable, which is an instance of MyHashSet such that its store is:
{"Mark Hamill"=>true, "Harrison Ford"=>true, "Anthony Daniels"=>true, "Ewan McGregor"=>true, "Natalie Portman"=>true}
However, if I try to run this I get an undefined method error message:
`<main>': undefined method `union' for #<MyHashSet:0x00000000f4e3b8> (NoMethodError)
I don't understand why Ruby does not pick this method.
You must use it like instance method not class method. And you can use self.class.new insted of MyHashSet.new
like this
class MyHashSet
attr_accessor :store
def initialize
#store = {}
end
def insert(el)
#store.merge!(el => true)
end
def include?(el)
#store[el]
end
def delete(el)
#store.select! {|key,value| key != el}
end
def to_a
#store.keys
end
def union(set)
result = self.class.new
result.store = self.store.merge(set.store)
result
end
end
output
set1=MyHashSet.new
# => #<MyHashSet:0x1efea79 #store={}>
set2=MyHashSet.new
# => #<MyHashSet:0x34c75a #store={}>
set1.insert("Mark Hamill")
# => {"Mark Hamill" => true}
set1.insert("Harrison Ford")
# => {"Mark Hamill" => true, "Harrison Ford" => true}
set1.insert("Anthony Daniels")
# => {"Mark Hamill" => true, "Harrison Ford" => true, "Anthony Daniels" => true}
set2.insert("Ewan McGregor")
# => {"Ewan McGregor" => true}
set2.insert("Natalie Portman")
#=> {"Ewan McGregor" => true, "Natalie Portman" => true}
set2.insert("Anthony Daniels")
# => {"Ewan McGregor" => true, "Natalie Portman" => true, "Anthony Daniels" => true}
set3 = set1.union(set2)
# => #<MyHashSet:0x1c7cbad #store={"Mark Hamill"=>true, "Harrison Ford"=>true, "Anthony Daniels"=>true, "Ewan McGregor"=>true, "Natalie Portman"=>true}>
btw:
Maybe you can modify your initialize method for better usage like this
def initialize(store = nil)
#store = store || {}
end
After this you can call union easly
def union(set)
self.class.new(self.store.merge(set.store))
end
so your final clas will looks like this
class MyHashSet
attr_accessor :store
def initialize(store = nil)
#store = store || {}
end
def insert(el)
#store.merge!(el => true)
end
def include?(el)
#store[el]
end
def delete(el)
#store.select! { |key, value| key != el }
end
def to_a
#store.keys
end
def union(set)
self.class.new(self.store.merge(set.store))
end
end
I found that the code works if I remove the "self." in front of "union":
class MyHashSet
attr_accessor :store
def initialize
#store = {}
end
def insert(el)
#store.merge!(el => true)
end
def include?(el)
#store[el]
end
def delete(el)
#store.select! {|key,value| key != el}
end
def to_a
#store.keys
end
def union(set)
result=MyHashSet.new
result.store=(self.store).merge(set.store)
result
end
end
I suppose this is because the method is to be called on an instance of the class rather than on the class itself.

How to Make a Ruby Class act Like a Hash with Setter

For academic reasons, I'd like to make an instance of Ruby class act like a hash.
GOALS
Initialize MyClass instance with a hash # success
Request values from instance of myClass, like a hash # success
Then set properties as a hash # fail
Although some discussion exists, I tried what's out there (1, 2) with no success. Let me know what I'm doing wrong. Thanks!
class MyClass
attr_accessor :my_hash
def initialize(hash={})
#my_hash = hash
end
def [](key)
my_hash[key]
end
def set_prop(key, value)
myhash[key] = value
end
end
test = myClass.new({:a => 3}) #=> #<MyClass:0x007f96ca943898 #my_hash={:a=>3}>
test[:a] #=> 3
test[:b] = 4 #=> NameError: undefined local variable or method `myhash' for #<MyClass:0x007f96ca9d0ef0 #my_hash={:a=>3}>
You declared set_prop, but you're using []= in tests. Did you mean to get this?
class MyClass
attr_accessor :my_hash
def initialize(hash={})
#my_hash = hash
end
def [](key)
my_hash[key]
end
def []=(key, value)
my_hash[key] = value
end
end
test = MyClass.new({:a => 3}) # success
test[:a] # success
test[:b] = 4 # success
test.my_hash # => {:a=>3, :b=>4}
module HashizeModel
def [](key)
sym_key = to_sym_key(key)
self.instance_variable_get(sym_key)
end
def []=(key, value)
sym_key = to_sym_key(key)
self.instance_variable_set(sym_key, value)
end
private
def to_sym_key(key)
if key.is_a? Symbol
return ('#'+key.to_s).to_sym
else
return ('#'+key.to_s.delete('#')).to_sym
end
end
end
You should write it as test = MyClass.new({:a => 3}) and the below code should work.
class MyClass
attr_accessor :my_hash
def initialize(hash={})
#my_hash = hash
end
def [](key)
#my_hash[key]
end
def []=(key,val)
#my_hash[key]=val
end
def set_prop(key, value)
#myhash[key] = value
end
end
test = MyClass.new({:a => 3})
test[:a]
test[:b]= 4
test.my_hash # => {:a=>3, :b=>4}

Ruby Factory Method rpsec temperature converter

not quite understanding factory method here...
here is the respec line:
Temperature.from_celsius(50).in_celsius.should == 50
Here is what I have now:
getting errors...not quite sure how to satisfy this. thanks
class Temperature
attr_accessor :f
attr_accessor :c
def initialize(args)
#f = args[:f]
#c = args[:c]
end
def in_fahrenheit
#f or
(#c*9.0/5.0)+32
end
def in_celsius
#c or
(#f-32)*(5.0/9.0)
end
def self.from_celsius(c)
new c
end
end
This should help
class Temperature
def initialize c
#c = c
end
def in_celsius
#c
end
def in_fahrenheit
#c *9.0 /5.0 +32
end
# factory pattern typically instantiates a new object
def self.from_celsius(c)
new c
end
end
puts Temperature.from_celsius(50).in_celsius #=> 50
puts Temperature.from_celsius(100).in_fahrenheit #=> 212
I would recommend against attr_accessor :c unless you want users to have public access to temp.c. Without it, users will be forced to use temp.in_celsius or temp.in_fahrenheit
You need to assign to :c in the initialize method. Then you need self.from_celsius to return a new instance of Temperature. You probably want something like this:
class Temperature
attr_accessor :c
def initialize c
#c = c
end
def in_celsius
#c
end
def in_fahrenheit
9/5 * #c + 32
end
def self.from_celsius(num)
Temperature.new(num)
end
def self.from_fahrenheit(num)
Temperature.new((num-32)*5/9)
end
end
Now rspec shows true
1.9.1p378 :047 > Temperature.from_celsius(50).in_celsius.should == 50
=> true
1.9.1p378 :131 > Temperature.from_fahrenheit(32).in_celsius.should == 0
=> true
The reason you're getting "error: Can't covert symbol to integer –" is because you're in your Temperature.from_celsius(50) you're passing it an integer when you're supposed to pass it a key & symbol for the options hash.
initialized
class Temperature
def initialize(opts = {})
#options = opts
end
class factory method
def self.from_celsius(x)
Temperature.new(:c => x)
end
instance method
def in_celsius
if #options[:c] == nil
return (#options[:f]-32) * (5/9.to_f)
else
return #options[:c]
end
end

Ruby object returned from method loses class type (reverts to base class array)

In the following code, the issue is that after calling method .find_name on an object type of LogsCollection, the returned object becomes a native array and does not remain type LogsCollection. I believe the correct approach might be to create a constructor/initializer that accepts an array and return a brand new object of the correct type. But I am not sure there is not a better way to accomplish this?
Can a Ruby-pro eyeball this code and suggest (at the code level) the best way to make the returned object from .find_name remain type LogsCollection (not array)?
class Log
attr_accessor :name, :expense_id, :is_excluded, :amount, :paid_to
def initialize(name, expense_id, is_excluded, amount, paid_to)
#name = name
#expense_id = expense_id
#is_excluded = is_excluded
#amount = amount
#paid_to = paid_to
end
end
class LogsCollection < Array
def names
collect do |i|
i.name
end
end
def find_name(name)
#name = name
self.select { |l| l.name == #name }
end
end
logs = LogsCollection.new
logs.push(Log.new('Smith', 1, false, 323.95, nil))
logs.push(Log.new('Jones', 1, false, 1000, nil))
logs = logs.find_name('Smith')
puts logs.count
unless logs.empty?
puts logs.first.name # works since this is a standard function in native array
puts logs.names # TODO: figure out why this fails (we lost custom class methods--LogsCollection def find_name returns _native_ array, not type LogsCollection)
end
Final code post-answer for anyone searching (note the removal of base class < array):
class Log
attr_accessor :name, :expense_id, :is_excluded, :amount, :paid_to
def initialize(name, expense_id, is_excluded, amount, paid_to)
#name = name
#expense_id = expense_id
#is_excluded = is_excluded
#amount = amount
#paid_to = paid_to
end
end
class LogsCollection
attr_reader :logs
def initialize(logs)
#logs = logs
end
def add(log)
#logs.push(log)
end
def names
#logs.collect { |l| l.name }
end
def find_name(name)
LogsCollection.new(#logs.select { |l| l.name == name })
end
end
logs = LogsCollection.new([])
logs.add(Log.new('Smith', 1, false, 323.95, nil))
logs.add(Log.new('Jones', 1, false, 1000, nil))
puts logs.names
puts '--- post .find_name ---'
puts logs.find_name('Smith').names
As you can see in the docs Enumerable#select with a block always returns an array. E.g.
{:a => 1, :b => 2, :c => 3}.select { |k,v | v > 1 }
=> [[:b, 2], [:c, 3]]
What you could do is have some sort of constructor for LogsCollection that wraps up a normal array as a LogsCollection object and call that in find_name.
As requested here's an example class (I'm at work and writing this while waiting for something to finish, it's completely untested):
class LogsCollection
attr_reader :logs
def initialize(logs)
#logs = logs
end
def names
#logs.collect { |i| i.name }
end
def find_name(n)
name = n
LogsCollection.new(#logs.select { |l| l.name == n })
end
# if we don't know a method, forward it to the #logs array
def method_missing(m, *args, &block)
#logs.send(m, args, block)
end
end
Use like
lc = LogsCollection.new
logs = lc.logs.find_name('Smith')

How to limit variable in ruby?

How can I limit a variable that belongs to new Class < Fixnum, between 0 and 255?
Or if I can't create a limit in subclass of Fixnim how to write my own class with limit?
Don't make the number a class, make access to that number limited as part of your class via a setter method.
Within your class never set the instance variable except via the setter method.
If you need to do this often, make a helper method for it:
class Module
def limited_value( name, range=0..100 )
attr_reader name
define_method(:"#{name}=") do |new_value|
if range.include?( new_value )
instance_variable_set :"##{name}", new_value
else
raise "Out of Bounds"
end
end
end
end
class Foo
limited_value :bar, 0..255
end
f = Foo.new
p f.bar #=> nil
f.bar = 10
p f.bar #=> 10
f.bar = 300
#=> tmp.rb:8:in `block in limited_value': Out of Bounds (RuntimeError)
You could alternatively choose to set the value to the nearest limit instead of raising a runtime error.
Write a non inherited class and use method_missing to call all functions from a instance variable, them, limit the return value.
class MyNum
instance_methods.each {|m| eval("undef " << m) }
def initialize(fixnum)
#num = fixnum
end
def method_missing(name, *args, &blk)
ret = #num.__send__(name, *args, &blk)
Numeric === ret ? MyNum.new([[ret, 0].max, 255].min) : ret
rescue NoMethodError
super
end
def inspect
"MyNum(#{#num.inspect})"
end
def class
MyNum
end
end
int = MyNum.new(50) # => MyNum(50)
int += 52 # => MyNum(102)
int.succ # => MyNum(103)
int + 300 # => MyNum(255)
int = -int # => MyNum(0)
int.zero? # => true
int == 0 # => true

Resources