I find that some classes in Ruby, especially the User class in Ruby on Rails, have a tendency to become absolutely enormous. That in itself is just about manageagable but the tests files for such large classes also become massive too.
To try and compartmentalise this complexity I would like to compose the user Class from several sub-classes.
class User < ApplicationRecord
include User::AuthMixin
include User::EmailsMixin
include User::SettingsMixin
end
Technically this works however it means that the Mixins end up referencing a bunch of properties on the User object that they don't actually know anything about. Take this EmailsMixin which compartmentalises all of the logic around multiple-emails per user:
module User::EmailsMixin
def email
primary_email.try :value
end
def update_email value
email = emails.build value: value
self.primary_email = email
end
...
end
This works but it doesn't smell real good...
While this all works it feels like bad smelling code. Why should the User::EmailsMixin be referencing a primary_email that it doesn't have any knowledge of? And this will be the nature of the mixins - they are all referencing properties on the parent object (which is the User in this case).
Not only this but now I'm using Sorbet Typechecking it doesn't pass typechecking. Sorbet points out precisely this error and states that Method primary_email does not exist on User::EmailsMixin. Which of course is true.
So I have three questions:
- Is this a reasonable paradigm for compartmentalising code? Please note that this is not code that can be moved into service objects, it relates directly to attributes on the user object (as per above)
- If it is a reasonable paradigm is there a way that reference the parent object in each of the mixins? Is there a way to say "this module is in fact part of the User object and will be included/merged later"
- If this isn't a reasonable way of breaking down vast classes into smaller ones then how else should I consider doing so (to re-iterate, service objects won't cut it here as the code needs to access the parent properties)