Converting a class into a hash in Ruby - ruby

I am trying to convert any class into a hash using ruby. The initial implementation I have done:
class Object
def to_hash
instance_variables.map{ |v|
Hash[v.to_s.delete("#").to_sym, instance_variable_get(v)] }.inject(:merge)
end
end
Everything seemed to work ok. But when I tried the following code:
class Person
attr_accessor :name, :pet
def initialize(name, pet)
#name = name
#pet = pet
end
end
class Pet
attr_accessor :name, :age
def initialize(name, age)
#name = name
#age = age
end
end
tom = Person.new("Tom", Pet.new("Tobby", 5))
puts tom.to_hash
I have got the following output
{:name=>"Tom", :pet=>#<Pet:0x0055ff94072378 #name="Tobby", #age=5>}
I am unable to hash the attribute pet of type Pet (or any other custom class)
Any ideas?
Edit
That's what I would expect to be returned:
{:name=>"Tom", :pet=>{ :name=>"Tobby", :age=>5}}

When you want to have associated objects to be returned as a hash too hen you have to call to_hash recursively:
class Object
def to_hash
return self if instance_variables.empty?
instance_variables
.map { |v| [v.to_s.delete("#").to_sym, instance_variable_get(v).to_hash] }
.to_h
end
end
tom = Person.new("Tom", Pet.new("Tobby", 5))
puts tom.to_hash
#=> { :name=>"Tom", :pet => { :name=>"Tobby", :age=>5 } }

Related

I have an array of objects with parameters(in hash). How can I list all the parameters of every object?

Item class
class Item
def initialize(options = {})
#name = options[:name]
#code = options[:code]
#category = options[:category]
#size = options[:size]
end
attr_accessor :name, :code, :category, :size
end
Music class
class Music < Item
def initialize(options = {})
super
#singer = options[:singer]
#duration = options[:duration]
end
attr_accessor :singer, :duration
end
Movie class
def initialize(options = {})
super
#director = options[:director]
#main_actor = options[:main_actor]
#main_actress = options[:main_actress]
end
attr_accessor :director, :main_actor, :main_actress
end
class Catalog
attr_reader :items_list
def initialize
#items_list = Array.new
end
def add(item)
#items_list.push item
end
def remove(code)
#items_list.delete_if { |i| i.code == code }
end
def show(code)
# comming soon
end
def list
#items_list.each do |array|
array.each { |key, value| puts "#{key} => #{value}" }
end
end
end
catalog1 = Catalog.new
music1 = Music.new(name: "Venom", code: 1, category: :music, size: 1234, singer: "Some singer", duration: 195)
music2 = Music.new(name: "Champion of Death", code: 2, category: :music, size: 1234, singer: "Some singer", duration: 195)
catalog1.add(music1)
catalog1.add(music2)
ruby version 2.6.0
list method is not working. I got undefined method `each' for <#Music:0x0000562e8ebe9d18>.
How can I list all keys and values in another way? Like:
name - "Venom"
code - 1
category - music.
I was thinking about it, but also I got a Movie class and that method gonna be too long
You push instances of Music into #items_list. That means #items_list.each do not return an array, but instances of Music and that Musik instances do not respond do each nor they have keys and values.
I suggest adding an instance method to your Music class that returns the expected output. For example a to_s method like this:
def to_s
"name \"#{name}\" code - #{code} category - #{category}"
end
and to change the list method in your Catalog to something like this:
def list
#items_list.each do |music|
puts music.to_s
end
end
Or when you want to return the values an array of hashed then add a to_h method to Music like this:
def to_h
{ name: name, code: code, category: category }
end
and call it like this:
def list
#items_list.map do |music|
music.to_h
end
end

Ruby wrong number of arguments

I'm trying to to create my getters and setters using attr_accessor. I want to assign a value to my variables.
Here is my code:
class Person
def initialize(name)
attr_accessor :name
end
def initialize(age)
attr_accessor :age
end
end
person1 = Person.new
person1.name = "Andre"
person1.age = 22
I get some trouble though. My error is:
q5.rb:6:in `initialize': wrong number of arguments (given 0, expected 1) (ArgumentError)
This is what you're trying to do:
class Person
attr_accessor :name, :age
end
person1 = Person.new
person1.name = "Andre"
person1.age = 22
An alternative approach, for example, could be:
class Person
attr_accessor :name, :age
def initialize(name, age)
#name = name
#age = age
end
end
person1 = Person.new("Andre", 22)
The error you're seeing is because you defined (and then re-defined) an initialize method that is expecting one parameter:
def initialize(name)
and then tried to create an object without supplying a parameter:
person1 = Person.new

Creating an object from a class which is inherited ruby

Im struggling on understanding (after googling) on how to implement this: I have a class:
class Student
# constructor method
def initialize(name,age)
#name, #age = name, age
end
# accessor methods
def getName
#name
end
def getAge
#age
end
# setter methods
def setName=(value)
#name = value
end
def setAge=(value)
#age = value
end
end
And lets say I have another class which inherits from Student
class Grade < Student
#constructor method
def initialize(grade)
super
#grade = grade
end
# accessor methods
def getGrade
#grade
end
# setter methods
def setGrade=(value)
#grade = value
end
end
I understand how to build an abject:
student = Student.new(name, age)
How can I build this Student (that I have just created) a Grade object associated with the student and how would I call the inherited object, for example i wanted to:
puts 'student name and associated grade'
I know I can place the grade variable within the Student class, but for the purpose of learning im doing it this way.
This code would do what you wanted:
class Grade
attr_accessor :value
def initialize value
#value = value
end
end
class Student
attr_accessor :name, :age, :grade
def initialize name, age, grade
#name, #age, #grade = name, age, Grade.new(grade)
end
end
st = Student.new 'John', 18, 5
puts "student #{st.name} and associated grade #{st.grade.value}"
First off, no need to define accessors in Ruby like that, it's far from idiomatic. Let's clean that up first:
class Student
attr_accessor :name, :age
def initialize(name, age)
#name =name
#age = age
end
end
class Grade
attr_accessor :value
def initialize(grade)
#value = grade
end
end
Secondly it doesn't seem like Grade should inherit from Student at all, just adjust the latter to also store a Grade instance variable:
class Student
attr_accessor :name, :age, :grade
def initialize(name, age, grade = nil)
#name =name
#age = age
#grade = grade
end
end
You can then instantiate a student like this:
student = Student.new("Test", 18, Grade.new(1))
Or because of the default value you leave off the grade and assign it later:
student = Student.new("Test", 18)
# later
student.grade = Grade.new(1)

create instance variable dynamically in ruby unknown number variables needed

This is the problem "I am trying to create a generic object, could be thought of as a "dynamic schema
object" each schema object will have a different number of instances variables." and this approach doesn't work.
class GenericObjectArray
def initialize
#data_fields = []
end
def data_fields(t)
#data_fields << t
end
def initialize(attrs = {})
attrs.each { |attr,val| instance_variable_set "##{attr}", val }
end
end
p GenericObjectArray.new(:data_fields=> "may_sales", :data_fields=>"june_sales", :data_fields=>"july_sales")
This is my approach, bu it doesnt work. I would like to set may_sales, june_sales, july_sales as an instance variables. Set all three as instance variables. It only returns that last one.
GenericObjectArray:0x007f8c5b883cd8 #data_fields="july_sales"
Think it from this approach:
You have objects (lets say GenericObject)
Objects have many attributes (GenericObject#attributes => [GenericObject::Attribute])
Attributes have a name, a value, and a type (GenericObject::Attribute#value, #name and #type)
Which translates into code like this:
class GenericObject
attr_accessor :attributes
def add_attribute(name, value, type)
(#attributes ||= []) << Attribute.new(name, value, type)
end
class Attribute
attr_accessor :name, :value, :type
def initialize(name, value, type)
#name, #value, #type = name, value, type
end
end
end
# so...
cat = GenericObject.new
cat.add_attribute :leg_number, 4, :integer
cat.add_attribute :fur_color, 'Orange', :color
cat.add_attribute :name, 'Garfield', :string
cat.attributes.each { |attr| puts "My cat's #{attr.name} is #{attr.value} (#{attr.type})" }
# My cat's leg_number is 4 (integer)
# My cat's fur_color is Orange (color)
# My cat's name is Garfield (string)
You can make a fancy initializer for GenericObject or whatever you see fit.
Or you can just to a little fix
class GenericObjectArray
def initialize(attrs = {})
attrs.each { |attr,val| instance_variable_set "##{attr}", val }
end
end
GenericObjectArray.new(:data_fields=> ["may_sales", "june_sales", "july_sales"])

Ruby metaprogramming: accessing class name

I'm fairly new to Ruby metaprogramming. I'm trying to write code which generates the
"dup" function for a class when it's created, using a list of fields which should be passed into the constructor. However, I can't figure out how to get access to the name of the class I'm creating, while I'm creating it.
So for example, if I had this code:
class Example
make_dup :name, :value
attr_accessor :name, :value
def initialize(name,value)
#name, #value = name, value
end
end
I'd want it to create the method:
def dup
Example.new(name,value)
end
I'm just getting stuck on how it would figure out to insert Example there.
Note that all classes have built-in dup and clone methods. You can customize what happens in them by adding an initialize_copy method, e.g.:
class Foo
attr_accessor :bar
def initialize_copy(orig)
super
#bar = #bar.dup
end
end
In case that isn't what you're truly looking for, you can access an object's class using its class method:
class Foo
def p_class
p self.class # Foo.new.p_class => Foo ; self is *a* `Foo'
end
def self.p_class
p self.class # Foo.p_class => Class ; self *is* `Foo'
end
end
def dup
self.class.new(name,value)
end
Maybe you can implement it this way:
module MyDup
def make_dup(*args)
define_method(:my_dup) do
obj = self.class.new(nil, nil)
args.each do |arg|
obj.send(arg.to_s + "=", self.send(arg))
end
obj
end
end
end
class Example
extend MyDup
make_dup :name, :value
attr_accessor :name, :value
def initialize(name,value)
#name, #value = name, value
end
end
e = Example.new("John", 30)
p e
d = e.my_dup
p d
Execution result as follows:
#<Example:0x000000022325d8 #name="John", #value=30>
#<Example:0x00000002232358 #name="John", #value=30>

Resources