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
Related
I'm trying to figure out a way to dynamically generate subclasses based on a parent class. In my specific case I'd want to have attr_accessor for every instance variable, initialized in my Parent class and inherited on the SubClasses.
My classes are three different models representing three different tables in a DB.
"Record" is my parent class where I want to store and write all of my code.
"Post" and "User" are the Subclasses inheriting.
My code
class Record
attr_reader :id
# attr_accessor
def initialize(**params)
#id = params[:id]
instance_variable_set("##{params.keys[0]}", params.values[0])
instance_variable_set("##{params.keys[1]}", params.values[1])
instance_variable_set(:#votes, params["votes"] || 0) if instance_of?(Post)
# p self.title
end
Want I want to achieve is setting attr_accessor as for example in my Subclass "Post" I want to call
post = Post.new(title: "New post", url: "some url")
puts post.title
I can access the title instance variable without raising a NoMethodError
Could someone guide me, or give me some hint?
Thanks
You're going about it backwards. A parent class should not have to know about or implement specific logic for its subclasses.
class Record
attr_reader :id
def initialize(**attributes)
attributes.each do |key, value|
send("#{key}=", value)
end
end
end
class Post < Record
attr_accessor :title
attr_accessor :votes
end
irb(main):066:0> Post.new(id: 1, votes: 10, title: "Hello World").title
=> "Hello World"
attr_accessor is just a metaprogramming convenience for defining methods so your accessor methods are inherited anyways. But if you're writing something like an Object Relational Manager you'll want to define your own macro method for defining attributes that lets you keep track of the attributes of a class:
module Attributes
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
#attributes ||= {}
end
end
# assigns the passed attributes to the instance
def initialize(**attributes)
attributes.each do |key, value|
send "#{key}=", value
end
end
# gets all the attributes of an instance
def attributes
self.class.attributes.keys.each_with_object(Hash.new) do |key, hash|
hash[key] = send(key)
end
end
module ClassMethods
# Inherits the attributes of the parent class
def inherited(subclass)
attributes.tap do |parent_attributes|
subclass.class_eval do
#attributes ||= {}.merge(parent_attributes)
end
end
end
# defines an attribute that is inherited
def attribute(name, type = nil, **kwargs)
#attributes[name] = { type: type }.merge(kwargs)
attr_accessor name
end
def attributes
#attributes
end
end
end
class Record
include Attributes
attribute :id, Integer
end
class Post < Record
attribute :title, String
attribute :votes, Integer
end
irb(main):101:0> Post.new(votes: 10, title: "Hello World").attributes
=> {:id=>nil, :title=>"Hello World", :votes=>10}
This stores the attribute definitions in a class instance variable which lets you attach "metadata" which opens up for features that you will want later such as typecasting, serialization and dirty tracking.
Consider the following class:
class Person
attr_accessor :first_name
def initialize(&block)
instance_eval(&block) if block_given?
end
end
When I create an instance of Person as follows:
person = Person.new do
first_name = "Adam"
end
I expected the following:
puts person.first_name
to output "Adam". Instead, it outputs only a blank line: the first_name attribute has ended up with a value of nil.
When I create a person likes this, though:
person = Person.new do
#first_name = "Adam"
end
The first_name attribute is set to the expected value.
The problem is that I want to use the attr_accessor in the initialization block, and not the attributes directly. Can this be done?
Ruby setters cannot be called without an explicit receiver since local variables take a precedence over method calls.
You don’t need to experiment with such an overcomplicated example, the below won’t work as well:
class Person
attr_accessor :name
def set_name(new_name)
name = new_name
end
end
only this will:
class Person
attr_accessor :name
def set_name(new_name)
# name = new_name does not call `#name=`
self.name = new_name
end
end
For your example, you must explicitly call the method on a receiver:
person = Person.new do
self.first_name = "Adam"
end
If the code is run with warnings enabled (that is ruby -w yourprogram.rb)
it responds with : "warning: assigned but unused variable - first_name", with a line-number pointing to first_name = "Adam". So Ruby interprets first_name as a variable, not as a method. As others have said, use an explicit reciever: self.first_name.
Try this:
person = Person.new do |obj|
obj.first_name = "Adam"
end
puts person.first_name
I want to use the attr_accessor in the initialization block, and not the attributes directly
instance_eval undermines encapsulation. It gives the block access to instance variables and private methods.
Consider passing the person instance into the block instead:
class Person
attr_accessor :first_name
def initialize
yield(self) if block_given?
end
end
Usage:
adam = Person.new do |p|
p.first_name = 'Adam'
end
#=> #<Person:0x00007fb46d093bb0 #first_name="Adam">
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"])
I saw this in a screencast and was just wondering what the '=' symbol does in this case.
def express_token=(token)
...
end
I would understand if it were something like this -
def express_token(token = nil)
The above (second code snippet) means setting nil as the default value of the tokens parameter. However, in the first code snippet, '=' is outside the brackets.
That snippet defines a Virtual Attribute (or a "setter" method) so that "express_token" looks like an attribute, even though it's just the name of the method. For example:
class Foo
def foo=(x)
puts "OK: x=#{x}"
end
end
f = Foo.new
f.foo = 123 # => 123
# OK: x=123
Note that the object "f" has no attribute or instance variable named "foo" (nor does it need one), so the "foo=" method is just syntactic sugar for allowing a method call that looks like an assignment. Note also that such setter methods always return their argument, regardless of any return statement or final value.
If you're defining a top-level setter method, for example, in "irb", then the behavior may be a little confusing because of the implicit addition of methods to the Object class. For example:
def bar=(y)
puts "OK: y=#{y}"
end
bar = 123 # => 123, sets the variable "bar".
bar # => 123
Object.new.bar = 123 # => 123, calls our method
# OK: y=123
Object.public_methods.grep /bar/ # => ["bar="]
Those methods let you set instance vars in a more indirect way: imagine you have a class Person
class Person < ActiveRecord::Base
attr_accessible :first_name, :last_name
def full_name
[#first_name, #last_name].join
end
def full_name=(name)
#first_name, #last_name = name.split(" ")
end
end
Then you can do something like this:
p = Person.new
p.full_name = "John Doe"
p.first_name # => "John"
p.last_name # => "Doe"
p.full_name # => "John Doe"
Let's take a look at the following example:
class NewDog
def initialize(breed)
#breed = breed
end
# create reader only
attr_reader :breed, :name
# setter method
def set_name(nm)
#name = nm
end
end
nd = NewDog.new('Doberman')
nd.set_name('Benzy')
puts nd.name
If you refactor the setter method to this:
def name=(nm)
#name = nm
end
other programmers know that the name= method behaves as a setter method. Also as show by #maerics it behaves like a virtual attribute.
The result looks like this:
class NewDog
def initialize(breed)
#breed = breed
end
# create reader only
attr_reader :breed, :name
# setter method
def name=(nm)
#name = nm
end
end
nd = NewDog.new('Doberman')
nd.name = 'Benzy'
puts nd.name
Its actually part of the name of the function. So it's a setter, in case you need separate functionality than default for getters and setters.
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