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?
As discussed in the above comments, I think that you should explicitly state the role
when creating your Relationship
s, 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.