0

This has driven me to drinking, so I'll post this question while I'm still sober...

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).

The application has 3 models so far:

1. Project

2. ProjectTodo

3. ProjectTodoComment

I wanted to have url paths that I could use as endpoints for the JSON api, so I nested the resources:

enter image description here

I wanted to keep the html.erb view files in order to have an administrative backend that was not reliant on JavaScript.

Because of the nesting, the routes have gotten rather convoluted. Here's an example from the the ProjectTodoComments view:

<tbody>
<% @project_todo_comments.each do |project_todo_comment| %>
  <tr>
    <td><%= project_todo_comment.comment %></td>
    <td><%= project_todo_comment.project_todo_id %></td>
    <td><%= link_to 'Show', project_project_todo_project_todo_comment_path(@project, @project_todo, project_todo_comment) %></td>
    <td><%= link_to 'Edit', edit_project_project_todo_project_todo_comment_path(@project, @project_todo, project_todo_comment) %></td>
    <td><%= link_to 'Destroy', [@project, @project_todo, project_todo_comment], method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>

By running rake routes I can show you the entire list of routes that are created: enter image description here

This nesting seems to have an overly complex effect on all of the code in the app. Here is the create method of the project_todo_comments_controller:

enter image description here

I'm having to pass in 3 objects in order to create the comment.

My plan was to be able to create versatile routes for any situation that I would be able to use in my JSON api.

By nesting the resources, it looks as if I have all the routes that I would need.

However, what if I add another dependent model - such as a User?

If the User model has a has_many association with the Project model, than I will have to nest the models 4 layers deep.

This just seems ridiculous - not to mention unmaintainable.

My question is...

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?

EDIT


Here are the Model associations:

Project

class Project < ActiveRecord::Base
  attr_accessible :title
  has_many :project_todos
end

ProjectTodo

class ProjectTodo < ActiveRecord::Base
  attr_accessible :project_id, :title
  belongs_to :project
  has_many :project_todo_comments
end

ProjectTodoComment

class ProjectTodoComment < ActiveRecord::Base
  attr_accessible :comment, :project_todo_id
  belongs_to :project_todo
end

Here is the entire controller for the most deeply nested model - ProjectTodoComment:

class ProjectTodoCommentsController < 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 => '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
3
  • Looks like you mixed up your erb file and your create method from the comments controller. Commented May 6, 2014 at 2:22
  • @aerook My mistake - fixed in the post. Thanks for the heads up. Commented May 6, 2014 at 2:31
  • Also, shallow-nested routes might reduce the complexity a lot. Commented Jun 26, 2015 at 4:14

1 Answer 1

0

I think you linked an html table from an erb file rather than the create method of the controller.

According to this, you could set up your api so that GET /projects/:project_id could return the entire project.project_todo.project_todo_comment object:

render json: @project,
  :include => { 
    :project_todos => { 
      :include => :project_todo_comments
    } 
  }  

You'd need to specify what nesting you want done at each endpoint. That'd let you clean up your routes. Can't help you much with the controller without seeing it. If you have your associations set up properly (which you'd need for the api change), you could just reference the todo and the todo_comments through the associations:

@project
@project.todos
@project.todos[n].comments # or however todos and comments are associated.
Sign up to request clarification or add additional context in comments.

Comments

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.