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
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.
I ran into some confusion over when/why or if it is just a matter of preference when initializing a method with a hash type structure.
class Person1
attr_reader :name, :age
def initialize(params)
#name = params[:name]
#age = params[:age]
end
end
me = Person1.new(name: 'John Doe', age: 27)
puts me.name
puts me.age
#----------------------------------------------
class Person2
attr_reader :name, :age
def initialize(name, age)
#name = name
#age = age
end
end
me = Person2.new('John Doe', 27)
puts me.name
puts me.age
#----------------------------------------------
class Person3
attr_reader :person
def initialize(name, age)
#person = { name: name,
age: age }
end
end
me = Person3.new('John Doe', 27)
puts me.person[:name]
puts me.person[:age]
If it is a matter of preference I like just passing the hash but I could see this being an issue if you need different attr reader, writer within the hash itself. Is there a rule of thumb? I see a lot of rails articles using params.
Your third way will most likely never appear in the wild - you are already constructing a person object, why does it have an accessor for person? The attributes should be on the class itself. The difference between 1 and 2 is mostly preference, but the second one can be advantageous when you regulary want to set only specific attributes.
Concering rails' usage of params: params is the hash that contains the request parameters of the particular request you are handling. As you deal with requests a lot in rails, it will appear in every controller and can also appear in some views.
Can you tell me why this isn't working?
The code below outputs student1 & student2 properly, but I cannot get the class method to work on student3.
All I am trying to do is assign a class method .create_with_species , with the species attribute = to "Human".
However, when I run the program, I get an error that "local variable or method sex is undefined". I'm a newbie and I can't figure out what I've done wrong!
It was my understanding that since I had identified "sex" in the Initialize method I should be able to use it within a class method such as create_with_species. I tried explicitly defining sex in the class method, as student.sex = sex, but still got the same error.
class Students
attr_accessor :sex, :age, :species
def self.create_with_species(species)
student = Students.new(sex,age)
student.species = species
return student
end
def initialize(sex, age)
#sex = sex
#age = age
puts "----A new student has been added----"
end
end
student1 = Students.new("Male", "21")
puts student1.sex
puts student1.age
puts
student2 = Students.new("Female", "19")
puts student2.sex
puts student2.age
student3 = Students.create_with_species("human")
sex and age are not defined. Do you mean to use a class variable? Using #age or #sex will result in nils, as no class attributes are set and the instance is not yet created, hence no instance variables set either. You don't need to state Students.new in create_with_species. new will suffice because you are scoped in Students. I think you would be better off passing a hash object to initialize, and just set the species variable there. It will be nil if not assigned explicitly, or you can have a default value.
Try this:
class Students
attr_accessor :sex, :age, :species
def initialize(options)
#sex = options[:sex]
#age = options[:age]
#species = options[:species]
puts "----A new student has been added----"
end
end
student1 = Students.new(:sex => "Male", :age => "21")
puts student1.sex
puts student1.age
puts student1.species
puts
student2 = Students.new(:sex => "Female", :age => "19")
puts student2.sex
puts student2.age
student3 = Students.new(
:sex => "Hermi", :age => "2", :species => "alien"
)
#sex and #age are instance attributes. You can't use them (what would they mean?) inside a class method. You've written just sex and age inside create_with_species, but again: what are these supposed to refer to? (In an instance method they'd be calls to the accessors, but at this point there's no instance to call the accessors on.)
Consider: when you say
student3 = Students.create_with_species("human")
what sex and age should this new student object get? You haven't specified any.
As far as the syntax goes, you have to use #sex and #age instead of sex and age. Otherwise they won't be interpreted as instance attributes.
But you do realize that the logic of the code is broken? I understand you want to use the static factory method pattern but this is not how you should do it since #sexand #age are nil when you call that method and those variables are instance attributes not class attributes.
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
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.