TurboEcto: A rich ecto component, including search sort and paginate

Turbo is a very rich ecto component,including search sort and paginate. Inspiration by ruby ransack and learn from rummage_ecto.

Finshed

iex> params = %{
  "q" => %{"title_like" => "hello123", "category_id_eq" => 1},
  "s" => "inserted_at+asc",
  "per_page" => 5, "page" => 10
}

iex> Turbo.Ecto.turboq(Product, params)
#Ecto.Query<from t in Product, where: t.type == ^1,
  where: like(t.title, ^"%hello123%"),
  order_by: [asc: t.inserted_at],
  limit: ^5, offset: ^45>

iex> Turbo.Ecto.turbo(Product, params)
%{
  datas: [Product.t(), ...],
  paginate: %{
    current_page: 10,
    next_page: 11,
    per_page: 5,
    prev_page: 9,
    total_count: 100,
    total_pages: 20
  }
}

TODO

  • Support and && or symbols.
iex> params = %{
    "q" => %{"title_or_body_like" => "hello123"}
}

iex> Turbo.Ecto.turboq(Product, params)
#Ecto.Query<from t in Product, where: like(t.body, ^"%hello123%"),
  where: like(t.title, ^"%hello123%")
  • Support assoc table.
iex> params = %{
    "q" => %{"category_name_like" => "elixir"}
}

iex> Turbo.Ecto.turboq(Product, params)
#Ecto.Query<from p in subquery(from p in "products"), join: c in assoc(p, :category), or_where: like(c.name, ^"elixir")>

check it here:

Thank you for reading, have a good day. :smile:

9 Likes

I got a quick look and it looks interesting.

One thing I would change and I think it would improve quality-of-life would be returning a struct from the turboq function and implementing Enumerable on it to iterate over the results.

The other thing is why there’s the use API at all - I don’t see it giving the user anything they can’t do just calling Turbo.Ecto functions directly.

4 Likes

@michalmuskala Thank you for your reply, it’s over-design, you are right, I know how to improve it. :slight_smile:

1 Like

This looks pretty cool. I was working on an internal tool to do something very similar. I was thinking of converting it to a library but have been looking at just using rummage. One thing that doesn’t stand out is including associated table data in the search, is this supported?

Yeah, in the todo lists, it will be supported soon.

@mikemccall

Table structure

Category Table

Field Type Comment
name string

Product Table

Field Type Comment
title string
body text
price float
category_id integer
available boolean

Variant Table

Field Type Comment
title string
price float
product_id integer

Input

url_query = http://localhost:4000/varinats?q[product_category_name_like]=elixir

Process:

iex> params = %{"q" => %{"product_category_name_like" = "elixir"}}
iex> Turbo.Ecto.turboq(Variant, params)

Expect output:

#Ecto.Query<from v in Varinat, join: p in Product,
 on: v.product_id == p.id, join: c in Category, on: p.category_id == c.id,
 where: like(c.name, ^"%elixir%")>

Three table assoc query, This is the result queryable you want?

Your readme says you have tests written but unless I’m missing something, I see no tests? It looks like just stubbed out files with doctest calls.

Yup, that’s close to I was thinking.

If I wanted to include the the variant title in the search what’s the approach?

http://localhost:4000/varinats?q[product_category_name_like, variant_title_like]=elixir

would add where and or_where clause

where: like(c.name, ^"%elixir%"), or_where: like(v.title, ^"%elixir%")

Yeah, seed this issue and this one

If you have other needs, please add issue :slight_smile:

1 Like

Yeah, just use doctest test the main API and BuildSearchQuery method.

Oh I’m not sure what that was. Usually I hit up tests to see a boat load of manual examples because it showcases how to use the library in every intended way.

1 Like

We’ve just built something similar ad hoc in our project :slight_smile: with some differences:
We didn’t want to couple params to DB operations and we wanted to filter on joined entities too,
so we decided to allow custom params e.g.

params = %{
  "q" => %{"shipped_before" => "01/01/2018"},
  "s" => "inserted_at+asc",
  "per_page" => 5, "page" => 10
}

And then for each query we need a callback module that implements the query and filter callbacks.

def query do
  Product
  |> join(:inner, [p], sh in assoc(p, :shippings))
end

def maybe_apply_filter_term_to_query(query, "shipped_before", date) do
  query
  |> where([p, sh], sh.date < ^date
end

And then we apply all filters one by one, add ordering and filtering. Saves nice amount of boilerplate, unifies params responsible for sorting and pagination, and allows for arbitrarily complicated queries :slight_smile:

If multiple people arrive at similar ideas, it means that those are good ideas! :slight_smile:

3 Likes

I commit some features.

  • 1 Support AND symbol condition example
  • 2 Support OR symbol condition example
  • 3 Support for multi-table association queries. example

Most of the implementation methods use regular matching, which is not very elegant and needs to be improved.

If you use Phoenix framework, and looking for full Phoenix support, turbo_html uses Turbo.Ecto and adds HTML support to it. which is currently a semi-finished product and I will improve it further.

If you don’t know much about the use of trubo_ecto, you can visit the mipha proj. Now he use turbo_ecto and turbo_html, you can experience it.

Thank you for reading.

1 Like