Search code examples
ruby-on-railsruby-on-rails-4enumsrolespundit

Setting Default Role using Enum based on Project creation


I have a Projects and a Relationships model to establish a 'following' relationship between a user and a project. I established three roles for the 'Relationship' using enum in the relationship model...they are admin, collaborator, and visitor. I need, however, to set the default role based upon the way in which the Relationship between user and project is established. The following simple scenarios are needed:

(a) A user who creates a project is automatically following the project...the Relationship role should be set to 'Admin' upon creation of the project

(b) if a visitor to the site simply navigates to a project profile page, they can click the 'Follow' button to establish a following relationship...however this should set the Relationship role to 'Visitor'

Relationship Model:

class Relationship < ActiveRecord::Base
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "Project"
  validates :follower_id, presence: true
  validates :followed_id, presence: true

  enum role: [:admin, :collaborator, :visitor]
  after_initialize :set_default_role, :if => :new_record?

  def set_default_role
    self.role ||= :admin
  end
end

Relationships Controller:

class RelationshipsController < ApplicationController
  before_filter :authenticate_user!

  def create
    @project = Project.find(params[:relationship][:followed_id])
    current_user.follow!(@project)
    # relationship.visitor! View railsapps documentation for devise pundit#roles
    redirect_to @project
  end

  def destroy
    @project = Project.find(params[:id]).followed
    current_user.unfollow!(@project)
    redirect_to @project
  end

  private

  def relationship_params
    params.require(:relationship).permit(:followed_id, :follower_id)
  end

Projects Controller

class ProjectsController < ApplicationController
  before_filter :authenticate_user!, only: [:create, :new, :edit, :update, :delete, :followers]

def create
    @project = current_user.own_projects.build(project_params)
    if @project.save
      if params[:project][:projectimage].present?
        render :crop
      else
        flash[:success] = "You've successfully created a project..."
        redirect_to @project
      end
    else
      render 'new'
    end
  end

  def update
    @project = Project.find(params[:id])
    if @project.update_attributes(project_params)
      if params[:project][:projectimage].present?
        render :crop
      else
        flash[:success] = "Project Created"
        redirect_to @project
        @project.followers << current_user #this establishes the following relationship as soon as a new project is created between user/project
      end
    else
      render 'edit'
    end
  end

end

User Model:

class User < ActiveRecord::Base
  has_many :own_projects, :class_name=>'Project'

  has_many :projects
  has_many :relationships, foreign_key: "follower_id", dependent: :destroy

  has_many :followed_projects, through: :relationships, source: :followed
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  def following?(some_project)
   relationships.find_by_followed_id(some_project.id)
  end

  def follow!(some_project)
   self.relationships.create!(followed_id: some_project.id)
   relationship.visitor!
  end

end

What would I need to change in my code in order to set the default role to either 'admin' or 'visitor' based on the two scenarios mentioned above?


Solution

  • As discussed in the above comments, I think that you should explicitly state the role when creating your Relationships, because the roles differ on conditions that are not inherent to the Relationship class.

    Now, as the Relationship is the middleman in the has many :through association between your User and Project models, you cannot simply use the standard way of connecting users to projects using << but have to construct the middleman Relationship explicitly including your custom params (such as the role).

    Yours solution should be analogical to the one posted here:

    class Project < ActiveRecord::Base
      has_many :relationships, foreign_key: :followed_id, dependent: :destroy
      has_many :users, through: :relationships
    end
    
    class User < ActiveRecord::Base
      has_many :relationships, foreign_key: :follower_id, dependent: :destroy
      has_many :projects, through: :relationships
    
      # this will create the relationship association with the 'visitor' role
      def follow_project!(some_project)
        self.relationships.create!(followed_id: some_project.id, role: :visitor)
        # I think you can even omit the ids here and work with objects:
        # self.relationships.create!(followed: some_project, role: :visitor)
      end
    
      # this will create the relationship association with the 'admin' role
      def administrate_project!(some_project)
        self.relationships.create!(followed: some_project, role: :admin)
      end
    
      # this will create the relationship association with the 'collaborator' role
      def collaborate_on_project!(some_project)
        self.relationships.create!(followed: some_project, role: :collaborator)
      end
    end
    
    class Relationship < ActiveRecord::Base
      belongs_to :follower, class_name: "User"
      belongs_to :followed, class_name: "Project"
    
      enum role: [:admin, :collaborator, :visitor]
    end
    

    The follow_, administrate_ and collaborate_on_project! methods work the same way but each sets different role in the relationship. Then, you can simply call the appropriate one from the controller, for example to set the 'admin' relationship when creating a project:

    class ProjectsController < ApplicationController
    
      def create        
        # ideally you should wrap multiple separate saves in a transaction so that
        # either they all succeed or all fail
        Project.transaction do
          # this creates a barebone project, without any association
          @project = Project.create!(project_params)
          # this associates the project to the current user with admin role
          current_user.administrate_project!(@project)
          # ...                 
        end
      end
    
    end
    

    Please be also sure to read rails guides on the :through associations carefully.