Creating an Expando Object in Ruby

Is there a better way to write this Expando class? The way it is written does not work. I am using Ruby 1.8.7

starting at https://gist.github.com/300462/3fdf51800768f2c7089a53726384350c890bc7c3

class Expando def method_missing(method_id, *arguments) if match = method_id.id2name.match(/(\w*)(\s*)(=)(\s*)(\.*)/) puts match[1].to_sym # think this was supposed to be commented self.class.class_eval{ attr_accessor match[1].to_sym } instance_variable_set("#{match[1]}", match[5]) else super.method_missing(method_id, *arguments) end end end person = Expando.new person.name = "Michael" person.surname = "Erasmus" person.age = 29 
+4
source share
2 answers

The easiest way to write this is to not write at all! :) See the OpenStruct class included in the standard library:

 require 'ostruct' record = OpenStruct.new record.name = "John Smith" record.age = 70 record.pension = 300 

If I were going to write this, I would do it like this:

 # Access properties via methods or Hash notation class Expando def initialize @properties = {} end def method_missing( name, *args ) name = name.to_s if name[-1] == ?= @properties[name[0..-2]] = args.first else @properties[name] end end def []( key ) @properties[key] end def []=( key,val ) @properties[key] = val end end person = Expando.new person.name = "Michael" person['surname'] = "Erasmus" puts "#{person['name']} #{person.surname}" #=> Michael Erasmus 
+9
source

If you're just trying to get a working Expando to use, use OpenStruct . But if you do this for educational value, let them fix the mistakes.

Arguments method_missing

When you call person.name = "Michael" , this translates to a call to person.method_missing(:name=, "Michael") , so you do not need to stretch the parameter using a regular expression. The value you assign is a separate parameter. Hence,

 if method_id.to_s[-1,1] == "=" #the last character, as a string name=method_id.to_s[0...-1] #everything except the last character #as a string #We'll come back to that class_eval line in a minute #We'll come back to the instance_variable_set line in a minute as well. else super.method_missing(method_id, *arguments) end 

instance_variable_set

Instance variable names begin with the @ symbol. This is not just syntactic sugar, it is actually part of the name. Therefore, to set the instance variable, you need to use the following line:

 instance_variable_set("@#{name}", arguments[0]) 

(Notice also how we pulled out the value that we assign from the arguments array)

class_eval

self.class refers to the Expando class as a whole. If you define attr_accessor on it, then each expando will have an accessor for this attribute. I do not think you want.

Rather, you need to do this inside the class << self block (is it a single class or eigenclass self ). This works inside eigenclass for self .

So we would do

 class << self; attr_accessor name.to_sym ; end 

However, the name variable is not actually available internally, so we need to first select the singleton class, and then run class_eval . The usual way to do this is to do this using the eigenclass native method eigenclass So, we define

  def eigenclass class << self; self; end end 

and then call self.eigenclass.class_eval { attr_accessor name.to_sym } )

Decision

Put it all together and the final solution works

 class Expando def eigenclass class << self; self; end end def method_missing(method_id, *arguments) if method_id.to_s[-1,1] == "=" name=method_id.to_s[0...-1] eigenclass.class_eval{ attr_accessor name.to_sym } instance_variable_set("@#{name}", arguments[0]) else super.method_missing(method_id, *arguments) end end end 
+2
source

Source: https://habr.com/ru/post/1335701/


All Articles