-1

I have a User model with a Role attribute, which I defined using enum.

enum role: {'Instructor': 0, 'Student': 1, 'Other': 2}

Now, I have another table Instructor with references from User table.

I have a Course table with references from Instructor.

I want only the Instructor to create a Course not any other role.

I am using Pundit for authorization. I am having problem in creating a new Course.

def create
  ...
  authorize @course

  @course.instructor = Instructor.where(user: current_user).first

The above query is rolling back but not saving the course.

Any suggestions would be of greater help.

app/policies/course_policy.rb

Rollback Error

app/policies/course_policy.rb

 class CoursePolicy < ApplicationPolicy
  attr_reader :user, :model

  def initialize (user, model)
    @user = user
    @course = model
  end

  def index?
    true
  end

  def show?
    true
  end

  def create?
    @user.Instructor?
  end

  def update?
    @user.Instructor_of? @course
  end

  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user = user
      @scope = scope
    end

    def resolve
      scope.all
    end
  end
end

Rollback Error Output:

ActiveRecord::AssociationTypeMismatch (Instructor(#70064238051700) expected, got #<ActiveRecord::Relation []> which is an instance of Instructor::ActiveRecord_Relation(#70064238083180)):

app/controllers/courses_controller.rb:31:in `create'
Started POST "/courses" for ::1 at 2019-04-13 06:02:33 +0700
Processing by CoursesController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"++6oBiY4MeOHZKyMwAJ8VqF9ACayve5e+cmMg7FqG4dbHVCfDpI3uqVK7g75+auf8OABUTEnXlm9jshWu/50EQ==", "course"=>{"name"=>"Ruby on Rails", "description"=>"jk.jlliyf", "start_date(1i)"=>"2019", "start_date(2i)"=>"4", "start_date(3i)"=>"12", "end_date(1i)"=>"2019", "end_date(2i)"=>"7", "end_date(3i)"=>"12"}, "commit"=>"Create Course"}
  User Load (0.7ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 2], ["LIMIT", 1]]
  ↳ /home/sagar/.rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb:98
  Instructor Load (1.8ms)  SELECT  "instructors".* FROM "instructors" WHERE "instructors"."user_id" = $1 ORDER BY "instructors"."id" ASC LIMIT $2  [["user_id", 2], ["LIMIT", 1]]
  ↳ app/controllers/courses_controller.rb:31
   (0.4ms)  BEGIN
  ↳ app/controllers/courses_controller.rb:34
   (0.4ms)  ROLLBACK
  ↳ app/controllers/courses_controller.rb:34
  Rendering courses/new.html.erb within layouts/application
  Rendered courses/_form.html.erb (44.0ms)
  Rendered courses/new.html.erb within layouts/application (46.7ms)
Completed 200 OK in 267ms (Views: 124.6ms | ActiveRecord: 32.4ms)

Course Model:

app/models/course.rb

    class Course < ApplicationRecord
        belongs_to :instructor
    end
Sai Sagar
  • 5
  • 3
  • Can you please share the rollback output? If it is an unauthorized error, please also share your policy file for courses (most likely `app/policies/course_policy.rb`). – Mark Merritt Apr 12 '19 at 22:28
  • I have added the image files of both Rollback Error and the course_policy.rb file. Please let me know if you need anything else. – Sai Sagar Apr 12 '19 at 22:46
  • Validation failed: Instructor must exist, this was the error that I got when I changed to @course.save! – Sai Sagar Apr 12 '19 at 23:22
  • I created a separate model for Instructor so that based on User roles whomever is an Instructor has the authority to create a Course. But I guess the User role is not being referenced here. – Sai Sagar Apr 13 '19 at 00:15
  • I understand all of that. But, where did you create the `Instructor` record for the `current_user`? Just because the `User.role` enum has a value of `Instructor` doesn't mean that you ever created an `Instructor` record in the instructors table. Out of curiosity, what are the attributes of an `Instructor` (i.e., the columns in the `instructors` table)? – jvillian Apr 13 '19 at 00:18
  • @jvillian Instructor table has only the user_id references from the User table no other attributes. – Sai Sagar Apr 13 '19 at 00:22
  • Do you ever expect an `Instructor` to have attributes other than the `user_id` reference? – jvillian Apr 13 '19 at 00:23
  • @jvillian nope. I don't want any other attributes for the Instructor – Sai Sagar Apr 13 '19 at 00:25

1 Answers1

0

Based on the comments thread, it seems that the use of an Instructor model is unnecessary.

Since, in your policy, you've already constrained the create action to a user with the Instructor role and you never expect Instructor to have attributes beyond user_id, then I think I would just do:

def create
  ...
  authorize @course
  @course.instructor = current_user
  ...
end

Now, I'm betting that Course has instructor_id, not user_id. But, that's ok. You'll just need to do:

class Course << ApplicationRecord 

  belongs_to :instructor, class_name: 'User' # or whatever your user class name is.

  ...

end

And now you'll be able to do:

@course.instructor

and get back the User that is the instructor for the course.

I'm also betting that Instructor has_many :courses which would normally allow you to do something like:

@instructor.courses

So, what you'll need to do is something like:

class User << ApplicationRecord 

  has_many :instructed_courses, class_name: 'Course', foreign_key: :instructor_id

  ...

end

And now you'll be able to do things like:

@user.instructed_courses
current_user.instructed_courses

I'm doing "instructed_courses" because, presumably, a User with the Student role will want to do something like:

@user.enrolled_courses

To pull that off, I think I would create:

class StudentCourse < Application 

    belongs_to :enrolled_course, class_name: 'Course'
    belongs_to :student, class_name: 'User' 

end

And then:

class User << ApplicationRecord 

  has_many :instructed_courses, class_name: 'Course', foreign_key: :instructor_id
  has_many :student_courses, foreign_key: :student_id
  has_many :enrolled_courses, through: :student_courses

  ...

end

And now you should be able to do:

@user.enrolled_courses
current_user.enrolled_courses

You'll probably also want to do something like:

class Course << ApplicationRecord 

  belongs_to :instructor, class_name: 'User' # or whatever your user class name is.
  has_many :student_courses, foreign_key: :enrolled_course_id
  has_many :students, through: :student_courses
  ...

end

So that you can do:

@course.students

Shazam!

jvillian
  • 19,953
  • 5
  • 31
  • 44
  • Thank you for the update. I updated as per your suggestions and now when I create a new course as 'Instructor' I'm getting an 'InvalidForeignKey in CoursesController' error. – Sai Sagar Apr 13 '19 at 01:14
  • Can you please edit your question to show the current status of your models? – jvillian Apr 13 '19 at 02:17
  • Thank you for your suggestion. I removed the Instructor model and used the User references directly in the Course model. Everything worked fine! – Sai Sagar Apr 14 '19 at 19:10
  • Great to hear. Please feel free to upvote/accept as you see fit and for future searchers. – jvillian Apr 14 '19 at 19:56