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
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 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>
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"]
I'm trying to make a method similar to attr_reader but I can't seem to get the instance of the class that the method gets called in.
class Module
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
variables = symbols.collect { |sym| ("#" << sym.to_s).to_sym }
attr_reader *symbols
(class << ModifyMethods; self; end).instance_eval do
define_method(*symbols) do
mod.instance_variable_get(*variables)
end
end
end
end
class Object
module ModifyMethods; end
def modify(&block)
ModifyMethods.instance_eval(&block)
end
end
class Klass
modifiable_reader :readable
def initialize
#readable = "this"
end
end
my_klass = Klass.new
my_klass.modify do
puts "Readable: " << readable.to_s
end
I'm not sure what it is you're trying to do.
If it helps, the spell for attr_reader is something like this:
#!/usr/bin/ruby1.8
module Kernel
def my_attr_reader(symbol)
eval <<-EOS
def #{symbol}
##{symbol}
end
EOS
end
end
class Foo
my_attr_reader :foo
def initialize
#foo = 'foo'
end
end
p Foo.new.foo # => "foo"
What I can understand from your code is that you want to have the modify block to respond to the instance methods of Klass, that's as simple as:
class Klass
attr_reader :modifiable
alias_method :modify, :instance_eval
def initialize(m)
#modifiable = m
end
end
Klass.new('john').modify do
puts 'Readable %s' % modifiable
end
About this tidbit of code:
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
...
Probably this can give you a hint of what is going on:
Class.superclass # => Module
Klass.instance_of?(Class) # => true
Klass = Class.new do
def hello
'hello'
end
end
Klass.new.hello # => 'hello'
When you are adding methods to the Module class, you are also adding methods to the Class class, which will add an instance method to instances of Class (in this case your class Klass), at the end this means you are adding class methods on your Klass class