Advertisement
Guest User

Untitled

a guest
Jan 27th, 2017
155
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.58 KB | None | 0 0
  1. require 'active_support/core_ext/hash/keys'
  2. require 'active_support/core_ext/hash/reverse_merge'
  3.  
  4. module ActiveSupport
  5. # Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered
  6. # to be the same.
  7. #
  8. # rgb = ActiveSupport::HashWithIndifferentAccess.new
  9. #
  10. # rgb[:black] = '#000000'
  11. # rgb[:black] # => '#000000'
  12. # rgb['black'] # => '#000000'
  13. #
  14. # rgb['white'] = '#FFFFFF'
  15. # rgb[:white] # => '#FFFFFF'
  16. # rgb['white'] # => '#FFFFFF'
  17. #
  18. # Internally symbols are mapped to strings when used as keys in the entire
  19. # writing interface (calling <tt>[]=</tt>, <tt>merge</tt>, etc). This
  20. # mapping belongs to the public interface. For example, given:
  21. #
  22. # hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1)
  23. #
  24. # You are guaranteed that the key is returned as a string:
  25. #
  26. # hash.keys # => ["a"]
  27. #
  28. # Technically other types of keys are accepted:
  29. #
  30. # hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1)
  31. # hash[0] = 0
  32. # hash # => {"a"=>1, 0=>0}
  33. #
  34. # but this class is intended for use cases where strings or symbols are the
  35. # expected keys and it is convenient to understand both as the same. For
  36. # example the +params+ hash in Ruby on Rails.
  37. #
  38. # Note that core extensions define <tt>Hash#with_indifferent_access</tt>:
  39. #
  40. # rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access
  41. #
  42. # which may be handy.
  43. class HashWithIndifferentAccess < Hash
  44. # Returns +true+ so that <tt>Array#extract_options!</tt> finds members of
  45. # this class.
  46. def extractable_options?
  47. true
  48. end
  49.  
  50. def with_indifferent_access
  51. dup
  52. end
  53.  
  54. def nested_under_indifferent_access
  55. self
  56. end
  57.  
  58. def initialize(constructor = {})
  59. if constructor.respond_to?(:to_hash)
  60. super()
  61. update(constructor)
  62. else
  63. super(constructor)
  64. end
  65. end
  66.  
  67. def default(key = nil)
  68. if key.is_a?(Symbol) && include?(key = key.to_s)
  69. self[key]
  70. else
  71. super
  72. end
  73. end
  74.  
  75. def self.new_from_hash_copying_default(hash)
  76. hash = hash.to_hash
  77. new(hash).tap do |new_hash|
  78. new_hash.default = hash.default
  79. new_hash.default_proc = hash.default_proc if hash.default_proc
  80. end
  81. end
  82.  
  83. def self.[](*args)
  84. new.merge!(Hash[*args])
  85. end
  86.  
  87. alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
  88. alias_method :regular_update, :update unless method_defined?(:regular_update)
  89.  
  90. # Assigns a new value to the hash:
  91. #
  92. # hash = ActiveSupport::HashWithIndifferentAccess.new
  93. # hash[:key] = 'value'
  94. #
  95. # This value can be later fetched using either +:key+ or +'key'+.
  96. def []=(key, value)
  97. regular_writer(convert_key(key), convert_value(value, for: :assignment))
  98. end
  99.  
  100. alias_method :store, :[]=
  101.  
  102. # Updates the receiver in-place, merging in the hash passed as argument:
  103. #
  104. # hash_1 = ActiveSupport::HashWithIndifferentAccess.new
  105. # hash_1[:key] = 'value'
  106. #
  107. # hash_2 = ActiveSupport::HashWithIndifferentAccess.new
  108. # hash_2[:key] = 'New Value!'
  109. #
  110. # hash_1.update(hash_2) # => {"key"=>"New Value!"}
  111. #
  112. # The argument can be either an
  113. # <tt>ActiveSupport::HashWithIndifferentAccess</tt> or a regular +Hash+.
  114. # In either case the merge respects the semantics of indifferent access.
  115. #
  116. # If the argument is a regular hash with keys +:key+ and +"key"+ only one
  117. # of the values end up in the receiver, but which one is unspecified.
  118. #
  119. # When given a block, the value for duplicated keys will be determined
  120. # by the result of invoking the block with the duplicated key, the value
  121. # in the receiver, and the value in +other_hash+. The rules for duplicated
  122. # keys follow the semantics of indifferent access:
  123. #
  124. # hash_1[:key] = 10
  125. # hash_2['key'] = 12
  126. # hash_1.update(hash_2) { |key, old, new| old + new } # => {"key"=>22}
  127. def update(other_hash)
  128. if other_hash.is_a? HashWithIndifferentAccess
  129. super(other_hash)
  130. else
  131. other_hash.to_hash.each_pair do |key, value|
  132. if block_given? && key?(key)
  133. value = yield(convert_key(key), self[key], value)
  134. end
  135. regular_writer(convert_key(key), convert_value(value))
  136. end
  137. self
  138. end
  139. end
  140.  
  141. alias_method :merge!, :update
  142.  
  143. # Checks the hash for a key matching the argument passed in:
  144. #
  145. # hash = ActiveSupport::HashWithIndifferentAccess.new
  146. # hash['key'] = 'value'
  147. # hash.key?(:key) # => true
  148. # hash.key?('key') # => true
  149. def key?(key)
  150. super(convert_key(key))
  151. end
  152.  
  153. alias_method :include?, :key?
  154. alias_method :has_key?, :key?
  155. alias_method :member?, :key?
  156.  
  157. # Same as <tt>Hash#fetch</tt> where the key passed as argument can be
  158. # either a string or a symbol:
  159. #
  160. # counters = ActiveSupport::HashWithIndifferentAccess.new
  161. # counters[:foo] = 1
  162. #
  163. # counters.fetch('foo') # => 1
  164. # counters.fetch(:bar, 0) # => 0
  165. # counters.fetch(:bar) { |key| 0 } # => 0
  166. # counters.fetch(:zoo) # => KeyError: key not found: "zoo"
  167. def fetch(key, *extras)
  168. super(convert_key(key), *extras)
  169. end
  170.  
  171. # Returns an array of the values at the specified indices:
  172. #
  173. # hash = ActiveSupport::HashWithIndifferentAccess.new
  174. # hash[:a] = 'x'
  175. # hash[:b] = 'y'
  176. # hash.values_at('a', 'b') # => ["x", "y"]
  177. def values_at(*indices)
  178. indices.collect { |key| self[convert_key(key)] }
  179. end
  180.  
  181. # Returns a shallow copy of the hash.
  182. #
  183. # hash = ActiveSupport::HashWithIndifferentAccess.new({ a: { b: 'b' } })
  184. # dup = hash.dup
  185. # dup[:a][:c] = 'c'
  186. #
  187. # hash[:a][:c] # => nil
  188. # dup[:a][:c] # => "c"
  189. def dup
  190. self.class.new(self).tap do |new_hash|
  191. set_defaults(new_hash)
  192. end
  193. end
  194.  
  195. # This method has the same semantics of +update+, except it does not
  196. # modify the receiver but rather returns a new hash with indifferent
  197. # access with the result of the merge.
  198. def merge(hash, &block)
  199. self.dup.update(hash, &block)
  200. end
  201.  
  202. # Like +merge+ but the other way around: Merges the receiver into the
  203. # argument and returns a new hash with indifferent access as result:
  204. #
  205. # hash = ActiveSupport::HashWithIndifferentAccess.new
  206. # hash['a'] = nil
  207. # hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
  208. def reverse_merge(other_hash)
  209. super(self.class.new_from_hash_copying_default(other_hash))
  210. end
  211.  
  212. # Same semantics as +reverse_merge+ but modifies the receiver in-place.
  213. def reverse_merge!(other_hash)
  214. replace(reverse_merge( other_hash ))
  215. end
  216.  
  217. # Replaces the contents of this hash with other_hash.
  218. #
  219. # h = { "a" => 100, "b" => 200 }
  220. # h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400}
  221. def replace(other_hash)
  222. super(self.class.new_from_hash_copying_default(other_hash))
  223. end
  224.  
  225. # Removes the specified key from the hash.
  226. def delete(key)
  227. super(convert_key(key))
  228. end
  229.  
  230. def stringify_keys!; self end
  231. def deep_stringify_keys!; self end
  232. def stringify_keys; dup end
  233. def deep_stringify_keys; dup end
  234. undef :symbolize_keys!
  235. undef :deep_symbolize_keys!
  236. def symbolize_keys; to_hash.symbolize_keys! end
  237. def deep_symbolize_keys; to_hash.deep_symbolize_keys! end
  238. def to_options!; self end
  239.  
  240. def select(*args, &block)
  241. dup.tap { |hash| hash.select!(*args, &block) }
  242. end
  243.  
  244. def reject(*args, &block)
  245. dup.tap { |hash| hash.reject!(*args, &block) }
  246. end
  247.  
  248. # Convert to a regular hash with string keys.
  249. def to_hash
  250. _new_hash = Hash.new
  251. set_defaults(_new_hash)
  252.  
  253. each do |key, value|
  254. _new_hash[key] = convert_value(value, for: :to_hash)
  255. end
  256. _new_hash
  257. end
  258.  
  259. protected
  260. def convert_key(key)
  261. key.kind_of?(Symbol) ? key.to_s : key
  262. end
  263.  
  264. def convert_value(value, options = {})
  265. if value.is_a? Hash
  266. if options[:for] == :to_hash
  267. value.to_hash
  268. else
  269. value.nested_under_indifferent_access
  270. end
  271. elsif value.is_a?(Array)
  272. if options[:for] != :assignment || value.frozen?
  273. value = value.dup
  274. end
  275. value.map! { |e| convert_value(e, options) }
  276. else
  277. value
  278. end
  279. end
  280.  
  281. def set_defaults(target)
  282. if default_proc
  283. target.default_proc = default_proc.dup
  284. else
  285. target.default = default
  286. end
  287. end
  288. end
  289. end
  290.  
  291. HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement