Rails4 - Maintain Context in Deeply Nested Hierarchy - ruby-on-rails-4

I have a 5 to 10 level deep model hierarchy in my Rails Project.
Example (this is not the actual hierarchy)
(has_many)
-- Project
-- SubProject
-- Blog
-- Post
-- Comment
-- Rating
Each Project has many Sub Projects, which has many Blogs, which has many Posts, and so forth.
So, in practice I want to be able to (for example) see all Posts of a specific Blog, see all Ratings of a specific Comment, and so forth. But I never want to loose the context of a earlier hierarchy level (e.g. Project).
Basically, the solution seems to be
resource :projects do
resource :sub_projects do
resource :blogs do
resource posts do
resource :comments do
resource :ratings do
# might go even deeper...
end
end
end
end
end
end
So I can navingate to e.g. projects/2/sub_projects/3/blogs/1/posts/5/comments/2/ratings
Is there a better solution?
Besides the ugly route path I think it will be painful to work with paths like
new_project_sub_project_blog_post_comment_rating_path(#project, #sub_project, #blog, #post, #comment, #rating)

Related

Rails 4.2+: deep nesting resources when it's required to display data from entire resource chain

I'm familiar with the guideline not to nest more than 1 level deep, and I understand the various discussions about having the shortest URLs possible both in and out of code.
Most of the StackOverflow questions and Googling I've done answer this by presenting use cases where one doesn't have to access all elements in the entire chain.
But what do you do when you have to access the parent resources further up the chain for every page you're working with?
/{ACCOUNT_SLUG}/applications/{APPLICATION_UUID}/borrower/employments/{UUID}
When dealing with a single employment record, I have to display account-specific information take from the parent account object via the ACCOUNT_SLUG, as well as application information. And technically, my borrower is also a DB query, but since there is only 1 for each application, I don't have to add an id/slug in the URL.
General advice is to do something like:
/employments/{UUID}
But if I do that, then in my controller code (and elsewhere) I still have to do:
#employment = Employment.find_by(uuid: params[:uuid])
#account = #employment.borrower.application.account
So I have to walk the entire parent association chain and execute those association DB queries. How does one resolve this kind of situation where the deep nested association has to be kept, from the first parent to the last child?
1: Add association attributes to all the children
class Employment
belongs_to :borrower
belongs_to :application
belongs_to :account
end
Now I've got a bunch of associations going on everywhere, just to walk a clear chain. This seems like "dependency hell" to me?...
2: Go with deep nested routes; Github does it
I've noticed Github actually employs deep nested routes:
github.com/{USERNAME}/{REPO}/settings/hooks
While not as deeply-nested as my use case, they still nest everything underneath the username and repo, and if you list out their verbose URL it is:
/github.com/accounts/{USERNAME}/repos/{REPO_NAME}/settings/hooks
Is anyone aware if Github has a method of optimizing this deep nesting, or are they just looking up the account and repo with every request and biting the DB query overhead (probably not that big a deal...)?
3: Deep nest the routes, but make your own URL helpers to keep it clean
Using /{ACOUNT_SLUG}/applications/{APPLICATION_UUID}/borrower/employments/{UUID}, the URL helper would look something like:
account_applications_borrower_employments_path(#account, #application, #borrower, #employment)
This could be cleaned up using some helpers:
def borrower_employment_path(#employment)
#borrower = #employment.borrower
#application = #borrower.application
#account = #application.account
account_applications_borrower_employments_path(#account, #application, #borrower, #employment)
end
Very detailed question. Kinda late to respond, but in the past when the problem of nested resources has bitten me, it usually ends up that I need to revisit the way I relationally modeled my database.
Of the possible solutions you suggested, number 1 is probably the best considering the opinionated nature of the Rails framework. Rails associations are very powerful and leveraging them is useful as your app scales.
Addressing concerns about dependencies between your models:
Without seeing your schema, I can only guess, but your modeling could look like this:
Class Account
has_many :applications
end
Class Application
belongs_to :account
has_one :borrower
has_one :employment
end
Class Borrower
belongs_to :application
end
Class Employment
belongs_to :application
has_one :account, through: :application
end
This is my best guess from the dissecting the info above. With this modeling, you would be able to do this in a controller action:
def action_name
#employment = Employment.find_by(uuid: params[:uuid])
#account = #employment.account
end
You would be able to add extra associations, say, if you wanted to do:
#employment.borrower
by adding:
class Employment
belongs_to :application
has_one :account, through: :application
has_one :borrower, through: :application
end
I always give this a good read every once and while just to keep it fresh:
http://guides.rubyonrails.org/association_basics.html#the-types-of-associations
Rails has generally discouraged heavily nested routing, despite Github's implementation of their application:
http://guides.rubyonrails.org/routing.html#nested-resources

Using non-trivial REST API and Ember

We're new to Ember, and our intended (ember-cli) app first works by opening a project (which we can think of a JSON object), and then acting on various sections of that project with various functions. We have this "pick your project first" approach neatly encapsulated in a Django REST api structure, e.g.
/projects/ lists all projects
/projects/1/ gives information about project 1
/projects/1/sectionA/ list all elements in sectionA of project 1
/projects/1/sectionA/2/ gives information about element 2 of sectionA in project 1
/projects/1/sectionA/2/sectionB/... and so on.
We made relatively good progress with the first two points in Ember using ember-data and this.store('project').find(...) etc. However, we've come unstuck trying to add further to our url (e.g. points 3., 4., and 5.). I believe our issues come from routing and handling multiple models (e.g. project and sectionA).
The question: what is the best way to structure the routes in Ember.js to match a non-trivial REST API, and use ember-data similarly?
Comments:
the "Ember way", and stuff working out of the box is preferred. Custom adapters and .getJSON might work, but we're not sure if we'll then lose out on what Ember offers.
we want the choice of project to affect the main app template. E.g. if a project does not have "sectionA", then a link to "sectionA" is not displayed in the main app. And, if the project does have "sectionA", we need the link to be to e.g. "/project/1/sectionA", i.e. dependant on the project open.
This seems similar to handling users (i.e. first I must "pick a user" and then continue), where the problem is solved outside of the URL (and is similar to using sessions as we have done in the past). However, we specifically want the project ID to be inside the URL, to remain stateless.
Bonus questions (if relevant):
how would we structure the models? Do we need to use hasMany/belongsTo and, if so, is this equivalent to just loading the whole project JSON in the first place?
can ember-data handle such complex requests? I.e. "give me item 2 from sectionA of project 1"? Can it do this "in one go", or do there have to be nested queries (i.e. "first give me project 1" and then from this "give me sectionA" and then from this "give me item 1")?
Finally, apologies if this is documented well somewhere. We've spent nearly a week trying to figure this out and have tried our best to find resources -- it's possible we just don't know what we're looking for.
I think this one will be a good thing to read: discuss.emberjs.com/t/… - you've got Tom Dale and Stefan Penner involved in the thread
My suggestion would be to change it to query params:
/projects?id=1&selectionA=a&selectionB=b
then, you won't have such problems. And yes, you can still use all the hasMany and belongsTo fields.
If there's anything unclear, I'll provide you with a longer answer (if I'm able to).
Check out ember-api-actions and ember-data-actions also ember-data-url-templates
Here's a few more resources from a blog I found. ember-data-working-with-custom-api-endpoints and ember-data-working-with-nested-api-resources

Sharing routing concerns between rails engines

I have two different isolated mountable rails engines; one named Core, other as Finance;
The core engine has a comment resource and routing concern like;
Core::Engine.routes.draw do
concern :commentable do
resources :comments
end
end
And the Finance engine has a invoice model;
Finance::Engine.routes.draw do
resources :invoices, concerns: :commentable
end
Both these engines added main app's Gemfile, and routes.rb file like below;
Gemfile;
gem 'core', path: "../core"
gem 'finance', path: "../finance"
routes.rb;
mount Core::Engine, at: "/"
mount Finance::Engine, at: "/"
At the finance gem; invoice show.erb has comment form like below;
<%= form_for [#invoice, #comment] %>
but it seems rails 4 can't share routing concerns between engines. I have found so many questions on stackoverflow, but still can't find a good solution.
Maybe this not avaliable in rails engines; is there any way two handle this.
Thanks.
Struggling a little bit with Ryan's answer (how to correctly include it where?), I came up with the following solution without explicitly using concerns but still sharing routes:
# 1. Define a new method for your concern in a module,
# maybe in `lib/commentable_concern.rb`
module CommentableConcern
def commentable_concern
resources :comments
end
end
# 2. Include the module it into ActionDispatch::Routing::Mapper to make
# the methods available everywhere, maybe in an initializer
ActionDispatch::Routing::Mapper.include CommentableConcern
# 3. Call the new method where you want
# (this way `concerns: []` does not work) obviously
Finance::Engine.routes.draw do
resources :invoices do
commentable_concern
end
end
This monkey patches ActionDispatch::Routing::Mapper, but as long as you only define new methods (and do not touch existing ones) it should be safe.
I don't think that it's possible to do that because each engine is its own container and you can't reach across between engines to do what you're attempting to do.
Instead, define a module which you can include in both contexts which define the same concern:
module CommentableConcern
def self.included(base)
base.instance_eval do
concern :commentable do
resources :comments
end
end
end
end
I think this is the only way you can accomplish that.

Opinion: Where to put this code in django app:

Trying to figure out the best way to accomplish this. I have inhereted a Django project that is pretty well done.
There are a number of pre coded modules that a user can include in a page's (a page and a module are models in this app) left well in the admin (ie: side links, ads, constant contact).
A new requirement involves inserting a module of internal links in the same well. These links are not associated with a page in the same way the other modules, they are a seperate many to many join - ie one link can be reused in a set across all the pages.
the template pseudo code is:
if page has modules:
loop through modules:
write the pre coded content of module
Since the links need to be in the same well as the modules, I have created a "link placeholder module" with a slug of link-placeholder.
The new pseudo code is:
if page has modules:
loop through modules:
if module.slug is "link-placeholder":
loop through page.links and output each
else:
write pre-coded module
My question is where is the best place to write this output for the links? As I see it, my options are:
Build the out put in the template (easy, but kind of gets messy - code is nice and neat now)
Build a function in the page model that is called when the "link placeholder is encountered) page.get_internal_link_ouutput. Essentially this would query, build and print internal link module output.
Do the same thing with a custom template tag.
I am leaning towards 2 or 3, but it doesn't seem like the right place to do it. I guess I sometimes get a little confused about the best place to put code in django apps, though I do really like the framework.
Thanks in advance for any advice.
I'd recommend using a custom template tag.
Writing the code directly into the template is not the right place for that much logic, and I don't believe a model should have template-specific methods added to it. Better to have template-specific logic live in template-specific classes and functions (e.g. template tags).

REST URIs and operations on an object that can be commented on, tagged, rated, etc

I'm doing research into a web API for my company, and it's starting to look like we might implement a RESTful one. I've read a couple of books about this now (O'Reilly's "RESTful web services" seeming the most useful) and have come up with the following set of URIs and operations for an object that can be commented on, tagged, and rated.
It doesn't really matter what the object is, as this scenario applies to many things on the net, but for the sake of argument lets say it's a movie.
Some of these seem to fit quite naturally, but others seem a bit forced (rating and tagging particularly) so does anybody have any suggestions about how these could be improved? I'll list them with the URI and then the supported verbs, and what I propose they would do.
/movies
GET = List movies
/movies/5
GET = Get movie 5
/movies/5/comments
GET = List comments on movie 5
POST = Create a new comment on movie 5
/movies/5/comments/8
GET = Get comment 8 on movie 5
POST = Reply to comment 8 on movie 5
PUT = Update comment 8 on movie 5
/movies/5/comments/8/flag
GET = Check whether the movies is flagged as inappropriate (404 if not)
PUT = Flag movie as inappropriate
/movies/5/rating
GET = Get the rating of the movie
POST = Add the user rating of the movie to the overall rating
Edit: My intention is that the movie object would contain its rating as a property, so I wouldn't really expect the GET method to be used here. The URI really exists so that the rating can be an individual resource that can be updated using the POST verb. I'm not sure if this is the best way of doing it, but I can't think of a better one
/movies/5/tags/tagname
GET = Check whether the movies is tagged with tagname (404 if not; but if it is tagged with the tag name should it return the actual tag resource by redirecting to something like /tags/tagname?)
PUT = Add tag tagname to the movie, creating the tag resource /tags/tagname if required
DELETE = Remove tag tagname from the movie, deleting the tag resource tags/tagname if nothing is tagged with it after this removal
Note that these wouldn't be the entire URIs, for example the URI to list the movies would support filtering, paging and sorting. For this I was planning on something like:
/movies/action;90s/rating,desc/20-40
Where:
action;90s is a semi-colon delimited set of filter criteria
rating,desc is the sort order and direction
20-40 is the range of item indices to get
Any comments about this API scheme too?
Edit #1
This post is getting quite long now! After reading some of the answers and comments, this is the changes from above I'm planning on making:
Tags will be handled as a group rather than individually, so they will be at:
/movies/5/tags
GET = List tags
POST = Union of specified tags and existing tags
PUT = Replace any current tags with specified tags
DELETE = Delete all tags
I'm still really not sure how to handle flagging a comment though. One option is that instead of POSTing to a comment replying to it, a comment object will include its parent so it can be POSTed to the general URI, i.e.
/movie/5/comment
POST = Create a new comment (which may be a reply to a comment)
I could then use the POST to a comment to flag it. But this still doesn't feel quite right.
/movie/5/comment/8
POST = Flag comment
Most of what you have looks good. There were just a couple of strange things I saw. When I put my URLs together, I try to follow these four principles.
Peel the onion
If you make the R in REST really be a resource then the resource URL should be able to be peeled back and still be meaningful. If it doesn't make sense you should rethink how to organize the resource. So in the case below, each makes sense. I am either looking at a specific item, or a collection of items.
/movies/horror/10/
/movies/horror/
/movies/
The following seems funny to me because flag isn't a resource, it's a property of the movie.
/movies/5/comments/8/flag -> Funny
/movies/5/comments/8/ -> Gives me all properties of comment including flag
Define the View
The last peice of the URL describes how to show the resource. The URL /movies/horror/ tells me I will have a collection of movies refined by horror. But there might be different ways I want to display that collection.
/movies/horror/simple
/movies/horror/expanded
The simple view might just be the title and an image. The expanded view would give a lot more information like description, synopsis, and ratings.
Helpers
After the resource has been limited and the proper view figured out, query string parameters are used to help the UI with the little stuff. The most common query string parameters I use are
p => Page
n => number of items to display
sortby => field to sort by
asc => sort ascending
So I could end up with a URL like
/movies/horror/default?p=12&n=50&sortby=name
This will give me the list of movies limited to horror movies with the default view; starting on page 12 with 50 movies per page where the movies are sorted by name.
Actions
The last thing needed are your action on the resource. The action are either collection based or item based.
/movies/horror/
GET -> Get resources as a list
POST -> Create, Update
/movies/horror/10/
GET -> Get resource as item
POST -> Update
I hope this helps.
I disagree with the edit. Queries should be defined by querystrings as per Martijn Laarman's post. i.e.:
/movies?genre=action&timeframe=90s&lbound=20&ubound=40&order=desc
Well, the way I see it some of the information you return now as objects could simply be added to the metadata of its parent object.
For instance, rating could be part of the response of /movies/5
<movie>
<title>..</title>
..
<rating url="movies/ratings/4">4</rating>
<tags>
<tag url="movies/tags/creative">creative</tag>
...
Removing a tag simply means posting the above response without that tag.
Also queries should go in URL variables, I believe:
/movies/?startsWith=Forrest%20G&orderBy=DateAdded
Based on my understanding of ROA (I'm only on chapter five of RESTful Web Services) it looks good to me.
This is an awesome initial draft for a spec of a REST API. The next step would to specify expected return codes (like you did with "404 No Tag Available"), acceptable Content-Types, and available content-types (e.g., HTML, JSON). Doing that should expose any additional chinks you'll need to hammer out.
#Nelson LaQuet:
Using the HTTP methods as they are actually defined gives you the safety of knowing that executing a GET on anything on a web site or service won't eat your data or otherwise mangle it. As an example (pointed out in RESTful Web Services) Google's Web Accelerator expects this behaviour -- as stated in the FAQ -- and presumably other services do too.
Also it gets you idempotency for free. That is doing a GET, DELETE, HEAD or PUT on a resource more than once is the same as doing it only once. Thus if your request fails then all you have to do is run it again.
This is not REST.
A REST API must not define fixed resource names or hierarchies (an obvious coupling of client and server). Servers must have the freedom to control their own namespace. Instead, allow servers to instruct clients on how to construct appropriate URIs, such as is done in HTML forms and URI templates, by defining those instructions within media types and link relations. [Failure here implies that clients are assuming a resource structure due to out-of band information, such as a domain-specific standard, which is the data-oriented equivalent to RPC's functional coupling].
http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven