preaction / Yancy

The Best Web Framework Deserves the Best Content Management System
http://preaction.me/yancy/
Other
54 stars 21 forks source link

Template Plugin #53

Open preaction opened 5 years ago

preaction commented 5 years ago

It would be nice if the editor could edit the application's templates. To do this, we need to:

  1. [ ] Create a Yancy::Plugin::Template that adds a new collection (templates) to the existing Yancy backend
    • [ ] Database schema for this table should be provided for all supported backends via a named migration
    • [ ] The table should have two fields: A name (which is the full path of the template including format [e.g. html] and template handler [e.g. ep]) and a template, which is the code of the template.
  2. [ ] When the application starts, load the templates so they are available to the application
    • Register a template handler for Yancy and make it the default handler.
      • This has the benefit of allowing some routes to bypass the Yancy templates by explicitly using the ep handler
    • The Yancy template handler should render the template from a cache (use the renderer cache)
      • We will need to copy parts of the EPRenderer to enable helpers and access to the controller
      • If the template doesn't exist in the cache, look for the template in the database. If it doesn't exist in the database, defer to the EPRenderer
      • We need to expire the cache regularly through a timer: Only the one worker that responds to the editor's API request to change the template will get the backend event to fire.
    • If the above does not work well, create a subclass of Mojolicious::Renderer and install that as the app's renderer (keeping the existing app renderer as a fallback). Then Yancy has total control over rendering and can delegate to Mojolicious::Renderer as desired.
  3. [ ] Add backend events (#52) to update the internal template cache when a template is created, updated, or deleted
mohawk2 commented 5 years ago

I'd suggest a handler attribute for the plugin, which takes the name as a parameter, and defaults to a sub calling the backend getting the templates collection, but could be overridden.

I'd also suggest that loading all the templates at startup won't scale. There may need to be some caching, but that can be in a later version. This is separate from the renderer cache, which is of course essential.

I'm not sure about the last point - perhaps just an expiring cache, with a default of 10 minutes' life, instead? (I like the general "backend events" concept, of course)

preaction commented 5 years ago

These are what the Mojolicious renderers do: All templates are cached upon first load from the filesystem or the data section and stored forever. The Mojolicious::Renderer cache does not expire. Reloading the app is the only way to reload the templates.

For the Yancy plugin, expiring the cache could happen on set/create/delete and then wait for the cache to be repopulated by a request (but why not just update the cache during the edit request and save the other users from having to wait?).

You're right that we will also need to expire the cache regularly through a timer: Only the one worker that responds to the editor's API request to change the template will get the backend event to fire.

The Mojolicious::Renderer's warmup sub does a scan for all the paths and data sections to find the templates. Since it won't find Yancy's, we can't override existing templates (which is a desirable feature). Reading the existing templates from the database is necessary so we can tell the renderer that the Yancy template plugin should handle those (indeed, we have to put our handler first in the list of possible handlers for that template name). There doesn't seem to be a public API for this, so we might need to say that overriding existing templates can't be done (which diminishes the usefulness of this plugin). [Edit: The other possibility if we are having to do very bad things to make this work is to create a subclass of Mojolicious::Renderer and then we have total control over rendering and can delegate to Mojolicious::Renderer as desired.]

I don't anticipate users having thousands of templates via this. If they do, they are modeling their data very wrongly. A basic markdown-based page content area can share space with this to have both static (markdown) and dynamic (template) content. We don't need to optimize for pathological cases.

If someone wants to change how templates are loaded (if not from the Yancy backend), they can write their own renderer like Yancy is. If some compelling arguments can be made for editing the Mojo::Template objects after they're created, we can add some events there.