Global ID and has_and_belongs_to_many

Jun 22, 2016

Recently I had to design a feature where user could save specific reports with preset filter options to make it easier to run them. The challenge is that reports can be ran across different types of records so modeling the relationships was interesting.

When users logs into dashboard they will see their saved reports and be able to run them with one click. Depending on report_type it will call separate classes with the actual queries for aggregating the data. Each report can gather data for one or more accounts or articles. And I set inverse_of: :nil because from Article or Account model I do not need to know which reports it’s included in.

The problem is we are storing all relationship / logic in the same class / table even though account report_type will never have :articles. So we have to create conditional validation. Plus there could be other filter params specific to only some of the reports. Overall it just feels like putting too many things in the same place.

Here you have to build custom validation that if report_type == :account then ReportMap can only belong_to Account record_type. Plus you must have relationships from the Account or Article side. And you first need to create Report and then ReportMap records (which are needed to actually generate data).

GlobaldID

The solution I settled on is using GlobalID where we have URIs like this gid://MyApp/Some::Model/id. We can store array of these on only one side of the relationship. It’s similar to regular polymorphic relationship but you can have many of them.

get_records method will return the actual objects based on their GlobalID using GlobalID::Locator.locate and records_type validation uses model_name to make sure they are all the same and of correct type.

You also could have a report that gathers data for both accounts and articles. For that you need to setup different when case in the validator method.

As you can see all of these solutions are much more complex that usual belongs_to and has_many. You have to create appropriate validations and test your code carefully.