Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify filters #11

Merged
merged 2 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/rocketsized/creator/country.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ defmodule Rocketsized.Creator.Country do
|> cast(attrs, [:name, :short_name, :code, :source])
|> cast_attachments(attrs, [:flag])
|> unique_constraint(:name)
|> unique_constraint(:code)
|> validate_required([:name, :short_name, :code, :flag])
end
end
35 changes: 19 additions & 16 deletions lib/rocketsized/rocket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -530,15 +530,15 @@ defmodule Rocketsized.Rocket do
end

def flop_vehicles_title(%Flop{} = flop, default \\ "") do
with ids = [_ | _] <- Flop.Filter.get_value(flop.filters, :search),
items = [_ | _] <- list_vehicle_filters_by_ids(ids) do
with slugs = [_ | _] <- Flop.Filter.get_value(flop.filters, :search),
items = [_ | _] <- search_slugs(slugs) do
for {type, filters} <- items |> Enum.group_by(& &1.type) do
filters_title = filters |> Enum.map(& &1.title) |> Enum.join(", ")

case type do
:country -> "from #{filters_title}"
:vehicle -> "#{filters_title}"
:manufacturer -> "by #{filters_title}"
:rocket -> "#{filters_title}"
:org -> "by #{filters_title}"
end
end
|> Enum.join(" or ")
Expand All @@ -560,6 +560,10 @@ defmodule Rocketsized.Rocket do
join(query, :inner, [v], assoc(v, :manufacturers), as: :manufacturers)
end

defp join_vehicle_assoc(query, :country) do
join(query, :inner, [v], assoc(v, :country), as: :country)
end

@spec vehicles_attribution_list(list(%{image_meta: %{attribution: String.t()}})) :: String.t()
def vehicles_attribution_list(vehicles) do
vehicles
Expand Down Expand Up @@ -624,30 +628,29 @@ defmodule Rocketsized.Rocket do
Repo.delete_all(Render)
end

alias Rocketsized.Rocket.VehicleFilter
alias Rocketsized.Rocket.SearchSlug

def list_vehicle_filters_by_query(q) do
from(vh in VehicleFilter,
def search_slugs_query(q) do
from(vh in SearchSlug,
where: ilike(vh.title, ^"#{q}%"),
or_where: ilike(vh.subtitle, ^"#{q}%"),
limit: 10
)
|> Repo.all()
end

@spec list_vehicle_filters_by_ids(list(String.t())) :: list(VehicleFilter.t())
def list_vehicle_filters_by_ids(ids) do
@spec search_slugs(list(String.t())) :: list(SearchSlug.t())
def search_slugs(slugs) do
groups =
ids
|> Enum.map(&VehicleFilter.Type.load(&1))
|> VehicleFilter.Type.to_valid_list()
|> Enum.group_by(& &1.type, & &1.id)
|> Enum.map(fn {type, ids} ->
dynamic([vf], vf.type == ^type and vf.id in ^ids)
slugs
|> Enum.map(&SearchSlug.Type.load!(&1))
|> Enum.group_by(& &1.type, & &1.slug)
|> Enum.map(fn {type, slugs} ->
dynamic([s], s.type == ^type and s.slug in ^slugs)
end)
|> Enum.reduce(fn group, query -> dynamic([_], ^query or ^group) end)

from(vf in VehicleFilter, where: ^groups, limit: 20) |> Repo.all()
from(s in SearchSlug, where: ^groups, limit: 20) |> Repo.all()
end

def list_vehicles_image_meta() do
Expand Down
7 changes: 4 additions & 3 deletions lib/rocketsized/rocket/render.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ defmodule Rocketsized.Rocket.Render do
use Ecto.Schema
use Waffle.Ecto.Schema
import Ecto.Changeset
alias Rocketsized.Rocket.Render.Image

schema "renders" do
field :filters, {:array, :string}
field :type, Ecto.Enum, values: [:poster_portrait, :poster_landscape, :wallpaper]
field :image, Rocketsized.Rocket.Render.Image.Type
field :image, Image.Type

timestamps()
end

@type t :: %__MODULE__{
filters: list(Rocketsized.Rocket.VehicleFilter.Type.type()),
image: Rocketsized.Rocket.Vehicle.Image.Type.type(),
filters: list(String.t()),
image: Image.Type.type(),
type: :poster_portrait | :poster_landscape | :wallpaper
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
defmodule Rocketsized.Rocket.VehicleFilter do
defmodule Rocketsized.Rocket.SearchSlug do
use Ecto.Schema

@primary_key false
schema "vehicle_filters" do
field :type, Ecto.Enum, values: [:vehicle, :country, :manufacturer]
field :id, :integer
schema "search_slugs" do
field :type, Ecto.Enum, values: [:rocket, :country, :org]
field :slug, :string
field :title, :string
field :subtitle, :string
field :source, :string
field :image, :string
end

@type t :: %__MODULE__{
type: :vehicle | :country | :manufacturer,
id: integer(),
type: :rocket | :country | :org,
slug: String.t(),
title: String.t(),
image: String.t() | nil,
source: String.t() | nil,
Expand Down
56 changes: 56 additions & 0 deletions lib/rocketsized/rocket/search_slug/type.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
defmodule Rocketsized.Rocket.SearchSlug.Type do
@behaviour Ecto.Type
import Ecto.Query
alias Rocketsized.Rocket.SearchSlug

def type, do: :string

def cast(value) do
{:ok, value}
end

def load("o_" <> slug), do: {:ok, %SearchSlug{type: :org, slug: slug}}
def load("c_" <> slug), do: {:ok, %SearchSlug{type: :country, slug: slug}}
def load("r_" <> slug), do: {:ok, %SearchSlug{type: :rocket, slug: slug}}
def load(_value), do: :error

def load!(value) do
{:ok, load} = load(value)
load
end

def dump(%{type: :org, slug: slug}), do: {:ok, "o_#{slug}"}
def dump(%{type: :country, slug: slug}), do: {:ok, "c_#{slug}"}
def dump(%{type: :rocket, slug: slug}), do: {:ok, "r_#{slug}"}
def dump(_), do: :error

def dump!(value) do
{:ok, dump} = dump(value)
dump
end

def equal?(a, b) do
a == b
end

def embed_as(_format), do: :self

def apply(query, %Flop.Filter{value: value, op: _op}, _opts) when length(value) > 0 do
groups =
value
|> Enum.map(&load!(&1))
|> Enum.group_by(& &1.type, & &1.slug)
|> Enum.map(fn
{:rocket, slugs} -> dynamic([v], v.slug in ^slugs)
{:country, slugs} -> dynamic([country: c], c.code in ^slugs)
{:org, slugs} -> dynamic([manufacturers: m], m.slug in ^slugs)
end)
|> Enum.reduce(&dynamic([_], ^&1 or ^&2))

where(query, [_], ^groups)
end

def apply(query, _flop, _opts) do
query
end
end
6 changes: 3 additions & 3 deletions lib/rocketsized/rocket/vehicle.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ defmodule Rocketsized.Rocket.Vehicle do
],
custom_fields: [
search: [
bindings: [:vehicle_manufacturers],
filter: {Rocketsized.Rocket.VehicleFilter.Type, :search, []},
ecto_type: Rocketsized.Rocket.VehicleFilter.Type,
bindings: [:manufacturers, :country],
filter: {Rocketsized.Rocket.SearchSlug.Type, :apply, []},
ecto_type: Rocketsized.Rocket.SearchSlug.Type,
operators: [:in]
]
]
Expand Down
75 changes: 0 additions & 75 deletions lib/rocketsized/rocket/vehicle_filter/type.ex

This file was deleted.

18 changes: 0 additions & 18 deletions lib/rocketsized_web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -398,24 +398,6 @@ defmodule RocketsizedWeb.CoreComponents do
"""
end

def input(%{type: "combobox"} = assigns) do
~H"""
<div phx-feedback-for={@name}>
<.label for={@id}><%= @label %></.label>

<.live_component
module={RocketsizedWeb.RocketgridLive.ComboboxComponent}
id={@id}
value={@value}
name={@name}
{@rest}
/>

<.error :for={msg <- @errors}><%= msg %></.error>
</div>
"""
end

# All other inputs text, datetime-local, url, password, etc. are handled here...
def input(assigns) do
~H"""
Expand Down
4 changes: 4 additions & 0 deletions lib/rocketsized_web/components/layouts/root.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
<.live_title suffix=" · Rocketsized">
<%= assigns[:page_title] %>
</.live_title>
<meta
name="description"
content="List and compare various launch vehicles against one another. Download posters featuring all of them put side by side."
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
Expand Down
39 changes: 39 additions & 0 deletions lib/rocketsized_web/filter_params.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
defmodule RocketsizedWeb.FilterParams do
alias Rocketsized.Rocket.SearchSlug.Type
alias Rocketsized.Rocket.SearchSlug

def add(%Flop{} = flop, %SearchSlug{} = search_slug) do
Map.update!(flop, :filters, fn filters ->
search = Flop.Filter.get_value(filters, :search) || []
Flop.Filter.put_value(filters, :search, [Type.dump!(search_slug) | search])
end)
end

def remove(%Flop{} = flop, %SearchSlug{} = search_slug) do
Map.update!(flop, :filters, fn filters ->
search = Flop.Filter.get_value(filters, :search) || []
Flop.Filter.put_value(filters, :search, List.delete(search, Type.dump!(search_slug)))
end)
end

def dump_params(params) do
{search, params} =
Keyword.get_and_update(params, :filters, &Flop.Filter.pop_value(&1, :search))

(params ++
(search
|> Enum.map(&Type.load!(&1))
|> Enum.group_by(& &1.type, & &1.slug)
|> Map.to_list()))
|> Enum.sort_by(fn {group, _slugs} -> group end)
end

def load_params(params) do
countries = Map.get(params, "country", []) |> Enum.map(&%SearchSlug{type: :country, slug: &1})
rockets = Map.get(params, "rocket", []) |> Enum.map(&%SearchSlug{type: :rocket, slug: &1})
orgs = Map.get(params, "org", []) |> Enum.map(&%SearchSlug{type: :org, slug: &1})
value = (countries ++ rockets ++ orgs) |> Enum.map(&Type.dump!(&1))

Map.put(params, "filters", [%{"field" => "search", "op" => "in", "value" => value}])
end
end
Loading
Loading