Created
August 6, 2026 12:12
-
-
Save roktas/763555b415daba1ff3184e5da50eab89 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # frozen_string_literal: true | |
| # Hash-backed, dot-accessible options object. A field name that would | |
| # collide with a real method on this object raises instead of silently | |
| # becoming unreachable via dot-notation. | |
| class DotHash | |
| # Raised when a field name collides with an existing method. | |
| class ReservedNameError < NameError | |
| end | |
| # @param hash [Hash] initial fields | |
| def initialize(hash = {}) | |
| @table = {} | |
| hash.each { |key, value| self[key] = value } | |
| end | |
| # @param key [Symbol, String] | |
| # @return [Object, nil] | |
| def [](key) | |
| @table[key.to_sym] | |
| end | |
| # @param key [Symbol, String] | |
| # @raise [ReservedNameError] if key collides with an existing method | |
| def []=(key, value) | |
| key = key.to_sym | |
| if reserved?(key) | |
| raise( | |
| ReservedNameError, | |
| "#{key.inspect} collides with a real #{self.class} method -- " \ | |
| "obj.#{key} would call that method, not return this value. " \ | |
| "Use a plain Hash if you need this exact key." | |
| ) | |
| end | |
| @table[key] = value | |
| end | |
| # @param key [Symbol, String] | |
| # @return [Boolean] | |
| def key?(key) = @table.key?(key.to_sym) | |
| alias has_key? key? | |
| # @param key [Symbol, String] | |
| # @return [Object, nil] the removed value | |
| def delete_field(key) = @table.delete(key.to_sym) | |
| # @yieldparam pair [Array(Symbol, Object)] | |
| # @return [Enumerator, self] | |
| def each_pair(&) | |
| return to_enum(__method__) { @table.size } unless block_given? | |
| @table.each_pair(&) | |
| self | |
| end | |
| alias each each_pair | |
| # @param other [#to_hash] | |
| # @raise [ReservedNameError] if any key in other collides with an existing method | |
| # @return [self] | |
| def merge!(other) | |
| other.to_hash.each { |key, value| self[key] = value } | |
| self | |
| end | |
| # @param other [#to_hash] | |
| # @return [DotHash] | |
| def merge(other) = dup.merge!(other) | |
| # @return [Hash] a dup of the internal table, never the live one | |
| def to_h(&block) = block ? @table.to_h(&block) : @table.dup | |
| alias to_hash to_h | |
| # @return [Hash] enables `case obj; in {foo:, bar:}` | |
| def deconstruct_keys(_keys) = @table | |
| def ==(other) | |
| other.is_a?(DotHash) && @table == other.to_hash | |
| end | |
| def inspect | |
| "#<#{self.class} #{@table.map { |k, v| "#{k}=#{v.inspect}" }.join(", ")}>" | |
| end | |
| alias to_s inspect | |
| # @raise [ReservedNameError] if the accessed name collides with an existing method | |
| def method_missing(method, *args) | |
| name = method.to_s | |
| if name.end_with?("=") | |
| self[name.delete_suffix("=")] = args.first | |
| else | |
| self[name] | |
| end | |
| end | |
| def respond_to_missing?(_method, _include_private = false) = true | |
| private | |
| # @return [Boolean] whether key is already a real method on this object | |
| def reserved?(key) | |
| self.class.method_defined?(key) || self.class.private_method_defined?(key) | |
| end | |
| def initialize_copy(other) | |
| super | |
| @table = other.to_hash | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment