Search code examples
ruby-on-railsdeviserelationshiphas-and-belongs-to-manycancancan

How to share a Rails model with other Devise users in read-only mode?


In a Rails 5 application, I have a shortlist model in a HABTM relationship with a user model, with the users controlled by Devise. This is all working as expected, where each User can see their own Shortlists (only).

class Shortlist < ApplicationRecord
  has_and_belongs_to_many :users
 ...

class User < ApplicationRecord
  devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable
  has_and_belongs_to_many :shortlists
  ...

class ShortlistsController < ApplicationController
  def index
    @shortlists = current_user.shortlists
    ...

Now I wish to allow users to "share" shortlists with other users, who will have read-only access to the shared shortlists.

I could add the second user to the shortlist (@shortlist.users << User.second) but this would give them full read-write access.

The cancancan gem looks promising, but I'm not sure how to configure the abilities, to allow User1 full control over their shortlists, and to give User2 read-only access to shortlists that User1 shares with them.

How can I restrict the second user to having read-only access?


Solution

  • Your solution in adding a owner_id column to the shortlists table is actually a good one as it lets you efficiently eager load the association and is a lot less cumbersome to deal with.

    If you wanted to keep track of the user which creates the record too through a join table thats possible through a roles system but it would be a lot more clunky. The Rolify gem is one such as example of a generic roles system.

    One thing you should do is use has_many through: instead of the near useless has_and_belongs_to_many:

    class Shortlist < ApplicationRecord
      belongs_to :owner, 
        class_name: 'User'
      has_many :shares
    end
    
    # rails g model user:belongs_to shortlist:belongs_to
    class Share < ApplicationRecord
      belongs_to :user
      belongs_to :shortlist
    end
    
    class User < ApplicationRecord
      has_many :shortlists, 
        foreign_key: :owner_id
      has_many :shares
      has_many :lists_shared_with_me,
        through: :shares,
        source: :shortlists 
    end
    

    This lets you add additional columns to the table like for example flags that will let you grant other users the rights to edit the shortlist or an automatic expiry date. You could also generate unique URLs for each share to keep track of which "share" is being used. None of that is possible with HABTM.