darnahsan

darnahsan

Custom filters on jsonb column using Flop

I am trying to implement custom filters on a table that has a jsonb column metadata using Flop. as per @woylie suggestions trying to use custom fields

Following the docs for custom fields came up with below code.

when trying to lookup metadata_is_active getting error

Postgrex expected a binary, got true. Please make sure the value you are passing matches the definition in your table or in your query or convert the value accordingly.

<url>?filters[0][field]=name&filters[0][value]=Expert&filters[1][field]=whatsapp_no&filters[1][value]=88827271111&filters[2][field]=metadata_is_active&filters[2][value]=true

for metadata_owner it throws no error just returns empty result even on values that should match

<url>?filters[0][field]=name&filters[0][value]=Expert&filters[1][field]=whatsapp_no&filters[1][value]=88827271111&filters[2][field]=metadata_owner&filters[2][value]=abc

I feel my noobness with dynamic and fragmented queries is probably where I am wrong.

organization.ex

@derive {
    Flop.Schema,
    filterable: [:name, :whatsapp_no, :metadata_is_active, :metadata_owner],
    sortable: [:name, :whatsapp_no, :inserted_at],
    max_limit: 100,
    default_limit: 50,
    adapter_opts: [
      custom_fields: [
        metadata_is_active: [
          filter: {OrganizationMetadataFilters, :metadata, []},
          ecto_type: :boolean
        ],
        metadata_owner: [
          filter: {OrganizationMetadataFilters, :metadata, []},
          ecto_type: :string
        ]
      ]
    ]
  }

  schema "organizations" do
    field :name, :string
    field :whatsapp_no, :string

    embeds_one :metadata, OrganizationMetadata

organization_metadata_filters.ex

defmodule Maverick.Organizations.OrganizationMetadataFilters do
  @moduledoc """
   Maverick.Organizations.MetadataFilters
  """

  import Ecto.Query

  def metadata(query, %Flop.Filter{field: name, value: value, op: op} = flop_filter, _) do
    metadata_field = field(name)
    metadata_value = value(name, value)

    expr =
      dynamic(
        [r],
        fragment(
          "metadata->>'?'",
          field(r, ^metadata_field)
        )
      )

    case metadata_value do
      {:ok, query_value} ->
        IO.inspect(flop_filter, label: "Flop filter")
        IO.inspect(metadata_field, label: "Metadata field")
        IO.inspect(query_value, label: "Metadata value")

        conditions =
          case op do
            :== -> dynamic([r], ^expr == ^query_value)
            :!= -> dynamic([r], ^expr != ^query_value)
            :> -> dynamic([r], ^expr > ^query_value)
            :< -> dynamic([r], ^expr < ^query_value)
            :>= -> dynamic([r], ^expr >= ^query_value)
            :<= -> dynamic([r], ^expr <= ^query_value)
          end

        IO.inspect(conditions, label: "conditions")

        where(query, ^conditions)

      :error ->
        IO.inspect("Error casting value #{value} for #{name}")
        query
    end
  end

  def field(:metadata_is_active), do: :is_active
  def field(:metadata_owner), do: :owner

  def value(:metadata_is_active, value), do: Ecto.Type.cast(:boolean, value)
  def value(:metadata_owner, value), do: Ecto.Type.cast(:string, value)
end

Marked As Solved

darnahsan

darnahsan

doing so fixes it for now

 def dynamic_expr(:metadata_active) do
    dynamic(
      [r],
      fragment(
        "(?->>'active')::boolean",
        field(r, :metadata)
      )
    )
  end

  def dynamic_expr(:metadata_owner) do
    dynamic(
      [r],
      fragment(
        "(?->>'owner')",
        field(r, :metadata)
      )
    )
  end

Flop RAW SQL

{"SELECT o0.\"id\", o0.\"name\", o0.\"whatsapp_no\", o0.\"metadata\", o0.\"inserted_at\", o0.\"updated_at\" FROM \"organizations\" AS o0 WHERE (o0.\"name\" = $1) AND (o0.\"whatsapp_no\" = $2) AND ((o0.\"metadata\"->>'active')::boolean = $3) ORDER BY o0.\"name\" LIMIT $4 OFFSET $5",
 ["Expert", "88827271111", true, 3, 0]}

Thanks for you help @woylie , I have another query for :in op, will discuss separately

Also Liked

woylie

woylie

With explicit type casting the 1st error is resolved but now I see the same empty result for both fields which mean they are not matching somehow.

Could you output the actual SQL? (Flop.validate → Flop.query → Ecto.Adapters.SQL.to_sql)

Also the wierd part is I see that metadata function is called twice for each request, is that expected ?

If you use offset/page-based pagination, Flop makes a second query to retrieve the total count. The parameters are only validated once, but the where clauses are built once for the main query and once for the count query.

woylie

woylie

Try passing the for option:

query = Flop.query(Organization, parsed_filters, for: Organization)

Otherwise Flop wouldn’t know how about the schema configuration.

Where Next?

Popular in Questions Top

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New
Mooodi
Given a string, how can I get access to its character by index? Enum.at("my_string", 2) doesn't work. Or rather, not char, but a substr...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New

We're in Beta

About us Mission Statement