grounded / afterburnercms

The CMS part.
BSD 3-Clause "New" or "Revised" License
16 stars 0 forks source link

How are pages supposed to be shown in the frontend? #81

Open knewter opened 11 years ago

knewter commented 11 years ago

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.

robyurkowski commented 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:

  1. The loss of controller customizability (i.e. every path lookup will go through the same controller) will mean that we have to adjust for that missing responsibility. So, for example, we can store custom layout and template data on the pathing object itself, but how do we handle things like response types? Should all requests simply respond to JSON? Or how do we detect that a response should respond to JSON? Do we need an additional path for a JSON exposure?
  2. If we're going to be doing our own custom routing, does it really make sense to use Journey any more?
  3. How the hell do we make 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?

knewter commented 11 years ago

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?

robyurkowski commented 11 years ago

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.