Given the following class:
class Test
attr_accessor :name
end
When I create the object, I want to do the following:
t = Test
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.
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
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.
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.
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.