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.
Related
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).
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.
In order to ask something like:
MyClass::create().empty?
How would I set up empty within MyClass?
Empty (true/false) depends on whether a class variable #arr is empty or not.
The question mark is actually part of the method name, so you would do this:
class MyClass
def empty?
#arr.empty? # Implicitly returned.
end
end
Exactly the same as I showed in the last post, but with a different method name.
First, create must return something with an empty? method. For example:
class MyClass
def self.create
[]
end
end
If you want to be operating on instances of MyClass as per your last question:
class MyClass
def self.create
MyClass.new
end
def initialize
#arr = []
end
def empty?
#arr.empty?
end
def add x
#arr << x
self
end
end
Here MyClass acts as a simple wrapper around an array, providing an add method.
pry(main)> MyClass.create.empty?
=> true
You might also need to check whether #arr is nil or not. This depends on your class definition of empty.
def empty?
!#arr || #arr.empty?
end
You could use Forwardable to delegate empty? from your class to the array:
require "forwardable"
class MyClass
extend Forwardable
def_delegators :#arr, :empty?
def initialize(arr)
#arr = arr
end
end
my_object = MyClass.new([])
my_object.empty? # => true
So I'm using the awesome trollop gem to do option parsing, but I'm having a general problem with the scope of the variables it's setting.
require 'trollop'
class MyClass
opts = Trollop::options do
opt :thing, "does something", default: "blah", type: String
end
def my_method
puts opts[:thing]
end
end
But I get:
undefined local variable or method `opts' for #<MyClass:0x0000010203c840> (NameError)
Any ideas what I'm doing wrong with my scope?
There are about six options here: instance variable, class instance variable, class variable, class constant, global variable, global constant. Which to use depends on your needs.
Instance Variable - each MyClass instance gets its own options:
class MyClass
def initialize
#opts = ...
end
def my_method
puts #opts[:thing]
end
end
Class Instance Variable - single value across the class that can be reassigned:
class MyClass
#opts = ...
class << self
attr_accessor :opts
end
def my_method
puts self.class.opts[:thing]
end
end
Class Variable - each MyClass and all subclasses share the same value (convenient syntax, but rarely a good idea):
class MyClass
##opts = ...
def my_method
puts ##opts[:thing]
end
end
Class Constant - single object that may be mutated, but not re-assigned. Easily accessed from this class, accessible from others via MyClass::OPTS:
class MyClass
OPTS = ...
def my_method
puts OPTS[:thing]
end
end
Global Variable - you can only have one of these in your entire app; often global variables are ill-advised, but perhaps appropriate for a standalone application's options:
$opts = ...
class MyClass
def my_method
puts $opts[:thing]
end
end
Global Constant - accessed from many classes, can't be set to a new value, but may be mutated:
OPTS = ...
class MyClass
def my_method
puts OPTS[:thing]
end
end
Shouldn't you just use instance variable?
require 'trollop'
class MyClass
def initialize
#opts = Trollop::options do
opt :thing, "does something", default: "blah", type: String
end
end
def my_method
puts #opts[:thing]
end
end
You are defining 'opts' as a local variable inside your class. Instances methods (like my_method) will not be able to access it. Is opts supposed to be "global" for the whole class? In that case:
class MyClass
##opts = Trollop::options...
def my_method
puts ##opts[:thing]
end
end
Or is there supposed to be a unique one for each instance of the class?
class MyClass
def initialize
#opts = Trollop::options...
end
def my_method
puts #opts[:thing]
end
end
This might be a good read: http://sporkmonger.com/2007/2/19/instance-variables-class-variables-and-inheritance-in-ruby
You'd want to make it either a class variable or an instance variable, depending on your needs.
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.