Search

Google
 

Monday, July 9, 2007

Why Radiant?

    My vision for Radiant, even though I'm not a member of the core team
would be one of a 'base' or 'core' CMS that Radiant is today. Radiant
would be a relatively neutral system from which developers could easily
extend to solve problems within their specific domain. My ideal
situation would be to simply be able to type something like
'script/radiant_plugin install radiant_blog' and automatically get
comments, trackbacks, tagging and other blog functionality automatically
integrated into the database and admin interface. Make a note that I
don't want to drop modules on top of the CMS, I want to fundamentally
change its operation to suit the problem domain.

What has to be covered?
I will assume that the installation of Radiant is clean and has not
been installed over anything else. My proposal is for a plugin system
that works easily and seamlessly alongside of the rails plugin system.
The goals of my proposals are the following:

1) The plugins should be compatible with the rails plugin system
2) The plugins should not be dependent on any plugins not controlled by
the radiant core team
3) All plugins should be non-destructive and should not heavily modify
the workings of the core CMS
4) All plugins should be easily removed and all changes should be
reversible (this is particularly important to migrations)
5) The burden of extending the functionality of the system should lie on
the developer of the plugin which means as little modification as
possible should be done to the current radiant system.

There are 4 main considerations when extending Radiant in a way that
allows things to be easily packaged. They are as follows:

1) Modifying controllers and models
2) Adding behaviors and radius tags
3) Adding table to the database
4) Modifying the administration views

Proposed Solutions
1) Modifying controllers and models
This is what rails plugins were meant to do. Since our plugins are
rails plugins we get this for free. There is one consideration. In order
for a plugin to not break compatibility as radiant gets upgraded the
preferred method of modifying controller and model logic is to use
before and after hooks so that if Radiant's core functionality is
changed in the future the plugin will not break that functionality.

2) Adding behaviors and radius tags
If you use the normal rails plugin structure then adding tags and
behaviors to radiant is easy.

3) Migrating the database
Migrating the database is slight more difficult as rails doesn't
have any built in methods for migrating out of plugin directories. This
is where I want to suggest another file in the main plugin directory
that sits alongside init.rb called radiant.rb. Radiant.rb is a file that
contains initialization code that is specific to the plugin being a
radiant plugin. Radiant.rb does not replace init.rb. This file will
refer to the directory with the database migrations along with any
future improvements to the plugin system that might come along.
A separate rake task will be needed to run the plugin migrations but
can easily be included in lib/tasks. This task will look for all plugins
with a radiant.rb file and attempt to migrate them into the current
environment's database. The one rule that should be applied to radiant
plugin migrations is that forward migrations must be non-destructive to
the 'core' CMS structure. In the event that two different plugins'
migrations clash the migration process itself will cause an error to be
reported to the user. There is the possibility that these migrations
don't increment the version number of the database and simply work as an
atomic database change that the normal migrations are unaware of. This
needs to be investigated a bit further, but this can be solved.
There should be a separate script command that gets the plugin like
a normal rails plugin, and then runs the migrations if the user chooses
to do so. There should also be a script for reverting the migrations and
deleting the plugin. These are relatively trivial to code.

4) Modifying the administration views
The problem of modifying the admin interface really can be a show
stopper. I have thought of a few solutions and I will present the one
solution that would get most of the way there and is easiest to
implement into the current system.

The solution is to rely on helpers to place callbacks during the
rendering of the of the administrative views. These filters would be
before each item in a list, form, or set of tabs and before the ending
of the list, form or set of tabs. The goal is to break down the
administrative interface into smaller pieces that can be independently
added onto by either changing the data before a list item or form
control gets rendered or by rendering new list items or form controls.

How coarse or fine should these helper callbacks be? Well my idea
for instance on the 'Edit Page' action is to place a call to a blank
helper method called before_title_input just before the title input box
and before_body_input just before the body input. This would allow
plugin developers to easily extend the existing forms and lists by
assigning before filters to the form or list element that is about to be
rendered. Tabs could be achieved through a bit of coercing of the same
methodology only with helpers that make callbacks inside of javascript
blocks (this might take some more investigation)

I'm of the opinion that simplicity should trump flexibility in most
of these cases and that the callbacks should be kept to a minimum to
allow modification of the default behavior. What might an implementation
of this system look like? Lets try and add a trackback URL to our New
Page and Edit Page action.

The *plugin helper* might look like

module Admin::PageHelper
before_filter :trackback_input, :only => [:end_new_form, :end_edit_form]

def trackback_input(*args)
trackback_input = ''
end
end

The *plugin controller* might look like

class Admin::PageController < ApplicationController
after_filter :do_trackback, :only => [:new, :edit]

def do_trackback
if request.post?
if @page.errors.blank?
# code for making a trackback requests using params[:trackbacks]...
end
end
end
end

What might we have to do to the radiant system to have this functionality?

The *radiant helpers* might look like

module ApplicationHelper
# holds the list of filter functions in some structure (possibly a
nested hash?)
attr_accessor :filters

def before_filter(filtered_method, options = {})
# add a new lambda with appropriate options to the filters list
end

def method_missing(*args)
# check if it starts with 'filters_for' and then run filters for the
proper helper
end
end

module Admin::PageHelper
def end_new_form(*args)
filters_for_end_new_form(args)
end

def end_edit_form(*args)
filters_for_end_edit_form(args)
end

... More Callbacks Go Here ...
end

The *radiant view* might look like

<%= start_form_tag %>

... default form stuff here ...

<%= end_new_form %>

<%= end_form_tag %>

The potential to have a long list of callbacks is definitely there
but I still think it is one of the easiest way to implement flexibility
into the administration system without hacking deeper into rails to
alter how views are rendered. I'd say there would be about 7 or 8
callbacks per helper. It might end up not being manageable but if there
is a standard naming convention it shouldn't be bad at all. This would
allow new callbacks to be added easily without breaking old
functionality. It also allows more than one plugin to register into a
callback chain so that more than one domain specific plugin can be
present at one time, leading to a cross-domain specific CMS.

No comments:

Is this post help u.