I have these two classes in Ruby:
Prod.rb
class Prod
attr_reader :code, :price
def initialize code, price
#code = code
#price = price
end
end
Buy.rb
class Buy
def initialize()
#items = []
end
def addToBasket item
#items << item
end
def changePrice
#items.each do |item|
item.price = 0.00
end
end
end
When I am testing the app with the code below, I get this error pointing to the item.price = 0.00 above:
test_1(MyTest): NoMethodError: undefined method 'price=' for #<Prod:0x24d76e8>
I can print the value of item.price but I cannot update it. Any ideas?
MyTest.rb
def setup
#prod1 = Prod.new("1", 19.95)
end
def test_1
b = Buy.new()
b.addToBasket(#prod1)
[...]
end
This is because you don't have a price= method defined in class Prod. You only defined a getter with attr_reader :code, :price. If you to create both getter and setter, user attr_accessor in your Prod class:
class Prod
attr_accessor :code, :price
def initialize code, price
#code = code
#price = price
end
end
You can learn more about getters and setters in ruby in my article: Ruby for Admins: Objects.
Related
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
I'm trying to dynamically create a set of classes as follows.
class Foo
attr_reader :description
end
['Alpha', 'Beta', 'Gamma'].each do |i|
klass = Class.new(Foo) do |i|
def initialize
#description = i
end
end
Object.const_set(i, klass)
end
rather than creating each class manually, e. g.:
class Alpha < Foo
def initialize
#description = 'Alpha'
end
end
What is the right way to do such a thing and how do I pass an iterator to a nested block?
how do I pass an iterator to a nested block?
By using a nested block. A def is not a block. A def cuts off the visibility of variables outside the def. A block on the other hand can see the variables outside the block:
class Foo
attr_reader :description
end
['Alpha', 'Beta', 'Gamma'].each do |class_name|
klass = Class.new(Foo) do
define_method(:initialize) do
#description = class_name
end
end
Object.const_set(class_name, klass)
end
a = Alpha.new
p a.description
--output:--
"Alpha"
You can also do what you want without having to create a nested block or the class Foo:
['Alpha', 'Beta', 'Gamma'].each do |class_name|
klass = Class.new() do
def initialize
#description = self.class.name
end
attr_reader :description
end
Object.const_set(class_name, klass)
end
--output:--
"Alpha"
"Gamma"
You're close. I think you want to make description a class instance variable (or possibly a class variable), rather than an instance variable. The description will be "Alpha" for all objects of class Alpha so it should be an attribute of the class. You would access it as Alpha.description (or Alpha.new.class.description). Here's a solution using a class instance variable:
class Foo
class << self
attr_reader :description
end
end
['Alpha', 'Beta', 'Gamma'].each do |i|
klass = Class.new(Foo)
klass.instance_variable_set(:#description, i)
Object.const_set(i, klass)
end
class Foo
attr_reader :description
end
['Alpha', 'Beta', 'Gamma'].each do |class_name|
eval %Q{
class #{class_name} < Foo
def initialize
#description = #{class_name}
end
end
}
end
On Execution:
Gamma.new.description
=> Gamma
I have a ruby class, and in one of the methods, it calls an external function, and pass in all instance variables, and continue with the return value. Here is the code:
class MyClass
attr_accessor :name1
attr_accessor :name2
...
attr_accessor :namen
def inner_func():
all_vars = ???? # how to collect all my instance variables into a dict/Hash?
res = out_func(all_vars)
do_more_stuff(res)
end
end
The problem is the instance variables might vary in subclasses. I can't refer them as their names. So, is there a way to do this? Or Am I thinking in a wrong way?
You can use instance_variables to collect them in an Array. You will get all initialized instance variables.
class MyClass
attr_accessor :name1
attr_accessor :name2
...
attr_accessor :namen
def inner_func():
all_vars = instance_variables
res = out_func(all_vars)
do_more_stuff(res)
end
end
You could keep track of all accessors as you create them:
class Receiver
def work(arguments)
puts "Working with #{arguments.inspect}"
end
end
class MyClass
def self.attr_accessor(*arguments)
super
#__attribute_names__ ||= []
#__attribute_names__ += arguments
end
def self.attribute_names
#__attribute_names__
end
def self.inherited(base)
parent = self
base.class_eval do
#__attribute_names__ = parent.attribute_names
end
end
def attributes
self.class.attribute_names.each_with_object({}) do |attribute_name, result|
result[attribute_name] = public_send(attribute_name)
end
end
def work
Receiver.new.work(attributes)
end
attr_accessor :foo
attr_accessor :bar
end
class MySubclass < MyClass
attr_accessor :baz
end
Usage
my_class = MyClass.new
my_class.foo = 123
my_class.bar = 234
my_class.work
# Working with {:foo=>123, :bar=>234}
my_subclass = MySubclass.new
my_subclass.foo = 123
my_subclass.bar = 234
my_subclass.baz = 345
my_subclass.work
# Working with {:foo=>123, :bar=>234, :baz=>345}
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>
Greets to all!
I want to describe each kind of product by a class:
# Base product class
class BaseProduct
prop :name, :price # Common properties for all inheritable products
end
class Cellphone < BaseProduct
prop :imei # Concrete property for this product
end
class Auto < BaseProduct
prop :max_speed, :color # Concrete properties for this product
end
c = Cellphone.new
c.prop_names # => [:name, :price, :imei]
a = Auto.new
c.prop_names # => [:name, :price, :max_speed, :color]
So, how to implement this? I spent 3 days on it but got no working code(
EDIT: Okay, try this:
class BaseProduct
class << self
def prop(*names)
attr_accessor *names
local_prop_names.push(*names)
end
def local_prop_names
#local_prop_names ||= []
end
def prop_names
if self == BaseProduct
local_prop_names
else
superclass.prop_names + local_prop_names
end
end
end
def prop_names
class << self; prop_names; end
end
end
class BaseProduct
prop :name
end
class Aero < BaseProduct
prop :tricksy
end
class Heart < Aero
prop :tiger
end
Aero.new.prop_names #=> [:name, :tricksy]
Heart.new.prop_names #=> [:name, :tricksy, :tiger]