In rails, the model methods find()
and find_by()
work in similar ways but have a few important differences. They can both be used on any model to search for a single instance of that model.
Movie.find_by(id: 1) # => #<Movie id: 1, title: "fellowship of the ring: extended edition", runtime: 228, created_at: "2019-03-02 20:55:54", updated_at: "2019-03-02 20:55:54">
Movie.find(1) # => #<Movie id: 1, title: "fellowship of the ring: extended edition", runtime: 228, created_at: "2019-03-02 20:55:54", updated_at: "2019-03-02 20:55:54">
A common use for either is in a RESTful show action
def show
@movie = Movie.find_by(id: params[:id])
render 'show.json.jbuilder'
end
Either gets the job done. There are differences, though.
In terms of flexibility, find_by
wins out. It can be used to search by any attribute (Movie.find_by(title: "fellowship of the ring: extended edition")
), whereas find
always only searches by id. This turns out not to be too big of a deal since there aren't many cases where you want only one result returned but want to search by something other than id (for more than a single result, use .where
, which has syntax identical to find_by
); it's worth considering, though.
How the two methods handle exceptions is their most significant point of difference, I believe. What happens if you're searching for an id that doesn't exist in your database?
Movie.all # => #<ActiveRecord::Relation [#<Movie id: 1, title: "fellowship of the ring: extended edition", runtime: 228, created_at: "2019-03-02 20:55:54", updated_at: "2019-03-02 20:55:54">, #<Movie id: 2, title: "two towers: extended edition", runtime: 235, created_at: "2019-03-02 20:55:54", updated_at: "2019-03-02 20:55:54">, #<Movie id: 3, title: "return of the king: extended edition", runtime: 251, created_at: "2019-03-02 20:55:54", updated_at: "2019-03-02 20:55:54">]>
# there is no movie with id = 4
Movie.find_by(id: 4) # => nil
Movie.find(4) # => ActiveRecord::RecordNotFound (Couldn't find Movie with 'id'=4)
find_by
returns nil
, whereas find
errors out. Personally, I prefer things that fail loudly. In this example, if I hit an api/movies/4
route in my browser, my show action assigns @movie = nil
, which will undoubtedly throw an error lately: most likely undefined method <something> for nil:nilClass
.
Hitting api/movies/4
when I'm using find
, on the other hand, gives me an error at the point at which my query found no results; it's a more readable error, too: I know exactly what went wrong when I see it.
Another thing: what if I made a mistake in writing the code? What if I'm excepting input from the params hash and instead of find_by(id: params[:id])
, I accidentally write find_by(id: params[:is])
? I get the same error (undefined method 'id' for nil:nilClass
in app/views/api/movies/show.json.jbuilder
). That shouldn't happen. I don't ever want to see an error and not know whether it's a developer error or user error. If I'm using .find
, on the other hand:
That's pretty readable.
In the above case, well-written happy and sad paths can mitigate a lot of the pain. I said that I like for things to fail loudly, but that's really only the case in development. I don't want my live app to fail loudly.
def show
@movie = Movie.find_by(id: params[:id])
if @movie
render 'show.json.jbuilder'
else
render json: {message: "Oops, we couldn't find that movie"}
end
end
def show
begin
@movie = Movie.find(params[:id])
rescue
render json: {message: "Oops, we couldn't find that movie"}
else
render 'show.json.jbuilder'
end
end