Subscribe to access all episodes. View plans →
Published April 2, 2018
Elixir 1.5.3
Phoenix 1.3.0
View source on GitHub
Here is our movie app and one feature that would be really nice to have is a way to search for movies. In this episode we’ll build a simple search form that will allow us to quickly find the movie we want based on its title.
Let’s get started.
We’ll open our app.html.eex
template.
Then we’ll add a form_for
where we want our search for to appear.
We’ll use @conn
since our search form doesn’t use any kind of schema.
And we’ll send our form data to our movie_path
with the index action.
Let’s also add our pull-right
class so our form appears and the right side of our site.
Template path: lib/teacher_web/templates/layout/app.html.eex
<%= form_for @conn, movie_path(@conn, :index), [class: "pull-right"], fn f -> %>
<%= search_input f, :query %>
<%= submit "Search" %>
<% end %>
Let’s go back to the browser and great we see our form.
Let’ try submitting it - and we get an error.
That’s because the form_for
defaults to a “POST” and our index action is a “GET”.
Let’s update our search form to use “GET” as the method.
Template path: lib/teacher_web/templates/layout/app.html.eex
<%= form_for @conn, movie_path(@conn, :index), [method: :get, class: "pull-right"], fn f -> %>
<%= search_input f, :query %>
<%= submit "Search" %>
<% end %>
Then let’s go back to the browser and try our search again (GODFATHER) it loads loads the page and we see our search params in the URL.
Now let’s check out our server logs so we can see what the structure of our search looks like.
(Check the logs)
We can see our search term will be the value of our key "query"
in our params.
Now that we know how to access our search term, we need to use it to search our movies.
Let’s open our movie_controller.ex
And in our index action, we’re ignoring the params. Let’s change that since this will contain our search query.
And then we can pass them into the Features.list_movies
function.
lib/teacher_web/controllers/movie_controller.ex
...
def index(conn, params) do
movies = Features.list_movies(params)
render(conn, "index.html", movies: movies)
end
...
Now that we’re passing our params in, let’s open our features.ex
module.
And we’ll update the list_movies
function to accept the params.
Now that we have our params here, how can we use them to search our movies?
In episode 31 we learned how to compose Ecto queries. Let’s use that same pattern here.
We want to create a pipeline that will build up our query and then hit the database.
The first thing we’ll need is our search term. We’ll use get_in
to get our query
form the params
.
Now we can take our Movie
and pipe it into a new function that we’ll need to create, but will contain our search query.
Let’s call it search
and we’ll pass our search_term
into it.
Then we’ll take its result and pipe it into Repo.all
lib/teacher/features/features.ex
...
def list_movies(params) do
search_term = get_in(params, ["query"])
Movie
|> Movie.search(search_term)
|> Repo.all()
end
...
Now let’s implement that search
function.
We’ll open our movie.ex
module.
And we’ll define our search
function - it will take two arguments, our query
and our search_term
.
Since we’ll be using the Ecto.Query
module here, let’s import it above. And we’ll limit it to only import the from
function with an arity of 2.
Now let’s go back to our function and let’s wrap our search_term
with wildcards (%
). Wrapping our search_term
in wildcards like this will select all movies that contain our search_term
in the columns we specify.
Then let’s define our Ecto query.
We’ll get all of our movies from the query
And since we’re using PosgreSQL let’s use ilike
to perform a case-insensitive search for all movies where our search_term
is contained in the title.
lib/teacher/features/movie.ex
defmodule Teacher.Features.Movie do
…
import Ecto.Query, only: [from: 2]
…
def search(query, search_term) do
wildcard_search = "%#{search_term}%"
from movie in query,
where: ilike(movie.title, ^wildcard_search)
end
end
Then if we go back to the browser and try our search - we see it’s working.
The params are being pulled in and our movie is returned.
Let’s also try it with all caps.
And great that works too.
But if we search for something not in the title, but in the description (wicked witch) nothing is returned.
This is because our query is only searching with our movie’s title.
Let’s update our search function to include our movie’s summary.
We’ll go back to our movie.ex
module.
And in our search
function let’s add or_where
With another ilike
for where our search is included in our movie summary.
lib/teacher/features/movie.ex
...
def search(query, search_term) do
wildcard_search = "%#{search_term}%"
from movie in query,
where: ilike(movie.title, ^wildcard_search),
or_where: ilike(movie.summary, ^wildcard_search)
end
...
Now let’s see if our updated search works.
We’ll go back to the browser and try a search term that is only in our a movie summary.
And great it gives us the correct movie.