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

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
Kurisu
For example for a current url like http://localhost:4000/cosmetic/products?_utf8=✓&amp;query=perfume&amp;page=2, I would like to get: ...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New

Other popular topics Top

Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
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 52341 488
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
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

Latest on Elixir Forum

We're in Beta

About us Mission Statement