Skip to content

Instantly share code, notes, and snippets.

@kalsan
Last active March 1, 2023 14:12
Show Gist options
  • Save kalsan/87826048ea0ade92ab1be93c0919b405 to your computer and use it in GitHub Desktop.
Save kalsan/87826048ea0ade92ab1be93c0919b405 to your computer and use it in GitHub Desktop.
MethodAccessibleHash, a possible replacement for OpenStruct
# OpenStruct has caveats and security problems and even leads to a rubocop offense:
# https://ruby-doc.org/stdlib-3.1.0/libdoc/ostruct/rdoc/OpenStruct.html#class-OpenStruct-label-Caveats
# https://msp-greg.github.io/rubocop/RuboCop/Cop/Style/OpenStructUse.html
#
# It's time to replace it. The feature I loved most about OpenStruct was being able to retrieve
# values by calling a method instead of having to use hash access syntax.
# The recommended way to go is Struct, but it is much more cumbersome than OpenStruct was.
# So here's MethodAccessibleHash that allows to do that, but without OpenStruct's security problems.
#
# This revision adds writers, direct creation from Hash, as well as enhanced merging.
#
# License: MIT
# Feel free to use this code anywhere you like. No warranty provided.
#
# Usage example:
# default_options = { foo: :bar }
# options = MethodAccessibleHash.new(default_options)
# options[:color] = :green
# options.foo # => :bar
# options.color # => green
class MethodAccessibleHash < ::Hash
# Takes an optional hash as argument and constructs a new
# MethodAccessibleHash.
def initialize(hash = {})
super()
hash.each do |key, value|
self[key.to_sym] = value
end
end
# @private
def merge(hash)
super(hash.symbolize_keys)
end
# @private
def method_missing(method, *args, &_block)
if method.end_with?('=')
name = method.to_s.gsub(/=$/, '')
self[name.to_sym] = args.first
else
self[method.to_sym]
end
end
# @private
def respond_to_missing?(_method, _include_private = false)
true
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment