Setting custom permissions within a "dashboard" area in rails - ruby-on-rails

I'm putting together a side project for a teacher/student type of website where the student will share a dashboard with the teacher. The teacher and student can both upload files and leave comments in the dashboard.
The part that I'm stuck on is the permission. For student, I've set up the index controller to this method
def index
#Homework = Homework.where(:user_id = current_user)
end
With this, I'm able to have the student only see the work that they have, but I'm confused on how to get the teacher to see each individual student's work?
Suggestions? Thanks

Here's a simple solution if you only ever need to support a single class in your application:
def index
if current_user.teacher?
#homeworks = Homework.all
else
#homeworks = Homework.where(user_id: current_user)
end
end
Otherwise, your Homework schema does not seem to be correctly designed. See your query below:
Homework.where(:user_id = <student_id>)
This works to retrieve a student's homeworks, but it does not work to retrieve a teacher's students' homeworks. You may need a class_id for a teacher to see each individual student's work:
Homework.where(:class_id = <class_id>, :user_id = <student_id>)
A Class has_many Teachers and has_many Students. This design will allow you to support multiple classes.
Some more guiding questions:
Is teacher/student both kept in the same User model?
How do you differentiate between teacher/student in your current User model?
Is there a "user_type" column somewhere in User?
What happens if the current_user is of the "teacher" user_type?
For complex user permissions, use CanCanCan: https://github.com/CanCanCommunity/cancancan

Don't use uppercase instance variables. ex: #Homework should be #homework
Check out the gem CanCan. Install it (follow the instructions, you should have to put something in application controller), Then, put in your ability file:
class Ability
include CanCan::Ability
def initialize(user)
can :manage, Homework, user_id: user.id
end
end
Then at the top of your StudentController put
load_and_authorize_resource
And the index action should look like:
#homework = #student.homework
Now, you didn't post your whole controller so this is a much as I can help.
I believe you may have a bigger underlying issue. You have students and teachers has_many homework i read in your comment. Then in your example you use user_id. You are likely overriding your students and teacher ownership of homework. You would need a has_and_belongs_to_many relationship OR you would need a student_id and teacher_id columns on the homework table
Cancan automatically generate a number of instance variables which can make it feel like magic. Watch the free railscasts on cancan the same guy who made the video wrote the CanCan library.

Related

In rails how to allow creation of new class and models at run time

I am facing a design problem with respect to a rails app I am developing for my company product right now. My app allows creation of two classes which are subclasses of a parent class.
class Coupon
include Commonelements
end
class ServiceCenterCoupon < Coupon
end
class DealershipCoupon < Coupon
end
When you go to the view and you want to create a new coupon, you select either of the two and a new coupon is created depending upon the params[:coupon_type]
In the controller:
if params[:coupon_type] == 'dealershipcoupon'
#coupon = DealershipCoupon.new(coupon_params)
if #coupon.save!
redirect_to #coupon
else
render :new
end
elsif params[:coupon_type] == 'servicecentercoupon'
#coupon = ServiceCenterCoupon.new(coupon_params)
if #coupon.save!
redirect_to #coupon
else
render :new
end
end
I wanna give admin users the ability to create new coupon types at the run time as well. Say, someone wants to create Repairshopcoupons class. What changes do I need to bring to the views for example add a new form or what params I need to add to the existing form to be able to create new sub classes of Coupons at run time?
I do understand that using
repairshopcoupon = Class.new()
can work. For example anonymous function like this code in the controller can work:
Repairshopcoupon = Class.new(Coupon) do
include ActiveModel::Validations
validates_presence_of :title
def self.name
"Oil Change"
end
end
#newrepairshopcoupon = Repairshopcoupon.new
#newrepairshopcoupon.save
But I am not sure.
My first questions is: What would be the proper flow if I want users to create new classes from the view. What should controller handle and how will it save?
My second question is: There are few customers who belong to both dealerships and service centers group. Each group has authority over what coupon type they can manage. I want these users who belong to multiple groups to be able to see respective coupon inventory as well as which users downloaded those. I feel the need of changing my data model so that all coupon inventory and download lists belong to exactly one authorized group but I don't have a concrete idea of what would be the best way.
My third question is: What would be the best approach to change my view/UX for creating and managing coupons so that the users of multiple groups would be able to switch between each inventory ? What would be the professional industry standard for UX deign in this case ?
Would really appreciate your help.
Letting the users of an application generate code at runtime is just a really bad idea as the amount of potential bugs and vulnerabilities is mind boggling as your basically allowing untested code to be injected into the app at runtime.
It will also wreck havoc with any class based caching in the application.
It also won't work with cloud platforms like Heroku that use an ephemeral file system which is created from the last code commit.
First off you probably don't actually need different classes for each "type" of coupon. You need to consider if the logic for each class is substantially different.
You can probably get by just by creating a polymorphic association to the issuer (the dealship or service center).
class Coupon < Coupon
belongs_to :issuer, polymorphic: true
end
If you want to avoid polymorphism than just set it up as a standard STI setup:
class Coupon
include Commonelements
end
class ServiceCenterCoupon < Coupon
self.table_name = 'coupons'
belongs_to :service_center
end
class DealershipCoupon < Coupon
self.table_name = 'coupons'
belongs_to :dealership
end

How should I structure two types of Roles in a Rails application?

I am working on a Ruby on Rails application that has two kinds of "Roles".
One will be where a user has multiple roles, such as "Admin", "Team Lead", etc. these will be defined in the seeds file and not generated by the user.
The other will be generated by a User and assigned to other users, such as "Chef", "Waiter", etc.
They are both similar in that they only have a name column. The former will be used with an authorization tool such as CanCanCan. I currently plan to allow users and roles to have many of the other using a has_many :through relationship.
class Role
has_many :user_roles
has_many :users, through: :user_roles
end
class User
has_many :user_roles
has_many :roles, through: :user_roles
end
class UserRole
belongs_to :user
belongs_to :role
end
My questions are:
Should the latter ("Chef", "Waiter", etc) be put in the same table? Separate tables?
Should I use some kind of inheritance?
What's a good practice for this situation?
I plan to use the term "Role" in the user interface for the latter, showing what "Roles" a user has. The former I guess is more about what privileges they have within the application.
If you go away from the technical side of roles and authentication, and try to describe the "things" from a more business oriented approach you make the distinction clearer for yourself.
What I understand is: You have a definition for a user of your application that is used to describe what authorization this user has, e.g. an "admin" has more rights than an "editor" or "community manager".
You also want these users of your application to be able to create names that are associated with (other?) users. Theses names have nothing to do with authorization, as I understood.
Maybe these names are more like tags, that people can assign?
I would keep both separated, as it shouldn't be able for a user to create a role, or modify existing roles, that could grant them access to admin features.
If you want to look at a tagging gem, I could recommend this one: https://github.com/mbleigh/acts-as-taggable-on I used this for several years and while it has its drawbacks, it's reliable.
I'd suggest having a look at rolify before rolling your own solution, as it will have solved some things that you'll have to reimplement / discover later (e.g. role queries, avoiding N+1 queries etc). It also integrates well with can?, and should work well for the authorisation part of your question.
Whilst it's not impossible to allow users to create new roles (by throwing an input form on top of Role.create), this starts to get messy, as you need to track which ones are for authorisation and which ones informative (and user created).
Since the two groups of things are for different purposes, I wholeheartedly agree with this other answer that it's cleaner to separate the user-generated entities, and look to implement them as tags. You may display all the "roles" together in certain views, but that doesn't mean that it makes sense to store them within a single table.
Side-note: if you do end up rolling your own solution, consider using HABTM here. The join table will still be created, but you won't have to manage the join table model. E.g.
has_and_belongs_to_many :users, join_table: :users_roles
Since you only have a limited number of roles, you could use a bitmask and store directly on the user model as a simple integer.
See this Railscasts for more information. That would be the most efficient, database and association wise, way to do this although perhaps not the simplest to understand. The only real restriction is that you can't alter the array of values you check against, only append to it.
Good luck!
I would create one more model, Permission, where you can create a list of all the permissions you want to manage under any given role.
Then have a many to many between Permissions and Roles.
From a UserRole instance then you will be able to list the permissions, and in the future you could add additional permissions to roles buy just running inserts in a couple of tables
Roles: Onwer, Chef, Cook, Waiter
Permission: can_cook, can_buy_wine, can_manage, can_charge_custome
Owner: can_manage, can_buy_wine, can_charge_customer
Chef: can_cook, can_manage
Waiter: can_charge_customer
Is would be a good start and you can evolve the role functionality to whatever your needs are without an external dependency.
Also, You can go just using Users table and adding role column as integer and give them a role code in default 0 integer.
#app/helpers/roles_helper.rb
module RolesHelper
#roles = {
'Default' => 0,
'Waiter' => 10,
'Chef' => 20,
'Superadmin' => 30
}
class << self
def list_roles
#roles.map{|k,v| [k,v] }
end
def code(str)
return #roles[str]
end
def value(id)
return #roles.key(id)
end
end
def require_default_users
unless current_user && current_user.role >= RolesHelper.code('Waiter')
redirect_to root_url(host: request.domain)
end
end
def require_superadmin_users
unless current_user && current_user.role >= RolesHelper.code('Superadmin')
redirect_to courses_path
end
end
end
access in controllers
sample:
class Admin::AdminController < ApplicationController
include RolesHelper
def sample_action_method
require_default_users #if non admin user redirect ...
puts "All Roles: #{RolesHelper.list_roles}"
puts "Value: #{RolesHelper.value(30)}"
puts "Code: #{RolesHelper.code('Superuser')}"
end
end

Figuring out relationships for a Document model that can be editted by multiple users

I'm trying to figure out how to set up the relationship/association for a Document model for my rails project. I have a User model and Users can friend each another (friendship model).
Now I want users to be able to invite (give access to) CERTAIN friends to modify and edit these documents similar to Google Docs.
This is my current approach to this problem:
Create a new relationship model called "group", essentially a subset of friends. Document belongs to a user and document belongs to a group. Users can then invite their friends into these group relationships and documents can be accessed/modified through these groups.
Therefore, User has many groups and group belongs to many users.
My question:
Is there a better approach to this problem?
You might consider a "Membership" join table. So:
Group >- Membership -< User
...since you requirement is some users will have abilities others wont (editing). There are all sorts of options from there, for example STI which would give you:
class Membership < ActiveRecord::Base
...general membership functions
end
class Editor < Membership
...editor specific code...
end
class Reviewer < Membership
...reviewer specific code...
end
And a controller class something like
class MembershipController
def create invitation
if invitation.for_editor?
# assuming invitation has one group, and group_memberships method that returns group.memberships
invitation.group_memberships.create! user: current_user
else
invitation.group_memberships.create! user: current_user
end
end
end
As an example, depending on your opinion of STI.
https://github.com/scambra/devise_invitable
might have more ideas.

Scoping the find method and nested assocations

I have run into an issue regarding how to identify which user owns particular resources so that I can prevent inappropriate access to them.
I have the following nested associations:
User has many
Profiles has one
SamplePage has many
Subjects
Once they become nested this deep it's become very unwieldy to access the user object via the associations and then compare that to current user e.g.:
#subject.sample_page.profile.user == current_user
I've read that a better way of restricting access is to scope the retrieval of a model to the current user. e.g:
#profile = current_user.profiles.find(params[:id])
That makes a lot of sense to me but how would I do a similar thing to get a Subject back? I've not found any examples that used nested associations.
not sure to understand what you want to do, and not sure i can help you since i'm a huge noob, but i would try something like this (assumed that current_user returns a User):
class Profile < ActiveRecord::Base
has_many :subjects, :through => :sample_pages
end
and in your controller:
#subject = current_user.profiles.subjects.find(params[:id])
more handy this way:
class User < ActiveRecord::Base
def subjects
profiles.subjects
end
end
#subject = current_user.subjects.find(params[:id])
all of this should be lazy loaded, as explained here : http://asciicasts.com/episodes/202-active-record-queries-in-rails-3
however, if it is a frequent operation, you may want to redesign things a bit, as long chains of associations mean heavy queries (lots of joins).

RoR: Different user roles for each new created record?

I want to make a record management system. The system will have 4 different user roles: Admin, Viewer, Editor and Reviewer.
While the first two are easy to implement using gems such as cancan and declarative authorization, the other two are not so simple.
Basically each new record is created by an Admin (only an Admin can create new records), and should have its own separate Editor and Reviewer roles. That is, a user can be assigned many different roles on different records but not others, so a user might be assigned Editor roles for Record A and C but not B etc.
Editor: can make changes to the record, and will have access to specific methods in the controller such as edit etc.
Reviewer: will be able to review (view the changes) made to the record and either approve it or submit comments and reject.
Viewer: Can only view the most recent approved version of each record.
Are there any ways of handling such record-specific user roles?
This can be accomplished without too much effort with the cancan gem and a block condition. A block condition checks for authorization against an instance. Assuming your Record class had an editors method that returns an array of authorized editors the cancan ability for updating a Record might look something like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
...
can :update, Record do |record|
record.editors.include?(user)
end
...
end
end
See "Block Conditions" on the CanCan wiki:
https://github.com/ryanb/cancan/wiki/Defining-Abilities
Update
Storing which users have which access to which records could be done many ways depending on your specific needs. One way might be to create a model like this to store role assignments:
class UserRecordRoles < ActiveRecord::Base
# Has three fields: role, user_id, record_id
attr_accessible :role, :user_id, :record_id
belongs_to :user_id
belongs_to :record_id
end
Now create a has_many association in the User and Record models so that all role assignments can be easily queried. An editors method might look like this:
class Record < ActiveRecord::Base
...
has_many :user_record_roles
def editors
# This is rather messy and requires lot's of DB calls...
user_record_roles.where(:role => 'editor').collect {|a| a.user}
# This would be a single DB call but I'm not sure this would work. Maybe someone else can chime in? Would look cleaner with a scope probably.
User.joins(:user_record_roles).where('user_record_roles.role = ?' => 'editor')
end
...
end
Of course there are many many ways to do this and it varies wildly depending on your needs. The idea is that CanCan can talk to your model when determining authorization which means any logic you can dream up can be represented. Hope this helps!

Resources