Loading resources from other than the default URL

I often find myslef in the need to split one action into multiple different ones, mostly for index actions. Here’s an example, we have a /posts resource, where each post has multiple attributes. We might want to do

  • /posts - list of posts
  • /posts?query=foo fulltext search
  • /posts?tags=foo,bar,baz - search by tags
  • /posts?random - random post
  • /posts?random&limit=3 - 3 random posts
  • /posts?featured - featured posts
  • /posts?recommended - recommended posts for the current user

Now this is just the begining of a long list, and of course these can mix. In ideal world, these could be just be URLs, for example we could get published posts at /posts/published, but that’s not possible to do with Ember Data.

The result is that the backend looks like this

def index
  if params[:query]
    render json: Post.all
  elsif params[:tags]
    ...
  elsif ...
    # and the list goes on
end

Of course I would make a custom router that would dispatch based on the params, but that doesn’t feel RESTful at all. It kinda reminds me of the old PHP days of /index.php?action=foo&params=bar.

If we do decide to use custom routes, it forces us to go with store.load and $.ajax, for example

$.getJSON("/posts/published").then(function(data) {
  this.get("store").load(App.Post, data.post);
}.bind(this));

but what if we’re sideloading in this case? We basically need to do that manually.

$.getJSON("/posts/published").then(function(data) {
  var store = this.get("store");

  store.load(App.Post, data.post);
  store.load(App.Comment, data.comments);
}.bind(this));

Am I the only one experiencing this? I found that if the app is small-medium size, it isn’t such a problem to stick everything into one action, but once it grows and you have a lot of different ways of using the same resource, this approach becomes completely unmanagable.

One approach you could consider is using resource nesting and query parameters to better capture the different actions you want to perform on your /posts resource.

For example, you could have a route like /posts/search that accepts query parameters for full text search, tags, and other filters. This would allow you to structure your backend code like this:

def search
  if params[:query]
    render json: Post.where('title LIKE ?', "%#{params[:query]}%")
  elsif params[:tags]
    render json: Post.tagged_with(params[:tags])
  # and so on
end

This would allow you to use Ember Data’s built-in support for sideloading to easily handle related resources. For example:

store.find('post', { search: { query: 'foo' } }).then(function(posts) {
  // the post records and their related records will be loaded into the store
});

You could also consider using a combination of resource nesting and query parameters to handle the different actions you want to perform on your /posts resource. For example:

/posts/featured - featured posts
/posts/recommended - recommended posts for the current user
/posts/random?limit=3 - 3 random posts

This would allow you to structure your backend code like this:

def featured
  render json: Post.where(featured: true)
end

def recommended
  render json: current_user.recommended_posts
end

def random
  render json: Post.order("RANDOM()").limit(params[:limit])
end

This approach would allow you to use Ember Data’s built-in support for sideloading and keep your API more RESTful.

1 Like