defmodule RMap.ActiveSupport do @moduledoc """ Summarized all of Hash functions in Rails.ActiveSupport. If a function with the same name already exists in Elixir, that is not implemented. Defines all of here functions when `use RMap.ActiveSupport`. """ @spec __using__(any) :: list defmacro __using__(_opts) do RUtils.define_all_functions!(__MODULE__) end import RMap.Ruby # https://www.rubydoc.info/gems/activesupport/Hash # [:as_json, :assert_valid_keys, :compact_blank, :compact_blank!, :deep_dup, :deep_merge, :deep_merge!, :deep_stringify_keys, :deep_stringify_keys!, :deep_symbolize_keys, :deep_symbolize_keys!, :deep_transform_keys, :deep_transform_keys!, :deep_transform_values, :deep_transform_values!, :except, :except!, :extract!, :extractable_options?, :reverse_merge, :reverse_merge!, :slice!, :stringify_keys, :stringify_keys!, :symbolize_keys, :symbolize_keys!, :to_query, :to_xml, :with_indifferent_access] # |> RUtils.required_functions([List, RMap.Ruby, REnum]) # × as_json # ✔ assert_valid_keys # × deep_dup # × deep_merge # ✔ deep_stringify_keys # ✔ deep_symbolize_keys # ✔ deep_transform_keys # ✔ deep_transform_values # × extractable_options? # × reverse_merge # ✔ stringify_keys # ✔ symbolize_keys # to_query # to_xml # with_indifferent_access TODO: Low priority @doc """ Validates all keys in a map match given keys, raising ArgumentError on a mismatch. ## Examples iex> RMap.assert_valid_keys(%{name: "Rob", years: "28"}, [:name, :age]) ** (ArgumentError) Unknown key: years. Valid keys are: name, age iex> RMap.assert_valid_keys(%{name: "Rob", age: "28"}, ["age"]) ** (ArgumentError) Unknown key: age. Valid keys are: age iex> RMap.assert_valid_keys(%{name: "Rob", age: "28"}, [:name, :age]) :ok """ @spec assert_valid_keys(map(), list()) :: :ok def assert_valid_keys(map, keys) do valid_keys_str = keys |> Enum.map(&IO.inspect(&1)) |> Enum.join(", ") each_key(map, fn key -> if(key not in keys) do raise ArgumentError, "Unknown key: #{key}. Valid keys are: #{valid_keys_str}" end end) end @doc """ Returns a map with all keys converted to strings. ## Examples iex> RMap.stringify_keys(%{name: "Rob", years: "28", nested: %{ a: 1 }}) %{"name" => "Rob", "nested" => %{a: 1}, "years" => "28"} """ @spec stringify_keys(map()) :: map() def stringify_keys(map) do transform_keys(map, &to_string(&1)) end @doc """ Returns a list with all keys converted to strings. This includes the keys from the root map and from all nested maps and arrays. ## Examples iex> RMap.deep_stringify_keys(%{name: "Rob", years: "28", nested: %{ a: 1 }}) %{"name" => "Rob", "nested" => %{"a" => 1}, "years" => "28"} iex> RMap.deep_stringify_keys(%{a: %{b: %{c: 1}, d: [%{a: 1, b: %{c: 2}}]}}) %{"a" => %{"b" => %{"c" => 1}, "d" => [%{"a" => 1, "b" => %{"c" => 2}}]}} """ @spec deep_stringify_keys(map()) :: map() def deep_stringify_keys(map) do deep_transform_keys(map, &to_string(&1)) end @doc """ Returns a map with all keys converted to atom. ## Examples iex> RMap.symbolize_keys(%{"name" => "Rob", "years" => "28", "nested" => %{ "a" => 1 }}) %{name: "Rob", nested: %{"a" => 1}, years: "28"} """ @spec symbolize_keys(map()) :: map() def symbolize_keys(map) do transform_keys(map, &String.to_atom(&1)) end @doc """ Returns a list with all keys converted to atom. This includes the keys from the root map and from all nested maps and arrays. ## Examples iex> RMap.deep_symbolize_keys(%{"name" => "Rob", "years" => "28", "nested" => %{ "a" => 1 }}) %{name: "Rob", nested: %{a: 1}, years: "28"} iex> RMap.deep_symbolize_keys(%{"a" => %{"b" => %{"c" => 1}, "d" => [%{"a" => 1, "b" => %{"c" => 2}}]}}) %{a: %{b: %{c: 1}, d: [%{a: 1, b: %{c: 2}}]}} """ @spec deep_symbolize_keys(map()) :: map() def deep_symbolize_keys(map) do deep_transform_keys(map, &String.to_atom(&1)) end @doc """ Returns a map with all keys converted by the function. This includes the keys from the root map and from all nested maps and arrays. ## Examples iex> RMap.deep_transform_keys(%{a: %{b: %{c: 1}}}, &to_string(&1)) %{"a" => %{"b" => %{"c" => 1}}} iex> RMap.deep_transform_keys(%{a: %{b: %{c: 1}, d: [%{a: 1, b: %{c: 2}}]}}, &inspect(&1)) %{":a" => %{":b" => %{":c" => 1}, ":d" => [%{":a" => 1, ":b" => %{":c" => 2}}]}} """ @spec deep_transform_keys(map(), function()) :: map() def deep_transform_keys(map, func) do map |> Enum.map(fn {k, v} -> cond do is_map(v) -> {func.(k), deep_transform_keys(v, func)} is_list(v) -> {func.(k), Enum.map(v, fn el -> deep_transform_keys(el, func) end)} true -> {func.(k), v} end end) |> Map.new() end @doc """ Returns a map with all values converted by the function. This includes the keys from the root map and from all nested maps and arrays. ## Examples iex> RMap.deep_transform_values(%{a: %{b: %{c: 1}}, d: 2}, &inspect(&1)) %{a: %{b: %{c: "1"}}, d: "2"} iex> RMap.deep_transform_values(%{a: %{b: %{c: 1}, d: [%{a: 1, b: %{c: 2}}]}}, &inspect(&1)) %{a: %{b: %{c: "1"}, d: [%{a: "1", b: %{c: "2"}}]}} """ @spec deep_transform_values(map(), function()) :: map() def deep_transform_values(map, func) do Enum.map(map, fn {k, v} -> cond do is_map(v) -> {k, deep_transform_values(v, func)} is_list(v) -> {k, Enum.map(v, fn el -> deep_transform_values(el, func) end)} true -> {k, func.(v)} end end) |> Map.new() end defdelegate atomize_keys(map), to: __MODULE__, as: :symbolize_keys defdelegate deep_atomize_keys(map), to: __MODULE__, as: :deep_symbolize_keys end