11

I'm trying to figure out how to make it so that a subclass of OpenStruct (or any class for that matter), or hash, will raise a custom exception if I try to access an attribute that hasn't been set. I couldn't get define_method and method_missing to do this so I'm clueless how it should be done in Ruby.

Here's an example:

class Request < OpenStruct... request = Request.new begin request.non_existent_attr rescue CustomError... 

I could imagine it would have to be something like this:

class Hash # if trying to access key: # 1) key exists, return key # 2) key doesn't exist, raise exception end 

Edit: Attributes that exist shouldn't raise an exception. The functionality I'm looking for is so that I can just access attributes freely and if it happens not to exist my custom exception will be raised.

6 Answers 6

9

If you need a strict hash, simply:

class StrictHash < Hash alias [] fetch end 

It works as expected:

hash = StrictHash[foo: "bar"] hash[:foo] # => "bar" hash[:qux] # stricthash.rb:7:in `fetch': key not found: :qux (KeyError) # from stricthash.rb:7:in `<main>' 
Sign up to request clarification or add additional context in comments.

2 Comments

What I was looking for was "dot access", not hash access. +1 For elegance, though.
It can be handy to apply the alias to a single instance: some_hash.instance_eval {alias [] fetch}. This can be applied recursively to convert an existing hash to raise on missing keys using brackets, but I wonder if there's a more elegant way.
9

I use something like

hash = { a: 2, b: 3 } Struct.new(*hash.keys).new(*hash.values).freeze 

to get an immutable object which will raise NoMethodError in case unexpected method is invoked

Comments

8

OpenStruct defines singleton accessor methods on the object when you set a new member, so you can use respond_to? to see if the member is valid. In fact you can probably just capture any method not defined with method_missing and throw the error unless it's a setter method name, in which case you pass it along to super.

class WhinyOpenStruct < OpenStruct def method_missing(meth, *args) raise NoMemberError, "no #{meth} member set yet" unless meth.to_s.end_with?('=') super end end 

1 Comment

Won't this raise an error if the method called is an internal method?
1

In ruby, whenever you write object.horray the message horray is sent to the object object, that will return a value. Since every horray is a message. If the object don't respond to this message, you can't distinguish between the object don't having an attribute with this name or if it don't have a method with this name.

So unless you will assume that no method can have a typo, it is not possible to do what you want to.

1 Comment

If you do this on an OpenStruct, you will no longer be able to make new members except at initialize.
1

I went with this solution which does exactly what I need:

class Request < Hash class RequestError < StandardError; end class MissingAttributeError < RequestError; end def initialize(hash) hash.each do |key, value| self[key] = value end end def [](key) unless self.include?(key) raise MissingAttributeError.new("Attribute '#{key}' not found in request") end super end end 

1 Comment

You can use the fetch method instead of overwritting [] here. ruby-doc.org/core-1.9.3/Hash.html#method-i-fetch
0

It's brutal, but you could overwrite the new_ostruct_member method to generate an error:

require 'ostruct' class CustomError < StandardError; end os = OpenStruct.new({:a=>1, :b=>1}) def os.new_ostruct_member(name) #just wrecking a single instance raise CustomError, "non-existing key #{name} called" end p os.a=3 p os.c=4 #=>non-existing key c called (CustomError) 

1 Comment

@Serialize wants to raise an error on reading a non-existent attribute.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.