How to iterate over Instance attributes name and value in Ruby? - ruby

I have this class
class Person
attr_accessor :id, :name
def initialize(id, name)
#id = id
#name = name
end
def print_attr
self.attributes do |attr_name, attr_value|
print "attribute name: #{attr_name}, attribute value: #{attr_value}"
end
end
end
print_attr is currently wrong. How can I implement it?

attributes is rails specific.
You need to iterate through instance_variables and then use instance_variable_get
def print_attr
instance_variables.each do |attr_name|
attr_value = instance_variable_get(attr_name)
puts "attribute name: #{attr_name}, attribute value: #{attr_value}"
end
end

Consider using as Struct to define your class if your class has many of these simple attributes. It will take care of the boring accesors and initialize method, leaving the clever stuff to you:
Person = Struct.new(:id, :name) do
def print_attr
self.each_pair do |attr_name, attr_value|
puts "attribute name: #{attr_name}, attribute value: #{attr_value}"
end
end
end
a = Person.new(123, "Joe")
a.print_attr # => attribute name: id, attribute value: 123
# => attribute name: name, attribute value: Joe
p a.class # => Person

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

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"])

How to define a Ruby Struct which accepts its initialization arguments as a hash?

I have a situation where I would like to create a class which accepts many arguments and has setters and getters in the fewest lines of code possible (for maintainability). I thought that using a Struct would be a good idea for this:
Customer = Struct.new(:id, :username, :first_name, :last_name, :address1, ...etc...)
Customer.new(123, 'joe', 'Joe', ...etc...)
However, I don't like having to know the exact order of the attributes. I prefer Ruby 2's keyword arguments feature:
class Customer
attr_accessor :id, :username, :first_name, ...etc...
def initialize(id:, username:, first_name:, last_name:, address1:, ...etc...)
#id = id
#username = username
#first_name = first_name
...etc...
end
end
Customer.new(id: 123, username: 'joe', first_name: 'Joe', ...etc...)
However, writing this all out requires a lot more code and is more tedious. Is there a way to achieve the same thing in a short-hand like the Struct?
In ruby 2.5 you can do the following:
Customer = Struct.new(
:id,
:username,
:first_name,
keyword_init: true
)
Customer.new(username: "al1ce", first_name: "alice", id: 123)
=> #<struct Customer id=123, username="al1ce", first_name="alice">
references:
https://ruby-doc.org/core-2.5.0/Struct.html
Cant you just do:
def initialize(hash)
hash.each do |key, value|
send("#{key}=", value)
end
end
UPDATE:
To specify default values you can do:
def initialize(hash)
default_values = {
first_name: ''
}
default_values.merge(hash).each do |key, value|
send("#{key}=", value)
end
end
If you want to specify that given attribute is required, but has no default value you can do:
def initialize(hash)
requried_keys = [:id, :username]
default_values = {
first_name: ''
}
raise 'Required param missing' unless (required_keys - hash.keys).empty?
default_values.merge(hash).each do |key, value|
send("#{key}=", value)
end
end
If you don't care about performance, you can use an OpenStruct.
require 'ostruct'
user = OpenStruct.new(id: 1, username: 'joe', first_name: 'Joe', ...)
user.first_name
=> "Joe"
See this for more details.
It's entirely possible to make it a class and define methods on it:
class Customer < Openstruct
def hello
"world"
end
end
joe = Customer.new(id: 1, username: 'joe', first_name: 'Joe', ...)
joe.hello
=> "world"
But again, because OpenStructs are implemented using method_missing and define_method, they are pretty slow. I would go with BroiSatse's answer. If you care about required parameters, you should so something along the lines of
def initialize(params = {})
if missing_required_param?(params)
raise ArgumentError.new("Missing required parameter")
end
params.each do |k,v|
send("#{k}=", v)
end
end
def missing_required_params?(params)
...
end
In addition, you can rely on the constructor of Struct itself.
def initialize(*args)
super(*args)
# put your magic here!
end
This way you avoid the side-affects of named parameters etc.
You can use Struct and reduce the amount of code to a minimum by adding a factory method (called build here) and if necessary a validate method to your struct
Struct.new("Example",:a,:b) do
def build(a:, b:nil)
s = Struct::Example.new(a,b)
s.validate
return s
end
def validate
unless a == 'stuff' || a == 'nonsense'
raise ValidationError, "broken"
end
end
end
m = Struct.Example.build(a: 'stuff')
where validate is intended to so something like check strings have certain values, rather than just relying on the required parameters check.
Now you only have to remember the order once, when you write the build method
Variation on the theme, but a bit more refined, works in any ruby
class Struct
module HashConstructable
def from_hash hash
rv = new
hash.each do |k, v|
rv.send("#{k}=", v)
end
rv
end
# alias_method :[], :from_hash
end
end
and then
class MyStruct < Struct.new(:foo, :boo)
extend Struct::HashConstructable
end
and you have best of both worlds this way - no funny name clashes and side-effects and it's clear want you want to do when you do it:
MyStruct.from_hash(foo: 'foo')
does exactly what you think it does. With a bit more possible side-effects but nicer syntax you can add the alias_method :[], :from_hash part, this allows you:
MyStruct[foo: 'foo']
this is also nice because it reminds (me) of the Hash[] constructor which creates a hash out of something that isn't hash.
This is one approach you could use.
class A
def initialize(h)
h.each do |k, v|
instance_variable_set("##{k}", v)
create_method("#{k}=") { |v|instance_variable_set("##{k}", v ) }
create_method("#{k}") { instance_variable_get("##{k}") }
end
end
end
def create_method(name, &block)
self.class.send(:define_method, name, &block)
end
a = A.new(apple: 1, orange: 2)
a.apple #=> 1
a.apple = 3
a.apple #=> 3
a.orange #=> 2
create_method is straight from the documentation for Module#define_method.

Creating a class dynamically

I'm trying to create a new class, without knowing the name of the class until it's supposed to be created.
Something like this;
variable = "ValidClassName"
class variable
end
Test = ValidClassName.new
If possible, i'd also appreciate som hints on how to dynamically add attributes (and methods) to this new class.
I'll be retreiving 'settings' for the class, and they will look something like this:
title :Person
attribute :name, String
attribute :age, Fixnum
But should not be designed to accept only that explicit file, the attributes might differ in number end type.
Which in the end will generate a class that should look something like:
class Person
def initialize(name, age)
#name_out = name
#age_out = age
end
end
Help?
A class gains its name when it is assigned to a constant. So It's easy to do in a generic fashion with const_set.
For example, let's say you want to use Struct to build a class with some attributes, you can:
name = "Person"
attributes = [:name, :age]
klass = Object.const_set name, Struct.new(*attributes)
# Now use klass or Person or const_get(name) to refer to your class:
Person.new("John Doe", 42) # => #<struct Person name="John Doe", age=42>
To inherit from another class, replace the Struct.new by Class.new(MyBaseClass), say:
class MyBaseClass; end
klass = Class.new(MyBaseClass) do
ATTRIBUTES = attributes
attr_accessor *ATTRIBUTES
def initialize(*args)
raise ArgumentError, "Too many arguments" if args.size > ATTRIBUTES.size
ATTRIBUTES.zip(args) do |attr, val|
send "#{attr}=", val
end
end
end
Object.const_set name, klass
Person.new("John Doe", 42) # => #<Person:0x007f934a975830 #name="John Doe", #age=42>
Your code would look something akin to this:
variable = "SomeClassName"
klass = Class.new(ParentClass)
# ...maybe evaluate some code in the context of the new, anonymous class
klass.class_eval { }
# ...or define some methods
klass.send(:title, :Person)
klass.send(:attribute, :name, String)
# Finally, name that class!
ParentClass.send(:const_set, variable, klass)
...or you could just use eval:
eval <<DYNAMIC
class #{name}
title :Person
attribute :name, String
# ...or substitute other stuff in here.
end
DYNAMIC

getting variable value dynamically

I have the following class
class User
attr_accessor :name, :age, :address
def initialize()
self.name = "new user"
self.age = 19
self.address = "address"
end
end
What I want if to have a method to get the assigned values to the attributes. Means, I have another method to get all the method names inside the above class
methods = self.public_methods(all = false)
and I can get the method name from that (I mean the getter of name etc..) and I want a way to pass that method name (which I have it as string) and get the return value of the method
Ex:
when I pass 'name' as the method name I should be able to get 'new user' as the value
hope I made my question clear, thanks in advance
** Please note, i have to use this way as my class has so many attributes and it has so many inherited classes. So accessing each and every attr individually is not possible :D
That’s what send is for:
user = User.new
user.name # => "new user"
user.send(:name) # => "new user"
getters = user.public_methods(false).reject { |m| m =~ /=$/ }
getters.each { |m| puts user.send(m) }
Using instance_variable_get is another option if you don’t have an accessor method:
user.instance_variable_get(:#name) # => "new user"
Using public_methods to get a list of attributes could be dangerous, depending on how you determine which methods to call. Instead, you could create your own class method which both defines the accessor and stores the attribute name for future use:
class User
class << self
attr_reader :fields
def field (*names)
names.flatten.each do |name|
attr_accessor name
(#fields ||= []) << name
end
end
end
field :name
field :age
field :address
end
user = User.new
user.name = "Me"
user.age = 22
user.address = "1234"
user.class.fields.each do |field|
puts user.send(field)
end

Resources