#394 STI and Polymorphic Associations pro
Nov 25, 2012 | 14 minutes | Active Record, Refactoring
Single Table Inheritance (STI) can help organize branching logic into separate classes, but a polymorphic association may be a better fit if there are unique database columns.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
Nice refactoring.
Btw. when using generate migration you can use these advanced features (https://github.com/rails/rails/blob/master/railties/test/generators/model_generator_test.rb#L116-183) to create indexes, polymorphic associations, etc from command line without editing the migrations by hand afterwards.
Didn't know this. Neat!
This is really awesome, thanks!
Thanks Jan !
Any reason why you're not using
references
in the migration?instead of
Or even
rails g migration add_profile_to_users profile:belongs_to{polymorphic}
Curious what the MemberProfile model would look like if it were a PORO and not inheriting from AR as suggested. It could not make use of AR associations in the model, so what would that look like? Thanks for a great episode!
do you mean if the guest profile would only be a simple nullobject instead of a whole AR object with a table?
there should be some nice content
http://robots.thoughtbot.com/post/20907555103/rails-refactoring-example-introduce-null-object
https://www.destroyallsoftware.com/screencasts/catalog/how-and-why-to-avoid-nil
you could define a simple method
profile would be the normal association
Yes, sorry I meant GuestProfile as a PORO, not MemberProfile, as Ryan states at around 13:40 in the railscast.
It sounds like he's suggesting there would be no AR association between GuestProfile and User, that there would be a null check on profile_id in the users table and if the value was null then the app would generate a PORO in memory to handle Guest related behaviors (validations, methods, etc). But how would that PORO be bound to the relevant User record?
I think I see what he's saying. Completely remove the polymorphic association, User and MemberProfile have a simple has_one/belongs_to association, guests are stored in Users table as well, but they have no MemberProfile so the profile_id foreign key in Users is null for guests and we conditionally build a PORO for guests when a null profile_id is found.
I use quite STI and polymorphism liberally, because I have sets of classes with exactly the same columns. Even so I learned a lot from this episode. I especially like the way you discussed the trade-offs between different approaches. Often, the hardest part is knowing the best option. I end up coding it one way, hating it later then refactoring it to another way. This can waste weeks of time if the models are heavily used throughout the application.
I also really like your authentication from scratch. When AuthLogic went through it's unsupported period (Rails 3.0) I was really stuck and really came to value being in control of my core logic. Often we use huge gems because we need 10 lines of code from them, and spend more time figuring out how to wrangle them to fit our needs when coding from scratch is tiny, fast and takes less time (Devise).
Thanks. Yet another episode worth a year of my pro subscription.
+1. I always do auth from scratch. It's just too personal to your app to 'outsource'.
While this comment is 3 months old, I had to chime in on this:
I also typically do auth from scratch, but have lost a job possibility based on that. They felt I was a 'lone wolf' for doing my own auth, and were afraid I could not work well with a team (You know, even though I have worked on teams up to almost a hundred people).
Yes, it probably said a lot about the company and was probably a good warning indicator that that might not be the best company in the world to work for.
I guess STI with a document database is also a very nice solution. You dont have any nuls in those types of databases and the data is more saved as 1 object instead of several rows.
Actually when you play with such DBs there is no need for STI cause there are no tables. And class inheritance becomes really good and natural approach then.
Just before the episode was release I started working on a gem to solve this very problem. It's called CanBe. I've also implemented the code from this episode to use my gem here.
Hey guys check out this gist https://gist.github.com/4421177 here at icalialabs have been using this for a while, enjoy!
What is the purpose of the :type column for the User model in the STI example? I don't see the point of this, a User record is never even instantiated?
How does this look when you have fields in both objects? How do create from a single form? For example, I want to track two different expense types: reimbursements and donations.
Reimbursements have a description, donations have name, address, etc., but both have an amount, a receipt, a category, and belong to an account.
I'm trying the polymorphic association approach, but having trouble figuring out how to create a new reimbursement/entry with all the necessary data and associated objects.
Did you manage to figure this out? I am trying to do exactly the same.
If some one needs a really fast way to make guest users with Devise:
https://github.com/plataformatec/devise/wiki/How-To:-Create-a-guest-user
I wonder if the
:dependent => :destroy
relationship should be in the user instead of the profile. This would probably only be a concern when transitioning between profiles, but if you want to delete the guest profile before adding the member profile you have to wait until after the user has been updated. On the other hand, if the user is destroyed, chances are you want the profile to be destroyed along with it.