Reputation: 1659
What's the standard way to initialize class (not object)?
What I did is created class_initialize
method and call it.
But I am ex C# programmer. Is there a better way?
class Specs
class << self
def universal_properties
[:hp, :engine_volume]
end
def compound_universal_properties
[:hp_per_volume]
end
def convertible_properties
[:weight, :torque]
end
def compound_convertible_properties
[:weight_per_hp]
end
private
def define_methods(type)
define_method(type) { instance_variable_get("@#{type}") }
define_method("#{type}=") do |value_and_unit|
send(type).send(:set, value_and_unit)
end
end
def class_initialize
universal_properties.each { |p| define_methods(p) }
convertible_properties.each { |p| define_methods(p) }
compound_universal_properties.each { |p| define_methods(p) }
compound_convertible_properties.each { |p| define_methods(p) }
end
end
class_initialize
public
def initialize
@weight = ConvertibleProperty.new(:weight)
...
end
...
end
Less important details:
I see by first answer that this code is confusing people and this is too long for a comment.
I didn't just create attr_accessors because for example :weight
and :torque
are class ConvertibleProperty
and have functionality like imperial.value
, imperial.unit
, metric.value
, metric.unit
, empty?
...
I am calling this code like this:
specs = Specs.new
specs.weight = 800, 'kg'
specs.hp = 300
specs.torque = 210, :metric
When I type specs.weight = 10, 'kg'
ruby translates that to specs.weight=([10, 'kg'])
and I don't want to replace weight with array [10, 'kg']
, I want to call set
method on it which stores original unit and value and provides metric
and imperial
function which each retuns a struct containing value
and unit
.
Upvotes: 0
Views: 71
Reputation: 121000
IMHO, the most idiomatic way would be to DSL this:
class Specs
def initialize
instance_exec(&Proc.new) if block_given?
end
def weight!(*args)
weight = ...
end
...
end
specs = Specs.new do
weight! 800, 'kg'
hp! 300
torque! 210, :metric
end
Other way round would be to specify proper accessors:
def torque=(*args)
# 210, :metric
@torque = ConvertibleProperty.new(...)
end
If the amount of variables is high, one might want to automize the creation of accessors:
PROPERTIES = {
'UniversalProperty': [:hp, :engine_volume],
'CompoundUniversalProperty': [:hp_per_volume],
'ConvertibleProperty': [:weight, :torque],
'CompoundConvertibleProperty': [:weight_per_hp]
}.freeze
PROPERTIES.each do |type, *props|
props.each do |prop|
attr_reader prop
define_method "#{prop}=" do |*args|
self.instance_variable_set(:"@#{prop}", Kernel.const_get(type).new(*args))
end
end
end
Upvotes: 2
Reputation:
Idiomatically, you would not create assignment methods to respond in such a way, and there isn't much value to metaprogramming here yet. I would work to write it out in simple terms first, then possibly refactor later if it becomes cumbersome to manage your different types.
Here is the idiomatic way:
class Specs
def weight
@weight ||= ConvertibleProperty.new(:weight)
end
def torque
@torque ||= ConvertibleProperty.new(:torque)
end
# [..]
end
specs = Specs.new
specs.weight.set(800, 'kg')
specs.torque.set(210, :metric)
Upvotes: 1