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.

Code Spelunking: Radiant extension deactivate

A long time ago when Radiant was just a young CMS, it allowed you to activate and deactivate extensions with the click of a button.

This was simply an awesome feature. But there was a problem: it didn't work.

Radiant couldn't really know how you built your extension, and you can do whatever you want in there, so deactivating an extension might not actually deactivate certain parts. I've long wanted to take the time to look into Mixology to see how this extension deactivation feature might be able to make it back in, but the problem will still exist that you can build an extension however you want. Even if Mixology provides a way, it would only work with extensions built in a particular way; it can't solve everything.

Even so, the feature was removed because: it doesn't really matter, and nobody who worked on the core cared enough to put time in to solve a problem around what seemed to be an edge use case. How often do you need to turn off an extension, and does it really need to happen when the application is running? It would be nice, for example, to have a client upgrade or downgrade their account with the click of a button, which would remove or add features. I'm working on several ideas for this, but that'll be a discussion for later.

So what's with the deactivate method in your extensions?

It might as well look like this:

def deactivate
      # Ha ha! Fooled you!
    end

Mostly, because of the generated code in many extensions you'll just have something that says:

def deactivate
      admin.tabs.remove "My Extension"
    end

That deactivate method is there because it was formerly used. It's like finding a dinosaur bone in your back yard, but everyone has a dinosaur bone in their back yard so it's not special.

Is this deactivate method ever used? Lets look:

The Radiant::ExtensionLoader has a method that looks like this:

def deactivate_extensions
      extensions.each &:deactivate
    end

Clearly, this will call the deactivate method on each extension. And the Radiant::ExtensionLoader::DependenciesObserver has this method:

def before_clear(*args)
      ExtensionLoader.deactivate_extensions
    end

Aha! So extensions are deactivated before they are cleared... And they are cleared... um... never? A new DependenciesObserver is created but where is the @observer object used?

This is where we start digging into Rails. Radiant bundles Rails, so we can stay within the source and find that action_controller/dispatcher.rb clears the dependencies. ActionController clears the dependencies and the instance of Radiant::ExtensionLoader::DependenciesObserver has the message before_clear sent to it, which calls each extension to run the deactivate method.

Does this mean that you can use the deactivate method in your extension? Yup. Put whatever you want in there. But still... it won't do anything, or at least not much. Yes, the method is called (go ahead and drop a puts 'hello from the deactivate method' in there and watch your logs) but only in Rails development mode. In production, the dependencies are not cleared, your app just runs.

You can still use it, you could even build a controller for your extension to run the deactivate method and do something with it, but you're on your own as far as the Radiant core is concerned.

So why keep that method in there at all? We even have specs built around it to ensure that it works properly. Sure, this means a bit of code complexity when the feature isn't used, but it's minimal and perhaps deactivate will come back in some way.

At a code sprint weekend over a year ago we discussed this method briefly and decided to leave it in because... well, maybe it will be of use in the future. I'm sure that it will be removed at some point if it is never used but for now keeping it in means that it can more easily come back and extensions using it will merely do things like remove some admin tab only to add it back in with the activate method and only in development mode.

If you've been wondering about that method, well, there you are.

Kicking the Gem Extension Tires

Josh French has been working on loading extensions in RadiantCMS as gems. I walked through the process and pushed out some changes to the Vapor extension to be loaded as a gem.

There's currently no plan to cram this into the official release of the 0.9 version of Radiant, but I'd like to see it happen. I think that extension development and usage is a sticking point for the growth of Radiant and the community of extensions and developers.

Creating the gem was relatively easy using the jeweler and gemcutter gems, and then (after installing the radiant-vapor gem) I required it in my Radiant environment.rb with config.gem "radiant-vapor".

Radiant extensions (by default) have an update task which will copy public files to the project's public directory such as images, stylesheets, and javascript files. The current downside of using extensions that are gems is that the rake tasks are not available from the extension. We'll solve this problem in some way, but currently extensions such as Vapor won't have much of a problem since there are no public files to worry about and even though this task won't be found rake radiant:extensions:vapor:migrate, this one will rake db:migrate:extensions and will make the necessary changes to the database since the migrations are loaded.

More to come in Radiant

The next release of Radiant CMS will be a great one. The interface is definitely nicer and should make running multiple extensions much cleaner with the new navigation scheme.

One annoyance that's been in it for a long time is the lack of a tag reference on the Snippet and Layout screens where they are arguably most needed. I hope to get an update for that in the next release. Radius tags on Pages should be discouraged, in my opinion, for the sake of the end user's understanding of the system so it seems backwards that we'd have a reference on the Page edit screens and not in a place where it's most needed.

If there's something that you think is missing, or a bug that needs fixing, please go vote for it.

RedDot CMS is a POS

Quite possibly the worst experience I've had in working with a content management system is with RedDot CMS. I'm currently working with a client to update their site which is built in RedDot.

The manufacturer of RedDot claims to be "The Content Experts" but perhaps they exclude user experience from that.

I'm using RedDot, which is apparently old and has been replaced with an newer version with a new name. Hopefully they've updated everything about the user experience which is a mess of pop-up windows and cryptic ways to search for and link to images and content. One of the worst offenses, is that hitting the return key after entering text into a form to search for content for a link the page goes blank... Blank, because I hit return, and didn't click on the custom search image button which is the only way it seems to submit the form.

An unofficial blog asks the question, "Is RedDot CMS Dead?". Sadly, no. Even though it's been replaced, there are probably many customers out there of RedDot resellers who must suffer it's existence.

Being a developer and user of RadiantCMS makes me look at this from a particular perspective and will help me guide development of Radiant. With this experience, I'm officially picking a fight with other CMSes. I hope to learn a lot from other solutions and give my customers a better experience, but mostly I think its important to talk more about what is considered "enterprise" and show that the emperor has no clothes.

If you're looking for a CMS, it should be easy to understand and easy to update. If you're considering RedDot, or some vendor that provides it, think again for your own benefit.

Cache key for collections in ActiveRecord

I've been working on the performance of a Rails application and much of my recent work involves simply caching. It was an application that was an excellent proof-of-concept which quickly turned into a production application.

We've got memcached setup and running and now it's a speedy little interface.

But one thing I knew I needed but wasn't sure how to do was cache a group of records. Rails fragment caching is super simple for one record:

<% cache [object] do %>
  ... this will be cached based upon your object.cache_key ...
<% end >

That's fine and dandy until you want to cache the view for not just one object in your loop, but the entire rendered view of @objects. As much as I looked around, I found that nobody has written about this problem. I still wonder if I'm being naive and there is some obvious way to do this that has simply slipped by me.

When I first tackled this, I had just a handful of objects in one particular view and would never have more, so I just concatenated the cache_keys of all of them and I was done. In another instance (with a larger number of objects) I tried something different by prepending another object which could be changing and appending a string: cache [user_role, @project_application,'task_lists'] do. This was painful because it required that I ensure that all the objects down the relationship tree call :touch and update their belongs_to parent until finally my @project_application was updated.

The task lists in this application don't just have tasks, they have attachments and notes too and this just felt like the invalidation of the cache was far too intertwined among the related objects for my liking.

Finally I realized that it was much simpler than that. What I need here is pretty simple: a cache_key for a collection.

A group_key method on a collection would be so much simpler than any convoluted process I attempted. Here's what I did:

def self.group_key
  count = ActiveRecord::Base.connection.select_value("select count(*) from widgets").to_s
  timestamp = ActiveRecord::Base.connection.select_value("select max(updated_at) from widgets").to_s.parameterize.wrapped_string
  count+'-'timestamp
end
That makes 2 simple calls to the database to get a parameterized representation of the most recent count and `updated_at` value for my Widget. If any of my objects change for any reason or if I add a new record, my group_key will be updated. I took this and put together a simple gem called `group_cache_key` which you may find on [gemcutter.org](http://gemcutter.org/gems/group_cache_key). The source is a bit different in that it includes a max `updated_at` and max `created_at` and it doesn't go back to the database for the information:
def cache_key
  if self.empty?
    'empty'
  else
    update_timestamp = max {|a,b| a.updated_at <=> b.updated_at }.updated_at.to_i.to_s
    create_timestamp = max {|a,b| a.created_at <=> b.created_at }.created_at.to_i.to_s
    self.first.class.to_s.underscore+'/'+length.to_s+'-'+create_timestamp+'-'+update_timestamp
  end
end

I hope you find it useful. It'll give you a cache key value of something like widget/2-1253224342-1253311589.

To use it, grab the gem (gem install group_cache_key) and add this to your config/environment.rb:

config.gem `group_cache_key`

Let me know what you think.

Update

I've pushed a newer version of the gem out there which creates a hash of the given ids. The hash is created in the order given, so if you're doing a sort in your interface, you'll get an entirely different cache_key.

Changes in Radiant AdminUI for 0.8.1+

There's a lot of focus on the visual changes to the Radiant interface, but there are some functional changes that should be discussed too. I've started playing with the new AdminUI as I try to understand the changes (since I haven't had time to take part in the development). The first thing I tackled was figuring out how to rearrange the navigation items and it's not too easy to understand what's going on.

First, here's the new structure of the tabs:

NavTab #=> [NavSubItem, NavSubItem, ...]

NavTab is a subclass of Array and it's accessed via admin.nav although admin.tabs will still be available (with a warning of deprecation) for some time.

I'll be posting details like this on the Radiant blog once the code is more developed.

There's debate about where Snippets ought to go: content or design? Do site editors use them, or do designer/developers use them? So that's a good place to start.

In order to do this, I had to generate an extension. Then, in my extension, I added this to the activate method:

admin.nav['content'].delete_if{ |t| t.name.to_s == 'snippets' }
    admin.nav['design'] << admin.nav_item(:snippets, "Snippets", "/admin/snippets")

That's complex, but it moves the "Snippets" link from the "Content" tab to the "Design" tab. It should be simpler; something like this would be nice:

admin.nav['content']['snippets'].nav_tab = admin.nav['design']

but better yet, I'd rather have:

admin.nav_items['snippets'].move_to('design', :after => 'pages')

It doesn't seem too valuable to implement that just to move one thing, but with the new tab/nav structure I would think that there will be a lot more customization by users/developers.

1) Does that syntax appeal to you? (Why or why not?)

To make it even simpler, it might be nice to be able to define structure in an initializer or a yaml file somewhere so that Radiant would load it's default setup, but then read your changes and rearrange the navigation accordingly. I wouldn't want to tackle that until other questions are answered, but it's an idea for the future.

Next, the recent changes only allow you to specify visibility on the main NavTab and not on the NavSubItem.

2) Do you want to be able to specify visibility on the NavSubItem?

Using the above example:

admin.nav_items['snippets'].move_to('design', :after => :pages, :visibility => :admin)

Personally, I think something like this is a must. I see tabs as a way to organize links and not as much of an authorization mechanism. By that I mean that in order to hide a section to all but a specific role, you'd need to put it in it's own tab even thought it might already make sense in a place like "Content".

These recent changes are going to cause a bit of pain for developers to upgrade the numerous extensions out there, but it'll be well worth it in the long run; especially if it's easy to move things around.

Updates in Radiant have been causing some pain do to the major changes being made recently and if you're not well-versed in the core application it can be difficult to keep up. Please post your questions to the mailing list where people are generally very helpful. Although we'd all like to avoid upgrade problems, they sometimes comes from changes in Radiant or changes in Rails, and we are doing what we can to create problem free upgrade paths. But this doesn't mean that the creators or maintainers of the particular extension that you use are yet aware of your problem. Hence, the mailing list is your best option for help.

Thanks very much to John and Sean for hacking away and getting this into the master branch.

UPDATE: There's plenty of discussion going on about this on the development email list, and I've got some related changes that I hope to add to the core here http://github.com/saturnflyer/radiant/commits/tab

Fat Free CRM on Heroku

Heroku is an excellent way to easily deploy Rails applications. But there's a trick to doing it: it's a read-only file system.

To some, this may be old news, but to others who have come across a project using SASS, you might feel like your out of luck. Fat Free CRM, for example is a great new project, and it uses SASS. Try running it on Heroku and you'll start getting frustrated. If you're not familiar with SASS, it automatically generates static CSS files for you in production, meaning it needs write access to the file system.

So, what do you do? The good thing is Heroku has a plugin to help with SASS. But, in this instance, it doesn't seem to work. I've installed it with Fat Free CRM and have pushed up to Heroku, but I get errors so rather than debug the issue you can try another option: http://github.com/mooktakim/heroku_sass_and_cache/tree/master.

Here's a step-by-step. First, if you don't have a Heroku account, signup and install the gem:

$ gem install heroku

Next, get the Fat Free CRM code:

$ git clone git://github.com/michaeldv/fat_free_crm.git

And turn it into a Heroku project

$ cd fat_free_crm
    $ heroku create

You'll need to install a plugin to manage the location for SASS files.

$ script/plugin install git://github.com/mooktakim/heroku_sass_and_cache.git

Read the documentation for the plugin, but you'll need to add this to config/routes.rb:

map.heroku_sass_and_cache

Just drop that on the first line of your routes.rb file and commit all of your changes.

$ git add .
    $ git commit -m "feeling sassy"
    $ git push heroku master

That last bit will deploy your application to Heroku. Fat Free CRM requires that you run the rake crm:setup task.

$ heroku rake crm:setup USERNAME=myusername PASSWORD=mypass EMAIL=my@email.com

After that, you should have a working Fat Free CRM on Heroku. Try heroku open and you'll land at your new login screen.

This will get you going, but you'll find that minor things like storing avatars won't work because they expect to be stored on the file system. Missing avatars aren't an application deal-breaker and I imagine that there might be some options in the future for separate storage options, or even just using Gravatar.

If you have any luck with the official Sass support on Heroku, leave a comment and I'll update the post.

Textpattern on Github

I'm a former user of Textpattern and I'm glad to see it more easily accessible on github. I hope it'll be easier for other users out there to contribute to the project.

My path to abandonment began when I built a real estate management interface on top of Textpattern. It was an interesting experience to say the least. The system is built to provide clear methods like graf() which I'm sure you would guess is a way to display an HTML paragraph element...

And of course, for your developing delight there are other methods like sLink(), eLink(), wLink(), dLink(), and aLink(). I'll leave it to you to guess what features they implement. My favorite is listed in there too.

I am glad, however, that in the very unfortunate event where I might need to use Textpattern again it'll be much more accessible to track other forks and branches. Cheers to the Textpattern community, from a thankful but recovering user.

Why you should use Devver

For good reason, developers share concern over not only a well-tested application, but also a regularly tested application. If you are managing a group of developers and are responsible for the outcome, wouldn't you first find a solution to allow your developers to run the test suite whenever they want? If your team members are running tests whenever they want on your massive application it'll probably slow them down, so of course you'll implement a continuous integration server. But then you're left with the requirement that they checkin their code. That could be a painful situation if they checkin some feature breaking hack. Enter Devver.

I'm participating in the Devver beta. When I got my invitation I immediately starting testing out the RadiantCMS source. While the code base is relatively simple, Radiant has some great test coverage including Cucumber features.

Radiant is a bit of a strange bird when it comes to Rails apps since it has it's own Radiant::Initializer rather than a Rails::Initializer. Devver wasn't quite expecting this, but Dan, Ben, and Avdi were extremely quick to respond to my requests.

There's been a good deal of development around the problem of running a massive test suite for an application. There's CruiseControl or CruiseControl.rb, and Integrity or Inotegration, or RunCodeRun and plenty of others that I'm missing. It seems to me that the problem with those approaches is that you need to checkin your code. In some respects, it's too late to run your tests when you've already checked in the code.

Of course you can use things like ZenTest with autotest. And there's spork for speeding up your tests as well. But a massive test suite that takes a while to run is something you might like to avoid running locally. Offload that to a server! Run rake devver:spec or rake devver:test. It's a very simple process.

You could, of course use testjour if it's a viable option in your development environment, but Devver is still probably much simpler to setup.

Installation is simple.

  1. Download and install the Devver gem
  2. Download and install the Devver Rakefile
  3. Configure Devver to use your API key
  4. Declare your gem dependencies
  5. Run it!

I've not been compensated in any way by Devver. They've taken a great idea (run tests before a code checkin) and made it easy and fast; and I thought you should know about it.

External website resources

I wrote a recent post about managing ALL of your content where I discuss the idea that your content is not just the content on your site.

In my continuing research for better ways to manage content I came across a real gem. Net::DNS which is a helpful tool for resolving DNS and gives you a different perspective than Ruby's Resolv.

If you want to check if a domain is valid then something as simple as this might do the trick:

Net::DNS::Resolver.start("google.com").answer.size > 0

But of course, there's much more to it than that. Check it out!

Ruby FuzzyHash

I came across an interesting way to use a Hash at http://github.com/joshbuddy/fuzzyhash/tree/master.

I may look into this further for Vapor since this is basically what that RadiantCMS extension needs to do.

I've altered the sample code but it does all the explaining:

>> hash = FuzzyHash.new  
>> hash[/^\d+$/] = 'number'  
>> hash[/.*/] = 'something'  
>> hash['chunky'] = 'bacon'  
>> hash['foo'] = 'vader'

>> hash['foo'] #=> 'vader'  
>> hash['food'] #=> 'something'  
>> hash['123'] #=> 'number'

Rails Metal in RadiantCMS

Radiant edge now supports loading Rails Metal from extensions!

I'm really excited to announce that. I had been working on it before the release of 0.8.0, but hadn't had the time to hammer it out before the release. Admittedly, I push some sloppy commits into the main repo, and I should have rebased them and cleaned them up. This was the first opportunity I'd had to look around at the way Rack middlewares are loaded in Rails, so a lot of my effort was just poking around. Gregg Pollack's screencast on Rack & Metal had some helpful tips in it. Check it out if you want to try out the new features in Radiant.

This will make things like checking a login status (like we're doing with Practice Greenheath and the Header Authorize extension) much faster.

More importantly, Vapor(the extension to allow users to write their own redirect rules which also caches all the rules so its nice and speedy) will be moving to metal. I've created a separate branch for this, but I'm considering backward compatibility so that if you've got an older version of Radiant, it'll still operate the same old way (by catching the requests in the SiteController). I'll need to re-evaluate the code before it goes into the master branch, but it works!

Managing ALL of Your Content

We've been working hard to provide great features to our customers that are using Radiant and we've been pushing a lot of that work out into the community. We've still got plenty more in store for our extension development and of course a lot more to contribute to Radiant.

One of the problems any organization might face is the task of keeping track of your content. Of course, you'd look to a content management system like Radiant, but even beyond your content is the content from others. Many organizations have relationships with others and their content is often linked back and forth. As the content changes, some things may get stale and we're working on a way to keep track of that.

Your content isn't just your content. Your site depends on its environment and you'll need to react to any changes in it.

When you're writing content for your site, you shouldn't just be worried about what you've got. You also need to make sure that your target sites (or sites on which you might comment or to which you might send your visitors) are up and running too. Site Watcher is a great way to keep track of what's happening on your own site and we'll be adding more features there, but there will be more to come for tracking the rest of the world too.

I'll be posting more about this in the future as we get ready to release our upcoming projects.

Pretending to Be Different

It seems to be very common to say in many ways that you are different, but when it comes to showing it, many companies must be too afraid to actually be different.

I've seen a TV commercial for the Audi Q5 which claims that it is unmistakable. From the behavior of the actors in the ad I would guess that it's supposed to be unmistakably different, but when it comes time to show how different it is you are shown that it is... black. The other cars, you see, are beige. Never mind that they look the same in every other way.

This ad just plain confuses the point:

Are you doing this? Is your company claiming to be different but not showing your differences?

What I Learned From a Vacuum

How often does someone want to buy a vacuum? It seems like just one of those things you do and then never do again until 20 years later when your vacuum finally dies.

Dyson makes me want to buy their products. I wonder, when I see their advertisements, if I'm the only one that feels that way. Certainly not, but actually wanting a vacuum is outside the realm of what people usually desire when it comes to material things. A new car, new iPod, new camera, sure. But a vacuum?

Perhaps its the fact that I'm already a satisfied owner and bummed out that I bought one so early that I'm missing out on the new features. They certainly handle their marketing well. They describe so clearly why their product is better; then they follow-through.

A few years ago my wife and I vacuumed our house (during some construction) with a shop vac thinking it would do the best job. We followed up with our Dyson and were astounded at what the supposedly powerful shop vac had missed.

alt textI recently received an email from Dyson about their new airmuscle and it got me thinking about how I need to improve. Dyson shows the process and explains in plain words their complex technology. I'm looking at that for some inspiration. Many clients avoid decision-making because of a lack of understanding. Technology is always increasing its pace in involvement in our lives and businesses, but understanding of it isn't... at least not for all.

Whenever I speak with someone about our work and how we might help, I first try to help them understand. Because even if I win your business, if you don't walk away with a better understanding then you've lost something.

More updates for Radiant comments

I'm slowly working on improving the commenting extension for Radiant.

The simple logic spam blocking has been working fine, and I've just updated it to ensure that the answers are not revealed in the HTML. Originally I just dropped the correct answer into a hidden field, but now the correct answer is hashed so that the value in the form is something like fc7272f83a6dbcfea6a3c81d3eb10e2e rather than the actual text.

Enjoy the more secure simple CAPTCHA system!

I've also added the ability to specify the number of comments per page on the admin side with Radiant::Config['comments.per\_page'] = 100. To any contributors, I am adding some features from the wild, but please write specs for your updates!

I hope to simplify the interface for managing comments as well, but that will come in time.

UPDATE

I've also sanitized the content_html so that you're no longer vulnerable to inserted script elements.

What partnership means

I subscribe to a newsletter from Bright Yellow Jacket where they always seem to have good insight (and you can sign up for it too).

In light of Paul's recent articles (Developer As Typist and Developer as (Fashion) Designer) on what a developer is or is not, I found the latest newsletter from Bright Yellow Jacket to be apropos.

Too often, many businesses may find a designer or developer in order to dole out commands rather than explore what a relationship with that vendor may provide. I thought it would be appropriate to re-broadcast the idea:

Two weekends ago, Sean O’Hair won the Quail Hollow Championship. Paul Tesori also won. Who’s Tesori you may ask? O’Hair’s caddy. This is important. As a golfer, you want someone to carry your clubs. It makes for a more enjoyable day of links. But you need more than that. You need a professional, and better yet, one that knows the game. A professional caddy can provide a second opinion; knowledge of the course; feedback on your shot, club choice and approach that is tailored to your skill set. O’Hair’s caddy carries his clubs, he’s also an ex-PGA player, but most importantly his caddy is his partner.

These are important ideas. I don't say this because I'm in a company that provides partnership like that, I'm in a company that provides partnership like that because these are important ideas.

I really like the way the folks at Bright Yellow Jacket created the analogy. When you want a professional job, you don't just hire someone to drag your clubs around for you.

Who says Perl is dead?

Many in the Ruby world might not be aware of the development in Perl 6 but a good friend pointed out an interesting post about Cross-language library loading on Parrot.

If you're not familiar with it, Parrot is "a virtual machine designed to efficiently compile and execute bytecode for dynamic languages." It's written in C, and the 'rakudo' implementation of Perl 6 runs on Parrot.

Take a gander at the rough-around-the-edges Ruby compiler for Parrot on github.