I am getting lots of push back from Rails because I have subclassed User into many different subclasses. In my application, not all users are equal. There's actually a lot of model objects and not every User type has access to them.
I also need a way to do polymorphic behaviour. For example, many methods will behave differently depending on the type. Isn't that what polymorphism is for?
But the thing is, I am always getting push back from Rails. The defaults - especially the way forms submit to parameter hashes - seem to work like non-subclassed models. Links and parameter hashes are just two ways in which the defaults really bite you.
What is the "right" way to handle complex logic for different types of Users in Rails? In Java, the subclassing model works - you don't have to go through hoops to get it to work the way you want to. But in Rails, it's hard to get subclasses to work with REST conventions, it punishes you when you forget to include :as => :user
, or it punishes you when you put a subclassed object in links, such as edit_user_path(@user)
<- bad idea!
There's one other area which is also very hard to deal with. Let's say I have a Company
model and it has many Users
. These users can be directors, instructors, trainees, etc - all different subclasses.
When we create the account, we might want to use accepts_nested_attributes_for :users
. However, if we use this, we can't specify the classes that it creates. Bloody hell!
It seems like everything in Rails is designed to not want you to subclass your models. If you don't subclass, everything 'just works'. But if you subclass, you are in for hell.
What is the solution?
Generally speaking, inheritance is discouraged in Ruby in favor of mixin behavior and delegation. Ruby and Rails can do that stuff but it tends to result in the push back you mentioned
Your particular example sounds like a case for delegation: have a User class which belongs to an employee (or vice versa). The type-specific behavior of that employee (eg director, instructor, etc) is all in that specific employee class. Then the user will delegate how to handle specific scenarios to the employee that it's joined with
Here's a trick I figured out. Don't assume it's intended behaviour though:
class UserSubclass < User
def self.model_name
User.model_name
end
end
Basically all models (that derive from ActiveModel) identify themselves, by default, based on the concrete class name. That's done through the class method #model_name
(it returns an instance of ActiveModel::Name
with self
as the parameter. Overriding it to return a specific class puts Rails on the right track. This way you keep that logic in your model and out of your templates.
It's basically about "say what you mean",
the framework has no way of knowing when you say redirect_to @user
if you meant the generic user form or the specialized employee user form.
This leads to a lot of redirect_to @user.becomes(User)
which you're free to DRY up
def to_base_class
becomes User
end
alias_method :to_b, :to_base_class
and write redirect_to @user.to_b
when your intention is to redirect to User
and not Employee
resource
Basically, elegant syntax like redirect_to @user
represents very deep coupling between model and view/controller and as you make the model and view/controller logics more complex cracks due to this coupling will begin to show and some additional effort in domain separation will need to be made or a bit more code will need to be written.
Rails isn't punishing you for using OOP, you're experiencing increased complexity of model <-> view <-> controller relationship:
once upon a time you model was your view and vice versa, now you have two model classes mapped to two view classes and if you want to use one view for the other model you will have to say it
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With