Search code examples
ruby-on-railsrolifyruby-on-rails-7

Rollify Gem User has_many polls/roles and Poll has many answerers and only one admin


Trying to creating a poll app with a User has_many polls/roles but the Poll has many answerers and only one admin.

user.rb

class User < ApplicationRecord
  rolify
  has_many :polls, dependent: :destroy, through: :roles, source: :resource, source_type: :Poll
end

poll.rb

class Poll < ApplicationRecord
  resourcify

  # has_many :users, through: :roles, class_name: 'User', source: :users
  has_many :answerers, -> { where(:roles => {name: ::answerers}) }, through: :roles, class_name: 'User', source: :users
  belongs_to :admin, -> { where(:roles => {name: :admin}) }, through: :roles, class_name: 'User', source: :users
end

keep running into the following error:

Unknown key: :through. Valid keys are: :class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading, :autosave, :required, :touch, :polymorphic, :counter_cache, :optional, :default

The error is caused by this line in poll.rb:

belongs_to :admin, -> { where(:roles => {name: :admin}) }, through: :roles, class_name: 'User', source: :users

Solution

  • You have run into a classic missconception caused by confusing semantics of belongs_to vs has_one.

    A belongs_to places the foreign key column on this models table. When you use belongs_to :admin Rails assumes that the you have polls.admin_id column that points to admins.id.

    belongs_to assocations are never indirect and thus do not a have a through: option. has_one does.

    If you want to guarentee that a Poll can only ever have one admin you want to not use Rolify for this specific case and instead use:

    class Poll < ApplicationRecord
      resourcify
      # ...
      belongs_to :admin, class_name: 'User'
    end
    

    That's fully OK. While Rolify provides a convenient way to add roles not every association in your application should be shoehorned into it. A direct link is way more efficient then going through two join tables and provides a guarentee that there is only one value.

    While you might be thinking "Well what if I just use has_one instead?". has_one gives no guarentee that that a Poll only has admin - it just adds LIMIT 1 to the query.

    Rolify uses the users_roles HABTM join table to join users and roles and there is no way you can for example add a uniqueness constraint to that table without it impacting the entire system.