Posts Tagged ‘metaprogramming’

Metaprogramming with RadiantCMS

January 26, 2010 1 comment

My short stint with Radiant in the near past was actually quite interesting ! — No I did not design websites or webpages. What I am talking about is the internals – making radiant plugins and creating radius tags, the overwhelming use of meta-programing and the ease of overriding and extending the base or extensions functionality.

First-up, Radiant is NOT for newbies. If you are looking to design some static webpages or basic content, I would probably not recommend radiant – there are other tools for this. Radiant however is very exciting if you are planning on things which are more complicated than static web-pages, like authorization based views, galleries, custom website configuration, etc. etc. If you are well versed and aware of page-parts, snippets, layouts and how they all interact with each other, then you can look at Radiant

Though radiant supports ALL basic HTML tags, it also supports radius tags — loaded via extensions. The excellent open source contributions as radiant extensions leaves very little to do on your own ;) but then again – there is nothing called a free lunch.

Its easy to override any existing functionality in core radiant or its extensions by some simple meta-programing. Some good insight here

module MyExtension
  module SiteControllerExt
    def self.included(base)
      base.class_eval {
        def new_method
          ... # whatever new functionality you want to add to the site controller

You can then activate the SiteController by adding these additional methods in the meta-class.

def activate
  SiteController.send(:include, MyExtension::SiteControllerExt)

You can modify existing behavior of any method by using the alias_method_chain as in the following example:

module MyExtension
  module SiteControllerExt
    def self.included(base)
      base.class_eval {
        def site_with_clone
          ... # pre-processing
          ... # post-processing

        alias_method_chain :site, :clone

The alias chain method ensures that we can write some code before or after the core method. Similarly you can override the views. More details here The above are some very simple basic rules of meta-programing.

base.class_eval {

This line of code is actually accessing the meta-class and changing it. In other words, we evaluate the Class object and update the methods in it! This helps us add or modify the functionality of the class.

Radiant Multihosting

Radius and multisite hosting is cool — but you should know how it works to reap the benefits. The following extensions help out: multi_site, scoped_admin.

Both these ensure that all pages are site_scoped. Using some basic meta-programing, each site now has its own ‘scope’. Remember the all important Page.current_site !! Sandip and  I busted our heads trying to figure out why the following was ‘wierd’. On the console

>> Site.find 13
=> #<Site id: 13, homepage_id: 74, position: 4, ... >
>> s = Site.find(13).layouts
=> []

This implies that The Site (with id 13) does not have any layouts! However, a direct find_by_sql had a different answer in store for us !

>> Layout.find_by_sql("select * from layouts where site_id=13")
=> [#<Layout id: 29, name: "Basic", content: "<html>\r\n ... >

This implies that the data is there in the database BUT not available from the associations! So frigging wierd. Then we got into the details of multi_site plugin and realized the 'error in our ways'.  Radiant is by default single site scoped. When we work with multi_site, we need to fool Radiant and tell it which site we are working with! The following works:

>> Page.current_site = Site.find(13)
>> s = Site.find(13)
>> s.layouts
=> [#<Layout id: 29, name: "Basic", content: "<head> ... lock_version: 0, site_id: 13> ]

So, it extremely important to ensure that the Page.current_site is set when working with multi-hosting. So, why did this happen or how is it so, you ask? The multi_site extension, uses some pretty cool metaprograming to override (using alias_method_chain) find, count, max, min, and other ActiveRecord methods. It defaults to ‘default_site’ if the current_site is not set !

Extension Loading and Configurations

Using Radiant extensions is what makes Radiant radiant! :) There are plenty of extensions that can be easily installed in the radiant setup. However, its important to remember the following:

  • environment.rb config.extensions is the file to ensure ‘extension load ordering’. This is very important especially if you want to override some extension functionality.
  • The :all symbol in config.extensions implies the remaining extensions. BUT remember that these extension are then loaded in ALPHABETICAL order! So, be careful to either ensure your call your extension ‘zzz_myextension’ (LoL) or ensure that any extensions you override load before you load your own extension.
  • The ‘settings’ extension is pretty cool for managing configurations. To add a configuration, you can simply do this:
  • Radiant::Config["key"] = value
  • Recommended way of adding default configurations is the have yml file use YAML::load_file in the extension activate function.
  • Remember, the configuration is saved in the database — so if you override the value from the GUI, the database gets updated — NOT the yml file. So you have to be careful.

Radius tags

Radius tags look something like this:

<r:content />
<r:gallery id="id> ... </r:gallery>

Radius tags help in managing the programing part of dynamic pages. You can add your own radius tags to ease the effort of inserting HTML code. Some cool examples are:

<r:gallery:lightbox_stuff />

This generates the HTML for loading JS and CSS for lightbox — simple. To create a lightbox:

<r:gallery id="3">
 <r:gallery:lighbox />

To add your own radius tags, you need to do the following in a module.

module MyExtensionModule
 module CustomTags

 include Radiant::Taggable

 tag "tractor" do |tag|
  desc "description of the tag"
  tag "tagname" do |tag|
    ... #custom logic

In the extension loader file, if you want to use the radius tags in the Pages, ensure this:

Page.send :include, MyExtensionModule::CustomTags

Enjoy the radiance!


Get every new post delivered to your Inbox.