graphiti-api / graphiti

Stylish Graph APIs
https://www.graphiti.dev/
MIT License
960 stars 138 forks source link

Problem in namespacing controllers #445

Open danjvarela opened 1 year ago

danjvarela commented 1 year ago

What I hope to achieve

Have routes mapped to namespaced controllers (Api::V1::NotesController) much like the one below:

image

Attempted solution:

  1. Create new rails app: rails new sample-graphiti-api --api -m https://www.graphiti.dev/template
  2. Press <ENTER> if asked for the namespace (default is /api/v1)
  3. rails generate model Note content:string
  4. rails generate graphiti:resource note content:string created_at:datetime (rails generate graphiti:resource api/v1/note content:string created_at:datetime doesn't work so we do it manually)
  5. Move app/resources/note_resource.rb to app/resources/api/v1/note_resource.rb and change NoteResource to Api::V1::NoteResource
    class Api::V1::NoteResource < ApplicationResource
    attribute :content, :string
    attribute :created_at, :datetime, writable: false
    end
  6. Move app/controllers/notes_controller.rb to app/controllers/api/v1/note_resource.rb and change NotesController to Api::V1::NotesController. Also, change all occurrences of NoteResource to Api::V1::NoteResource. File should look like this:

    class Api::V1::NotesController < ApplicationController
    def index
    notes = Api::V1::NoteResource.all(params)
    respond_with(notes)
    end
    
    def show
    note = Api::V1::NoteResource.find(params)
    respond_with(note)
    end
    
    def create
    note = Api::V1::NoteResource.build(params)
    
    if note.save
      render jsonapi: note, status: 201
    else
      render jsonapi_errors: note
    end
    end
    
    def update
    note = Api::V1::NoteResource.find(params)
    
    if note.update_attributes
      render jsonapi: note
    else
      render jsonapi_errors: note
    end
    end
    
    def destroy
    note = Api::V1::NoteResource.find(params)
    
    if note.destroy
      render jsonapi: { meta: {} }, status: 200
    else
      render jsonapi_errors: note
    end
    end
    end
  7. Change the routes to:
    Rails.application.routes.draw do
    namespace :api do
    namespace :v1 do
      resources :notes, defaults: { format: :jsonapi }
    end
    end
    end
  8. Run the migrations and start the server
  9. Do a GET /api/v1/notes. An error occurs:

Graphiti::Errors::InvalidEndpoint ( Api::V1::NoteResource cannot be called directly from endpoint /api/v1/notes#index!

      Either set a primary endpoint for this resource:

      primary_endpoint '/my/url', [:index, :show, :create]

      Or whitelist a secondary endpoint:

      secondary_endpoint '/my_url', [:index, :update]

      The current endpoints allowed for this resource are: [{:path=>:"/api/v1/notes", :full_path=>:"/api/v1/api/v1/notes", :url=>:"http://localhost:3000/api/v1/api/v1/notes", :actions=>[:index, :show, :create, :update, :destroy]}]

):

danjvarela commented 1 year ago

So far, the only workaround that works for me is overriding the endpoint_namespace and model of Api::V1::NoteResource like so:

class Api::V1::NoteResource < ApplicationResource
  self.endpoint_namespace = ''
  self.model = Note
  attribute :content, :string
  attribute :created_at, :datetime, writable: false
end
bilouw commented 1 year ago

Hi,

I used the same workaround ! But i did self.endpoint_namespace = '' in ApplicationRessource that inherit from Graphiti::Resource. Then, all my resource inherit from ApplicationResource so i don't need to override endpoint_namespace in every resources.

Also, you don't need to override self.model in every resource. In ApplicationResource again, override the infer_model method with something like this:

# Extends our Graphiti needs
class << self
  def infer_model
    name&.split('::')&.last&.sub("Resource", "")&.safe_constantize
  end
end

You may need to override others to make it work !