views:

92

answers:

2

This seems like it should have a straightforward answer, but after much time on Google and SO I can't find it. It might be a case of missing the right keywords.

In my RoR application I have several models that share a specific kind of string attribute that has special validation and other functionality. The closest similar example I can think of is a string that represents a URL.

This leads to a lot of duplication in the models (and even more duplication in the unit tests), but I'm not sure how to make it more DRY.

I can think of several possible directions...

  1. create a plugin along the lines of the "validates_url_format_of" plugin, but that would only make the validations DRY
  2. give this special string its own model, but this seems like a very heavy solution
  3. create a ruby class for this special string, but how do I get ActiveRecord to associate this class with the model attribute that is a string in the db

Number 3 seems the most reasonable, but I can't figure out how to extend ActiveRecord to handle anything other than the base data types. Any pointers?

Finally, if there is a way to do this, where in the folder hierarchy would you put the new class that is not a model?

Many thanks.

UPDATE:

One potential solution using Matt's mixin suggestion below (and using the URL example). Note, this is closer to psuedocode than real ruby and is intended to convey the principle rather than perfect syntax.

First, create a url mixin:

module Url
  def url_well_formed?
    [...]
  end

  def url_live?
    [...]
  end
end

And in a Site model, include this module:

Class Site < ActiveRecord:Base
  include Url

  validate :url_well_formed?
end

And when I need to check that the site at the URL is live, do...

if site.url_live?
  [...]
end

The thing this doesn't solve is how to make the unit tests DRY. If I have another model, say Page, that also uses the Url mixin, it will still need a duplicate set of unit tests for the URL. Of course, I could put these in a helper, but that seems messy.

Is there a more fundamental solution, or is this as good as it gets?

+2  A: 

You could create a module with all the common methods, and import the module? Google for mixins.

Matt Grande
Thanks Matt. I can see how mixins will get me part of the way (making the models themselves pretty DRY). Now I'm wondering how I can also make the unit tests DRY, but that is probably a more general question around mapping unit tests to modules.
Greg
+2  A: 

Create an abstract model:

class CommonBase < ActiveRecord::Base
  self.abstract = true # makes the model abstract
  validate_format_of :url_field, :with => /.../
end

Inherit your models from the abstract model:

class User < CommonBase
end

class Post < CommonBase
end
KandadaBoggu
Thanks KandadaBoggu. Can one create a unit test that maps to an abstract model? And an organizational question: where would you put this?
Greg