Open knewter opened 11 years ago
Relevant issues: #8, #14, #15.
I think that having some sort of Route object (heretofore we've been calling it Path
, which you yourself suggested, @knewter, lol) is a bit of an inevitability, especially with the requirement in CMS that we serve content and pages with completely arbitrary pathing - i.e. without the built-in RESTful namespacing under pages
or whatnot.
In the past, we've done glob routes and allowed other routes as well, but that has led to a lot of pain and confusion, so if we go the route of using a glob instead of a discrete pathing entity, we need to make sure we are appending the glob at the latest possible point so that users can write routes without having to prepend them to the CMS's routes.
If we do decide to serve all requests via Path
lookups, I have a couple of concerns that I think will need to be addressed before we can declare it functional:
url_for
work with viewable entities?At any rate, the Path entity I've been pondering looks something like this (this'll need adjustment, because it's the first time I've actually put it on paper):
class Path
# Is this a POST or a PUT or a GET? We should be careful to match.
# @return [Array] The possible request types
attr_reader :request_type
# The actual path string, relative to the root.
attr_reader :path
# The options that should be merged into params. For example, if
# this returns {:show_comments => true}, the Conductor should be able to
# check `params[:show_comments]` (or maybe check @path.options? Dunno)
# and see entry point-specific options.
#
# This would also be where we would store individual IDs for vanity/standard URLs.
# I.E. If we had /blog/posts/9 and /blog/my_blogpost_slug, they could both conceivably
# refer to the same entry, but both would have options of [:blog][:post_id] = 9.
attr_reader :options
# The conductor that this path should return to be instantiated by the controller
attr_reader :conductor
# Is this path the canonical path for the resource?
attr_reader :canonical
# Let paths be local to only certain locales, i.e. in French, I might have a separate path that
# is locale-specific (or I might not let people access an item through a french locale, so the
# lookup shouldn't return a path if I try to query for it in that language.
# TLDR: localized routes
attr_reader :locales
# Which template should the controller render?
attr_reader :template
# Which layout should the controller render?
attr_reader :layout
# If this is present, redirect to this location instead of instantiating a controller:
attr_reader :redirect_href
end
Of course, there will have to be a MenuEntry
entity that owns each path and specifies its title, etc...
WDYT, @parndt, @knewter, @ugisozols, @gogogarrett?
I'm liking it a lot. So a default Page that got created might also see something like this:
# this is of course pseudocode/made up
page = Page.create(title: 'this is neat', body: 'lolcat.jpg')
Path.create(options: { id: page.key }, request_type: Path::GET, path: '/pages/this-is-neat', conductor: 'FetchesPage', canonical: true, locales: [:en], template: 'default', layout: 'application')
Then visiting http://example.com/pages/this-is-neat would trigger the PathsController, which would figure out which Conductor to handle the request via, pass those options into the conductor, and then render that template/layout against the return data.
What's going to bite us with this?
Pedantry first: I imagined the request_type
to be an array, since there are legitimate situations where people would want to POST and to GET a URL (imagine a simple search form on the index of a resource). Instead of just passing the parameters, we could pass the entire request down to the conductor, which would give us access to the params but also to request.method
to figure out what's going on.
I also imagine that simply leaving template and layout nil would just force them to default. In JSON-like situations, I imagine you could actually pass an explicit false to layout so it wouldn't render (assuming we're adding additional paths for JSON, and not just taking our existing ones and tacking on .json
to all of them.
In terms of interface, we'll have to make sure that there is a friendly way of choosing conductors. I envision namespacing/categorizing them, and then giving each a simple description. So instead of choosing FetchesPages
or FetchingPage
from a dropdown, you'd have a grouped select with something like...
Pages
--> Index (Pages::ShowsIndex)
--> Show (Pages::ShowsPage, plus a select box to select a page)
Blog
--> Index (Blog::ShowsIndex)
--> Archive (Blog::ShowsArchive)
--> Category (Blog::ShowsCategory, plus a select box for category)
--> Posts (Blog::ShowsPost, plus a select box for post ID)
--> Author (Blog::ShowsByAuthor, plus a select box for author ID)
And maybe to be really freaking cool, we could have a backend ExplainsFunctionality that lists these out in long form ;)
To clarify something I wrote last night, I only mean to say that doing a mixed named-route + glob/wildcard-route mix is really painful, because it's hard for end users to get a predictable outcome. If we enforce using paths, then we shouldn't have so many issues.
Where is the pain? Permissions have the potential to be sticky, but I imagine permissions will fall to conductors, so this only promises a shadow of pain.
What should the to_s
look like? If we're associating paths with potentially multiple menu items, we want to make sure it's clear (and preferably short...)
With regards to URL for, we have helpers in Rails to parse out form submission paths from an entity. I think the challenge will be in figuring out how to make url_for
in forms to return proper submission path when they may not be predictable (or even exist).
Still, I don't think any of these issues are show-stoppers -- I think they're worth going ahead with this so we can actually start pondering those things.
Are we going to have a Route object? I know we have had discussions about this, just was going to implement something so the frontend could see pages, and realized that I wasn't sure how we were laying out which page was visible where.
We talked about providing different mechanisms for seeing a given piece of content. For now, so I can implement something, I'm just going to load the page with the title "Home" and we can iterate from there. Stupid and working is better than well-planned and unimplemented. Iteration above all, &c. &c.
@parndt @robyurkowski @GeekOnCoffee do we have this sketched out? I forget the names, but we had discussed there being something like a
Route
object that would link to a particular piece of content, optionally with a settings hash that could be passed into that object. For instance, one link to a blog post might have comments, and another might not, but they would be the same blog post.Anyway, like I say, I'll just use the title for now, no slug, and we can figure it out when we figure it out.