Uni Ecto Plugin «iPad AUTHENTIC»
defp order_by_relevance(query, nil), do: query defp order_by_relevance(query, term) do from q in query, order_by: [desc: fragment( "ts_rank(to_tsvector('english', ?), plainto_tsquery('english', ?))", fragment("coalesce(?, '') || ' ' || coalesce(?, '')", q.title, q.content), ^term )] end end # lib/my_app_web/controllers/search_controller.ex defmodule MyAppWeb.SearchController do use MyAppWeb, :controller alias MyApp.Blog
execute(""" CREATE INDEX posts_search_vector_idx ON posts USING GIN(search_vector) """) end end # lib/my_app/search/query_builder.ex defmodule MyApp.Search.QueryBuilder do import Ecto.Query def build_search_query(schema, params) do base_query = from s in schema uni ecto plugin
defp update_search_vector(changeset) do case changeset do %Ecto.Changesetvalid?: true -> Ecto.Changeset.put_change(changeset, :search_vector, fragment( "to_tsvector('english', coalesce(?, '') || ' ' || coalesce(?, ''))", changeset.data.title, changeset.data.content )) _ -> changeset end end end # priv/repo/migrations/20240101000000_add_full_text_search.exs defmodule MyApp.Repo.Migrations.AddFullTextSearch do use Ecto.Migration def change do # Add GIN index for full-text search execute(""" CREATE INDEX posts_search_idx ON posts USING GIN(to_tsvector('english', title || ' ' || content)) """) do: query defp order_by_relevance(query
defp apply_filters(query, []), do: query defp apply_filters(query, filters) do Enum.reduce(filters, query, fn :category, category, q -> from p in q, where: p.category == ^category :published_after, date, q -> from p in q, where: p.inserted_at >= ^date end) end term) do from q in query
test "search handles empty query" do results = Blog.search_posts("") assert results == [] end end
# Optional: Add precomputed tsvector column alter table(:posts) do add :search_vector, :tsvector end