I am building a Rails 3 app and I am working on a design for a sophisticated user permissions tool where a Company user determines specific roles for each PM.
Imagine this scenario - the Company wants to establish specific roles over three types of data.
Project table
Client table
Corporate Account (i.e. Company table)
I am thinking of adding a Role polymorphic table with these fields:
user_id (the user this role applies to, unless all_users == true)
item type (such as "Project", "Client", or "Company", unless all_items == true)
item id (as above)
role (such as "read", "edit", "destroy", or even "custom")
all_users (boolean: does this item's role apply to all users)
all_items (boolean: does this user's role apply to all items)
company_id (the company who 'owns' this role)
I feel that CanCan would be a nice lean way of accomplishing this, but here's my question. 1. Is the above table a good way to do this? 2. Could CanCan tie in with this to create an effective solution?
Your table seems like a sensible way to implement a very complicated authorization scheme, which seems to be what you're after.
As far as CanCan goes, yes it will tie in perfectly with this. All CanCan does is provide you with the ability to define authorization on actions, models, etc. according to certain criteria. These criteria could be anything (e.g. day of the month, etc.) but are usually tied to roles. So all you'll have to do is specify the authorization rights according to the information in your Role table and the specific model instance that is going to be evaluated.
Related
I want to split Devise user into different types? Like, for example: user go to the registration page and sign up, but based on whether they're a teacher or a student they will registered and login to see different navbar? How would I do that using a checkbox?
It is excellently explained in their Wiki.
In my opinion you should consider option number 1. and 3:
Separate model for student and teacher, if they have different attributes
One model for both with additional column role. It will be appropriate if models have the same attributes.
Then on your views just check what is the role / type of user and present proper content.
I'm pretty new with Ruby on Rails. I have done tutorials and an online class. I know enough to have my way around the development environment, to create tables, controllers, views etc. However, the classes and tutorials don't really show you in depth things you may have to work with in a real software development situation.
I'm in the process of converting a piece of software to the Ruby on Rails platform. Here is the situation. I have a Salesman controller that has salesman information. Then i have a client controller that handles client information. A salesman has many clients and clients have one salesman. I already coded the REST architecture for the Salesman object. Now, i have to create the client. But before i can get to the new action in the controller, i have to select a salesman first. This select salesman page is going to be needed on many other occasions as i will have other controllers such as commissions , purchase orders etc that have to be tied to a salesman first. How do i proceed to code such a "temporary select" page before actually going to the new action in the controller. Can we define custom actions after which i would just redirect_to the new action passing the salesman id as a param?
You need to design the app similar to a multi-tenant app. In your case, Sales Person is like a tenant.
Here is the conceptual design:
add current_id class attribute to SalesMan
cattr_accessor :current_id, instance_writer: false, instance_reader: false
In all your models that belong to a sales person, define the default scope:
default_scope { where(sales_man_id: SalesMan.current_id) }
Ensure that current sales man is set before executing any actions that depend on sales man being pre-selected. There are multiple approaches for achieving this and it also depends what kind of user experience you want to provide. Couple of options:
Only show the navs such as New Client only if a salesman is pre-selected. Provide a menu option to select a salesman. In that action set SalesMan.current_id.
If the salesman is going to be different across many subsequent transactions, you may want to implement a salesman selection (step 1 of workflow) through a controller before action. For example, when user selects New Client nav option, the before action can redirect to sales man selection and set a flag. SalesMan controllers selected action should redirect to New Client action and ensure that proper flags (parameters) are set to avoid cycles. Define a new controller SalesManSelectionController and subclass all the controllers whose models depend on salesman.
I am building an Application where alongside general public pages there will be a login page and 3 Dashboard Versions for each 3 role levels.
Table
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('firstname');
$table->string('surname');
$table->string('username');
$table->string('email')->unique();
$table->string('password', 60);
$table->string('role');
$table->rememberToken();
$table->datetime('created_at');
$table->datetime('updated_at');
});
With that i have another table Roles:
Schema::create('roles', function (Blueprint $table) {
$table->increments('id');
$table->string('role_type');
});
Relationship
Users::hasOne('roles');
Roles::hasMany('users');
Essentially on login function i want to check the users role_type and then redirect them based on the role.
I would like to know if this table structure and idea is correctly thought out.
Additionally when a user accesses a specific route i will have the route either display login if no user or 404/503 if user is logged in But does not match a specific role_type.
Thanks guys
On your users table instead of the role column you have, it should be these two lines:
$table->integer('role_id')->unsigned()->nullable();
$table->foreign('role_id')->references('id')->on('roles')->onDelete('set null');
The downside to this is it allows a null value for the 'role_id' column on users. The reason it needs this is to deal with what would happen if a Role that is assigned to a user is deleted.
But a better solution would be to use a many-to-many relationship, I personally would suggest using Entrust, since it has what you want built in, plus more options that you might want later.
I've been playing around with writing a RoR 4.2 app that will allow the following behaviors:
Admin users to create a list of conference courses that will include topic, description, etc
Conference attendees can register with the website and then select the conference courses they'd like to attend.
Conference attendees should not be able to create conference courses, only to select from a list of courses listed in the database and to select them to attend. The must be a way to associate many users with the same course.
When a user selects a course, the seating capacity for that course should be decremented (the details of this aspect of the solution are a secondary requirement at the time)
For the sake of discussion, let's say I am using Devise with user roles all figured out and configured. So I have one user.rb model/table.
I also created a model/table called course.rb.
I created a join model/table called course_selection.rb.
I configured these tables using the has_many through associations with the join table belonging to each like so:
class User < ActiveRecord::Base
has_many :course_selections
has_many :courses, :through => :course_selections
end
class Course < ActiveRecord::Base
has_many :course_selections
has_many :users, :through => :course_selections
end
class CourseSelection < ActiveRecord::Base
belongs_to :user
belongs_to :course
end
I have this set up and am able to create new users, but the users with non-admin roles are able to create courses (but I haven't set up any kind of role based exclusions for these users to prevent them from creating courses). When I call a RESTful route such as
http://localhost:3000/users/3/courses
I am expecting to see only a list of courses associated with this particular user. Instead I am seeing ALL the courses listed in the database.
My questions are:
Can the model associations strategy I've selected be used to accomplish the behavioral goals I've specified?
How would the controllers be set up so that a user's course selection is established, but that they are not using the new/create methods to actually create the course?
How would the forms be set up (I'm using simple_form) to allow the user to select from the list of existing courses?
Thanks for your help. I haven't found any tutorials that seem to address this challenge.
Can the model associations strategy I've selected be used to accomplish the behavioral goals I've specified?
Yes, those models are a fine start.
How would the controllers be set up so that a user's course selection is established, but that they are not using the new/create methods to actually create the course?
The typical Rails REST-like way is to have a controller CourseSelectionsController that manages the association.
Suppose you have a view page where a user can select a course. The view would submit the data to the CourseSelectionsController, with params user_id and course_id. The controller then creates the CourseSelection model, which associates the user and course.
The CourseSelectionsController would have typical actions for "create", "read", "delete". If the course selection also has other data, such as a price, or time slot, etc. then there can be an action for "update".
By the way, in your kind of application, the middle model is often called Enrollment instead of CourseSelection, and the REST controller is called EnrollmentsController.
How would the forms be set up (I'm using simple_form) to allow the user to select from the list of existing courses?
Typically in app/views/course_selections. The form is based on CourseSelections (not Users, and not Courses). The form shows a list of courses. When the user submits the form, it does a PUT (or POST) to CourseSelectionsController.
If you want the form to be able to handle multiple courses, one way is to use AJAX as each course is selected or unselected. Another way is to use multiple select fields, and make a controller method that can iterate on all the selected/unselected courses.
I read the two posts here and here, but still have trouble figuring out how everything is tied together. Basically I have 2 types of users, Trainers and Clients. They share some common attributes (email, phone, first name, last name, etc), but they will also have some custom attributes.
Assuming STI is the way to go, I would have 3 models:
User (devise)
Trainer (inherits from User)
Client (inherits from User)
When the user signs up, they should be able to use the same form and just select from a drop down if they're a trainer or client. Once the form is submitted how do I go about specifying the type of user that has just been created? Do I need logic in the controller to check the user type, and then run Trainer.create() or Client.create()?