0

I have a webapp that lets users rate movies. Any time a user searches for movies, data for up to 20 movies is fetched from an external api and sent to my Rails backend, where it checks to see if that movie already exists in the database. If it is there, it returns the movie info. If it is not yet in the database, it first adds it before returning the info.

This works fine for a while, but for some reason I do not understand, eventually the movies in the movies in the database start acting strangely, one by one they do not show up on the webapp and give me error messages in my Rails logs. If I change find_or_create_by! to find_or_create_by (no space) in the controller I don't get the error message in the logs, and most of the movie shows up correctly, but its :id (not api_id), and thus, ratings and users associations are not returned.

I thought that maybe duplicate entries were getting created despite my validations but when I check the effected movie in question in the Rails console with Movie.where(api_id: 269) I only get one entry, so that does not seem to be the case.

I'm having trouble deciphering the logs to figure out why this happen in the first place, one by one to movies in the database. The only way I have found to get things back to working correctly is to destroy all the movies in the movie table, but this also means I have to destroy all the ratings as well. I'd like to figure out exactly what is happening to make mess the entries up, making them unusable.

My controller:

class Api::V1::MoviesController < ApplicationController

  def index
      movies =  Movie.all
      render json: movies
  end

  def show
      movie = Movie.find(params[:id])
      render json: movie
  end

  def create
      movie = Movie.find_or_create_by!(movie_params)
      render json: movie
  end

private
  def movie_params
      params.require(:movie).permit(:title, :poster_path, :overview, :api_id, :release_date)
  end

end

My class:

class Movie < ApplicationRecord
  has_many :ratings
  has_many :users, through: :ratings
  validates :api_id, uniqueness: true
end

Serializer:

class MovieSerializer < ActiveModel::Serializer
  attributes :id, :title, :poster_path, :api_id, :overview, :release_date
  has_many :ratings
  has_many :users, through: :ratings
end

Schema:

  create_table "movies", force: :cascade do |t|
    t.string "title"
    t.string "poster_path"
    t.text "overview"
    t.integer "api_id"
    t.string "release_date"
    t.index ["api_id"], name: "index_movies_on_api_id", unique: true
  end

Log of a movie not rendering successfully:

Started POST "/api/v1/movies" 
Processing by Api::V1::MoviesController#create as JSON
Parameters: {"api_id"=>269, "title"=>"Breathless", "poster_path"=>"/9Wx0Wdn2EOqeCZU4SP6tlS3LOml.jpg", "overview"=>"A young car thief kills a policeman and tries to persuade a girl to hide in Italy with him.", "release_date"=>"1960-03-16", "movie"=>{"title"=>"Breathless", "poster_path"=>"/9Wx0Wdn2EOqeCZU4SP6tlS3LOml.jpg", "overview"=>"A young car thief kills a policeman and tries to persuade a girl to hide in Italy with him.", "api_id"=>269, "release_date"=>"1960-03-16"}}
User Load (1.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
Movie Load (2.3ms)  SELECT "movies".* FROM "movies" WHERE "movies"."title" = $1 AND "movies"."poster_path" = $2 AND "movies"."overview" = $3 AND "movies"."api_id" = $4 AND "movies"."release_date" = $5 LIMIT $6  [["title", "Breathless"], ["poster_path", "/9Wx0Wdn2EOqeCZU4SP6tlS3LOml.jpg"], ["overview", "A young car thief kills a policeman and tries to persuade a girl to hide in Italy with him."], ["api_id", 269], ["release_date", "1960-03-16"], ["LIMIT", 1]]
(1.9ms)  BEGIN
Movie Exists? (1.5ms)  SELECT 1 AS one FROM "movies" WHERE "movies"."api_id" = $1 LIMIT $2  [["api_id", 269], ["LIMIT", 1]]
(2.7ms)  ROLLBACK
Completed 422 Unprocessable Entity in 65ms (ActiveRecord: 9.6ms | Allocations: 10309)
FATAL -- : [8dce0090-dd46-4fc6-8370-08902fa5e13c]   
app/controllers/api/v1/movies_controller.rb:14:in `create'

Log of a movie rendering properly:

Started POST "/api/v1/movies" for 67.86.25.53 at 2021-05-04 21:02:22 +0000
Processing by Api::V1::MoviesController#create as JSON
Parameters: {"api_id"=>26317, "title"=>"Man with a Movie Camera", "poster_path"=>"/Ded5Kl7MDZQTbFpoNI62tLVWUN.jpg", "overview"=>"A cameraman wanders around Moscow, Kharkov, Kiev and Odessa with a camera slung over his shoulder, documenting urban life with dazzling invention.", "release_date"=>"1929-01-08", "movie"=>{"title"=>"Man with a Movie Camera", "poster_path"=>"/Ded5Kl7MDZQTbFpoNI62tLVWUN.jpg", "overview"=>"A cameraman wanders around Moscow, Kharkov, Kiev and Odessa with a camera slung over his shoulder, documenting urban life with dazzling invention.", "api_id"=>26317, "release_date"=>"1929-01-08"}}
User Load (1.4ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
Movie Load (1.3ms)  SELECT "movies".* FROM "movies" WHERE "movies"."title" = $1 AND "movies"."poster_path" = $2 AND "movies"."overview" = $3 AND "movies"."api_id" = $4 AND "movies"."release_date" = $5 LIMIT $6  [["title", "Man with a Movie Camera"], ["poster_path", "/Ded5Kl7MDZQTbFpoNI62tLVWUN.jpg"], ["overview", "A cameraman wanders around Moscow, Kharkov, Kiev and Odessa with a camera slung over his shoulder, documenting urban life with dazzling invention."], ["api_id", 26317], ["release_date", "1929-01-08"], ["LIMIT", 1]]
CACHE User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
[active_model_serializers]   Rating Load (1.4ms)  SELECT "ratings".* FROM "ratings" WHERE "ratings"."movie_id" = $1  [["movie_id", 3745]]
[active_model_serializers]   User Load (1.3ms)  SELECT "users".* FROM "users" INNER JOIN "ratings" ON "users"."id" = "ratings"."user_id" WHERE "ratings"."movie_id" = $1  [["movie_id", 3745]]
[active_model_serializers] Rendered MovieSerializer with ActiveModelSerializers::Adapter::Attributes (6.75ms)
Completed 200 OK in 16ms (Views: 5.6ms | ActiveRecord: 5.4ms | Allocations: 2946)
2
  • You can check why the records can't be persisted in the database, as you're passing all in the movie_params, some of them (:title, :poster_path, :overview, :api_id, :release_date) might be giving you troubles. Commented May 6, 2021 at 19:25
  • @SebastianPalma yes, this seems to be the case, thank you! Commented May 6, 2021 at 21:59

1 Answer 1

1

The main problem is that you're passing every attribute of a movie to find_by_or_create_by!. That means that Rails will try to create a duplicate record if any of the attributes have changed. This will cause an issue with the uniqueness constraint on api_id.

Instead you want something like:

Movie.create_with(
  movie_params
).find_or_create_by!(api_id: params.dig(:movie, :api_id))

Where you just find the records by the unique attribute.

Or if you actually want something more like an insert/update:

Movie.find_or_initialize_by(
  api_id: params.dig(:movie, :api_id)
).update!(movie_params)

This will update the record if it exists with fresh data from the API.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you so much, I was not familiar with the #dig operator, this will be very useful!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.