Skip to content

Commit

Permalink
Handle underscore_to_dash except and only
Browse files Browse the repository at this point in the history
  • Loading branch information
wasnotrice committed Nov 2, 2018
1 parent 5f8a77c commit 436f508
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 10 deletions.
48 changes: 41 additions & 7 deletions lib/jsonapi/utils/underscore.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule JSONAPI.Utils.Underscore do
Helpers for replacing underscores with dashes.
"""

def underscore?, do: Application.get_env(:jsonapi, :underscore_to_dash, false)
def underscore?, do: Application.get_env(:jsonapi, :underscore_to_dash, false) != false

@doc """
Replace dashes between words in `value` with underscores
Expand Down Expand Up @@ -42,9 +42,13 @@ defmodule JSONAPI.Utils.Underscore do
"_top__posts_"
"""
def underscore(value) when is_atom(value) do
value
|> to_string
|> underscore
if underscore?(value) do
value
|> to_string
|> underscore()
else
to_string(value)
end
end

def underscore(value) when is_binary(value) do
Expand All @@ -56,14 +60,44 @@ defmodule JSONAPI.Utils.Underscore do
end

def underscore(value) when is_map(value) do
Enum.into(value, %{}, &underscore/1)
Enum.into(value, %{}, &underscore(&1))
end

def underscore({key, value}) do
if is_map(value) do
{underscore(key), underscore(value)}
if underscore?(key) do
if is_map(value) do
{underscore(key), underscore(value)}
else
{underscore(key), value}
end
else
{underscore(key), value}
end
end

defp underscore?(key) do
config = Application.get_env(:jsonapi, :underscore_to_dash)
config_specifies_underscore?(config, key)
end

defp config_specifies_underscore?(true, _), do: true
defp config_specifies_underscore?(false, _), do: false

defp config_specifies_underscore?(config, key) when is_list(config) do
if Keyword.has_key?(config, :only) do
config_specifies?(config, :only, key)
else
!config_specifies?(config, :except, key)
end
end

defp config_specifies?(config, only_or_except, key) when is_list(config) do
case Keyword.get(config, only_or_except) do
list when is_list(list) ->
Enum.member?(list, key)

_ ->
false
end
end
end
6 changes: 5 additions & 1 deletion test/jsonapi_query_parser_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ defmodule JSONAPI.QueryParserTest do
[
author: JSONAPI.QueryParserTest.UserView,
comments: JSONAPI.QueryParserTest.CommentView,
best_friends: JSONAPI.QueryParsertTest.UserView
best_friends: JSONAPI.QueryParsertTest.UserView,
_private_friend: JSONAPI.QueryParserTest.UserView,
nonstandard__friend: JSONAPI.QueryParserTest.UserView
]
end
end
Expand Down Expand Up @@ -93,6 +95,8 @@ defmodule JSONAPI.QueryParserTest do
config = struct(Config, view: MyView)
assert parse_include(config, "author.top-posts").includes == [author: :top_posts]
assert parse_include(config, "best-friends").includes == [:best_friends]
assert parse_include(config, "_private-friend").includes == [:_private_friend]
assert parse_include(config, "nonstandard__friend").includes == [:nonstandard__friend]
end
end

Expand Down
174 changes: 172 additions & 2 deletions test/serializer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,38 @@ defmodule JSONAPISerializerTest do
end
end

# The `metadata` field may contain arbitrary key-value data, such as user-supplied JSON.
defmodule PostWithMetadataView do
use JSONAPI.View

def fields,
do: [
:text,
:body,
:full_description,
:inserted_at,
:metadata,
:_private_attribute,
:nonstandard__attribute
]

def meta(data, _conn), do: %{meta_text: "meta_#{data[:text]}"}
def type, do: "mytype"

def relationships do
[
author: {JSONAPISerializerTest.UserView, :include},
best_comments: {JSONAPISerializerTest.CommentView, :include}
]
end

def links(data, conn) do
%{
next: url_for_pagination(data, conn, %{cursor: "some-string"})
}
end
end

defmodule UserView do
use JSONAPI.View

Expand Down Expand Up @@ -358,7 +390,7 @@ defmodule JSONAPISerializerTest do
assert Enum.count(encoded.included) == 4
end

describe "when underscore_to_dash == true" do
describe "when underscore_to_dash: true" do
setup do
Application.put_env(:jsonapi, :underscore_to_dash, true)

Expand All @@ -374,6 +406,9 @@ defmodule JSONAPISerializerTest do
id: 1,
text: "Hello",
inserted_at: NaiveDateTime.utc_now(),
metadata: %{series_title: "Beginnings", __private_code: 123, double__underscore: true},
_private_attribute: "please do not touch",
nonstandard__attribute: "no rules apply",
body: "Hello world",
full_description: "This_is_my_description",
author: %{id: 2, username: "jbonds", first_name: "jerry", last_name: "bonds"},
Expand All @@ -386,7 +421,7 @@ defmodule JSONAPISerializerTest do
]
}

encoded = Serializer.serialize(PostView, data, nil)
encoded = Serializer.serialize(PostWithMetadataView, data, nil)

attributes = encoded[:data][:attributes]
relationships = encoded[:data][:relationships]
Expand All @@ -395,6 +430,14 @@ defmodule JSONAPISerializerTest do
assert attributes["full-description"] == data[:full_description]
assert attributes["inserted-at"] == data[:inserted_at]

# Maps are affected
assert attributes["metadata"]["series-title"] == data[:metadata][:series_title]
assert attributes["metadata"]["__private-code"] == data[:metadata][:__private_code]
assert attributes["metadata"]["double__underscore"] == data[:metadata][:double__underscore]

assert attributes["_private-attribute"] == data[:_private_attribute]
assert attributes["nonstandard__attribute"] == data[:nonstandard__attribute]

assert Enum.find(included, fn i -> i[:type] == "user" && i[:id] == "2" end)[:attributes][
"last-name"
] == "bonds"
Expand All @@ -410,6 +453,133 @@ defmodule JSONAPISerializerTest do
end
end

describe "when underscore_to_dash: except" do
setup do
Application.put_env(:jsonapi, :underscore_to_dash,
except: [:full_description, :_private_attribute, :metadata]
)

on_exit(fn ->
Application.delete_env(:jsonapi, :underscore_to_dash)
end)

{:ok, []}
end

test "serialize properly uses underscore_to_dash on both attributes and relationships" do
data = %{
id: 1,
text: "Hello",
inserted_at: NaiveDateTime.utc_now(),
metadata: %{series_title: "Beginnings", __private_code: 123, double__underscore: true},
_private_attribute: "please do not touch",
nonstandard__attribute: "no rules apply",
body: "Hello world",
full_description: "This_is_my_description",
author: %{id: 2, username: "jbonds", first_name: "jerry", last_name: "bonds"},
best_comments: [
%{
id: 5,
text: "greatest comment ever",
user: %{id: 4, username: "jack", last_name: "bronds"}
}
]
}

encoded = Serializer.serialize(PostWithMetadataView, data, nil)

attributes = encoded[:data][:attributes]
relationships = encoded[:data][:relationships]
included = encoded[:included]

assert attributes["inserted-at"] == data[:inserted_at]
assert attributes["nonstandard__attribute"] == data[:nonstandard__attribute]

# Not modified
assert attributes["full_description"] == data[:full_description]
assert attributes["metadata"] == data[:metadata]
assert attributes["_private_attribute"] == data[:_private_attribute]

assert Enum.find(included, fn i -> i[:type] == "user" && i[:id] == "2" end)[:attributes][
"last-name"
] == "bonds"

assert Enum.find(included, fn i -> i[:type] == "user" && i[:id] == "4" end)[:attributes][
"last-name"
] == "bronds"

assert List.first(relationships["best-comments"][:data])[:id] == "5"

assert relationships["best-comments"][:links][:self] ==
"/mytype/1/relationships/best-comments"
end
end

describe "when underscore_to_dash: only" do
setup do
Application.put_env(:jsonapi, :underscore_to_dash,
only: [:full_description, :inserted_at, :last_name]
)

on_exit(fn ->
Application.delete_env(:jsonapi, :underscore_to_dash)
end)

{:ok, []}
end

test "serialize properly uses underscore_to_dash on both attributes and relationships" do
data = %{
id: 1,
text: "Hello",
inserted_at: NaiveDateTime.utc_now(),
metadata: %{series_title: "Beginnings", __private_code: 123, double__underscore: true},
_private_attribute: "please do not touch",
nonstandard__attribute: "no rules apply",
body: "Hello world",
full_description: "This_is_my_description",
author: %{id: 2, username: "jbonds", first_name: "jerry", last_name: "bonds"},
best_comments: [
%{
id: 5,
text: "greatest comment ever",
user: %{id: 4, username: "jack", last_name: "bronds"}
}
]
}

encoded = Serializer.serialize(PostWithMetadataView, data, nil)

attributes = encoded[:data][:attributes]
relationships = encoded[:data][:relationships]
included = encoded[:included]

# Modified
assert attributes["full-description"] == data[:full_description]
assert attributes["inserted-at"] == data[:inserted_at]

# Not modified
assert attributes["metadata"] == data[:metadata]
assert attributes["nonstandard__attribute"] == data[:nonstandard__attribute]
assert attributes["_private_attribute"] == data[:_private_attribute]

# Modified
assert Enum.find(included, fn i -> i[:type] == "user" && i[:id] == "2" end)[:attributes][
"last-name"
] == "bonds"

assert Enum.find(included, fn i -> i[:type] == "user" && i[:id] == "4" end)[:attributes][
"last-name"
] == "bronds"

# Not modified
assert List.first(relationships["best_comments"][:data])[:id] == "5"

assert relationships["best_comments"][:links][:self] ==
"/mytype/1/relationships/best_comments"
end
end

test "serialize does not merge `included` if not configured" do
data = %{
id: 1,
Expand Down

0 comments on commit 436f508

Please sign in to comment.