Using Rails Concerns to apply the same validations for columns that have different names
Using Rails Concerns are a very effective way to write DRY code.
With a single file you can add validations and methods to any model that includes
the concern.
However things can get tricky when you want the same behavior but for columns that are named different for each model.
I ran into this issue when working at a gifting company. They had products that had their own codes which were used to calculate taxes. Each product belonged to a brand and that brand could set a default code to be used as a fallback if the product tax code was empty.
I wanted to add a set of validations for Product#tax_code and Brand#tax_code_default
In An Ideal World
If the two columns were named the same it would have been incredibly simple.
We want the tax code to start with 2 letters followed by 6-7 digits, so this regex will do the trick for us.
And then just add include this concern in our models
Unfortunately the two columns are not the same. One is tax_code
while the other one is tax_code_default
so we need to get more creative with this.
Setting the column names
The first that we need to do is have the concern keep track of what the different column names are.
Let's create a class method that will store this value for us.
Then we can call the set_tax_code_column
like so:
This will set the tax_code_column on Application Load.
Adding Validations
The whole point of this column was to add validations so let's get to it.
- First we include a generic validation to
included
block:validate :validate_tax_code
- The validations are going to run for instances but the column name is stored at the class level so this is how we obtain the column name:
tax_code_column = self.class.tax_code_column
- Then we use the
read_attribute
method to grab the value.new_tax_code = read_attribute(tax_code_column)
NOTE: Normally we'd just call the column name directly likeproduct.tax_code
, but since the column names are different we need to make use of theread_attribute
column. - Then we add the actual validation
return false if /^[A-Z]{2}\d{6,7}$/.match?(new_tax_code)
errors.add(tax_code_column, 'is invalid')
And the final result is this!
So there you have it. This is a way we can use the same validation in a concern for columns that are named differently.