Get name of anonymous class while inherited process - ruby

Is there are any possibility to get name of the anonymous class in the inherited, and not raise "fail A"? Should be made through Class object creation (no eval or similar).
class A
def self.inherited(base)
raise 'fail A' unless base.name
end
end
B = Class.new(A)
# or
Object.const_set :B, Class.new(A)
The code above doesn't work, because anonymous class isn't yet initialized so it cannot be set to a specific constant.

No.
An anonymous class doesn’t have a name until it’s been assigned to a constant (e.g. (B = Class.new).name #=> "B"). Since assignment does not happen until after the class instance has been created (during which hooks—including inherited—are called), there’s no way you can get the name until afterwards.

What about something like:
class Superclass
def self.inherited(base)
raise 'Invalid class name' unless #forced_anonymous_subclass_name == :A
end
def self.forced_anonymous_subclass_name
#forced_anonymous_subclass_name
end
def self.with_forced_anonymous_subclass_name(class_name)
#forced_anonymous_subclass_name = class_name
yield
ensure
#forced_anonymous_subclass_name = nil
end
end
sc = Superclass
sc.with_forced_anonymous_subclass_name(:A) do
Object.const_set sc.forced_anonymous_subclass_name, Class.new(sc)
end #=> A
sc.with_forced_anonymous_subclass_name(:B) do
Object.const_set sc.forced_anonymous_subclass_name, Class.new(sc)
end #=> RuntimeError
Feel free to ask if unclear/improvements/etc.

Related

Instance variable in a class method

I found this neat delegator based 'tee' implementation on SO:
https://stackoverflow.com/a/6410202/2379703
And I'm curious what is means for #targets (instance variable) means in the context of a class method:
require 'logger'
class MultiDelegator
def initialize(*targets)
#targets = targets
end
def self.delegate(*methods)
methods.each do |m|
define_method(m) do |*args|
#targets.map { |t| t.send(m, *args) }
end
end
self
end
class <<self
alias to new
end
end
log_file = File.open("debug.log", "a")
log = Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file)
I get that it defining the methods write/close but #targets isn't even defined at this point since .to (aliased to new) has yet to be called so I'd assume #targets is nil.
Can anyone give an explanation as to the logistics of how this code works? Does ruby not even attempt to access/resolve #targets until the method in question is attempted to be called, which would be by the logger after it was instantiated?
The define_method method is called on a class to create an instance method. Inside that method, the self (and the instance variable) are instances of the class.
For example:
class Foo
#bar = "CLASS"
def initialize
#bar = "INSTANCE"
end
def self.make_method
define_method :whee do
p #bar
end
end
end
begin
Foo.new.whee
rescue NoMethodError=>e
puts e
end
#=> undefined method `whee' for #<Foo:0x007fc0719794b8 #bar="INSTANCE">
Foo.make_method
Foo.new.whee
#=> "INSTANCE"
It is correct that you can ask about instance variables that have never been created, at any time:
class Bar
def who_dat
puts "#dat is #{#dat.inspect}"
end
end
Bar.new.who_dat
#=> dat is nil
The same is true of other aspects of the language. As long as the code in the method is syntactically valid, it may be defined, even if invoking it causes a runtime error:
class Jim
def say_stuff
stuff!
end
end
puts "Good so far!"
#=> Good so far!
j = Jim.new
begin
j.say_stuff
rescue Exception=>e
puts e
end
#=> undefined method `stuff!' for #<Jim:0x007f9c498852d8>
# Let's add the method now, by re-opening the class
class Jim # this is not a new class
def stuff!
puts "Hello, World!"
end
end
j.say_stuff
#=> "Hello, World!"
In the above I define a say_stuff method that is syntactically valid, but that calls a method that does not exist. This is find. The method is created, but not invoked.
Then I try to invoke the method, and it causes an error (which we catch and handle cleanly).
Then I add the stuff! method to the class. Now I can run the say_stuff method (on the same instance as before!) and it works just fine.
This last example shows how defining a method does not run it, or require that it would even work when it is run. It is dynamically evaluated each time it is invoked (and only at that time).

Instantiating objects only once in singletons in ruby

I have been for the last couple of days working on this Factory pattern based class where you can register classes, give them unique names, then use them to create objects on the fly. My class looks as such:
module AisisWriter
class ClassFactory
class << self
undef_method :new
attr_accessor :registered_objects
def register(class_name, klass, params = nil)
if !params.nil? && !params.is_a?(Array)
raise ArgumentError, "Params must be an array."
end
if registered?(class_name)
raise ArgumentError, "Class name already registered."
end
#registered_object[class_name] = {:class_name => klass, :params => !params.nil? ? params.flatten : nil}
end
def registered?(class_name)
if #registered_object.nil?
return false
end
#registered_object.include? class_name
end
def create(class_name, params = nil)
if !params.nil? && !params.is_a?(Array)
raise ArgumentError, "Params must be an array."
end
if !registered?(class_name)
raise ArgumentError, "Class does not exist in the registered classes."
end
klass = #registered_object[class_name]
if !params.nil
klass[:class_name].new(params.flatten)
else
flass[:class_name].new(*klass[:params])
end
end
end
end
end
But I have some questions, which arose as I was writing tests.
in the register(...) function I do:
#registered_object[class_name] = {:class_name => klass, :params => !params.nil? ? params.flatten : nil}
This fails because #registered_object is nil. How do I initialize it only once? In php I would write a getInstance() method and say if the class instance is set, don't set it again, while we set the class instance also set other variables that only need to be set once. Would I have a get_instance method here?
In relation to question one, can I chain methods in ruby. so if I needed to get an instance of this class, which just sets #registered_object to {} once, could I then do: AisisWriter::ClassFactory.get_instance.register(...) Does ruby support that?
The basis here is to make sure that when this class is called upon, we check if #register_object is nil, and if so set it to a new instance of {}
If there is a easier solution I am all ears.
You can do
def registered_object
#registered_object ||= {}
end
Then call registered_object instead of #registered_object.
This is a common Ruby idiom. It means:
If #registered_object is falsy, then set it to {}.
If you want an actual singleton factory, then Ruby has the Singleton module which helps with that!
You can define your class like this:
module AisisWriter
class ClassFactory
include Singleton
attr_accessor :registered_objects
def initialize
#registered_object = {}
end
def register(class_name, klass, params = nil)
raise ArgumentError, "Class name already registered." if registered? class_name
#registered_object[class_name] = {:class_name => klass, :params => params}
end
def registered?(class_name)
#registered_object.key? class_name
end
def create(class_name, params = nil)
raise ArgumentError, "Class does not exist in the registered classes." unless registered? class_name
klass = #registered_object[class_name]
klass[:class_name].new *Array(params || klass[:params])
end
end
end
This provides an #initialize method where you can set up all your singleton state, but ::new is uncallable as you'd expect. Instead, you use ::instance.
And then you'd use it like:
AisisWriter::ClassFactory.instance.register(...)
AisisWriter::ClassFactory.instance.create(...)
The factory will be instantiated only once when #instance is called for the first time.

Metaprograming: custom initialize

I have to write a my_initialize method in class Class, so that it works in this manor, when used in another class:
class Person
my_initialize :name, :surname
end
is equivalent to :
class Person
def initialize(name, surname)
#name, #surname = name, surname
end
end
It also has to raise an ArgumentError if a wrong number of arguments is passed. For example Person.new("Mickey") is invalid. I know that my code should look something like:
class Class
def my_initialize(*args)
args.each do |arg|
self.class_eval("?????")
end
end
end
I just started to read metaprogramming, but can't find anything useful for my problem. Any ideas how to do this task?
class Class
def my_initialize(*vars)
define_method :initialize do |*args|
if args.length != vars.length
raise ArgumentError, 'wrong number of arguments'
end
vars.zip(args).each do |var, arg|
instance_variable_set :"##{var}", arg
end
end
end
end
class C
my_initialize :a, :b
end
The Module#define_method method takes a method name and block and defines the method for that module. In this case, the module is C. The Object#instance_variable_set method takes an instance variable name and a value and sets it. The instance of Object in this case would be an instance of C.
By the way, it is best to avoid using methods where you pass a string of code in to be evaluated. I would recommend passing blocks instead.
Here's another way that does not use define_method.
class Class
def my_initialize(*vars)
str = "def initialize(*args)
raise ArgumentError if args.size != #{vars.size}
#{vars}.zip(args).each do |var, arg|
instance_variable_set(\"#\#{var}\", arg)
end
end"
class_eval str
end
end
class C
my_initialize :a, :b
end
c = C.new("Betty", "Boop")
#=> #<C:0x00000102805428 #a="Betty", #b="Boop">
C.private_instance_methods(false)
#=> [:initialize]
c.instance_variables
#=> [:#a, :#b]
C.new("Betty")
#=> ArgumentError

How to pass a method to instance_eval?

I want to call instance_eval on this class:
class A
attr_reader :att
end
passing this method b:
class B
def b(*args)
att
end
end
but this is happening:
a = A.new
bb = B.new
a.instance_eval(&bb.method(:b)) # NameError: undefined local variable or method `att' for #<B:0x007fb39ad0d568>
When b is a block it works, but b as a method isn't working. How can I make it work?
It's not clear exactly what you goal is. You can easily share methods between classes by defining them in a module and including the module in each class
module ABCommon
def a
'a'
end
end
class A
include ABCommon
end
Anything = Hash
class B < Anything
include ABCommon
def b(*args)
a
end
def run
puts b
end
end
This answer does not use a real method as asked, but I didn't need to return a Proc or change A. This is a DSL, def_b should have a meaningful name to the domain, like configure, and it is more likely to be defined in a module or base class.
class B
class << self
def def_b(&block)
(#b_blocks ||= []) << block
end
def run
return if #b_blocks.nil?
a = A.new
#b_blocks.each { |block| a.instance_eval(&block) }
end
end
def_b do
a
end
end
And it accepts multiple definitions. It could be made accept only a single definition like this:
class B
class << self
def def_b(&block)
raise "b defined twice!" unless #b_block.nil?
#b_block = block
end
def run
A.new.instance_eval(&#b_block) unless #b_block.nil?
end
end
def_b do
a
end
end

How can one set property values when initializing an object in Ruby?

Given the following class:
class Test
attr_accessor :name
end
When I create the object, I want to do the following:
t = Test.new {name = 'Some Test Object'}
At the moment, it results in the name attribute still being nil.
Is that possible without adding an initializer?
ok,
I came up with a solution. It uses the initialize method but on the other hand do exactly what you want.
class Test
attr_accessor :name
def initialize(init)
init.each_pair do |key, val|
instance_variable_set('#' + key.to_s, val)
end
end
def display
puts #name
end
end
t = Test.new :name => 'hello'
t.display
happy ? :)
Alternative solution using inheritance. Note, with this solution, you don't need to explicitly declare the attr_accessor!
class CSharpStyle
def initialize(init)
init.each_pair do |key, val|
instance_variable_set('#' + key.to_s, val)
instance_eval "class << self; attr_accessor :#{key.to_s}; end"
end
end
end
class Test < CSharpStyle
def initialize(arg1, arg2, *init)
super(init.last)
end
end
t = Test.new 'a val 1', 'a val 2', {:left => 'gauche', :right => 'droite'}
puts "#{t.left} <=> #{t.right}"
As mentioned by others, the easiest way to do this would be to define an initialize method. If you don't want to do that, you could make your class inherit from Struct.
class Test < Struct.new(:name)
end
So now:
>> t = Test.new("Some Test Object")
=> #<struct Test name="Some Test Object">
>> t.name
=> "Some Test Object"
There is a general way of doing complex object initialization by
passing a block with necessary actions. This block is evaluated in the
context of the object to be initialized, so you have an easy access to
all instance variables and methods.
Continuing your example, we can define this generic initializer:
class Test
attr_accessor :name
def initialize(&block)
instance_eval(&block)
end
end
and then pass it the appropriate code block:
t = Test.new { #name = 'name' }
or
t = Test.new do
self.name = 'name'
# Any other initialization code, if needed.
end
Note that this approach does not require adding much complexity
to the initialize method, per se.
As previously mentioned, the sensible way to do this is either with a Struct or by defining an Test#initialize method. This is exactly what structs and constructors are for. Using an options hash corresponding to attributes is the closest equivalent of your C# example, and it's a normal-looking Ruby convention:
t = Test.new({:name => "something"})
t = Test.new(name: "something") # json-style or kwargs
But in your example you are doing something that looks more like variable assignment using = so let's try using a block instead of a hash. (You're also using Name which would be a constant in Ruby, we'll change that.)
t = Test.new { #name = "something" }
Cool, now let's make that actually work:
class BlockInit
def self.new(&block)
super.tap { |obj| obj.instance_eval &block }
end
end
class Test < BlockInit
attr_accessor :name
end
t = Test.new { #name = "something" }
# => #<Test:0x007f90d38bacc0 #name="something">
t.name
# => "something"
We've created a class with a constructor that accepts a block argument, which is executed within the newly-instantiated object.
Because you said you wanted to avoid using initialize, I'm instead overriding new and calling super to get the default behavior from Object#new. Normally we would define initialize instead, this approach isn't recommended except in meeting the specific request in your question.
When we pass a block into a subclass of BlockInit we can do more than just set variable... we're essentially just injecting code into the initialize method (which we're avoiding writing). If you also wanted an initialize method that does other stuff (as you mentioned in comments) you could add it to Test and not even have to call super (since our changes aren't in BlockInit#initialize, rather BlockInit.new)
Hope that's a creative solution to a very specific and intriguing request.
The code you're indicating is passing parameters into the initialize function. You will most definitely have to either use initialize, or use a more boring syntax:
test = Test.new
test.name = 'Some test object'
Would need to subclass Test (here shown with own method and initializer) e.g.:
class Test
attr_accessor :name, :some_var
def initialize some_var
#some_var = some_var
end
def some_function
"#{some_var} calculation by #{name}"
end
end
class SubClassedTest < Test
def initialize some_var, attrbs
attrbs.each_pair do |k,v|
instance_variable_set('#' + k.to_s, v)
end
super(some_var)
end
end
tester = SubClassedTest.new "some", name: "james"
puts tester.some_function
outputs: some calculation by james
You could do this.
class Test
def not_called_initialize(but_act_like_one)
but_act_like_one.each_pair do |variable,value|
instance_variable_set('#' + variable.to_s, value)
class << self
self
end.class_eval do
attr_accessor variable
end
end
end
end
(t = Test.new).not_called_initialize :name => "Ashish", :age => 33
puts t.name #=> Ashish
puts t.age #=> 33
One advantage is that you don't even have to define your instance variables upfront using attr_accessor. You could pass all the instance variables you need through not_called_initialize method and let it create them besides defining the getters and setters.
If you don't want to override initialize then you'll have to move up the chain and override new. Here's an example:
class Foo
attr_accessor :bar, :baz
def self.new(*args, &block)
allocate.tap do |instance|
if args.last.is_a?(Hash)
args.last.each_pair do |k,v|
instance.send "#{k}=", v
end
else
instance.send :initialize, *args
end
end
end
def initialize(*args)
puts "initialize called with #{args}"
end
end
If the last thing you pass in is a Hash it will bypass initialize and call the setters immediately. If you pass anything else in it will call initialize with those arguments.

Resources