views:

52

answers:

4

I'm looking for a way to add properties to my already defined class at runtime, or better:

class Client
    attr_accessor :login, :password
    def initialize args = {}
        self.login    = args[:login]
        self.password = args[:password]
    end
end

But then, I have this hash

{:swift_bic=>"XXXX", :account_name=>"XXXX", :id=>"123", :iban=>"XXXX"} 

and I want this hash to become part of my client instance like

client = Client.new :login => 'ludicco', :password => 'xxxxx'

then with a miraculous magic

client @@%$%PLIM!!! {:swift_bic=>"XXXX", :account_name=>"XXXX", :id=>"123", :iban=>"XXXX"} 

I would be able to access

client.swift_bic => 'XXXX'
client.account_name => 'XXXX'
client.id => 123

and I also would like to maintain a proper object structure like:

Client.new(:login => 'ludicco', :password => 'xxxxx').inspect
#<Client:0x1033c4818 @password='xxxxx', @login='ludicco'>

after the magic

client.inspect
#<Client:0x1033c4818 @password='xxxxx', @login='ludicco', @swift_bic='XXXX', @account_name='XXXX' @id => '123', @iban => 'XXXX'>

which would give me a very nice and well formatted json after that

Is it possible at all?

I'm getting this hash from a webservice, so I wouldn't know if theres a new property in there, and then I would have to update my app each time they perform an upgrade on their service. So, I'm sort of trying to avoid this :/

Thanks chaps.

:)

A: 

Take a look at Object.method_missing. This will be called whenever your object is called with a method that isn't defined. You can define this function and use it to check the undefined method against the name of one of your hash values. If it matches, return the hash value.

You can also define your own inspect function and generate an output string containing whatever you want it to contain.

bta
+2  A: 

The method_missing approach would work, but if you're going to use the accessors a lot after adding them, you might as well add them as real methods, like this:

class Client
  def add_attrs(attrs)
    attrs.each do |var, value|
      class_eval { attr_accessor var }
      instance_variable_set "@#{var}", value
    end
  end
end

This will make them work like normal instance variables, but restricted to just one client.

mckeed
HAHAAA!!, and the miraculous magic exists, thanks a lot mckeed, exactly what I was looking for! cheers
ludicco
Doesn't work for me by default (I tried 1.8.7 and 1.9.1) I got it to work by invoking the class_eval on self's singleton class. It is all the same, except **line 3** becomes: `(class << self ; self ; end).class_eval { attr_accessor var }` An alternative way to write that would be `(class << self ; self ; end).send :attr_accessor , var`
Joshua Cheek
Shoot, good call. This does require ActiveSupport the way I wrote it.
mckeed
A: 

I'm going to recommend against method_missing for this - that creates a lot of 'magic' functionality that can't be documented easily or understood without working through the method_missing body. Instead, look into OpenStruct as suggested - you could even make your own class that inherits from it, like:

class Client < OpenStruct
  ...
end

and you'll be able to initialize a Client with whatever hash you receive.

dunedain289
A: 

Hi, I think the best solution would be mckeed's

But here is another idea to think about. You could subclass OpenStruct if you wanted to:

require 'ostruct'

class Client < OpenStruct
  def initialize args = {}
    super
  end
  def add_methods( args = Hash.new )
    args.each do |name,initial_value|
      new_ostruct_member name
      send "#{name}=" , initial_value
    end
  end
end

client = Client.new :login => 'ludicco', :password => 'xxxxx'
client.add_methods :swift_bic=>"XXXX", :account_name=>"XXXX", :iban=>"XXXX" , :to_s => 5
client # => #<Client login="ludicco", password="xxxxx", swift_bic="XXXX", account_name="XXXX", iban="XXXX", to_s=5>

client.swift_bic      # => "XXXX"
client.account_name   # => "XXXX"

There are two issues with this solution, though. OpenStruct uses method_missing, so if you define a method like id, on 1.8 it will go find the object_id instead of finding your method.

Thse second issues is that it uses some private knowledge of how OpenStruct is implemented. So it could be changed in the future, breaking this code (for the record, I checked 1.8.7 - 1.9.2 and this was compatible)

Joshua Cheek