donnior
donnior

Reputation: 1065

How to set hash instance's value like this in Ruby?

I have two classes A and B, both of them have some options, normally I will use one Hash to store options like @options[:name]='xxxx'; now I want refactored it using meta programming,

class A
    set_option :name, "james"
    set_option :address, "some street"

    def hello
        puts @options[:name]
        puts @options[:address]
    end
end


class B
    set_option :age, 18

    def greeting
        put @options[:age]
    end
end 

Here I want to use set_option to set key&value pairs to one hash instance @options, how can I do it?

Further I want to wrap the solution to a separate Module.

UPDATED:

thanks first, all your answers are valuable to me and make me clearer, now I realize that some thing I want is not so correctly, so what if I make the question like this?

``` like this:

class A
    set_option :name, "james"
    set_option :provider, 'twitter'

    def hello
        puts option[:name]
    end
end


class B
    set_option :name, "not james"

    def greeting
        put option[:name]
    end
end 

After much thought I think What I really want is different option hash instance for different class, not class's instance.

Here is what I want and it can work.

module HasOptions
  def self.included(cls)
    cls.class_eval do
      def self.set_option(key, value)
        options[key] = value
      end

      def self.options
        @options ||= {}
      end

      def options
        self.class.options
      end
    end
  end
end


class Baz
  include HasOptions
  set_option :name, "bad"

  def greeting
        puts options[:name]
  end
end

class Foo
  include HasOptions
  set_option :name, "foo"

  def greeting
        puts options[:name]
  end
end

Thanks for all your help.

Upvotes: 2

Views: 406

Answers (3)

Alex D
Alex D

Reputation: 30445

Using an instance variable, as you are trying to do, won't work. However, you can use a method like this:

module HasOptions
  def self.included(cls)
    cls.class_eval do
      def self.set_option(key, value)
        (@@options ||= {})[key] = value
      end
    end
  end

  def options
    @options ||= @@options.dup
  end
end

This implementation allows you to set per-instance options, without overwriting the common options for all instances.

Upvotes: 5

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

It looks like you are messing things a little bit. Let me try to list the questions you want to be solved:

  • store options in an instance of class variable Hash (did I got it correct?—one hash for all the instances of your classes);
  • use a pretty notation for setting options.

Nothing of the above requires meta-programming actually. To satisfy first condition you are simply to find the first common ancestor of all your classes and [monkey]patch it with your @@options var and set_option method. If you did not decide to provide your own superclass for all your classes, let’s patch the very ancestor (e.g. Kernel module):

module Kernel
  def set_option name, value
    (@@options ||= {})[name.to_sym] = value
  end
end

Now any class may include set_option “instruction” to put option in shared options hash (consider using instance variable @options instead of @@options to make options instance-specific, or use the Alex D’s solution.)

If you want to have curly fancy syntax for option setting (with defaults, inplace checkers, ruches and luxury goods), you are to use DSL here. The first example in this article shows how to implement pure set_option with DSL. The further tuning is only limited by your imagination.

Upvotes: 2

ck3g
ck3g

Reputation: 5929

In case you want init instance options you should use instance methods to do it.

module OptionSetter
  def set_option(key, value)
    @options[key] = value
  end
end

class Base
  def initialize(options = {})
    @options = {}
    options.each do |key, value|
      set_option key, value
    end
  end
end

class A < Base
  include OptionSetter

  def hello
    puts @options[:name]
    puts @options[:address]
  end
end

class B < Base
  include OptionSetter

  def greeting
    puts @options[:age]
  end
end

A.new(name: "james", address: "some street").hello
B.new(age: 18).greeting

More common way to make it in ruby is using attr_accessor's

class Base
  attr_accessor :options

  def initialize(options = {})
    @options = options
  end
end

class A < Base
  def hello
    puts @options[:name]
    puts @options[:address]
  end
end

class B < Base
  def greeting
    puts @options[:age]
  end
end

A.new(name: "james", address: "some street").hello
B.new(age: 18).greeting

# another approach
james = A.new
james.options[:name] = "james"
james.options[:address] = "some street"
james.hello

Upvotes: 1

Related Questions