Does this sound familiar?
Asking for advice on a forum – “I’ve got a bulk update task for my database and I’ve read that I shouldn’t put that sort of thing into a migration – what’s the best way to do it?”
And the reply came back – “Just use a PORO”.
I looked it up but I couldn’t find any references to POROs in the Ruby on Rails documentation and I didn’t want to ask again.
In a Rails application your code is divided into several well-defined areas; models, controllers, views, jobs and so on.
This works in your favour – your code is neatly organised and, in general, you can pick up someone else’s Rails app and you know exactly what’s what.
But Rails is a framework built on top of the Ruby language – and you’re not limited to just Rails concepts, such as models and controllers. In fact, at any time you like, you can throw in a PORO – a plain old Ruby object. In other words, something that doesn’t represent a Rails thing; it’s just standard Ruby code.
Personally I have a liking for Ruby Structs – you can define the attributes they have and it generates a constructor that lets you fill in those attributes. Something like:
class UpdatesDatabaseRecordsWithSomething < Struct.new(:start_date, :some_data)
items_to_update = MyModel.updated_since start_date
items_to_update.find_each do | item |
updater = UpdatesDatabaseRecordsWithSomething.new(5.days.ago, "important update")
But you can use any old piece of Ruby you like – the fact that it’s not an ActiveRecord model or an ActionController is what makes it a PORO.
So where do you keep these POROs?
Well, that depends what it’s doing.
I often keep them in app/models if I’ve only got a couple of them and they’re generally helpers for various models. But I often create an app/services folder and place them in there as well – this is useful when I’ve got a number of “application processes” to represent. And how do you know if you’re representing application processes? If your PORO class is called something like CalculatesTaxRatesOnOrder or ProcessesNotifications (in other words the name is not a simple noun but instead starts with a verb) then it’s probably a process and something that wants to live in app/services.
And, if you did have a database update task, how would you trigger it?
Write a PORO to do the update, then add a rake task to lib/tasks that instantiates your object and tells it to do its thing. Rake tasks are easily triggered from the command line (and many hosting environments have ways of starting them on your servers from your local machine too). But by placing the actual code that does the work in a PORO you’re making the code that does the work easily testable and potentially reusable.