title: Rails 4 - Creating a flexible JSON API with nested models
body: <p>This has driven me to drinking, so I'll post this question while I'm still sober...</p>
  
  <p>I am attempting to use Rails to construct an API that will send json to the client to be consumed by a JavaScript framework (AngularJS or EmberJS).</p>
  
  <p>The application has <strong>3</strong> models so far:</p>
  
  <p><strong>1. Project</strong> </p>
  
  <p><strong>2. ProjectTodo</strong> </p>
  
  <p><strong>3. ProjectTodoComment</strong></p>
  
  <p>I wanted to have url paths that I could use as endpoints for the JSON api, so I nested the resources:</p>
  
  <p><img src="https://i.stack.imgur.com/r74X0.png" alt="enter image description here"></p>
  
  <p>I wanted to keep the html.erb view files in order to have an administrative backend that was not reliant on JavaScript.</p>
  
  <p>Because of the nesting, the routes have gotten rather convoluted.  Here's an example from the the ProjectTodoComments view:</p>
  
  <pre><code>&lt;tbody&gt;
  &lt;% @project_todo_comments.each do |project_todo_comment| %&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;%= project_todo_comment.comment %&gt;&lt;/td&gt;
      &lt;td&gt;&lt;%= project_todo_comment.project_todo_id %&gt;&lt;/td&gt;
      &lt;td&gt;&lt;%= link_to 'Show', project_project_todo_project_todo_comment_path(@project, @project_todo, project_todo_comment) %&gt;&lt;/td&gt;
      &lt;td&gt;&lt;%= link_to 'Edit', edit_project_project_todo_project_todo_comment_path(@project, @project_todo, project_todo_comment) %&gt;&lt;/td&gt;
      &lt;td&gt;&lt;%= link_to 'Destroy', [@project, @project_todo, project_todo_comment], method: :delete, data: { confirm: 'Are you sure?' } %&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;% end %&gt;
  </code></pre>
  
  <p></p>
  
  <p>By running <code>rake routes</code> I can show you the entire list of routes that are created:
  <img src="https://i.stack.imgur.com/EA98I.png" alt="enter image description here"></p>
  
  <p>This nesting seems to have an overly complex effect on all of the code in the app.  Here is the create method of the <code>project_todo_comments_controller</code>:</p>
  
  <p><img src="https://i.stack.imgur.com/HSg6q.png" alt="enter image description here"></p>
  
  <p>I'm having to pass in <strong>3</strong> objects in order to create the comment.</p>
  
  <p>My plan was to be able to create <strong>versatile</strong> routes for any situation that I would be able to use in my JSON api.</p>
  
  <p>By nesting the resources, it looks as if I have all the routes that I would need.</p>
  
  <p><strong>However, what if I add another dependent model - such as a User?</strong></p>
  
  <p>If the <strong>User</strong> model has a has_many association with the <strong>Project</strong> model, than I will have to nest the models <strong>4</strong> layers deep.</p>
  
  <p>This just seems ridiculous - not to mention unmaintainable.  </p>
  
  <p>My question is...</p>
  
  <p><strong>Is this the only way to go about creating a flexible JSON API in Rails that has a lot of models with relationships, or is there a better way?</strong></p>
  
  <p><strong>EDIT</strong></p>
  
  <hr>
  
  <p>Here are the Model associations:</p>
  
  <p><strong>Project</strong></p>
  
  <pre><code>class Project &lt; ActiveRecord::Base
    attr_accessible :title
    has_many :project_todos
  end
  </code></pre>
  
  <p><strong>ProjectTodo</strong></p>
  
  <pre><code>class ProjectTodo &lt; ActiveRecord::Base
    attr_accessible :project_id, :title
    belongs_to :project
    has_many :project_todo_comments
  end
  </code></pre>
  
  <p><strong>ProjectTodoComment</strong></p>
  
  <pre><code>class ProjectTodoComment &lt; ActiveRecord::Base
    attr_accessible :comment, :project_todo_id
    belongs_to :project_todo
  end
  </code></pre>
  
  <hr>
  
  <p>Here is the entire controller for the most deeply nested model - ProjectTodoComment:</p>
  
  <pre><code>class ProjectTodoCommentsController &lt; ApplicationController
    before_action :set_project_todo_comment, only: [:show, :edit, :update, :destroy]
  
    before_filter :load_project
    before_filter :load_todo
  
    def index
      @project_todo_comments = @project_todo.project_todo_comments.all
    end
  
    def show
      @project_todo_comment = @project_todo.project_todo_comments.find(params[:id])
    end
  
    def new
      @project_todo_comment = @project_todo.project_todo_comments.new
    end
  
    def edit
      @project_todo_comment = @project_todo.project_todo_comments.find(params[:id])
    end
  
    def create
      @project_todo_comment = @project_todo.project_todo_comments.new(params[:project_todo_comment])
  
      respond_to do |format|
        if @project_todo_comment.save
          format.html { redirect_to [@project, @project_todo, @project_todo_comment], :notice =&gt; 'Project todo comment was successfully created.' }
          format.json { render action: 'show', status: :created, location: @project_todo_comment }
        else
          format.html { render action: 'new' }
          format.json { render json: @project_todo_comment.errors, status: :unprocessable_entity }
        end
      end
    end
  
    def update
      @project_todo_comment = @project_todo.project_todo_comments.find(params[:id])
      respond_to do |format|
        if @project_todo_comment.update_attributes(params[:project_todo_comment])
          format.html { redirect_to [@project, @project_todo, @project_todo_comment], notice: 'Project todo comment was successfully updated.' }
          format.json { head :no_content }
        else
          format.html { render action: 'edit' }
          format.json { render json: @project_todo_comment.errors, status: :unprocessable_entity }
        end
      end
    end
  
    def destroy
      @project_todo_comment = @project_todo.project_todo_comments.find(params[:id])
      @project_todo_comment.destroy
      respond_to do |format|
        format.html { redirect_to project_project_todo_project_todo_comments_url }
        format.json { head :no_content }
      end
    end
  
    private
  
      def load_project
        @project = Project.find(params[:project_id])
      end
  
      def load_todo
        @project_todo = ProjectTodo.find(params[:project_todo_id])
      end
  
  
      def set_project_todo_comment
        @project_todo_comment = ProjectTodoComment.find(params[:id])
      end
  
      def project_todo_comment_params
        params.require(:project_todo_comment).permit(:comment, :project_todo_id)
      end
  end
  </code></pre>