Get attr_reader, writer, or accessor oustide of the class - ruby

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

Related

Creating an array of superclasses for a particular class

I'm trying to create an array of all the superclasses for the given class. I tried to solve this problem by using a loop. Why is this not working?
class Object
def superclasses
array = []
klass = self.superclass
unless klass == nil
array << klass
klass = klass.superclass
end
array
end
end
class Bar
end
class Foo < Bar
end
p Foo.superclasses # should be [Bar, Object, BasicObject]
unless isn't a loop. What you're looking for is until:
class Object
def superclasses
array = []
klass = self.superclass
until klass == nil
array << klass
klass = klass.superclass
end
array
end
end
class Bar
end
class Foo < Bar
end
p Foo.superclasses # Prints "[Bar, Object, BasicObject]"
Furthermore, you don't need a new method for this. There's already a method called Module#ancestors which does basically what you want:
class Bar
end
class Foo < Bar
end
p Foo.ancestors # Prints "[Foo, Bar, Object, Kernel, BasicObject]"
Note that the return value of ancestors includes Foo itself, and modules which have been included in the inheritance chain, like Kernel. If you don't want that, you can define superclasses like this:
class Module
def superclasses
ancestors[1..-1].select{|mod| mod.is_a? Class }
end
end
class Bar
end
class Foo < Bar
end
p Foo.superclasses # Prints "[Bar, Object, BasicObject]"
You need a loop to continue looking up superclasses:
def superclasses
array = []
klass = self.superclass
while klass
array << klass
klass = klass.superclass
end
array
end
I think it looks a bit nicer to use recursion
def superclasses
return [] if superclass.nil?
[superclass].concat superclass.superclasses
end
And really this method should be defined in Class because not every Object responds to superclass.
Ruby's built-in way of doing this is Module#ancestors. It's not exactly the same though, because that also takes into account included modules. The built-in way is the correct way to do this, however, since the strict chain of superclasses doesn't really give you the whole picture in Ruby.
Another way to get superclasses
class Object
def superclasses
ObjectSpace.each_object(Class).select { |klass| klass < self }
end
end

Setting not initialized instance variable

Is it elegant to use instance variables in a class which are not initialized and setting them using other methods? Or maybe there is a better way to do that?
class Klass
def initialize(a)
#a = a
end
def set_b(b)
#b = b
end
end
In contrast to other languages, If you do not initialize an instance variable it will always be nil (whereas in certain other languages you could get something undefined).
As long as other methods of Klass do not depend on the instance variable actually having a value, this should be ok.
As for getters and setters, there are attr_accessor, attr_reader and attr_writer (see the docs).
class Klass
attr_accessor :b
# there's also attr_reader and attr_writer
def initialize(a)
#a = a
end
end
k = Klass.new :foo
k.b = :bar
k.b
#=> :bar
k.a
#=> undefined method `a' for #<Klass:0x007f842a17c0e0 #a=:foo, #b=:bar> (NoMethodError)
The way you are doing it works but Ruby defined attr_accessor, attr_reader and attr_writer for that purpose.
attr_reader: create method to read 'a'
attr_writer: create method to write 'a'
attr_accessor: create methods to read and write 'a'
I think the best way to do that is to use attr_accessor:a
class Klass
attr_accessor:a
def initialize(a)
#a = a
end
end
Then you can do:
k = Klass.new "foo" #example
k.a = "bar"

Variables in Ruby method names created by attr_accessor

I have an object that is using attr_accessor. I want to be able to call a method on that object with a variable with #send. My problem is that the = method doesn't seem to work.
class Foo
attr_accessor :bar
end
class Test_class
def test_method(x)
f = Foo.new
f.send(x)
f.send(x) = "test" #this doesnt work
f.send("#{x} =") "test" #this also doesn't work
# How can I set bar?
end
end
t = Test_class.new
t.test_method("bar")
You want f.send "#{x}=", "test". In Ruby, method names may include punctuation, such as = or !. The methods created by attr_accessor :bar are simply named bar and bar=. In fact, attr_accessor :bar is just shorthand for:
def bar
#bar
end
def bar=(value)
#bar = value
end
When you're calling foo.bar = "baz", you're actually calling the #bar= method with foo as the receiver and "bar" as the first parameter to the function - that is, foo.bar=("baz"). Ruby just provides syntactic sugar for methods ending in = so that you can write the more natural-looking form.

Ruby pass self as instance in initialize

def initialize()
#attribute = AnotherClass.new(self)
end
I am going to ruby from python. In python, I know I can pass self as an object to a function.
Now, I want to pass self to another class's initialize method. How can I do that?
Just the way you would expect:
class A
def initialize(foo)
#foo = foo
end
end
class B
attr_reader :bar
def initialize
#bar = A.new(self)
end
end
B.new.bar.class #=> A

Best Way to Abstract Initializing Attributes

What's the best way to abstract this pattern:
class MyClass
attr_accessor :foo, :bar
def initialize(foo, bar)
#foo, #bar = foo, bar
end
end
A good solution should take superclasses into consideration and be able to handle still being able to have an initializer to do more things. Extra points for not sacrificing performance in your solution.
A solution to that problem already (partially) exists, but if you want a more declarative approach in your classes then the following should work.
class Class
def initialize_with(*attrs, &block)
attrs.each do |attr|
attr_accessor attr
end
(class << self; self; end).send :define_method, :new do |*args|
obj = allocate
init_args, surplus_args = args[0...attrs.size], args[attrs.size..-1]
attrs.zip(init_args) do |attr, arg|
obj.instance_variable_set "##{attr}", arg
end
obj.send :initialize, *surplus_args
obj
end
end
end
You can now do:
class MyClass < ParentClass
initialize_with :foo, :bar
def initialize(baz)
#initialized = true
super(baz) # pass any arguments to initializer of superclass
end
end
my_obj = MyClass.new "foo", "bar", "baz"
my_obj.foo #=> "foo"
my_obj.bar #=> "bar"
my_obj.instance_variable_get(:#initialized) #=> true
Some characteristics of this solution:
Specify constructor attributes with initialize_with
Optionally use initialize to do custom initialization
Possible to call super in initialize
Arguments to initialize are the arguments that were not consumed by attributes specified with initialize_with
Easily extracted into a Module
Constructor attributes specified with initialize_with are inherited, but defining a new set on a child class will remove the parent attributes
Dynamic solution probably has performance hit
If you want to create a solution with absolute minimal performance overhead, it would be not that difficult to refactor most of the functionality into a string which can be evaled when the initializer is defined. I have not benchmarked what the difference would be.
Note: I found that hacking new works better than hacking initialize. If you define initialize with metaprogramming, you'd probably get a scenario where you pass a block to initialize_with as a substitute initializer, and it's not possible to use super in a block.
This is the first solution that comes to my mind. There's one big downside in my module: you must define the class initialize method before including the module or it won't work.
There's probably a better solution for that problem, but this is what I wrote in less than a couple of minutes.
Also, I didn't keep performances too much into consideration. You probably can find a much better solution than me, especially talking about performances. ;)
#!/usr/bin/env ruby -wKU
require 'rubygems'
require 'activesupport'
module Initializable
def self.included(base)
base.class_eval do
extend ClassMethods
include InstanceMethods
alias_method_chain :initialize, :attributes
class_inheritable_array :attr_initializable
end
end
module ClassMethods
def attr_initialized(*attrs)
attrs.flatten.each do |attr|
attr_accessor attr
end
self.attr_initializable = attrs.flatten
end
end
module InstanceMethods
def initialize_with_attributes(*args)
values = args.dup
self.attr_initializable.each do |attr|
self.send(:"#{attr}=", values.shift)
end
initialize_without_attributes(values)
end
end
end
class MyClass1
attr_accessor :foo, :bar
def initialize(foo, bar)
#foo, #bar = foo, bar
end
end
class MyClass2
def initialize(*args)
end
include Initializable
attr_initialized :foo, :bar
end
if $0 == __FILE__
require 'test/unit'
class InitializableTest < Test::Unit::TestCase
def test_equality
assert_equal MyClass1.new("foo1", "bar1").foo, MyClass2.new("foo1", "bar1").foo
assert_equal MyClass1.new("foo1", "bar1").bar, MyClass2.new("foo1", "bar1").bar
end
end
end
class MyClass < Struct.new(:foo, :bar)
end
I know this is an old question with perfectly acceptable answers but I wanted to post my solution as it takes advantage of Module#prepend (new in Ruby 2.2) and the fact that modules are also classes for very simple solution. First the module to make the magic:
class InitializeWith < Module
def initialize *attrs
super() do
define_method :initialize do |*args|
attrs.each { |attr| instance_variable_set "##{attr}", args.shift }
super *args
end
end
end
end
Now let's use our fancy module:
class MyClass
prepend InitializeWith.new :foo, :bar
end
Note that I left our the attr_accessible stuff as I consider that a separate concern although it would be trivial to support. Now I can create an instance with:
MyClass.new 'baz', 'boo'
I can still define an initialize for custom initialization. If my custom initialize take an argument those will be any extra arguments provided to the new instance. So:
class MyClass
prepend InitializeWith.new :foo, :bar
def initialize extra
puts extra
end
end
MyClass.new 'baz', 'boo', 'dog'
In the above example #foo='baz', #bar='boo' and it will print dog.
What I also like about this solution is that it doesn't pollute the global namespace with a DSL. Objects that want this functionality can prepend. Everybody else is untouched.
This module allows an attrs hash as an option to new(). You can include the module in a class with inheritance, and the constructor still works.
I like this better than a list of attr values as parameters, because, particularly with inherited attrs, I wouldn't like trying to remember which param was which.
module Attrize
def initialize(*args)
arg = args.select{|a| a.is_a?(Hash) && a[:attrs]}
if arg
arg[0][:attrs].each do |key, value|
self.class.class_eval{attr_accessor(key)} unless respond_to?(key)
send(key.to_s + '=', value)
end
args.delete(arg[0])
end
(args == []) ? super : super(*args)
end
end
class Hue
def initialize(transparent)
puts "I'm transparent" if transparent
end
end
class Color < Hue
include Attrize
def initialize(color, *args)
p color
super(*args)
p "My style is " + #style if #style
end
end
And you can do this:
irb(main):001:0> require 'attrize'
=> true
irb(main):002:0> c = Color.new("blue", false)
"blue"
=> #<Color:0x201df4>
irb(main):003:0> c = Color.new("blue", true, :attrs => {:style => 'electric'})
"blue"
I'm transparent
"My style is electric"

Resources