Last active
June 4, 2025 20:15
-
-
Save woahdae/44ffff96096c71c780aecffdb4c4c53f to your computer and use it in GitHub Desktop.
Module to use the Gerund pattern on ActiveRecord models
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
module Gerund | |
def self.included(base) | |
base.extend ClassMethods | |
end | |
module ClassMethods | |
delegate :model_name, | |
:sti_name, | |
:finder_needs_type_condition?, | |
:_to_partial_path, | |
to: :superclass | |
end | |
# Not great to monkeypatch this, but it's the least amount of code | |
# that does what we want. The issue here is #becomes is super handy | |
# with gerunds, but the instance returned from #becomes has @attributes | |
# literally as a pointer to the original attributes hash. This is a | |
# problem if our subclass defines more attributes with the ::attribute method | |
# - assignment won't work because the parent instance attributes won't have | |
# the subclass' attributes defined. This monkeypatch fixes that, just in time | |
# for anything that depends on these attributes, including `after_initialize` | |
# callbacks. | |
def _run_initialize_callbacks | |
extra_attrs = self.class._default_attributes.keys - @attributes.keys | |
# Copy over any extra attributes from the gerund to the original attribute | |
# set. Personally I'd like it to be a new set of attributes, but our code | |
# accidentally depends on this in a couple places, and I guess I'd rather not | |
# have it two ways so let's do it the way Rails ships with. | |
extra_attrs.each do |attr| | |
@attributes.instance_variable_get(:@attributes)[attr] = | |
self.class._default_attributes.instance_variable_get(:@attributes)[attr] | |
end | |
super | |
end | |
# GlobalID depends on the model to get class_name, rather than passing | |
# it in directly. Maybe someday we can try to get GlobalID to be easier | |
# to work with in this way; until then, this works. | |
def to_global_id(*args) | |
becomes(self.class.superclass).to_global_id(*args) | |
end | |
alias :to_gid :to_global_id | |
end |
Nice! It appears so, thanks. I'll test it in our app, and I appreciate the inspiration to keep writing about this.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey @woahdae,
Thanks alot for this gist and your blog post about gerunds. I am trying it out and really enjoying it so far.
One quick thing.
I don't think the monkey patch on line 25 is needed again since rails 7.1
Here is a link to the commit that should have fixed that problem rails/rails@2f5136a
Let me know what you think and looking forward to reading more about how you use gerunds with mutiple inheritence and extending them at run time.