4
\$\begingroup\$

There is a pattern that keeps coming up in my ruby code and I'm somewhat ambivalent about it. It's somewhere between a Hash and a Struct. Basically I used method_missing to return/set values in the Hash unless I want to override a specific value with some logic or flatten out the complex underlying structure in the JSON. It's very flexible and quickly allows code changes, but it also hides much of the structure of the object.

In effect, the structure is in the data ( JSON file ) and not in the code. Is this an effective Pattern or just asking for trouble down the road?

class AttributeKeeper

    def initialize
      @attributes = Hash.new
    end

    def import
       // Special import methods usually from JSON
    end 

    def export
       // Export to JSON with maybe some data verification along the way.
    end

    def special_value= (value )
        // Perform data check on special value
        @attributes[special_value] = value
    end 

    def checked_value
        // Return value if passes checks. 
    end

    def method_missing(meth, *args, &block)
      smeth = meth.to_s
      trim = smeth.chomp('=') #Handle setter methods.
      unless ( smeth == trim )
        @attributes[trim] = args[0]
      else
        @attributes[smeth]
      end
    end

    def responds_to?(meth)
      smeth = meth.to_s
      if( @attributes[smeth] or smeth[-1,1] == '=')
        true
      else
        super
      end
    end


end 
\$\endgroup\$
3
\$\begingroup\$

Useful to create mock, small scripts, remote api mapping... the github ruby api Octokit.rb is using this kind of pattern.

Don't use this for huge (1000+) collection of objects with intensive call on them.

  • The method_missing, symbol conversion will compromise your performance, generating a memory bloat and a lot of garbage collection,
  • the Hashie isn't optimized as ActiveRecord::Base (method_missing triggering a define_method) so method call resolution has a always higher cost.

Another aspect is that you tend to put your code at the wrong place (UtilityClass#full_name) instead of the actual class (user.full_name).

\$\endgroup\$
  • \$\begingroup\$ In practice my "real" objects tend to be subclasses of this object. The whole point is to make user.full_name "easy" rather than user.validate(JSON['subhash']['full_name']) \$\endgroup\$ – Fred the Magic Wonder Dog Dec 18 '13 at 16:29
  • \$\begingroup\$ I'm fine with this I often use Hashie to mock some value object. The ancestor is a real good trick but then you can define the method and relying on the method missing only at the first call. \$\endgroup\$ – mestachs Dec 19 '13 at 14:42
3
\$\begingroup\$

I have the feeling that your patterns is very similar to the idea behind OpenStruct and similar libraries, such as Hashie::Mash.

So I would say, it's definitely not an anti-pattern in some cases. Such structures are very helpful when parsing and converting structured inputs, for example an API call.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.