When you’re getting more and more into Rails or any other framework, there is a time when you think that the default is not what you need. This happened to me in that case in Ruby on Rails.
Let me raise my case:
The app I was working on had two production apps running on Heroku. Both apps automatically deploy from the same Github repo after CI was passed. Unfortunately, on one of the apps, there had to be one very small difference within one model. However, they had to still stay synchronized in the future for every code change, except for this little difference and the database, of course.
How should this one app do something different from the other when they have to have the same code behind the scene?
Quickly, I found that the difference I could use is the URL. I needed to validate a model and call a callback upon it, if the user was on a certain domain.
As an initial thought, I put out something like this:
That was too easy of course, already thinking that you cannot access the domain name while being in the model, on the server…
Rails` Validation Context
Going on search, I found Rails actually has native support for context! I just have to give the context when I save `my_model` in the controller, and I could use it like that:
Looks good, right? Okay, what happened here? Within the controller, I check if the user is on the correct URL by checking the request. If the user is, I save the model by forwarding a context. This context can then be accessed within the model so that the validation is only called when the corresponding context is given.
So far so good! But damn, this can’t be used on callbacks, and if you have different validations for different contexts, your “if-statement” when saving with a context can get quite long.
The PORO (plain old ruby object) Way
We’re still coding ruby, right? In the end, a Rails model is nothing else but a PORO with some Rails magic in it. Let’s use that!
We can simply give a non-database instance variable to the model and use that to check if the validation/callback should run. This way, of course, this variable will not be written into the database, but we don’t need it there, as it is just some context, right?
Let’s PORO it up:
What we’re doing here is quite basic in the end. After getting the permitted params from the form in the front-end, which, in the end, is just a hash, we add another key-value pair
on_my_domain: on_my_domain? that resolves to either
on_my_domain: true or
on_my_domain: false . This will add our context instance variable using the
attr_writer to the model’s instance. We can access that using
@on_my_domain , or in our case via
on_my_domain? . The validations, callbacks, and everything else can now use that information!
Just keep in mind, once the instance is gone, the “context” will also be gone.
Make it more Railsy
With all the above, it is already working perfectly fine! In a bigger app, though, it might be interesting to use such a context variable in various controllers and for different models. Fortunately, we’re talking about Rails here, so relax :)
We can put our frontend check into a helper, so it can be used within several controllers like that:
Let’s finish it up and write a concern, so that every single model that needs it, can use our context variable:
I hope that this helps you. Have a great one :)