I'm struggling with a simple question how to instantiate a class with arguments.
For example i have a class with an initializer that takes two arguments and a class method:
class MyClass
attr_accessor :string_1, :string_2
def initialize(string_1, string_2)
#string_1 = string_1
#string_2 = string_2
end
def self.some_method
# do something
end
end
If some_method were an instance method i could instantiate a new object of MyClass and call the instance method like:
MyClass.new("foo", "bar").some_method
But how can i achieve that for the MyClass itself and the class method instead of an instance?
Something like MyClass.self("foo", "bar").some_method does not work.
You could do this.
class MyClass
singleton_class.send(:attr_accessor, :string_3)
end
MyClass.string_3 = "It's a fine day."
MyClass.string_3 #=> "It's a fine day."
#string_3 is a class instance variable.
I believe the conventional way to initiate a class and then run a method on those values would be like:
class MyClass
def initialize(string_1, string_2)
#string_1 = string_1
#string_2 = string_2
end
def some_method
"#{#string_1} #{#string_2}"
end
end
a = MyClass.new("foo", "bar")
puts a.some_method #=> "foo bar"
If you want to use attr_accessor then you can bypass the some_method to return those values:
class MyClass
attr_accessor :string_1, :string_2
def initialize(string_1, string_2)
#string_1 = string_1
#string_2 = string_2
end
end
a = MyClass.new("foo", "bar")
puts a.string_1 + a.string_2 #=> "foobar"
Related
I'm currently doing some metaprogramming with ruby, and I'm trying to isolate the methods of class (that class is in another file, that I get by a require). I can get all the methods, thanks to klass.public_instance_methods(false), but I in the sametime, the array given also have all the attributes of the class. How could I isolate them ? In others related questions on SO, they suggest to use klass.instance_variables but when I do that, it only returns an empty array.
I can't seem to wrap my head around that one. I don't understand why there isn't a method specifically for that already...
For example:
I have in a file this class :
class T
attr_reader:a
def initialize(a)
#a = a
end
def meth
#code here
end
end
And, in another file, i have
require_relative 'T.rb'
class meta
def initialize
methods = T.public_instance_methods(false) #=> here methods = [:a,:meth] but I would want only to have [:meth]
#rest of code
end
end
For class defined like this:
class Klass
attr_accessor :variable
def initialize(variable)
#variable = variable
end
def method
end
end
you can find public non-attr instance methods using public_instance_methods and instance_variables methods.
public_instance_methods = Klass.public_instance_methods(false)
# [:method, :variable, :variable=]
instance_variables = Klass.new(nil).instance_variables
# [:#variable]
getters_and_setters = instance_variables
.map(&:to_s)
.map{|v| v[1..-1] }
.flat_map {|v| [v, v + '=']}
.map(&:to_sym)
# [:variable, :variable=]
without_attr = public_instance_methods - getters_and_setters
# [:method]
This is impossible. Ruby's "attributes" are completely normal methods. There is no way to distinguish them from other methods. For example, these two classes are completely indistinguishable:
class Foo
attr_reader :bar
end
class Foo
def bar
#bar
end
end
You can try to be clever and filter them out based on instance variables, but that is dangerous:
class Foo
# can filter this out using #bar
attr_writer :bar
def initialize
#bar = []
end
end
class Foo
def initialize
#bar = []
end
# this looks the same as above, but isn't a normal attribute!
def bar= x
#bar = x.to_a
end
end
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 have a class A, which I would like to anonymously extend and add a class method to the child class. E.g.:
class A
end
Class.new A do
def self.new_class_method
puts 'I am a class method'
end
end.new_class_method
=> I am a class method
The above example works well, unless you want to access some variables outside of the def self.new_class_method block. E,g,
greeting = 'hello'
Class.new A do
def self.new_class_method
puts greeting + ' I am a class method'
end
end.new_class_method
=> NameError: undefined local variable or method `greeting'
I am using Ruby 1.8.7, which is sad because I believe Ruby 1.9+ contains an analog to define_method which adds a class method. Does anyone have a work around for 1.8.7?
I have tested the below in Ruby 1.8.7 :-
greeting = 'hello'
class A
end
Class.new A do
meta_klass = class << self; self ;end
meta_klass.send(:define_method, :new_class_method) do
puts greeting + ' I am a class method'
end
end.new_class_method
# >> hello I am a class method
As Ruby 1.8.7 doesn't support Object#singleton_class, I used meta_klass = class << self; self ;end. This method is available since 1.9.2, I think.
You can also use extend() to pry open an object's singleton class. Calling extend(module) adds the methods in the module to the calling object's(i.e. the receiver's) singleton class. So if you call extend(module) when self=A, i.e. inside class A, then the module's methods will be inserted into A's singleton class, and the methods in A's singleton class are also known as class methods of A:
class A
end
greeting = "hello"
Class.new(A) do
extend(
Module.new do
define_method(:greet) do
puts greeting
end
end
)
end.greet
--output:--
hello
And you can rewrite that like this (although then it's not as tricky):
class A
end
greeting = "hello"
Class.new(A) do
m = Module.new do
define_method(:greet) do
puts greeting
end
end
extend(m)
end.greet
...which isn't much different than:
class A
end
greeting = "hello"
m = Module.new do
define_method(:greet) do
puts greeting
end
end
Class.new(A) do
extend(m)
end.greet
...which moves the closure out of the class, and doesn't seem very tricky at all because it only opens up two scope gates instead of three.
Also note, extend() is a public method, so it doesn't require the trickery of a private method, i.e. where you can't specify an explicit receiver, so you have to create a context in which self is the object you want to call the private method on. In other words, you can specify an explicit receiver for extend(). How about the class that is returned by Class.new(A)?
class A
end
greeting = "hello"
Class.new(A).extend(
Module.new do
define_method(:greet) do
puts greeting
end
end
).greet
--output:--
hello
Hey, tacking on ".greet" there works! Uh oh, that has the makings of a one liner:
class A
end
greeting = "hello"
Class.new(A).extend(Module.new {define_method(:greet) {puts greeting} }).greet
--output:--
hello
Yeech!
More generally,
class A
end
class Object
def meta_def name, &blk
(class << self; self; end).instance_eval { define_method name, &blk }
end
end
greeting = 'hello'
Class.new A do
meta_def :new_class_method do
puts greeting + ' I am a class method'
end
end.new_class_method
#=> hello I am a class method
If you find this useful, don't thank me, thank some lucky stiff (which I saw mentioned by Jay Fields).
Not sure if this will solve your problem, but changing Greeting to uppercase (making it a constant) would work...
class A
end
Greeting = 'hello'
Class.new A do
def self.new_class_method
puts Greeting + ' I am a class method'
end
end.new_class_method
I'm new to Ruby and confused about how I can make a method in Class similar to :attr_accessor, in that it adds methods to a user class, but so these added methods have access to a pre-initialized instance variable. It's difficult for me to explain so here is a greatly simplified sample of my efforts:
class Class
def super_accessor_wow(attr_name)
attr_name = attr_name.to_s
new_var_name = "#crazy_var_name"
instance_variable_set(new_var_name, ["hi", "everyone"])
module_eval(%Q/
def super_#{attr_name}()
return ##{attr_name}
end
def super_#{attr_name}=(value)
##{attr_name} = value
end
def greetings
return #{new_var_name}
end
/)
end
end
This is how I'm trying to use the new method on Class to modify my own class:
class Foo
super_accessor_wow(:bar)
end
foo1 = Foo.new()
foo1.super_bar = 1000
puts foo1.super_bar
puts foo1.greetings.inspect
The first puts prints '1000'
The second puts prints 'nil', so my instance_variable_set call in super_accessor_wow seemingly has no effect.
I expected that the second puts would print '['hi', 'everyone']' by the way. All of this code is contained in a single Ruby file.
Your instance_variable_set is called when you call super_accessor_wow during the class definition. No instance of the class exists yet. You create an instance of the class when you call new. You could add your #crazy_var_name initialization to the constructor, or you could define it in the greetings method:
Put the default in a class variable, and initialize the instance variable in the constructor (be aware that this creates a constructor for your class, and if you then create your own constructor, it will override this one):
class Class
def super_accessor_wow(attr_name)
attr_name = attr_name.to_s
new_var_name = "#crazy_var_name"
new_var_name_default = "##{new_var_name}"
module_eval(%Q/
#{new_var_name_default} = ["hi", "everyone"]
def initialize()
#{new_var_name} = #{new_var_name_default}
end
def super_#{attr_name}()
return ##{attr_name}
end
def super_#{attr_name}=(value)
##{attr_name} = value
end
def greetings
return #{new_var_name}
end
/)
end
end
class Foo
super_accessor_wow(:bar)
end
foo1 = Foo.new()
foo1.super_bar = 1000
puts foo1.super_bar
puts foo1.greetings.inspect
puts Foo.class_variable_get('##crazy_var_name').inspect
puts foo1.instance_variable_get('#crazy_var_name').inspect
Outputs:
1000
["hi", "everyone"]
["hi", "everyone"]
["hi", "everyone"]
Define it in the greetings method:
class Class
def super_accessor_wow(attr_name)
attr_name = attr_name.to_s
new_var_name = "#crazy_var_name"
module_eval(%Q/
def super_#{attr_name}()
return ##{attr_name}
end
def super_#{attr_name}=(value)
##{attr_name} = value
end
def greetings
#{new_var_name} = ["hi", "everyone"] unless #{new_var_name}
return #{new_var_name}
end
/)
end
end
class Foo
super_accessor_wow(:bar)
end
foo1 = Foo.new()
foo1.super_bar = 1000
puts foo1.super_bar
puts foo1.greetings.inspect
Outputs
1000
["hi", "everyone"]
As noted in the comment, instance_variable_set takes a symbol, not a string, so we'll fix that up first.
instance_variable_set(new_var_name.to_sym, ["hi", "everyone"])
But the big issue is that instance_variable_set isn't being called by an instance of Foo, it's being called by the Foo class itself. So, an instance variable is being set, but not on what you expected.
Foo.instance_variable_get(:#crazy_var_name).inspect
# ["hi", "everyone"]
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