Single-table inheritance might be what you need. In a nutshell: it allows several classes with the same kind of data to be put into one table. The only requirement is that that table has a type
column, a string
.
Basically, it's about common sense. Let's say, a user can have passes to an event: a vendor's pass and a faculty member's pass. He might have both. Let's create a Pass
model, bearing in mind that we'll need different kinds of it. But we'll use it later. For now let's just stick to has_many through
:
rails g model Pass type:string user:references event:references
Migrate this and we won't need to modify our database anymore. We'll only modify Ruby. We should have got a class Pass
, we'll need to mark its role in association:
class Pass < ActiveRecord::Base
belongs_to :user
belongs_to :event
end
All right. Then we'll have this sort of User
and Event
:
class Event < ActiveRecord::Base
has_many :passes
has_many :users, through: :passes
end
class User < ActiveRecord::Base
has_many :passes
has_many :events, through: :passes
end
Here's where the STI magic comes. Let's create two more classes.
rails g model VendorPass --no-migration --parent=Pass
rails g model FacultyPass --no-migration --parent=Pass
We've generated some classes without database tables (we don't need them). They are empty and we won't change it: they inherit a Pass
and that's all we need. But we'll need to create some extra associations between our User
, Event
and the new passes. In the end, I've found this working:
class Event < ActiveRecord::Base
# We already had this
has_many :passes
has_many :users, through: :passes
# New stuff!
has_many :vendor_passes
has_many :vendors, through: :vendor_passes, source: :user
has_many :faculty_passes
has_many :faculty_members, through: :faculty_passes, source: :user
end
class User < ActiveRecord::Base
# We already had this
has_many :passes
has_many :events, through: :passes
# New stuff!
has_many :vendor_passes
has_many :vendor_events, through: :vendor_passes, source: :event
has_many :faculty_passes
has_many :faculty_events, through: :faculty_passes, source: :event
end
Rails maintains its own understanding of VendorPass
which is "it's a Pass
, whose type
is VendorPass
", same with FacultyPass
.
The good parts:
- Easy to imagine: data structure seems sane and logical
- We're free to add more types of
Pass
es without changing the database
The bad parts:
- No way to add extra fields to only specific subclasses of
Pass
: they're all in the same table
- Associations look a bit repetitive and cumbersome
- Rails only allows
type
to be a string
, not the fastest type to compare