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]
Related
How do I create an attribute on an object dynamically and assign an object to its value?
I have the following code:
class Component
def initialize
end
end
class BaseClass
def initialize
end
# define class methods
def self.create_component(**args)
# create attr_accessor with name "ball"
# set ball = Component.new
end
end
class ChildClass < BaseClass
create_component :name => "ball"
create_component :name => "baseball"
def initialize
end
end
My goal is that when the ChildClass calls "create_component" method, this should create an attribute with the name provided in :name parameter and instantiate a "Component" object to this attribute.
object = ChildClass.new
object.ball #=> to return object reference (Component class 1)
object.baseball #=> to return object reference (Component class 2)
You can define BaseClass like so:
class Component; end
class BaseClass
##components = []
def initialize
##components.each do |attr|
instance_variable_set("##{attr}", Component.new)
end
end
def self.create_component(name:)
attr_reader name.to_sym
##components << name
end
end
class ChildClass < BaseClass
create_component :name => "ball"
create_component :name => "baseball"
end
object = ChildClass.new
object.ball
# => #<Component:0x00007fa62fbdf3f8>
object.baseball
# => #<Component:0x00007fa62fbdf3a8>
Basically you do three things:
Create a class-level instance variable ##components which stores a list of the custom attr_reader names
Inside create_component, call attr_reader to create the method and also add it to the ##components list
Inside initialize, set initial values for each of the ##components (you can also give custom values for these using initialize arguments if you like).
You could do that as follows.
class Component
end
class BaseClass
def create_component(name)
self.class.send(:attr_accessor, name)
instance_variable_set("##{name}", Component.new)
end
end
class ChildClass < BaseClass
end
c = ChildClass.new
c.create_component "ball"
#=> #<Component:0x000056f36c73c2e0>
c.create_component"baseball"
#=> #<Component:0x000056f36c7533f0>
c.methods & [:ball, :baseball]
#=> [:ball, :baseball]
c.instance_variables
#=> [:#ball, :#baseball]
c.ball
#=> #<Component:0x000056f36c73c2e0>
c.baseball
#=> #<Component:0x00005602be1eb560>
c.ball = 1
c.ball
#=> 1
Note that
self.class.send(:attr_accessor, name)
could be replaced with
self.class.class_eval { attr_accessor name.to_sym }
That the instance variables are being added to an instance of a child class of BaseClass is of no matter. It would be essentially the same question if they were to be added to an instance of BaseClass.
This creates an instance method, such as:
def baseball
#baseball ||= Component.new
end
For the baseball= it just uses regular attr_writer:
class Component
end
class BaseClass
def self.create_component(name:)
class_eval <<~EOB
attr_writer :#{name}
def #{name}
##{name} ||= Component.new
end
EOB
end
end
class ChildClass < BaseClass
create_component name: "ball"
create_component name: "baseball"
end
This seems to work:
> c = ChildClass.new
> c.ball
=> #<Component:0x00007f8b270cfa58>
> c.baseball
=> #<Component:0x00007f8b270dbdd0>
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 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.
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>
What's the shortest, one-liner way to list all methods defined with attr_accessor? I would like to make it so, if I have a class MyBaseClass, anything that extends that, I can get the attr_accessor's defined in the subclasses. Something like this:
class MyBaseClass < Hash
def attributes
# ??
end
end
class SubClass < MyBaseClass
attr_accessor :id, :title, :body
end
puts SubClass.new.attributes.inspect #=> [id, title, body]
What about to display just the attr_reader and attr_writer definitions?
Extract the attributes in to an array, assign them to a constant, then splat them in to attr_accessor.
class SubClass < MyBaseClass
ATTRS = [:id, :title, :body]
attr_accessor(*ATTRS)
end
Now you can access them via the constant:
puts SubClass.ATTRS #=> [:id, :title, :body]
There is no way (one-liner or otherwise) to list all methods defined by attr_accessor and only methods defined by attr_accessor without defining your own attr_accessor.
Here's a solution that overrides attr_accessor in MyBaseClass to remember which methods have been created using attr_accessor:
class MyBaseClass
def self.attr_accessor(*vars)
#attributes ||= []
#attributes.concat vars
super(*vars)
end
def self.attributes
#attributes
end
def attributes
self.class.attributes
end
end
class SubClass < MyBaseClass
attr_accessor :id, :title, :body
end
SubClass.new.attributes.inspect #=> [:id, :title, :body]
Heres an alternative using a mixin rather than inheritance:
module TrackAttributes
def attr_readers
self.class.instance_variable_get('#attr_readers')
end
def attr_writers
self.class.instance_variable_get('#attr_writers')
end
def attr_accessors
self.class.instance_variable_get('#attr_accessors')
end
def self.included(klass)
klass.send :define_singleton_method, :attr_reader, ->(*params) do
#attr_readers ||= []
#attr_readers.concat params
super(*params)
end
klass.send :define_singleton_method, :attr_writer, ->(*params) do
#attr_writers ||= []
#attr_writers.concat params
super(*params)
end
klass.send :define_singleton_method, :attr_accessor, ->(*params) do
#attr_accessors ||= []
#attr_accessors.concat params
super(*params)
end
end
end
class MyClass
include TrackAttributes
attr_accessor :id, :title, :body
end
MyClass.new.attr_accessors #=> [:id, :title, :body]
Following up on Christian's response, but modifying to use ActiveSupport::Concern...
module TrackAttributes
extend ActiveSupport::Concern
included do
define_singleton_method(:attr_reader) do |*params|
#attr_readers ||= []
#attr_readers.concat params
super(*params)
end
define_singleton_method(:attr_writer) do |*params|
#attr_writers ||= []
#attr_writers.concat params
super(*params)
end
define_singleton_method(:attr_accessor) do |*params|
#attr_accessors ||= []
#attr_accessors.concat params
super(*params)
end
end
def attr_readers
self.class.instance_variable_get('#attr_readers')
end
def attr_writers
self.class.instance_variable_get('#attr_writers')
end
def attr_accessors
self.class.instance_variable_get('#attr_accessors')
end
end
Class definitions
class MyBaseClass
attr_writer :an_attr_writer
attr_reader :an_attr_reader
def instance_m
end
def self.class_m
end
end
class SubClass < MyBaseClass
attr_accessor :id
def sub_instance_m
end
def self.class_sub_m
end
end
Call as class methods
p SubClass.instance_methods - Object.methods
p MyBaseClass.instance_methods - Object.methods
Call as instance methods
a = SubClass.new
b = MyBaseClass.new
p a.methods - Object.methods
p b.methods - Object.methods
Both will output the same
#=> [:id, :sub_instance_m, :id=, :an_attr_reader, :instance_m, :an_attr_writer=]
#=> [:an_attr_reader, :instance_m, :an_attr_writer=]
How to tell which are writer reader and accessor?
attr_accessor is both attr_writer and attr_reader
attr_reader outputs no = after the method name
attr_writer outputs an = after the method name
You can always use a regular expression to filter that output.
It could be filtered with the equal sign matching logic:
methods = (SubClass.instance_methods - SubClass.superclass.instance_methods).map(&:to_s)
methods.select { |method| !method.end_with?('=') && methods.include?(method + '=') }
#> ["id", "title", "body"]