0

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.

2
  • Could you include an example of how you want to call this code? For e.g. Specs.weight('etc'), or Specs.new('etc') Commented Nov 21, 2016 at 14:19
  • Done. I hope it explains it. :) Commented Nov 21, 2016 at 14:23

2 Answers 2

2

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
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks. If I was doing it again, I'd do it like second case cause I like the specs.weight = 10, 'lbs' syntax. But I have 13 properties which are 4 different types (classes). This seems like a mess to create accessors for all manually.
What about my class_initialize idea, is that non ruby way for some reason and why?
Whether you want to meta-program those 13 props, you might do it in more explicit manner, please see an update.
Thank you. So you'd just put code like that in the class, you wouldn't wrap it into a method and then call that method like I did with class_initialize ?
There is absolutely no reason to have the method unless you are going to call this method at leasr twice. It just makes code noisy.
|
1

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

usage

specs = Specs.new
specs.weight.set(800, 'kg')
specs.torque.set(210, :metric)

1 Comment

Thanks. That is how I started, except I initialized @weight, @torque... in #initialize. But why no suggestions for class_initialize. Is that like a big no-no in ruby to initalize the class? Why?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.