How to avoid undefined error with not required value

I have member associated with two kind of prefectures.
Both prefectures is not required in database.

I have following code.

  schema "members" do
    field :name, :string

    belongs_to :prefecture1, Sample.Prefecture
    belongs_to :prefecture2, Sample.Prefecture
  end

When I try to get members and get JSON in view, it cause errors.
Because prefectures is not alway associated with members.

  def list_members(member_id) do
    query = from m in Member,
                 where: m.member_id == ^member_id,
                 preload: [:prefecture1, :prefecture2]

    Repo.all(query)
  end
  def render("member.json", %{member: member}) do
      %{id: member.id,
        prefecture1_name: member.prefecture1.prefecture_name,
        prefecture2_name: member.prefecture2.prefecture_name}
  end
UndefinedFunctionError at GET /api/members/2281
function nil.prefecture_name/0 is undefined

Please give me some advice how to avoid error and to handle this case.

It depends what You want to do, a simple if can return “” if nil, but if You don’t want the key, You might do it like this.

def render("member.json", %{member: member}) do
  Enum.reduce([:prefecture1, :prefecture2], %{id: member.id}, fn x, acc -> 
    prefecture = Map.get(member, x) 
    key = String.to_atom("#{x}_name") 
    if prefecture, do: Map.put(acc, key, prefecture.prefecture_name), else: acc
  end)
end

But it’s not tested… and I don’t like to generate atoms dynamically.

You mean I need to put some logic which return map?

You cannot call prefecture_name on an empty prefecture, so You should test it’s presence first.

Yes, I understand. However I did not know how to know presence and to fill with ‘’ value.
I have to understand your sample code first.

prefecture1_name: if member.prefecture1, do: member.prefecture1.prefecture_name, else: ""

# or if nil is ok

prefecture1_name: if member.prefecture1, do: member.prefecture1.prefecture_name
1 Like

I think you are right. However it’s ashamed I can’t write right code. I got syntax error.

If you really need to fall back to the empty string when having a nil input then you can just have a convenience function somewhere in your views, controller or the helpers:

def prefecture_name(%{name: name}) when is_binary(name), do: name
def prefecture_name(%{name: nil}), do: ""

And just call it like this:

  def render("member.json", %{member: member}) do
      %{
        id: member.id,
        prefecture1_name: prefecture_name(member.prefecture1),
        prefecture2_name: prefecture_name(member.prefecture2)
      }
  end

This should fail, I think… It’s not the name (prefecture_name), but the prefecture which can be nil.

Isn’t it like this?

def prefecture_name(%{prefecture_name: name}) when is_binary(name), do: name
def prefecture_name(nil), do: ""
1 Like

Ah, oops. Oversight. You are right.

But in any case I never do this: if something, do: :yes, else: :no – it reads like a PHP-ism and is not explicit. I’d always check for nil specifically and I never cared what is “truthy” or “falsy”. Code readability > everything else!

1 Like

I agree, i prefer not to use if, but I do exception for one liner command :slight_smile: I miss ternary operator

I would just do this…

  def render("member.json", %{member: member}) do
    %{
      id: member.id,
      prefecture1_name: member.prefecture1[:prefecture_name] || "",
      prefecture2_name: member.prefecture2[:prefecture_name] || "",
    }
  end

By accessing the prefecture#'s fields via [:fieldname] instead of .fieldname it will pass the existing nil on, then the || "" will convert any false or nil (nil in this case) to "".

Yeah but as @kokolegorille pointed out, the object itself might be nil, not the attribute. I overlooked this as well!

@dimitarvp Correct, that’s precisely why I used [...] for that, it handles when prefecture1 or prefecture2 is nill and just passes it on without looking up :prefecture_name.

1 Like

Don’t mind me, I caught the flu again and I’m doing more damage to the forum instead of contributing. :icon_eek:

Sorry!

2 Likes

Heh, happens to us all, some of my work is out for the ‘common cold’. ^.^