Ruby ruby, she’s my baby. DSLs.


Well, not really. Not yet, anyway. But I did have some success with DSLs. Couple of tricks to it.

First – controller code can just be jammed in the rails controllers directory. This seems to get rid of at least some of the “you must bounce rails for your code changes to be visible” aspect.

So. My controller has require 'qbe/data_source'.

data_source.rb defines classes QbeDataSource – the thing we are trying to make, QbeDataSourceBuilder – the thing implementing the DSL, and the bootstrap method qbe_data_source. At the end, it calls

load 'qbe/qbe_name.ds'
load 'qbe/qbe_taxon.ds'

Now – eventually I’ll work out how to make it scan the code directory and find those files, but at the moment I’m concerned that something like that might not work on the production environment.

Anyway.

qbe_taxon.ds looks like this:

qbe_data_source(:taxon) {
  test_dsl
  test_dsl
  view 'APNI_PORTAL_TAXON'  
}

So what happens? When load is executed, in invokes a function qbe_data_source with two arguments – the symbol that will be used to talk about the data source (which the user should never see), and a block to be executed. The function looks like this:

def qbe_data_source(name, &block)
  src = QbeDataSourceBuilder.new(name)
  src.instance_exec &block
end

Create a new QbeDataSourceBuilder object (with a constructor parameter) and execute the block in the context of that object.

First, the QbeDataSourceBuilder names a new QBEDataSource and remembers it:

  def initialize(name)
    @ds = QbeDataSource.new(name)
  end

QbeDataSource remembers the fact that a data source got built in a class variable:

  @@data_sources = Hash.new

  def self.sources
    @@data_sources
  end
  
  def initialize(name)
    @name = name.to_sym
    @@data_sources[@name] = self
  end

THis is available to the rest of the app as QBEDataSource.sources.

Next, the block is executed in the context of the builder. When the block invokes view 'APNI_PORTAL_TAXON', the view method of the builder is called. It uses reflection to set the instance variable of the QbeDataSource:

  def view(v)
    @ds.instance_variable_set(:@view, v)
  end

And so on.

So, how does this all get used?

Well, QbeDataSource has a test method. It’s pretty minimal at the moment:

def test 
    raise 'no data source name' unless defined? @name and @name
    raise 'no view name' unless defined? @view and @view
  end

The goal is that we should be able to run a basic test of all the queries in the app, so that we don’t get the situation of a mess of old, broken web pages as a result of movement in the underlying data model. We therefore have a test action in the QbeController:

  def test

    flash_warn 'QBE test page should not be visible in production'
    flash.discard
    
    @results = Hash.new
    
    QbeDataSource.sources.each_pair do |k, v|
      begin
        v.test
      rescue Exception => e
        @results[k] = e
      else
        @results[k] = nil
      end
    end    
    
    render 'qbe/test', layout: 'no_sidebar'
  end

And that does the job. All the methods are called, all the results stuck in a results hash, and the html page displays it. Naturally, we will rig up the menu bar so that the link to the test page is not displayed in production.

And that, at this stage, is it. Ruby DSL on-track.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: