0

Im building an API in which i have a model called Studio. Studio has many movies and many series, and movies and series have many characters of their own. Also, a character can be in many movies and/or many series, and belongs to a studio. I got all the models relational tables logistics figured out by now, but what i'm trying to do right now, i can't think of a way.

So my routes are designed so i can do something like localhost:3000/api/v1/studios/1/characters -> this way i display all the studio's (studio_id: 1) characters (from all the movies and series that belong to that studio). That is fairly easy with just doing something like:

@characters = Character.where(studio_id:@studio.id)

In my CharactersController, where i have

def get_studio
    @studio = Studio.find(params[:studio_id])
end

Now i am trying to design the logistics so i can do localhost:3000/api/v1/studios/1/seriees/1/characters And only get the characters from that specific seriees. localhost:3000/api/v1/studios/1/movies/1/characters should also be valid.

I tried

def get_movie
    @movie = Movie.find(params[:movie_id])
end`

and

def get_serie
    @serie = Seriee.find(params[:seriee_id])
end

in my CharactersController, with before_action :get_serie and before_action :get_movie at the very start of the code, but it gives me an error because i'm never getting both a movie_id and a seriee_id, it's either one or the other, so it can't search for both at the same time.

The POST from character i will only do from localhost:3000/studios/1/characters so that's not a problem, i only want to be able to GET from localhost:3000/studios/1/movies/1/characters and localhost:3000/studios/1/seriees/1/characters

I hope i didn't ramble to much and you are able to understand me. If you think that my way of solving this problem is just not right, please tell me as i am just learning and any advice would be very valuable.

0

1 Answer 1

1

I doubt you actually want to nest those resources more then one level deep:

Rule of thumb: resources should never be nested more than 1 level deep. A collection may need to be scoped by its parent, but a specific member can always be accessed directly by an id, and shouldn’t need scoping (unless the id is not unique, for some reason).
- Jamis Buck

Given the domain here it does not make sense that you would have to specify the studio to get the characters for a given movie. The goal of nesting in REST is to make the relationships between two entities explicit by making them part of the URL structure - not to be overly explicit.

Excessive nesting leads to some very overcomplicated code when it comes to stuff like url generation - I mean do you really want to write?

 url_for [:ap1, :v1, studio, movie, :characters]

You can fix these issues by using the shallow: true option:

resources :studios do
  resources :movies, shallow: :true do
    resources :characters
  end
end

or by avoiding "russian doll" calls to resources:

resources :studios do
  resources :movies, only: [:new, :create]
end

resources :movies, only: [:show, :update, :destroy] do
  resources :characters, only: [:create, :index]
end

You also really should be using assocations to fetch the nested items.

class CharactersController
  before_action :set_movie, only: [:index, :create]

  # GET /api/v1/movies/1/characters.json
  def index
    @characters = @movie.characters
  end


  private

  def set_movie
    @movie = Movie.find(params[:movie_id])
  end
end

In general code like Character.where(studio_id: @studio.id) should be avoided as it leaks the implementation details of the relationship. Your controller should not need to know how characters and studios are related - it just knows that movie has a method named characters.

And I don't really want to get into the details of your modeling here but Character.where(studio_id: @studio.id) will not work with the requirement that movies most likely contains a subset of the characters of a studio - not their entire cinematic universe. Instead you need a join table that links movies and characters.

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

4 Comments

Yes i do have sort of a "russian doll" routes.rb... it's just a little hard to wrap my head around on how i should be facing this problem. I need to be able to return all the movies from a studio, all the series from a studio and all the characters from a studio (Say, all MCU Characters). But also individually return the list of Characters from a single movie. And a Character should return a list of movies/series he's been in too. I have the Join tables set up so i'll try and rethink my routes... Thank you for your help.
You would really want a variety of endpoints like /studios/marvel/characters and /movies/amazing-spider-man/characters. One tip is to use the module: option when setting up the routes to send it to Studios::CharactersController and Movies::CharactersController. instead of jamming everything into a single controller.
In some cases you can also have the show or index response of a resource contain a bunch of additional data. For example the JSON response for /studios/marvel could also contain list of characters or a list of movies. There is no real right answer to API design here and you have to way the number of requests needed vs how heavy your responses are as including everything for every possible use case can eat a lot of bandwidth.
I've decided i'll take a simpler approach and not nest my routes for now. it just comes with so much trouble and it's just a small project, no need to get so nit picky on this for now. Maybe later on i will try and do the nesting thing. For now i just want it to work. Thank you, you were very helpful.

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.