Search code examples
ruby-on-railssingle-table-inheritance

Is there a simpler way than using STI or polymorphic associations?


I have been reading a lot on the difference between STI and polymorphic associations and decided to use STI:

user.rb
Class User < ActiveRecord::Base
   has_many :articles
end

article.rb
Class Article < ActiveRecord::Base
   belongs_to :users
end

sport.rb
Class Sport < Article
end

politic.rb
Class Politic < Article
end

food.rb
Class Food < Article
end

create_table "articles", force: :cascade do |t|
t.string   "title"
t.string   "artwork"
t.integer  "user_id"
t.datetime "created_at",                 null: false
t.datetime "updated_at",                 null: false
t.boolean  "approved",   default: false
t.string   "type"

However, upon further reading, this becomes even more complicated. All I am really looking to do is to find some way to sort my articles by type. For example, is it possible that I simply have a string column tag and specify that tag must be either politics, sports, or food?


Solution

  • In this case, use an enum:

    #app/models/article.rb
    class Article < ActiveRecord::Base
      enum article_type: [:sport, :politic, :food] #-> "article_type" (int) column required
    end 
    

    The only drawback to this would be that you can only assign one enum value to your model; from the use case you've outlined, it seems that's what you need.


    The enum will allow you to use the following:

    @article = Article.find params[:id]
    
    @article.sport?    #-> true
    @article.politic?  #-> false
    @article.food?     #-> false
    
    @article.profile_type #-> "sport"
    

    You also get a set of class methods to identify the various objects you need from the db:

    @sports_articles = Article.sport #-> collection of "sport" articles
    

    To create an @article through a form, you'll need collection_select:

    #app/views/articles/new.html.erb
    <%= form_for @article do |f| %>
       <%= f.collection_select :profile_type, Article.profile_types, :first, :first %>
       <%= f.submit %>
    <% end %>
    

    Update

    Pagination occurs on data received from the db.

    Thus, if you wanted to "include" data in the pagination, you'd just have to make sure you're pulling it from the db. To do this, you'd need to include as many article_types as you want:

    #app/models/article.rb
    class Article < ActiveRecord::Base
       scope :by_type, (types) -> { where(article_type: Array.new(types)) }
    end
    

    This will allow you to use the following:

    @articles = Article.by_type(:sport, :politic).paginate(page: params [:page], per_page: 12)
    

    As per the docs:

    Conversation.where(status: [:active, :archived])