AE
Ash Elixir•3y ago
Blibs

Parse a new FilterForm from url params

I don't know if I'm missing something, but I can't find a way to fetch the components from a filter form as a map to add it as params for the URL query and then parse it back to a new FilterForm from these same params? For example, let's say I have a FilterForm with 2 predicates, I want to extract that information into a map so I can use it to form my url like this:
filter_params = FilterForm.some_function(form)

push_patch(socket, to: "/my_url?#{filter_params}")
filter_params = FilterForm.some_function(form)

push_patch(socket, to: "/my_url?#{filter_params}")
And then, inside a handle_params, get that filter_params back and recreate that same FilterForm:
form = FilterForm.parse(User, filter_params)
form = FilterForm.parse(User, filter_params)
23 Replies
ZachDaniel
ZachDaniel•3y ago
form.params will get you the passed in params So you can use that to save in query params And the FilterForm.validate(form, params_from_url) would use those params
Blibs
BlibsOP•3y ago
But that doesn't include added predicates AFAIK For example, this code:
%{id: id} = form_1 = FilterForm.new(User)

%{components: [%{id: component_id}]} = form_2 =
FilterForm.add_predicate(form_1, :first_name, :contains, "Eduardo")
%{id: id} = form_1 = FilterForm.new(User)

%{components: [%{id: component_id}]} = form_2 =
FilterForm.add_predicate(form_1, :first_name, :contains, "Eduardo")
If I just get the params, I will get this map:
params: %{
"components" => %{},
"id" => "656bd5d9-cb14-4934-9954-f425b087d78f",
"negated" => false,
"operator" => "and"
},
params: %{
"components" => %{},
"id" => "656bd5d9-cb14-4934-9954-f425b087d78f",
"negated" => false,
"operator" => "and"
},
As you can see, it doesn't include any information about the predicate that I added The only way I see this work is if I do something like this:
%{params: params, components: components} = form_2

components = Enum.map(components, fn %{params: params} -> params end)

params = Map.put(params, "components", components)

FilterForm.new(User, params: params)
%{params: params, components: components} = form_2

components = Enum.map(components, fn %{params: params} -> params end)

params = Map.put(params, "components", components)

FilterForm.new(User, params: params)
ZachDaniel
ZachDaniel•3y ago
Ah, try FilterForm.params(form)
Blibs
BlibsOP•3y ago
There is not such function in FilterForm, do you mean params_for_query/1? Either way, using params_for_query/1 will give me a empty "components"map, so it doesn't solve the issue:
iex(160)> FilterForm.params_for_query(form_2)
%{"components" => %{}, "negated" => false, "operator" => "and"}
iex(160)> FilterForm.params_for_query(form_2)
%{"components" => %{}, "negated" => false, "operator" => "and"}
So, this is my solution for now:
defp convert_to_params(filter_form) do
%{params: params, components: components} = filter_form

components = Enum.map(components, fn %{params: params} -> params end)

params |> Map.drop(["id"]) |> Map.put("components", components) |> :erlang.term_to_binary() |> Base.encode64()
end
defp convert_to_params(filter_form) do
%{params: params, components: components} = filter_form

components = Enum.map(components, fn %{params: params} -> params end)

params |> Map.drop(["id"]) |> Map.put("components", components) |> :erlang.term_to_binary() |> Base.encode64()
end
This will return a Base64 with all the data I need and then I can decode it and create a new form with it or validate an existing one with these as params. This feels very much like a hack, but is the best solution I could come up with for now. Also, another odd thing (I can create a new post if you prefer) is that the validate function will internet a empty string predicate value as nil even thought if I add it using add_predicate I can set the value to "". This breaks things since the query will not working with nil (it will return zero results) but works fine if the value is "" and the operation is contains. Is this the desired behavior? If so can we add an option to change it on demand?
ZachDaniel
ZachDaniel•3y ago
🤔 yeah, interesting So the idea is that you'd do it with params_for_query So I would definitely not suggest using term_to_binary, since params_for_query should do what you want
Blibs
BlibsOP•3y ago
The problem is that params_for_query need to also filter out fields that have a nil value, otherwise the query encoder will fail, that's why I decided to just use term_to_binary instead of trying to handle all these corner cases But yeah, if we can make params_for_query always return a valid map to be encoded, that's the best approach
ZachDaniel
ZachDaniel•3y ago
I just pushed a fix up for the nil/"" thing oh So all you need is for "components" to be non empty?
Blibs
BlibsOP•3y ago
I will explain the corner cases in about 1~2 hours, I need to leave the PC right now Yep, right now, if I add a predicate to the form, it will not show up in the params_for_query response. For example, in the code I showed in the first post, I add a predicate to filter :first_name with value "Eduardo" and operator contains. When I run the params_for_query, I don't get any data for that predicate, meaning that if I use the params_for_query response, I will lose that information and i will not be able to copy/paste the resulting URL and get the same filters.
ZachDaniel
ZachDaniel•3y ago
Okay, I pushed up some better handling of this can you try ash_phoenix main?
Blibs
BlibsOP•3y ago
yep, just a sec Seems to be working, I will try some corner cases and report back @Zach Daniel I was wondering, do you think it would make sense to also hide the ids from predicates on the params_for_query reply and then just regerate them when running validate or new? Unless I'm missing something, I can't see what we would lose if that id is just regenerated and at the same time it would make the query URL way smaller
ZachDaniel
ZachDaniel•3y ago
Yep I accidentally removed that code So we should not include the id
Blibs
BlibsOP•3y ago
But we also need that validate and new functions regenerate the id correct? I didn't test with the main code, but with the latest release, if I remove the ids, it will generate the predicate with nil as the id Also, I think I found a bug. Let's say I have this FilterForm:
%AshPhoenix.FilterForm{
id: "47d28a15-cc1c-418f-b537-4ea19be16fe6",
resource: Marketplace.Accounts.User,
transform_errors: nil,
name: "filter",
valid?: true,
negated?: false,
params: %{
"components" => %{},
"id" => "47d28a15-cc1c-418f-b537-4ea19be16fe6",
"negated" => "false",
"operator" => "and"
},
components: [
%AshPhoenix.FilterForm.Predicate{
id: "960374e7-3e68-4406-b922-56755a3754d7",
field: :first_name,
value: nil,
transform_errors: nil,
operator: :contains,
params: %{
"field" => :first_name,
"id" => "960374e7-3e68-4406-b922-56755a3754d7",
"operator" => :contains,
"value" => nil
},
arguments: %AshPhoenix.FilterForm.Arguments{
input: %{},
params: %{},
arguments: [],
errors: []
},
negated?: false,
path: [],
errors: [],
valid?: true
}
],
operator: :and,
remove_empty_groups?: false
}
%AshPhoenix.FilterForm{
id: "47d28a15-cc1c-418f-b537-4ea19be16fe6",
resource: Marketplace.Accounts.User,
transform_errors: nil,
name: "filter",
valid?: true,
negated?: false,
params: %{
"components" => %{},
"id" => "47d28a15-cc1c-418f-b537-4ea19be16fe6",
"negated" => "false",
"operator" => "and"
},
components: [
%AshPhoenix.FilterForm.Predicate{
id: "960374e7-3e68-4406-b922-56755a3754d7",
field: :first_name,
value: nil,
transform_errors: nil,
operator: :contains,
params: %{
"field" => :first_name,
"id" => "960374e7-3e68-4406-b922-56755a3754d7",
"operator" => :contains,
"value" => nil
},
arguments: %AshPhoenix.FilterForm.Arguments{
input: %{},
params: %{},
arguments: [],
errors: []
},
negated?: false,
path: [],
errors: [],
valid?: true
}
],
operator: :and,
remove_empty_groups?: false
}
And I receive this as params for the validate:
%{
"components" => %{
"0" => %{
"field" => "surname",
"id" => "960374e7-3e68-4406-b922-56755a3754d7",
"operator" => "contains",
"value" => ""
}
}
}
%{
"components" => %{
"0" => %{
"field" => "surname",
"id" => "960374e7-3e68-4406-b922-56755a3754d7",
"operator" => "contains",
"value" => ""
}
}
}
After running the validate, this is the result FilterForm:
%AshPhoenix.FilterForm{
id: "47d28a15-cc1c-418f-b537-4ea19be16fe6",
resource: Marketplace.Accounts.User,
transform_errors: nil,
name: "filter",
valid?: true,
negated?: false,
params: %{
"components" => %{
"0" => %{
"arguments" => nil,
"field" => "surname",
"id" => "960374e7-3e68-4406-b922-56755a3754d7",
"negated" => false,
"operator" => "contains",
"path" => nil,
"value" => ""
}
},
"id" => "2c7e5ecf-ad41-4828-9d0d-12181ebec628",
"negated" => false,
"operator" => "and"
},
components: [
%AshPhoenix.FilterForm.Predicate{
id: "960374e7-3e68-4406-b922-56755a3754d7",
field: :surname,
value: nil,
transform_errors: nil,
operator: nil,
params: %{
"arguments" => nil,
"field" => "surname",
"id" => "960374e7-3e68-4406-b922-56755a3754d7",
"negated" => false,
"operator" => nil,
"path" => nil,
"value" => nil
},
arguments: %AshPhoenix.FilterForm.Arguments{
input: %{},
params: %{},
arguments: [],
errors: []
},
negated?: false,
path: [],
errors: [],
valid?: true
}
],
operator: :and,
remove_empty_groups?: false
}
%AshPhoenix.FilterForm{
id: "47d28a15-cc1c-418f-b537-4ea19be16fe6",
resource: Marketplace.Accounts.User,
transform_errors: nil,
name: "filter",
valid?: true,
negated?: false,
params: %{
"components" => %{
"0" => %{
"arguments" => nil,
"field" => "surname",
"id" => "960374e7-3e68-4406-b922-56755a3754d7",
"negated" => false,
"operator" => "contains",
"path" => nil,
"value" => ""
}
},
"id" => "2c7e5ecf-ad41-4828-9d0d-12181ebec628",
"negated" => false,
"operator" => "and"
},
components: [
%AshPhoenix.FilterForm.Predicate{
id: "960374e7-3e68-4406-b922-56755a3754d7",
field: :surname,
value: nil,
transform_errors: nil,
operator: nil,
params: %{
"arguments" => nil,
"field" => "surname",
"id" => "960374e7-3e68-4406-b922-56755a3754d7",
"negated" => false,
"operator" => nil,
"path" => nil,
"value" => nil
},
arguments: %AshPhoenix.FilterForm.Arguments{
input: %{},
params: %{},
arguments: [],
errors: []
},
negated?: false,
path: [],
errors: [],
valid?: true
}
],
operator: :and,
remove_empty_groups?: false
}
As you can see, for some reason the operator field changed to nil even though the value is passed in the params
ZachDaniel
ZachDaniel•3y ago
🤔🤔🤔 Okay, we’ll I’m not at my laptop but it looks like we have 3 issues so far 1. Not stripping ids from params 2. Not regenerating ids when nil. 3. Somehow losing that operator
Blibs
BlibsOP•3y ago
Yep, indeed
ZachDaniel
ZachDaniel•3y ago
So you can probably PR the fix for #1 pretty easily Just need to remove id from the list, line 799 in filter form Would do it but I’m on my phone
Blibs
BlibsOP•3y ago
All right, let me try that I finished 1 will create the PR after I finish the other ones. 2 is working fine in main. Working on 3 right now
Blibs
BlibsOP•3y ago
Regarding 3, the issue is in this if. The logic here will change both operator and value to nil if the field changes (which it does since I changed it from :first_name to :surname. Not sure if I should change anything here or this is the correct behavior
No description
Blibs
BlibsOP•3y ago
personally I would like to be able to define a default value for both value and operator in this case instead of simply changing it to nil So, I created two branches just with some suggestion regarding how to handle this. The first one https://github.com/sezaru/ash_phoenix/tree/add_default_values_for_validate adds a options field to the validate function that allows you to pass a default value for value and operator in case they need to be reset. You would use it like this:
FilterForm.validate(form_2, params, default_value: "", default_operator: :contains)
FilterForm.validate(form_2, params, default_value: "", default_operator: :contains)
The other one https://github.com/sezaru/ash_phoenix/tree/add_option_to_not_reset_on_validate adds a options field to the validate function that allows you to define if you want to reset the values if field changes. You would use it like this:
FilterForm.validate(form_2, params, reset_on_change?: false)
FilterForm.validate(form_2, params, reset_on_change?: false)
Both solutions solve the issue, but I'm not sure if they translate well to what you expect as inputs of a validate function
ZachDaniel
ZachDaniel•3y ago
I like the second option best I can imagine that it would be better to default that to false so you'd have to say reset_on_change?: true, but lets start with defaulting to true SO if you want to PR that I'll accept it 🙂
Blibs
BlibsOP•3y ago
Ok, I just created the PR
ZachDaniel
ZachDaniel•3y ago
merged 🙂
Blibs
BlibsOP•3y ago
Any idea when you will push a new version of that library?
ZachDaniel
ZachDaniel•3y ago
new release should be going out soon

Did you find this page helpful?