4 Simple Steps - Extending Ruby Objects - The Tip of the Iceberg with DCI

You've got a few Rails applications under your belt, perhaps several. But something is wrong. As you go, your development slows and your classes become bloated and harder to understand.

Keep your program simple

While you're doing your best to follow the concept of keeping your controllers skinny and your models fat, your models are getting really fat.

The answer is simple: cut the fat.

Your models don't need to have every method they will ever need defined in the class. The reality is that the objects that your application handles only need those methods when they need them and not at any other time. Read that again if you must, because it's true.

Step 1: Evaluate your code and look for a place to separate concerns

This is the point where you look at your code and try to realize what object needs what and when.

For example, if users of your system need to approve a friend request they only need to do it in the context of viewing that request. Your User class or @current_user object doesn't need this ability at any other time.

Step 2: Prepare your test suite

If you want to make your code simpler and easier to understand, write tests. You must do this first.

Even if you're only intending to change one small thing (just one tiny piece), write a test for that. You need a baseline.

Step 3: Create the Object Roles

Take your friend approval (or whatever it is) method or methods and put them in a module.

You might want to drop this into some namespace such as ObjectRole::FriendApprover or if you know your name won't clash with anything else, just go with FriendApprover.

Here's a sample of what this might look like:

module FriendApprover
      def approve(friend_request)
        friend_request.approved = true
        friend_request.save
        increment_friends
        notify_new_buddy(friend_request.user_id)
      end

      def increment_friends
        friend_count += 1
        save
      end

      def notify_new_buddy(buddy_id)
        BuddyMailer.notify_buddy(buddy_id, "We're officially friends!")
      end
    end

It doesn't really matter what my sample code is, you get the picture: take the methods from your User class that do the approval and put them in your FriendApprover module.

The unit tests you had for these methods can now be simplified and applied to the module. The test just needs to check that some object agrees to the contract that the methods expect.

Step 4: Extend your user

Extend your user. Thats little "u" user. Your class doesn't need this module, your object does.

Open up your controller where you usually call current_user.approve(friend_request) and change it to:

current_user.extend FriendApprover
    current_user.approve(friend_request)

That's it.

What you've just done

You've made your code more obvious.

It's only in this context that a user needs to perform this action and this change has limited the scope of those methods to a very concrete area.

  • Your User class is smaller making your cognitive strain easier
  • Your User unit test is smaller
  • You have a clear separation of concerns with your new Object Role module
  • You've inherently made these methods reusable

But what about...

Yes, there's more to it. Of course there's more you can do, but with this simple concept you can do a lot of cleanup of both your code, and your ability to reason about your code.

What is DCI?

For now, I'll leave the description of what DCI is to this article but I'll be writing more about the concepts in Data Context and Interaction.

How delete on a Hash works in Ruby... or how you thought it worked

Are you new to Ruby? Or were you once? Cool, me too.

When you first bump into Hash#delete you think you might be able to use it and just remove the items you don't want, but it doesn't work that way.

You think "I'll just do this:"

hashy = {'this' => 'that', 'something' => 'else', 'secret' => 'I love NKOTB'}
    hashy = hashy.delete('secret')

And bingo you've got your hash without that nasty secret. But what you actually found is that delete will remove that key from the hash and return the deleted value and not the altered hash.

Perhaps this is old news to you, but to new developers this is the moment where they say "Oh, right, it gives me the value that I don't want."

Well here's a quick idea to give you what you want:

class Hash
      def except(which)
        self.tap{ |h| h.delete(which) }
      end
    end

Now you can use the same code above but run:

pre>hashy = {'this' => 'that', 'something' => 'else', 'secret' => 'I love NKOTB'} hashy = hashy.except('secret')

That'll return your hash without the part you want. That could be simplified to this:

hashy = {'this' => 'that', 'something' => 'else', 'secret' => 'I love NKOTB'}
    hashy.except('secret')

because the delete will alter the hash. So you need to be aware that if you want to keep the original hash you'll need to:

hashy = {'this' => 'that', 'something' => 'else', 'secret' => 'I love NKOTB'}
    new_hash = hashy.dup.except('secret')

Enjoy!

Always check in schema.rb

Sometimes developers are unsure about Rails' schema.rb file and whether or not it should be checked in to source control. The answer is simple: yes.

As you are familiar, Rails encourages developers to alter the database state through migrations. One migration adds some table, and the following migrations add more, change something, or remove something. And as you well know, this is a great way to keep developers in sync with each other's changes to the database structure.

With each migration you add and subsequently run, your schema.rb file is altered to represent the final state of your database. Seemingly because this is automatically done for you, some developers think this file should be left out of your project; it should not.

Don't store it just because David Heinemeier Hansson said you should

First things first, this is a terrible reason to add schema.rb to your source control. Often, in an argument about something like this it's easy to look to an authority, find a relevant quote and say "See, he said so." Yes, there is a commit in Rails where he suggests you store schema.rb, but this was done to end an argument, it is not an argument itself.

Authorities are authorities because of their deep understanding of something, not because they command you to do things. But often people will allow authorities to command them to do things because they are authorities. David Heinemeier Hansson is an authority on Rails and his control of the project allowed him to make that commit and force the direction of Rails. If you agree with this commit, agree because of the reasoning, not because DHH said so.

Don't store generated files, except for schema.rb

As a general rule of thumb in compiled languages, you don't store generated files. The reason for this is not because an authority said so or because many consider it to be a good rule to follow, but because you want your code to generate the proper files for correct execution of the application.

If you add your generated files to your source control you may in the future find that you've altered your code in a way that prevents a particular file from being generated at all. But if that file exists regardless of the bug you introduced you're likely to find errors in the application's behavior. Tracking that down may be tough until you realize that you've checked-in your generated files.

Additionally, if you store generated file X in source control but change the code to instead generate file Y, you'll leave unused code in your project possibly giving other developers the misconception that it is important in some way. Worse yet, depending on your application the mere presence of file X could affect the execution of your application. I've heard things like this appropriately called "code turds." If it's not going to be used, it shouldn't be there.

Regardless of all of this, schema.rb doesn't affect the execution of your application so there is no danger in storing it in source control. Avoiding generated files in source control is a good rule to follow, but knowing when to break that rule is important too. Leave the code turds out and schema.rb in.

File churn in source control is not an issue with schema.rb

If you are concerned about the amount of churn (that is, frequent changes) you have with the development of your schema.rb file, then you probably are actively developing your database, or you have a problem elsewhere and need to get your team to work on a clearer picture of what your database should do.

Churn in schema.rb can actually be valuable, however. It's easy to overlook multiple migration files changing things in your database, but in reviewing code commits the amount of churn in an area of schema.rb can reveal problems with your development team.

Conflicts in schema.rb are valuable

If your team is making dueling commits over the purpose of a database field, your problem is not with resolving conflicts in schema.rb, it's with resolving conflicts between and among your developers about the structure of your database. Keeping schema.rb in source control will help to reveal this.

Before you commit any of your files to your master/production/whatever branch, you should

  1. run your tests
  2. pull down and merge any other changes
  3. re-run your migrations if any new ones were pulled down
  4. re-run your tests
  5. commit/push your changes (including schema.rb)

Following those steps ensures that your tests are run against the database that everyone else will have and ensures that the schema.rb file you commit is the latest and most up-to-date. Maybe you don't want to run your tests twice, that's fine, but be sure to run them after you pull down the latest code for the project and merge your changes.

Store it because schema.rb is a representation of the current state of the database

At any point in development, you can look at schema.rb to give you an accurate representation of your database structure. Other developers can checkout the project and run rake db:schema:load and almost instantly they are ready to develop (barring any sample data they need to load).

For a new team member, there is absolutely no need to change or rename anything in the database as may be done with migrations. Your ultimate goal to begin development is to have a database in a desired state. It doesn't matter a bit if a field name was changed from "login" to "username" or if your :text field was once a :string field in your first migration. For your database structure, the final state is all that matters, and schema.rb does this for you.

Application code mixed with migrations can cause problems

Sometimes you may have a need to alter your structure and do something like ProductType.reset_column_information in a migration and add some data. For now, I'll avoid the discussion on whether or not that's appropriate, but if you are doing this a problem may arise when at some point in time you remove the ProductType model, or rename it to Category. In that case, you'll need to go back and maintain your migrations... read that again you'll need to maintain your migrations. This is a pointless exercise: use schema.rb.

This is also an example of why you shouldn't mix database seeding and migrations.

Migrations are slow

Relying on migrations to get up to speed for development is slow and gets slower as your application's database changes and as the number of your migrations increases. Because schema.rb skips over changes and represents the final (and desired) state of your database, it's fast.

Your blank slate production database only needs the final state

A production database only needs the final state assuming, of course, that the database is a blank slate. Running all of the migrations in production to get where schema.rb would be is not necessary. schema.rb weeds out all of the changes for you and gets the job done quickly.

"But what about a database that already has a structure?" you may ask. Then all you need is the migrations that haven't been run; you'll never run rake db:schema:load on an existing production database.

Keep schema.rb with your code

schema.rb in your project's source control adds value for all developers. It loads the desired state quickly, it gives you a clear representation of your database structure, it reveals conflicts and unnecessary churn, it doesn't affect the execution of your application. Add schema.rb to your source control and add value for your team.

Ruby Metaprogramming is Awesome

If you've come to read about how wrong I am about metaprogramming, don't worry, I'm sure I'll follow-up with a post about how bad it is to do metaprogramming and how it causes you real, physical pain.

If, however, you've come to find out about how awesome metaprogramming is, then you can tell by the title of this article that you are in the right place! Now that's authoritative!

The best part about this article is that it is for you! That's right, newbie. I'm not going in-depth, I'm merely going to discuss how a dash of metaprogramming solved an annoying problem.

I do a lot of work with Radiant. And Radiant manages pages well, but there are plenty of extensions that allow you to do other things. You can, for example, add the ability to upload files and whatnot. Or you can add the ability to edit stylesheets and javascripts.

That's pretty cool. Radiant does a good job of managing pages in the pages table. But stylesheets and javascripts are not pages. Some of the samples that Radiant bundles have stylesheets as regular pages in the page tree and are tied to an almost blank layout that sets a particular content-type like text/css. Yuck. So Chris Parrish, the author of SNS, added text_assets to handle other types of content like stylesheets and javascripts.

One of his reasons for creating this extension is the weirdness of having a stylesheet in your page tree: "No more confused users wonder what those fancy pages are." Saaaweet! Thanks, Chris.

All is well until you find out that the SNS extension doesn't let you use your typical Radius tags within javascripts or stylesheets like:

#something { background: url('< r:assets:url title="my_image" />'); }

Nooooooo! Why, God, why!?!

Well, with SNS, you're dealing with a TextAsset and not a Page. But if you're familiar with Radiant, you know that page tags aren't only used on pages, they're used on snippets and layouts too. So what's the deal with that?

All of the radius tags that you use in Radiant are evaluated in the context of a single page. Pages, snippets, and layouts are all used in reference to rendering a Page. But SNS renders a TextAsset and as a result, it doesn't have any of the fancy tags added. Afterall, it's class TextAsset < ActiveRecord::Base and not class TextAsset < Page.

You read that right: text assets are not pages. Bummer, dude.

Well, what if you could include the same modules as are included in the Page model? Then you could use those tags in your stylesheets.

Fire up your console because you're about to see how to do it. In a rails console (for a Radiant instance), try this:

Page.included_modules

And you'll get back a bunch that don't matter for you. Let's simplify that:

>> Page.included_modules.select{|m| m.to_s =~ /Tags$/}
    => [TextileTags, SmartyPantsTags, MarkdownTags, StandardTags]

Awesome. All we need to do is include all of those modules into TextAsset and we're done! Almost.

Since we're dealing with the SNS extension, you'll also see that it includes Sns::PageTags which adds tags to output stylesheets and javascripts. Those, we don't need. So we can filter them out and include them into TextAsset:

TextAsset.class_eval {
      Page.included_modules.select{|m| m.to_s =~ /Tags$/}.reject{|m| m == Sns::PageTags }.each do |mod|
        include mod
      end
    }

Bingo! Now we're in business. Except for the fact that it doesn't work.

So we need to update the context in which these tags are evaluated. Since they are all written expecting to be included in a Page model, the global variables for the tags need to have knowledge of a page.

TextAssetContext.class_eval {
      def initialize(text_asset)
        super()
        globals.page = text_asset # This is the important line that adds the hook for all existing tags for pages.
        globals.text_asset = text_asset
        text_asset.tags.each do |name|
          define_tag(name) { |tag_binding| text_asset.render_tag(name, tag_binding) }
        end
      end
    }

There you have it. All it took was some class_eval and some looping over included_modules.

page tags in stylesheets And now you'll have happy users. Because even though Chris solved the problem of weird stylesheet pages in the page tree, the solution introduced a problem where editors of the site expect the radius tags to simply work everywhere. Now they do.

The above code assumes that the only valuable tags are those whose modules have "Tags" at the end of their names. That's a reasonable expectation, but we can go even further by inspecting the included modules of the included modules to see if Radiant::Taggable is there:

TextAsset.class_eval {
      Page.included_modules.reject{|m| m == Sns::PageTags }.each do |mod|
        if mod.included_modules.any? {|inc| inc == Radiant::Taggable }
          include mod
        end
      end
    }

So go ahead and install the page attachments, or paperclipped, or whatever extension with sns and just do script/extension install sns_page_hook, or better yet: gem install radiant-sns_page_hook-extension. Please let me know if you find any problems with it.

Radiant 0.9 Official Release

Finally, after a long, long wait: 0.9 is out.

It's my first non prerelease gem to push out for Radiant but I'm proud of all the work that I and many, many others did to make it happen. John Long put the UI updates together with the help of a good group. Keith Bingman managed all that went into internationalization and the many contributors that made it happen. William Ross added features such as pagination in the standard Radius tags, and pagination in the admin screens. Josh French gave us the ability to load extensions from gems. John Muhl, our newest core team member increased the general quality in testing for bugs and improving the overall quality of the application.

There are many contributors, I'm just listing some from the top of my head. Thank you to everyone of the contributors, be you bug reporters, committers, or just users who discuss your needs on the mailing list.

While this release is an exciting improvement, I'm even more excited about the future of the project. More to come...

Rack Cache on Heroku with Memcached

The convenience of deploying application on Heroku is attractive especially with their add-ons and the free options that they provide, in particular Memcache.

If you're working with an application which needs to manage it's cache with Rack::Cache, you'll want to have fast responses for your metastore. The meta information about your content is probably the most important part of your cache since it's checked by many clients for the status of the content: is it fresh or stale? See more about How Web Caches Work.

Typically you might setup Rack::Cache like this:

config.middleware.use Rack::Cache, :metastore => 'file:tmp/cache/meta', :entitystore => 'file:tmp/cache/entity'

5MB of Memcache is a decent place to start for free and it's integrated into your application without any effort on your part. So on Heroku you can use Memcache as your metastore like this:

$cache = Memcache.new
    config.middleware.use Rack::Cache, :metastore => $cache, :entitystore => 'file:tmp/cache/entity'

That's simple enough, and it's just as easy if you're deploying Radiant:

$cache = Memcache.new
    config.middleware.use Radiant::Cache, :metastore => $cache

If you want to look at an example of a simple app that does this, there's an easy to understand sample application on github. Enjoy your speedy metastore.

Radiant Page#find_by_url

On a recent project a client asked about overriding Page#find_by_url and when that actually occurs. I think the answer should be explained for everyone working with it.

20 second summary

This is an in-depth look at the method that gathers pages within Radiant. In short, if you want to do special page finding, create a subclass of Page and write your own find_by_url method to adjust the way Radiant behaves. Every page will respond to this method and return appropriate pages according to the requested url. In the admin interface, you can select your special page type to make that page behave as you have specified.

Simple finding

find_by_url is defined both as a class method and an instance method. Let's look at the class method Page.find_by_url from the Page model:

class << self
      def find_by_url(url, live = true)
        root = find_by_parent_id(nil)
        raise MissingRootPageError unless root
        root.find_by_url(url, live)
      end
      # ...
    end

First, it looks for the root page, which is considered the page with no parent_id. If no root page is found it raises a MissingRootPageError exception; otherwise, it calls the instance method find_by_url on the root page.

This class method takes 2 arguments: the url (really the path matched in the routes from request) to be found, and a live flag which defaults to true (more about that later).

Finding the first page

The find_by_url instance method is a bit more complex. Let's take a look:

def find_by_url(url, live = true, clean = true)
      return nil if virtual?
      url = clean_url(url) if clean
      my_url = self.url
      if (my_url == url) && (not live or published?)
        self
      elsif (url =~ /^\#{Regexp.quote(my_url)}([^\\/]*)/)
        slug_child = children.find_by_slug($1)
        if slug_child
          found = slug_child.find_by_url(url, live, clean)
          return found if found
        end
        children.each do |child|
          found = child.find_by_url(url, live, clean)
          return found if found
        end
        file_not_found_types = ([FileNotFoundPage] + FileNotFoundPage.descendants)
        file_not_found_names = file_not_found_types.collect { |x| x.name }
        condition = (['class_name = ?'] * file_not_found_names.length).join(' or ')
        condition = \"status_id = \#{Status[:published].id} and (\#{condition})\" if live
        children.find(:first, :conditions => [condition] + file_not_found_names)
      end
    end

Wow. There's a lot going on there and there's room for some refactoring, but for now let's just walk through it.

First, nil will be returned if the page is virtual?. A page, by default, is not virtual. This is stored in the database in a boolean field, but you may override this in any subclass of Page that you create. For now, let's assume that your page isn't and won't be virtual and we'll get back to what it means.

Next, we clean the url if the clean flag is set to true (which it is by default). clean_url simply ensures that the url being checked is properly formatted and that any doubling of slashes is fixed. So this right//here//// becomes this /right/here/.

The next step shows us why we clean the url. A local variable is setup to compare against the page's url.

my_url = self.url
    if (my_url == url) #...

What is a page's url? It's calculated by the page's slug and the slugs of it's ancestors. In short, if your current page's slug is 'here' and it's parent page is 'right', and that page's parent is the home page (with a slug of '/') then your current page's url is '/right/here/'.

So we check to see that to see if it is the same as the url in the request. But also, in this comparison, we check to see if the live flag is set and is false or if the page is published?.

This live flag is a bit strange in appearance:

my_url = self.url
    if (my_url == url) && (not live or published?)

By default, this not live returns false (since live is true by default and we reverse it with not) so it moves on to published?. You might set live to false in other situations, but for now we'll just go with this.

A page is published? if it's status (as stored in the database) is the 'Published' Status.

So if the incoming url matches the current page's url (which at the first pass is the root or home page), then we return with the current page:

my_url = self.url
    if (my_url == url) && (not live or published?)
      self

Finding deeper pages

If it isn't true that the incoming url and the current page's url are equal, then we move on to the next step:

my_url = self.url
    if (my_url == url) && (not live or published?)
      self
    elsif (url =~ /^#{Regexp.quote(my_url)}([^\/]*)/)

Here it matches the incoming url against a Regexp of the current page's url. When it starts, we're matching the root page which has a url of '/'. If that's the incoming url, it would have been caught in the original if block, but we ended up at the elsif. The Regexp that's used matches the next slug in the incoming url. So if the incoming url is '/right/here/' then it will match the slug 'right'.

From that match, we find the current page's children by their slug (remembering that the current page is the root, with a slug of '/'):

elsif (url =~ /^#{Regexp.quote(my_url)}([^\/]*)/)
      slug_child = children.find_by_slug($1)

If it finds that 'slug_child', then we call find_by_url on that page to loop down the tree to find the final page that we want (which would be the page that responds to the url '/right/here' or in this simple case, the page with a slug of 'here'). If it finds the page, then it returns the found page:

slug_child = children.find_by_slug($1)
      if slug_child
        found = slug_child.find_by_url(url, live, clean)
        return found if found
      end

In that if slug_child block, the slug_child.find_by_url acts as a loop. Because every page responds to this method and will do exactly what is happening here for the root page, each page will search it's children for a slug matching the slug from the incoming url and any found page will likewise call find_by_url to search it's children as well.

There is some room here for some optimization in the way we do a lookup for a page, but for now it works and we can get to the refactoring another time.

When no slug is found: customizing the finder

If the slug_child is not found (and no child matches that slug) then this if slug_child block is never hit and we move to the next step. This is where the magic happens for subclasses of Page:

children.each do |child|
        found = child.find_by_url(url, live, clean)
        return found if found
      end

It asks each child of the current page if it responds to find_by_url and returns any found page.

So even if none of the pages are found by the slug, we still ask the children if they respond to find_by_url. Why would we do this?

The answer lies in one of the included extensions: Archive.

The ArchivePage is a subclass of page which provides it's own find_by_url method. The ArchivePage#find_by_url will check the incoming url for it's details and if it meets certain requirements (namely that there is a standard date format in the url such as 'articles/2010/06/22') then it will find the appropriate page type such as ArchiveDayIndexPage, ArchiveMonthIndexPage or ArchiveYearIndexPage and return the proper page. If none of those are found it just calls super and calls the original Page#find_by_url.

This can act as your router for your custom page types. If you want to return a particular page type, such as a ProductsPage and your url is '/products/1234' then you can create a ProductPage which has it's own find_by_url method and would find your ProductDetailsPage to display a standard view of all of your products based upon the slug '1234' which I'd assume would be a product id, but could be anything you want.

Handling 404

Lastly, if none of this finds any pages to return, Radiant has a FileNotFoundPage page which allows you to easily create your own 404 error message for content that isn't found. You can subclass a FileNotFoundPage page to provide your own behavior there too. But when searching for a match to an incoming url, Radiant will find deeply nested 404 pages. So you can create a FileNotFoundPage as a child of your root page, but you can also create a FileNotFoundPage as a child of your ProductsPage to return an appropriate message to someone looking for '/products/not-a-valid-url'.

Here's the code for that last step:

file_not_found_types = ([FileNotFoundPage] + FileNotFoundPage.descendants)
      file_not_found_names = file_not_found_types.collect { |x| x.name }
      condition = (['class_name = ?'] * file_not_found_names.length).join(' or ')
      condition = "status_id = #{Status[:published].id} and (#{condition})" if live
      children.find(:first, :conditions => [condition] + file_not_found_names)

The live flag comes into play here again and optionally allows you to find pages that are not published. By default live is true, so in this instance we only check for a FileNotFoundPage that is published.

Radiant has a 'development' mode which would find unpublished pages, but that's a subject for another discussion.

I hope this gives you a good understanding of how Radiant finds its content, and how you can easily bend it to behave differently by creating a subclass of Page and writing your own find_by_url method. If I've left anything out or if you want me to cover some other aspect, let me know in the comments.

Combining show_for and stonewall

I'd been doing some thinking about simplifying the display of records in Rails applications and fortunately came across show_for.

show_for is a formtastic-like approach to displaying details about database records. It helps you DRY up your views and can even reflect on associations with code like this:

<% show_for @user do |u| %>
  <%= u.attribute :name %>
  <%= u.attribute :nickname, :in => :profile %>
  <%= u.attribute :confirmed? %>
  <%= u.attribute :created_at, :format => :short %>
  <%= u.attribute :last_sign_in_at, :if_blank => "User did not access yet",
                  :wrapper_html => { :id => "sign_in_timestamp" } %>

  <% u.attribute :photo do %>
    <%= image_tag(@user.photo_url) %>
  <% end %>

  <%= u.association :company %>
  <%= u.association :tags, :to_sentence => true %>
<% end %>

But since I'm working on an application which uses stonewall (which allows you to limit the display of a record's details), I needed the 2 of them to work together.

stonewall allows you to guard the display of your data from your models:

stonewall do |s|
  s.varies_on :aasm_state

  s.guard_attribute :secret_cultivation_details

  # ... role details here
end

It provides a allowed? method which you can use to ask an object if the current user is allowed to see the content.

So I quickly put together show_for_stonewall which will limit the display of certain fields if stonewall prevents it. It was as simple as using alias_method_chain to check that an attribute is not allowed

!allowed?(@object, current_user, attribute_name)

You can edit your show_for translation yaml and add a redacted key for the content to display if a field is not allowed for the current user.

Adventures in Javascript

I wrote a simple Javascript library for myself in 2006 called simpleDOM.

At the time, there was a problem with using innerHTML in Internet Explorer to write content to a page and then select it when traversing the DOM or submit a form with the added content. Not only that, but it's expensive to process and manipulate DOM objects in the browser while they are displayed so this library gave me an easy way to create document fragments and manipulate those to then be injected in the right place.

Over time, I got into working with other libraries and problems like this were solved for me, so I never did much more with it.

I've still got it working in one production site but haven't looked at it in a long time. I thought I'd put it out there on github for posterity.

Enabler: control Radiant sites

I've put together an extension which will allow you to turn on and off your instances of Radiant.

Enabler will allow you to post to your sites to disable and enable it's ability to serve content. You might use this extension if you host sites for your clients and they pay to you a monthly fee for service. If your customer has refused payment or if their payment bounces you can post to the_website.com/admin/disable/your-api-key and the cache will be cleared and the site will serve a simple message that the site is down.

You can change the message by adding a message parameter to your disable post. Just post to the_website.com/admin/disable/your-api-key?message=Please%20pay%20your%20bills

This will allow your customers to continue to login to the admin area but they won't be serving up any public content until their bill is paid and you post back to re-enable the site.

It's built for Radiant 0.9 and you can just gem install radiant-enabler-extension and load it up in your environment.

This isn't an extension you'd use manually and you'd probably want to tie it to a billing service such as Spreedly so that a site is automatically turned off when there is no payment.

To prevent delicious malicious hackers from scanning sites for this extension it will respond with a typical Radiant response for the URL rather than a 403 Forbidden error code as you might expect. If someone posts to your site with an invalid key, Radiant will simply respond with the usual 404 from your FileNotFound page.

If you think it could use a feature to turn off admin access too, just code and send me some pull requests.

Radiant projects and Rails applications

Whenever I talk about Radiant with developers in the Ruby/Rails community a few things come up in the conversation.

  1. Why does it use extensions instead of plugins?
  2. Choosing Radiant means you're choosing to create a Radiant project and not simply integrating a great CMS into another application.

These are 2 issues that will be addressed.

First, I think it makes the most sense for extensions to become more like plugins rather than do some cold-turkey switch over. Commits like this one make extensions much more predictable for Rails developers. Slowly converting extensions means a smoother upgrade path for developers.

Second, while you currently can't easily include Radiant itself as a plugin, we'll be working on that and I'd love to hear ideas about how to do it. Mislav Marohnic has done some work to update Radiant to Rails 3 and I think the future is bright for an easy to use and extend CMS in your Rails projects.

And there's an extension to Radiant which allows you to alter your controllers to use views defined in the Layouts section of the UI. I haven't personally worked with it in a while, so it might need some tweaks to allow plugins to do the same.

As I mentioned in a previous post, Radiant easily loads application plugins just like a regular Rails app, so you're not entirely restricted in your application development to what Radiant provides.

But even when I talk to people about the project and they find that I'm leading the development, the negative comments become a bit more sheepish. But I'd want to hear the complete opposite. If there are problems or complaints with application integration, the project can't grow to solve them unless they are heard. By all means, complain loudly but if you have suggestions write some code too. Brian Doll wrote about his opinion on CMS development and created some code to back it up. His post is embellished a bit, but the squeaky wheel gets the grease (be it your own, or some from the community) so it's good to hear reasoned arguments for different issues. The problem with this, I think, is that I hadn't heard the complaints, but I don't know if they were mentioned on the email list. On the other hand, Rit seems to be trying to solve a problem that isn't a particular case that Radiant is geared toward so the split in philosophy is just fine.

I'm always looking for feedback at DCRUG and talking to people at RubyNation was great. Everyone at B'more on Rails who comes down to DC are always really welcoming and are happy to offer lots of feedback about Radiant, so I'm hoping to get up there for their events too.

Patrick Peak of BrowserCMS fame and I often chat about the approaches to content management at DCRUG and it's a great way to think about systems in a different way. If you're working on a project that needs a CMS, has a CMS, or might involve a CMS, then give me a call at 571-403-0338.

Radiant and plugins

Radiant loads plugins just like any typical Rails application.

If you want to load custom routes, they are loaded from the plugin's config/routes.rb file. Custom controllers and views? They are pulled from the plugin's app directory.

So if you need to pull in an application plugin which does all this for you, you're free to do so. There's still plenty of work to be done with the core to make things simpler, but loading plugins like a regular Rails application is no problem. You're still constrained by the code in ApplicationController if you inherit from that, but you don't necessarily need to inherit from ApplicationController for any particular reason.

But Radiant's extensions can also bring plugins with them. So, for example, if you want to work with the AdminUI in Radiant but want to pull in a plugin's code there, you can work with vendor/extensions/your_extension/vendor/plugins/your_plugin. Yes, that works too, even the config/routes.rb

In both your project's vendor/plugins and your extension's vendor/plugins you can load the interface you need with controllers, views, models, metal, etc.

If you're working with Radiant, you might be able to use some development direction or even a simple consultation to give your plans a head-check. Give me a call at 571-403-0338

Commenting in RadiantCMS with a gem

The comments extension is now a gem.

Add config.gem 'radiant-comments-extension' and do everything else as you would with a regular extension installation.

Update: I should note this in the article too since Jeff asked about this in the comments. Public files are copied into your project with rake radiant:extensions:update_all rather than rake radiant:extensions:comments:update.

GMail User Experience

I was happily tapping away at my keyboard in Gmail when upon hitting the send button I saw this error message asking me if I meant to attach some files.

It says "You wrote 'are attached' in your message, but there are no files attached. Send anyway?"

Even though I wasn't actually referring to any attached files and had no plans to attach any, I was so happy to see that. I've both sent and received plenty of emails with no attachment only to have a follow-up 10 minutes later with a message saying "Oops. I forgot the attachment, here it is."

There's not much of an incentive for Google to spend time working on adding a feature like that and this is the only time I've ever seen it occur so it's not like they need to keep up with anybody in that regard.

A great thing is the sum of tiny details.

Bad interface design at Bank of America

I've been using Bank of America for online banking for quite some time and while their service is usually great, their interface design is sorely in need of some love.

Exhibit A:
I've been using Bank of America for online banking for quite some time and while their service is usually great, their interface design is sorely in need of some love.

Exhibit A:

This example shows 2 very different ways to allow a user to choose a yes or no answer in the same form. "Radio buttons or a select list!? Oh, it's such a tough choice... let's just use both!"

Exhibit B:

You would think from this image that the "Paid Invoices" tab has been selected. It is not. The Unpaid invoices is selected currently. This caused a ridiculous amount of confusion when I first began using this service. I've written to them about this, but apparently it's not enough of a problem to change.

But really, Bank of America, what is going on in your UI design department?
This example shows 2 very different ways to allow a user to choose a yes or no answer in the same form. "Radio buttons or a select list!? Oh, it's such a tough choice... let's just use both!"

Exhibit B:

You would think from this image that the "Paid Invoices" tab has been selected. It is not. The Unpaid invoices is selected currently. This caused a ridiculous amount of confusion when I first began using this service. I've written to them about this, but apparently it's not enough of a problem to change.

But really, Bank of America, what is going on in your UI design department?

JRuby, Rails, and OC4J on OAS... oh the pain

Basic Rails app in version 2.3.4.

I kept running into errors like:

Error loading listener 'org.jruby.rack.rails.RailsServletContextListener', class not found

Setting up JDBC Resources became painful too. OAS, it seems, doesn't really care what you do after you first create your connection details. I kept testing my connection with changes to the settings and it kept running the test with the original information. To fix this, I had to delete the connection and recreate it.

I saw bugs pop up like:

09/09/11 15:56:05.734 dms: Servlet error
    java.lang.OutOfMemoryError: PermGen space
    09/09/11 15:56:07.998 dms: Servlet error
    java.lang.OutOfMemoryError: PermGen space
    09/09/15 11:09:21.756 dms: 10.1.3.4.0 Stopped
    09/09/15 11:09:22.325 Error in bean MBeanServerEjb: Error
    deserializing EJB-session 'MBeanServerEjb': null
    java.io.EOFException
      at java.io.ObjectInputStream$BlockDataInputStream.peekByte(ObjectInputStream.java:2502)

It turns out that I'm not alone in this experience. The difference in my case was that we were already giving a gigabyte of memory to our instance and we still saw this problem. So my habit became bouncing the OAS after a few deployments.

Radiant Config in source control

I'm currently maintaining the settings extension which provides a nice interface for editing the Radiant::Config settings.

In general it's a nice extension for people who know what they are doing, but I tend not to use it in production sites. The settings are not likely to be updated often, and I hate to add to an interface where more features are not absolutely necessary.

Of courI'm currently maintaining the settings extension which provides a nice interface for editing the Radiant::Config settings.

In general it's a nice extension for people who know what they are doing, but I tend not to use it in production sites. The settings are not likely to be updated often, and I hate to add to an interface where more features are not absolutely necessary.

Of course you should be backing up your database, but if you want to store settings with your source control and manage updates with deployments you can use 2 lesser known rake tasks in Radiant:

rake radiant:config:export
    rake radiant:config:import

Those will generate and import config/radiant_config.yml file for you which you can use during your deployment process.

UPDATE

I should also mention the extra details. If you have multiple radiant applications running on a server, you can set the RADIANT_CONFIG_PATH to some location where you share your standard settings. Just run:

rake radiant:config:import RADIANT_CONFIG_PATH=/var/custom_radiant/custom_config.yml

And you can put all your standard things like settings for paperclippedse you should be backing up your database, but if you want to store settings with your source control and manage updates with deployments you can use 2 lesser known rake tasks in Radiant:

rake radiant:config:export
    rake radiant:config:import

Those will generate and import config/radiant_config.yml file for you which you can use during your deployment process.

UPDATE

I should also mention the extra details. If you have multiple radiant applications running on a server, you can set the RADIANT_CONFIG_PATH to some location where you share your standard settings. Just run:

rake radiant:config:import RADIANT_CONFIG_PATH=/var/custom_radiant/custom_config.yml

And you can put all your standard things like settings for paperclipped

An army of volunteers with daytime jobs

I love working with Radiant CMS and the community and available extensions are growing by the day. New and enthusiastic people come to the project and are won over. There are a lot of great ideas out there about what it could and what it should become.

Like any project ideas are all over the place, but the actual workers might be hard to find. John Long recently called out for people to get more involved and help out if you love it or even if you hate it but want it to be better.

My favorite image to illustrate the way that Radiant is developed is this one. Radiant developers are weekend warriors (and are apparently really busy doing other things on Thursdays).

If you want things to get better, or to change in any way, blog about it, find bugs and request features, and best of all, vote with your code.

Another way to make navigation

John Long posted a great write-up of different ways to make navigation in Radiant. But there's a certain way that I like to skin that cat which wasn't covered: using <r:find>, <r:children:each> and <r:if_ancestor_or_self>.

See it at this gist

Non-standard extension locations in Radiant

Sometimes I'll have the need to load extensions from a non-standard location.

I might have 2 instances of Radiant installed on the same server which may need similar but different functionality.

I can keep the code updated for both if I symlink their extension directories to "/var/radiant_extenions" for example, but then I'd need to mess with the environment files to specify the loading of extensions with config.extensions = [:this, :that, :the_other] in one and config.extensions = [:this, :that, :custom] in another if the requirements of the sites aren't exactly the same.

Instead, if I know I'll be just running "rake db:migrate:extensions" for the standard extensions I can place those in my shared location and symlink them as "vendor/standard_extensions" and put my site-specific extensions in "vendor/extensions". To get that working all you need to do is add this to each environment and I'm good to go: config.extension_paths << "#{Rails.root}/vendor/standard_extensions"

Rake tasks won't be loaded from there automatically though, so you'd need to be careful about what you put in your set of standards.

In the future, using extensions as gems will be nice, but creating a directory to manage standard extensions is helpful and just running "rake db:migrate:extensions" will load their migrations and bring them up to date in each database.