Ruby on Rails applications are modelled around active record yet what if your application is based on a domain which needs to be provided in a programmatic way? In other words, your application, controller and views stand on a fixed set of Ruby classes yielding the context. In a manual fashion one would implement this by creating controller and views for each specific class. Assuming that the classes of this domain share the same interface this approach would introduce a lot of redundancy. With meta-programming this amount can be cut down.
For this example we look at a Rails application implementing an analytics platform which will providing useful information using the following metrics.
- Unique page visitors
- Number of visits
- Number of goal conversions
- Total time spent
Each metric has to provide
compute method which defines how the quantitative value is calculated. Naturally, these metrics want to be translated to Ruby classes. Another requirement is to display and visualize each metric result on a different page. The more metrics are added the more sophisticated the platform would become, hence it would be reasonable to have a simple interface for adding new metrics without writing the same boilerplate code and over again. A simple layout would look like this:
The whole point of this pattern is whenever a new metric class is added it should automatically appear within the application and become accessible through views. As a first pass all available metrics should be listed on an index page, favorably in an active record fashion using
How can this be implemented without extending
ActiveRecord::Base? After all our classes are not persisted in a database. For starters let us add our own
Metric.all method which should return all metric classes, here kept in an array.
How can this array be prepoulated? Certainly, by iterating over files in a subdirectory, for instance in
app/models/metrics and then includimg them. While this is feasible there is an easier and more flexible way by utilizing Ruby’s
inherited(subclass)Callback invoked whenever a subclass of the current class is created.
Not only is this more elegant but it actually allows to add new classes dynamically to the stack even at runtime:
Extending this to the view each metric could implement a specific view with possibly a default view which can be reused for very simple metrics. Each specific view would be stored inside a partial. Thinking in hierarchies concrete metrics could subclass other concrete metrics for sharing specific behaviors. For instance revenue could be based on the number of goal conversions:
Hence, there might be a scenario in which we want to fallback to the nearest ancestor implementing the most concrete view for this metric. The code for this could look like the following:
For this to work
Metric needs to extend
Default implementations of model.model_name, which are used by ActionPack (for instance, when you using
render :partial => model)
This pattern should have given a small overview of how to integrate models into the realm of active models without the need to use active record directly. Rails has come a long way to refactor essential functionality out of active record into dedicated APIs allowing us to implement powerful constructs winged by Ruby’s meta-programming capabilities.