Every time I use AR & dry-monads together, I keep thinking about how neat it would be if ActiveRecord finders & associations returned Maybes (on queries) or Results (on mutations). I had a quick look last night to satisfy my own curiosity, and found that find_by is doable:
module ActiveRecord
module Core
module ClassMethods
alias_method :old_find_by, :find_by
def find_by(*args)
Maybe(old_find_by(*args))
end
end
end
end
I imagine that anything beyond this would get hairy quickly, though. Has anyone tried?
I’d recommend to continue encapsulating your business objects on modules/commands/systems you can test by their own independent of the data abstraction/orm.
I agree with Esparta that monkey-patching AR is not a good idea. It is not a good idea in general.
You can add a simple wrapper around your models that would delegate to the underlying methods and wrap results with Monads. I also recommend defining your own data-access method and treating AR “finders” and other query DSL methods as “private” from the application point of view.
I don’t feel I was very clear, sorry I’m in complete agreement that monkey-patching is almost never worth it.
I was thinking more along the lines of a mixed-in module that detects the finders and associations that Rails already declared, and wraps them similar to Monads::Do.
Speaking as someone who actually did monkeypatch ActiveRecord, I also agree with Esparta’s advice here: don’t. I don’t regret doing it, because I knew what I was getting myself into; but if you look at the sourcecode you will see the lengths I had to go to.
My approach to ActiveRecord actually veers far to the opposite direction: you should treat ActiveRecord as an entirely private API. I make the model constants private and make my own public APIs to query data.
Here’s a quick example of what I mean
module Certificates
class Record < ApplicationRecord
self.table_name = "certificates"
end
private_constant :Record
def self.find_by_name(cn)
M::Maybe(Record.find_by(cname: cn))
end
end
Why go to this trouble? Have you ever looked at the public interface of an ActiveRecord model? The API is huge. Most people give up and just integration-test everything with factories or fixtures. But the downside to that is that your test suite becomes intolerably slow.
I wrote a greenfield API service with this method, and was able to hit 100% test coverage with miniscule amounts of factory tests; the vast majority simply stubs out the interfaces under test. Only the stuff that literally talks to the DB needs to be integration tested this way.
Perhaps it’s a little scorched-earth, but AR is such a complex interface that I feel like it will become the center of gravity if you allow it. A data mapper pattern is better, but if you must use AR this is what I recommend.
I’m doing something similar to that on my current project; more like a traditional repository pattern. The idea of an embedded Record class is a new one to me. Very interesting, thank you