|
1 |
| -require "active_support/core_ext/hash/indifferent_access" |
2 |
| - |
3 | 1 | module HashWithDotAccess
|
4 |
| - class Hash < ActiveSupport::HashWithIndifferentAccess |
5 |
| - def respond_to_missing?(key, *) |
| 2 | + module Utils |
| 3 | + def self.normalized_value(obj, value) |
| 4 | + return value if value.instance_of?(obj.class) |
| 5 | + |
| 6 | + case value |
| 7 | + when ::Hash |
| 8 | + obj.class.new(value) |
| 9 | + when Array |
| 10 | + value = value.dup if value.frozen? |
| 11 | + value.map! { normalized_value(obj, _1) } |
| 12 | + else |
| 13 | + value |
| 14 | + end |
| 15 | + end |
| 16 | + end |
| 17 | + |
| 18 | + class Hash < ::Hash |
| 19 | + class << self |
| 20 | + undef_method :[] |
| 21 | + end |
| 22 | + |
| 23 | + def initialize(hsh = nil) |
| 24 | + super |
| 25 | + return unless hsh |
| 26 | + |
| 27 | + update(hsh) |
| 28 | + self.default_proc = hsh.default_proc |
| 29 | + self.default = hsh.default |
| 30 | + end |
| 31 | + |
| 32 | + def respond_to_missing?(key, *args) |
| 33 | + return false unless args.empty? |
| 34 | + |
6 | 35 | return true if "#{key}".end_with?("=")
|
7 | 36 |
|
8 | 37 | key?(key)
|
9 | 38 | end
|
10 | 39 |
|
11 |
| - def method_missing(key, *args) |
12 |
| - if "#{key}".end_with?("=") |
13 |
| - self["#{key}".chop] = args.first |
| 40 | + def key?(key) = super(key.to_s) |
| 41 | + |
| 42 | + alias_method :has_key?, :key? |
| 43 | + alias_method :include?, :key? |
| 44 | + alias_method :member?, :key? |
| 45 | + |
| 46 | + # save previous method |
| 47 | + alias_method :_assign, :[]= |
| 48 | + |
| 49 | + def [](key) = super(key.to_s) |
| 50 | + |
| 51 | + def []=(key, value) |
| 52 | + _assign(key.to_s, Utils.normalized_value(self, value)) |
| 53 | + end |
| 54 | + |
| 55 | + alias_method :store, :[]= |
| 56 | + |
| 57 | + def fetch(key, *args) = super(key.to_s, *args) |
| 58 | + |
| 59 | + def assoc(key, *args) = super(key.to_s) |
| 60 | + |
| 61 | + def values_at(*keys) = super(*keys.map!(&:to_s)) |
| 62 | + |
| 63 | + def fetch_values(*keys) = super(*keys.map!(&:to_s)) |
| 64 | + |
| 65 | + def method_missing(method_name, *args) |
| 66 | + key = method_name.to_s |
| 67 | + if key.end_with?("=") |
| 68 | + key_chop = key.chop |
| 69 | + self.class.define_method(key) { |value| self[key_chop] = value } |
| 70 | + self[key.chop] = args.first |
14 | 71 | elsif self.key?(key)
|
| 72 | + self.class.define_method(key) { self[key] } |
15 | 73 | self[key]
|
16 | 74 | elsif default_proc
|
| 75 | + super unless args.empty? |
17 | 76 | default_proc.call(self, key)
|
18 | 77 | else
|
| 78 | + super unless args.empty? |
19 | 79 | default
|
20 | 80 | end
|
21 | 81 | end
|
22 | 82 |
|
23 |
| - def update(other_hash) |
24 |
| - if other_hash.is_a? HashWithDotAccess::Hash |
25 |
| - super(other_hash) |
26 |
| - else |
27 |
| - other_hash.to_hash.each_pair do |key, value| |
28 |
| - if block_given? && key?(key) |
29 |
| - value = yield(convert_key(key), self[key], value) |
| 83 | + def update(*other_hashes) |
| 84 | + other_hashes.each do |other_hash| |
| 85 | + if other_hash.is_a? HashWithDotAccess::Hash |
| 86 | + super(other_hash) |
| 87 | + else |
| 88 | + other_hash.to_hash.each do |key, value| |
| 89 | + key = key.to_s |
| 90 | + if block_given? && key?(key) |
| 91 | + value = yield(key, self[key], value) |
| 92 | + end |
| 93 | + _assign(key, Utils.normalized_value(self, value)) |
30 | 94 | end
|
31 |
| - regular_writer(convert_key(key), convert_value(value)) |
32 | 95 | end
|
33 |
| - self |
34 | 96 | end
|
| 97 | + |
| 98 | + self |
35 | 99 | end
|
36 | 100 |
|
37 |
| - private |
| 101 | + alias_method :merge!, :update |
38 | 102 |
|
39 |
| - def convert_value(value, options = {}) # :doc: |
40 |
| - if value.is_a? ::Hash |
41 |
| - if options[:for] == :to_hash |
42 |
| - value.to_hash |
43 |
| - else |
44 |
| - value.with_dot_access |
45 |
| - end |
46 |
| - elsif value.is_a?(Array) |
47 |
| - if options[:for] != :assignment || value.frozen? |
48 |
| - value = value.dup |
49 |
| - end |
50 |
| - value.map! { |e| convert_value(e, options) } |
51 |
| - else |
52 |
| - value |
53 |
| - end |
| 103 | + def merge(...) = dup.update(...) |
| 104 | + |
| 105 | + def replace(...) |
| 106 | + clear |
| 107 | + update(...) |
| 108 | + end |
| 109 | + |
| 110 | + def dig(*args) |
| 111 | + super(args[0].to_s, *args[1..]) |
| 112 | + end |
| 113 | + |
| 114 | + def delete(key) = super(key.to_s) |
| 115 | + |
| 116 | + def except(*keys) = super(*keys.map!(&:to_s)) |
| 117 | + |
| 118 | + def slice(*keys) = self.class.new(super(*keys.map!(&:to_s))) |
| 119 | + |
| 120 | + def select(...) |
| 121 | + return to_enum(:select) unless block_given? |
| 122 | + |
| 123 | + dup.tap { _1.select!(...) } |
| 124 | + end |
| 125 | + |
| 126 | + def reject(...) |
| 127 | + return to_enum(:reject) unless block_given? |
| 128 | + |
| 129 | + dup.tap { _1.reject!(...) } |
| 130 | + end |
| 131 | + |
| 132 | + def transform_values(...) |
| 133 | + return to_enum(:transform_values) unless block_given? |
| 134 | + |
| 135 | + dup.tap { _1.transform_values!(...) } |
| 136 | + end |
| 137 | + |
| 138 | + def compact |
| 139 | + dup.tap { _1.compact! } |
54 | 140 | end
|
55 | 141 | end
|
56 |
| -end |
57 | 142 |
|
58 |
| -class Hash |
59 |
| - def with_dot_access |
60 |
| - HashWithDotAccess::Hash.new(self) |
| 143 | + module Refinements |
| 144 | + refine ::Hash do |
| 145 | + def as_dots |
| 146 | + HashWithDotAccess::Hash.new(self) |
| 147 | + end |
| 148 | + end |
61 | 149 | end
|
62 | 150 | end
|
0 commit comments