Skip to content

olegantonyan/polymorphic_enum_type

Repository files navigation

PolymorphicEnumType

CI

Storing class name as a string for each record is bad idea performance-wise. This gem enables ActiveRecord::Enum as type column in polymorphic associations. Unlike https://github.com/clio/polymorphic_integer_type this gem does not monkey-patch anything. In fact, all it does is adding enum to your model with values as Hash where key is your class name and value is integer associated with this class. And that's it, literally a single line of code does the trick.

Installation

Install the gem and add to the application's Gemfile by executing:

$ bundle add polymorphic_enum_type

If bundler is not being used to manage dependencies, install the gem by executing:

$ gem install polymorphic_enum_type

Usage

Extend PolymorphicEnumType module in a model and add enum_type: true to belongs_to:

class Comment < ActiveRecord::Base
  extend PolymorphicEnumType
  belongs_to :commentable, polymorphic: true, enum_type: true
end

The database table must have commentable_type integer field instead of default string:

create_table :comments do |t|
  t.bigint  :commentable_id
  t.bigint  :commentable_type

  ...
end

Create initializer, for example config/initializers/polymorphic_enum_type.rb and set the mapping integer to class name there:

PolymorphicEnumType.configure do |config|
  config.add :commentable, { 1 => 'Article', 2 => 'Post' }
  config.add :imageable, { 1 => 'Comment', 2 => 'User' }
end

Note: The mapping here can start from whatever integer you wish, but I would advise not using 0. The reason being that if you had a new class, for instance Avatar, and also wanted to use this polymorphic association but forgot to include it in the mapping, it would effectively get to_i called on it and stored in the database. "Avatar".to_i == 0, so if your mapping included 0, this would create a weird bug.

Migrating an existing association

If you want to convert a polymorphic association that is already a string, you'll need to set up a migration. (Assuming SQL for the time being, but this should be pretty straightforward.)

class CommentsToPolymorphicEnumType < ActiveRecord::Migration

  def up
    change_table :comments do |t|
      t.bigint :new_commentable_type
    end

    execute <<-SQL
      UPDATE comments
      SET new_commentable_type = CASE commentable_type
                                   WHEN 'Post' THEN 2
                                   WHEN 'Article' THEN 1
                                 END
    SQL

    change_table :comments, bulk: true do |t|
      t.remove :commentable_type
      t.rename :new_commentable_type, :commentable_type
    end
  end

  def down
    change_table :comments do |t|
      t.string :new_commentable_type
    end

    execute <<-SQL
      UPDATE comments
      SET new_commentable_type = CASE commentable_type
                                   WHEN 2 THEN 'Post'
                                   WHEN 1 THEN 'Article'
                                 END
    SQL

    change_table :comments, bulk: true do |t|
      t.remove :commentable_type
      t.rename :new_commentable_type, :commentable_type
    end
  end
end

Lastly, you will need to be careful of any place where you are doing raw SQL queries with the string (commentable_type = 'Post'). They should use the integer instead. However, when using ActiveRecord query methods and passing attributes as symbols there you still have to use strings:

Comment.create(text: 'good', commentable: Post.create(text: '123'))
artcile = Article.create(text: '1234')
Comment.create(text: 'good1', commentable: artcile)
Comment.create(text: 'good2', commentable: artcile)

# using where with attr: value syntax works with wtrings - ActiveRecord converts strings to enum values prior to query
assert_equal 1, Comment.where(commentable_type: 'Post').count
assert_equal 2, Comment.where(commentable_type: 'Article').count

# whenever you need to write raw SQL query you have to use integers, since ActiveRecord no longer converts strings to integers for enum in such cases
assert_equal 0, Comment.where("commentable_type = 'Post'").count
assert_equal 0, Comment.where("commentable_type = 'Article'").count

# commentable_types holds hash used for enum
assert_equal 1, Comment.where("commentable_type = ?", Comment.commentable_types['Post']).count
assert_equal 2, Comment.where("commentable_type = ?", Comment.commentable_types['Article']).count

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/olegantonyan/polymorphic_enum_type. 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 PolymorphicEnumType project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

About

Allows integer database field for polymorphic association type instead of string via enum.

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published