Related Articles Plugin for Typo

Getting the Code

The plugin can be installed by either checking out the source code (into `vendor/plugins/related_articles_search`).

svn co http://www.engross.org/svn/typolatest/trunk .

Or, you can use the Rails way:

$ script/plugin install http://www.engross.org/svn/typolatest/trunk$ mv vendor/plugins/trunk vendor/plugins/related_articles_search

Note that the `script/plugin` install goes into the wrong location, I don’t know the best way to fix this just yet, but perhaps I’ll just add an alias inside my web server configuration?

If you just want to browse around the code, feel free to do so from Trac.

Creating the Plugin

The first step was to generate the plugin

$ script/generate plugin related_articles_search

Rails generates some stub files (more convention magic), which we can then move our code into. Of course, we don’t just want to start moving files around, we want to make sure we can still run our tests and prove that the functionality still works. So let’s start by moving those tests into our plugin.

Moving the Tests

We’re attempting to extract the `related_articles` instance method from `Article` into a plugin. That way, we can deploy the update by just installing a plugin and extending code, rather than modifying the existing Typo code (which would also require the user to have an SVN working copy).

So, let’s move the tests from inside `article_test.rb` to `related_articles_search.rb`. Once we’ve done that, let’s run them and see what happens so we know what to try and fix – we’ll guide our work by what the tests tell us. So what do we get?

NoMethodError: undefined method ‘fixtures’ for RelatedArticlesSearchTest:Class

This is because we need to tell Rails to load our application, all of our model classes, and the various Rails test helpers (including the `fixtures` helper). So, we change the top of the test to look as follows (the key is the second line – despite it’s path ugliness :))




123456789
require 'test/unit'require File.dirname(__FILE__) + '/../../../../test/test_helper'class RelatedArticlesSearchTest < Test::Unit::TestCase  fixtures :contents, :articles_tags, :tags    def test_returns_no_related_articles    assert_equal 0, contents(:article3).related_articles.length  end

Although we no longer get the same message, we still need to move our fixtures over so they’re contained within the Plugin.

Moving the Fixtures

Since this is an example of work which takes us outside of Rails’ _convention_al comfort zone, time to take a peek into how Rails actually loads the fixtures during tests and then figure out how we’ll do it.

Rails’ test helper mixes in the `fixtures` class method which allows you to leverage some of Ruby’s syntactic sugar when you use




fixtures :contents, :articles_tags, :tags

Looking through the code inside `fixtures.rb` it’s possible to see how it loads the fixtures:




123456
def read_fixture_files  if File.file?(yaml_file_path)    # YAML fixtures    begin      yaml_string = ""      Dir["#{@fixture_path}/**/*.yml"].select {|f| test(?f,f) }.each do |subfixture_path|

It looks for any `*.yml` files inside the `fixture_path`. Brilliant.

This in turn is set via Rails’: `Test::Unit::TestCase.fixture_path=(path)`. So, all we have to do is change the fixtures path inside our `TestCase`. So, let’s go ahead and do that. So, at the top of our test file let’s try and do




1234567
require 'test/unit'require File.dirname(__FILE__) + '/../../../../test/test_helper'class RelatedArticlesSearchTest < Test::Unit::TestCase  self.fixture_path = File.dirname(__FILE__) + '/fixtures'    fixtures :contents, :articles_tags, :tags

That way, we’ll stick all our fixtures stuff into a `fixtures` subdirectory. If we re-run the tests now, we probably ought to see some failures since the files aren’t where they’re expected to be


1) Error:test_can_find_a_limited_result_set(RelatedArticlesSearchTest):Errno::ENOENT: No such file or directory - /Users/pingles/work/typo-latest/vendor/plugins/related_articles_search/test/fixtures/contents    method open in fixtures.rb at line 327

Success! In a kind of failing way. Still, it’s always good to have a failing test first. Let’s go and add those files into a new directory and run again

1) Error:test_can_find_a_limited_result_set(RelatedArticlesSearchTest):StandardError: No fixture with name 'related_article_3' found for table 'contents'    method contents in fixtures.rb at line 480    method test_can_find_a_limited_result_set in related_articles_search_test.rb at line 43

Cool, we now know we’re loading the fixture files correctly (because it can’t find anything within them).

So let’s move the content over from the old fixtures and into these files. (Note that this is too boring to describe, safe to say, we’ve moved some setup stuff into these files. I’ve also made a few test changes so we’re not dependent on Typo’s own fixture contents. You can check it out in the Subversion repository

Run the tests again and we see…

Loaded suite /Users/pingles/work/typo-latest/vendor/plugins/related_articles_search/test/related_articles_search_testStarted.......Finished in 0.522689 seconds.7 tests, 11 assertions, 0 failures, 0 errors

Sweet! So our test (and test assets) are now part of our plugin. Time to do the easy bit next and just move the `related_articles` method.

Moving the Finder

So, let’s cut the `related_articles` method code from `Article.rb` and put it into our module. Our `related_articles_search.rb` file will now look like this




12345678910111213141516
module RelatedArticlesSearch  def related_articles(*options)    related = []    self.tags.each do |tag|      tag.articles.collect {|a| related << a if a.published && a != self }    end        if options.length == 0      related    else      params = options[0]      count = params[:limit] - 1      related[0..count]    end  endend

If we re-run the tests now, we’ll see a number of errors appear because we haven’t mixed in our module yet and the `related_articles` method no longer exists on `Article`:


1) Error:test_can_find_a_limited_result_set(RelatedArticlesSearchTest):NoMethodError: undefined method `related_articles' for #

Mixing in the Module

So, into our plugin’s `init.rb` we add the following




12
require 'related_articles_search'Article.send :include, RelatedArticlesSearch

After reading a few bits about mixing in a module my first instinct was to write




12
require 'related_articles_search'Article.include RelatedArticlesSearch

But, because `include` is a private method, that wasn’t going to work. The answer is to tell `Article` (by way of sending a message) to do it.

If we re-run the tests now we’ll see these all pass and we know that our module is working correctly, and we have a functioning Typo plugin!

To Sum Up

We have extracted our plugin complete with tests and test data. Users should now be able to safely install the plugin and verify it’s installed correctly by running the test, avoiding the need to patch the Typo source code.

So, all that’s necessary to install the Sidebar component now is

1. Install the Related Articles Search plugin that provides the `related_articles` instance method to `Article`.
2. Unzip the `related_articles_component.zip` inside your `/components/plugins/sidebars` directory.

Nice huh? No need to patch Typo code, at least not just yet – I’d definitely like to contribute something though.

Well today’s exercise was much easier than I expected it to be, and it’s nice to find there’s a way to package most of this stuff together. Hopefully other users can get it going successfully, please let me know if you can or can’t!

I’ve taken a look at Piers’ changes for Typo and it definitely looks interesting. I’ll get to work on porting the related articles code to the new sidebar architecture and hopefully get an updated version up on Subversion this evening.