Enable tagged search
This commit is contained in:
@@ -2,7 +2,7 @@ defmodule MusicLibrary.Records do
|
|||||||
import Ecto.Query, warn: false
|
import Ecto.Query, warn: false
|
||||||
alias MusicLibrary.Repo
|
alias MusicLibrary.Repo
|
||||||
|
|
||||||
alias MusicLibrary.Records.Record
|
alias MusicLibrary.Records.{Record, SearchParser}
|
||||||
|
|
||||||
@fields [:id, :type, :artists, :format, :title, :release, :genres, :musicbrainz_id, :cover_hash]
|
@fields [:id, :type, :artists, :format, :title, :release, :genres, :musicbrainz_id, :cover_hash]
|
||||||
|
|
||||||
@@ -24,15 +24,46 @@ defmodule MusicLibrary.Records do
|
|||||||
limit = Keyword.get(opts, :limit, 20)
|
limit = Keyword.get(opts, :limit, 20)
|
||||||
offset = Keyword.get(opts, :offset, 0)
|
offset = Keyword.get(opts, :offset, 0)
|
||||||
|
|
||||||
q =
|
search =
|
||||||
from r in Record,
|
query
|
||||||
where: like(r.title, ^"%#{query}%") or like(r.artists, ^"%#{query}%"),
|
|> build_search()
|
||||||
order_by: [r.artists[0]["sort_name"], r.title],
|
|> limit(^limit)
|
||||||
limit: ^limit,
|
|> offset(^offset)
|
||||||
offset: ^offset,
|
|> select(^@fields)
|
||||||
select: ^@fields
|
|
||||||
|
|
||||||
Repo.all(q)
|
Repo.all(search)
|
||||||
|
end
|
||||||
|
|
||||||
|
def search_records_count(query) do
|
||||||
|
search = build_search(query)
|
||||||
|
|
||||||
|
Repo.aggregate(search, :count)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_search(query) do
|
||||||
|
{:ok, parsed_query} = SearchParser.parse(query)
|
||||||
|
|
||||||
|
base_search =
|
||||||
|
from r in Record,
|
||||||
|
order_by: [r.artists[0]["sort_name"], r.title]
|
||||||
|
|
||||||
|
Enum.reduce(parsed_query, base_search, fn
|
||||||
|
{:artist, artist}, search ->
|
||||||
|
search |> where([r], like(r.artists, ^"%#{artist}%"))
|
||||||
|
|
||||||
|
{:album, album}, search ->
|
||||||
|
search |> where([r], like(r.title, ^"%#{album}%"))
|
||||||
|
|
||||||
|
{:mbid, mbid}, search ->
|
||||||
|
search |> where([r], r.musicbrainz_id == ^mbid)
|
||||||
|
|
||||||
|
{:query, raw_query}, search ->
|
||||||
|
search
|
||||||
|
|> where(
|
||||||
|
[r],
|
||||||
|
like(r.title, ^"%#{raw_query}%") or like(r.artists, ^"%#{raw_query}%")
|
||||||
|
)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def count_records do
|
def count_records do
|
||||||
@@ -48,14 +79,6 @@ defmodule MusicLibrary.Records do
|
|||||||
Repo.all(q)
|
Repo.all(q)
|
||||||
end
|
end
|
||||||
|
|
||||||
def search_records_count(query) do
|
|
||||||
q =
|
|
||||||
from r in Record,
|
|
||||||
where: like(r.title, ^"%#{query}%") or like(r.artists, ^"%#{query}%")
|
|
||||||
|
|
||||||
Repo.aggregate(q, :count)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_record!(id), do: Repo.get!(Record, id)
|
def get_record!(id), do: Repo.get!(Record, id)
|
||||||
|
|
||||||
def get_latest_record! do
|
def get_latest_record! do
|
||||||
|
|||||||
@@ -28,14 +28,18 @@ defmodule MusicLibrary.Records.SearchParser do
|
|||||||
Parse a search query.
|
Parse a search query.
|
||||||
|
|
||||||
iex> MusicLibrary.Records.SearchParser.parse("")
|
iex> MusicLibrary.Records.SearchParser.parse("")
|
||||||
{:ok, %{}}
|
{:ok, %{query: ""}}
|
||||||
iex> MusicLibrary.Records.SearchParser.parse("marbles")
|
iex> MusicLibrary.Records.SearchParser.parse("marbles")
|
||||||
{:ok, %{query: "marbles"}}
|
{:ok, %{query: "marbles"}}
|
||||||
iex> MusicLibrary.Records.SearchParser.parse("artist:marillion album:marbles")
|
iex> MusicLibrary.Records.SearchParser.parse("artist:marillion album:marbles")
|
||||||
{:ok, %{artist: "marillion", album: "marbles"}}
|
{:ok, %{artist: "marillion", album: "marbles"}}
|
||||||
iex> MusicLibrary.Records.SearchParser.parse("artist:marillion album:fugazi artist:fish")
|
iex> MusicLibrary.Records.SearchParser.parse("artist:marillion album:fugazi artist:fish")
|
||||||
{:ok, %{artist: "marillion fish", album: "fugazi"}}
|
{:ok, %{artist: "marillion fish", album: "fugazi"}}
|
||||||
|
iex> MusicLibrary.Records.SearchParser.parse(~s(artist:"the pineapple thief" wilderness))
|
||||||
|
{:ok, %{artist: "the pineapple thief", query: "wilderness"}}
|
||||||
"""
|
"""
|
||||||
|
def parse(""), do: {:ok, %{query: ""}}
|
||||||
|
|
||||||
def parse(query) do
|
def parse(query) do
|
||||||
{:ok, result, _rest, _context, _line, _byte_offset} = search_parser(query)
|
{:ok, result, _rest, _context, _line, _byte_offset} = search_parser(query)
|
||||||
{:ok, normalize(result)}
|
{:ok, normalize(result)}
|
||||||
|
|||||||
@@ -96,6 +96,73 @@ defmodule MusicLibraryWeb.RecordIndexTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "Tagged search" do
|
||||||
|
setup [:create_records]
|
||||||
|
|
||||||
|
test "supports raw queries", %{conn: conn, records: records} do
|
||||||
|
[record | _rest] = records
|
||||||
|
qs = [query: record.title]
|
||||||
|
{:ok, index_live, _html} = live(conn, ~p"/records?#{qs}")
|
||||||
|
|
||||||
|
record_row =
|
||||||
|
index_live
|
||||||
|
|> with_target("#records-#{record.id}")
|
||||||
|
|
||||||
|
record_row_html = record_row |> render()
|
||||||
|
|
||||||
|
assert record_row_html =~ escape(record.title)
|
||||||
|
assert record_row_html =~ to_string(record.release)
|
||||||
|
assert record_row_html =~ Record.format_short_label(record.format)
|
||||||
|
assert record_row_html =~ record.release
|
||||||
|
assert record_row_html =~ ~p"/covers/#{record.id}?vsn=#{record.cover_hash}"
|
||||||
|
|
||||||
|
for artist <- record.artists do
|
||||||
|
assert record_row_html =~ escape(artist.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "supports filters", %{conn: conn, records: records} do
|
||||||
|
{artist_with_most_records, _records_count} =
|
||||||
|
records
|
||||||
|
|> Enum.frequencies_by(fn r ->
|
||||||
|
[artist] = r.artists
|
||||||
|
artist.name
|
||||||
|
end)
|
||||||
|
|> Enum.max_by(fn {_artist, count} -> count end)
|
||||||
|
|
||||||
|
{present, absent} =
|
||||||
|
Enum.split_with(records, fn r ->
|
||||||
|
[artist] = r.artists
|
||||||
|
artist.name == artist_with_most_records
|
||||||
|
end)
|
||||||
|
|
||||||
|
qs = [query: ~s(artist:"#{artist_with_most_records}"), page_size: 30]
|
||||||
|
{:ok, index_live, _html} = live(conn, ~p"/records?#{qs}")
|
||||||
|
|
||||||
|
for record <- present do
|
||||||
|
record_row =
|
||||||
|
index_live
|
||||||
|
|> with_target("#records-#{record.id}")
|
||||||
|
|
||||||
|
record_row_html = record_row |> render()
|
||||||
|
|
||||||
|
assert record_row_html =~ escape(record.title)
|
||||||
|
assert record_row_html =~ to_string(record.release)
|
||||||
|
assert record_row_html =~ Record.format_short_label(record.format)
|
||||||
|
assert record_row_html =~ record.release
|
||||||
|
assert record_row_html =~ ~p"/covers/#{record.id}?vsn=#{record.cover_hash}"
|
||||||
|
|
||||||
|
for artist <- record.artists do
|
||||||
|
assert record_row_html =~ escape(artist.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for record <- absent do
|
||||||
|
refute has_element?(index_live, "#records-#{record.id}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "Updating record metadata" do
|
describe "Updating record metadata" do
|
||||||
test "can navigate to the record edit form", %{conn: conn} do
|
test "can navigate to the record edit form", %{conn: conn} do
|
||||||
record = record_fixture()
|
record = record_fixture()
|
||||||
|
|||||||
Reference in New Issue
Block a user