Skip to content

Instantly share code, notes, and snippets.

@joshsarna
Last active March 2, 2019 22:13
Show Gist options
  • Save joshsarna/0286ffb30b5d4189c39dc9a09e13e34b to your computer and use it in GitHub Desktop.
Save joshsarna/0286ffb30b5d4189c39dc9a09e13e34b to your computer and use it in GitHub Desktop.

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.

Flexibility

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.

Error handling

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.

screen shot 2019-03-02 at 1 22 43 pm

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.

screen shot 2019-03-02 at 1 22 22 pm

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:

screen shot 2019-03-02 at 2 02 48 pm

That's pretty readable.

Happy and Sad Paths

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.

Basic exception handling with find_by:

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

Basic exception handling with find:

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment