Discriminable
This is a Ruby gem that implements single-table inheritance (STI) for ActiveRecord models using string, integer and boolean column types.
In other words, it allows to use any (existing) model attribute to discriminate between different subclasses in your class hierarchy. This makes storing class names in a type
column redundant.
Also, it supports aliased attributes and multiple values per subclass.
Installation
bundle add discriminable
or
gem install discriminable
Usage
class Order < ActiveRecord::Base
include Discriminable
discriminable_attribute :state
end
class Cart < Order
discriminable_value :open
end
Cart.create
# => #<Cart id: 1, state: "open">
Order.all
# => #<ActiveRecord::Relation [#<Cart id: 1, state: "open">]>
Features
Compatible with enums
class Order < ActiveRecord::Base
include Discriminable
enum state: { open: 0, processing: 1, invoiced: 2 }
discriminable_attribute :state
end
class Cart < Order
discriminable_value :open
end
class Invoice < Order
discriminable_value :invoiced
end
Aliased attributes
In case you are working with a legacy database and cannot change the column name easily itβs easy to reference an aliased attribute in the discriminable_attribute
definition.
class Property < ActiveRecord::Base
include Discriminable
alias_attribute :kind, :kind_with_legacy_postfix
# Aliased attributes are supported when specifying the discriminable attribute
discriminable_attribute :kind
end
class NumberProperty < Property
discriminable_value 1
end
Multiple values
Sometimes, in a real project, you may want to map a number of values to a single class. This is possible by specifying:
class OptionProperty < Property
# The first mention becomes the default value
discriminable_values 2, 3, 4
end
Note that when creating new records with e.g. OptionProperty.create
a default value needs to be set in the database for this discriminable class. The Discriminable gem uses the first value in the list as the default.
Comparison with standard Rails
Rails STI
values | string | integer | boolean | enum | decimal | β¦ |
---|---|---|---|---|---|---|
single | π‘ class.name only |
π΄ | π΄ | π΄ | π΄ | π΄ |
multiple | π΄ | π΄ | π΄ | π΄ | π΄ | π΄ |
Discriminable Gem
values | string | integer | boolean | enum | decimal | β¦ |
---|---|---|---|---|---|---|
single | π’ | π’ | π’ | π’ | π’ | π’ |
multiple | π’ | π’ | π’ | π’ | π’ | π’ |
βMultipleβ means that more than one value can map to a single subclass. This may or may not be useful for your use case. In standard Rails, the a single class name obviously maps to a single class.
Prerequisites
Rails 5+ is required.
Also, in order to make this work in development
environment the class hierarchy needs to be loaded. You can do this like so in your development.rb
file:
config.eager_load_paths += Dir['app/models/**/*.rb']
ActionDispatch::Reloader.to_prepare do
Dir['app/models/**/*.rb'].each { |file| require_dependency file }
end
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/gregorw/discriminable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Discriminable project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
Related work
The idea for this Gem was influenced by βBye Bye STI, Hello Discriminable Modelβ by Randy Burkes. This Gem has started out with his code.
See also:
- Rails single table inheritance and DelegatedType
- Java JPA discrimanator
- Python model inheritance
- Discriminator gem.