App structure for roles-based RESTful resources - ruby-on-rails

Is there a consensus best approach to implementing user roles when using RESTful resource routes?
Say I have the following resources:
User has_many Tickets
Event has_many Tickets
Ticket belongs_to Person, Event
And then further say I have two types of Users: customers and agents. Both will log into the system, but with different resource access and functionality based on their roles. For example:
Customers can access:
Event index, show
Ticket index (scoped by user), show, buy/create, return/delete
Person create, show, update
Agents can access:
Event index, show, create, update, delete
Ticket index, show, sell/create, update, refund/delete
Person index, show, create, update, delete
Which of the 4 general approaches below will be cleaner and more flexible?
Separate controllers within role folders and resources in namespaces, eg:
namespace "agent" do
resources :events, :tickets, :people
end
namespace "customer" do
resources :events, :tickets, :people
end
Separate controllers by role, eg:
AgentController
def sell_ticket, etc
CustomerController
def buy_ticket, etc
Shared controllers with separate actions where needed, eg:
TicketController
before_filter :customer_access, :only => :buy
before_filter :agent_access, :except => :buy
def buy #accessed by customer to create ticket
def sell #accessed by agent to create ticket
Shared actions with conditional statements, eg:
TicketController
def create
if #role == :customer
#buy ticket
elsif #role == :customer
#sell ticket
end
end

I would suggest using a combination of the last two proposed implementations. They adhere to RESTful representation, they put authorization at the appropriate level (controllers), and it is a scalable implementation.
REST is, essentially, about accessing nouns with verbs. So you want Agents and Customers to perform actions (verbs) in relation to Tickets, Users, and Events (nouns). In order to accurately represent these nouns you should have a controller for each. Customers can then identify the resource they are looking for by the URL, http://example.com/events/22. From here you can use Rails' routing to represent context for various resources, ie http://example.com/events/22/tickets by doing something like:
resource :events do
resource :tickets
end
By adhering to a RESTful architecture, you are buying into the end to end principle. The paradigm for representing objects needs to be only responsible for that. It shouldn't try to authenticate. That isn't its job. Authorization should happen in the controllers. I would highly recommend looking into gems like CanCan or Declarative Authorization that set all of this up for you.
Finally, this model scalable. By keeping authorization separate from the representation of your resources you only have to use it if you need it. This keeps your application light, flexible, and simple.

While they both deal with creating Tickets, the Agent / Ticket selling vs. the Customer / Ticket purchasing seem different enough to me that they should be separated. Eventually they could further diverge since they are being used so differently from the beginning.
It is possible to have shared controller functionality either with modules or by inheriting from a common parent controller:
module TicketsControllersCommom
# common helper methods
end
class TicketsController < ApplicationController
include TicketsControllersCommom
# actions
end
class AgentTicketsController < ApplicationController
include TicketsControllersCommom
# actions
end
I might treat the agent parts as a sort of admin section, with the customer parts being the default:
/events/xx/tickets # collection
/events/xx/tickets/xx # member
# etc.
/events/xx/agent/tickets # collection
/events/xx/agent/tickets/xx # member
# etc.
Or, if you have a lot of admin-type stuff, like if the agents manage the events as well, you can namespace a whole section:
/agent/events/xx/tickets
/agent/events/xx/edit
# etc.

If you use the same model for customer and agent tickets, there should be no major difference between how they are handled in controller. So, create action will be always like this:
#ticket = Ticket.new(params[:ticket])
if #ticket.save
redirect_to #ticket
else
render :action => "new"
end
But your views can be simply customized:
<% if customer? %>
Customer area.
<% else %>
Agent area.
<% end %>

Related

Finding the resource one level above in path (nested resources) - Rails

I am building an app which has a resource setup like the following:
User
Team
Invite
Project
Invite
users have one team. teams have many projects. users can be invited to join at either the teams level (and have access to any projects owned by the teams) or invited at the project level (only giving the invitee access to the single project).
I am trying to set up Invites to dynamically find it's parent resource (i.e: Team or Project). As I understand it, the best way would be to look at the path. Currently the path looks something like:
/teams/:id/invites/
/teams/:id/projects/:id/invites
Is it possible to look one "nesting level" back from the current resource in the path to find the parent resource in a controller action (e.g: invites#new)?
Thanks!
Clarification
I want to be able to use the same invites code for both the teams and projects resources. When the invites#new action is called, it checks the path to see what resource has called it. if the path is /teams/:id/invites/, it will return team and I can then find by :id, if the path is /teams/:id/projects/:id/invites, it will return project and again, I can then find by :id.
Is this possible?
You should not be nesting more than one level deep in the first place.
Rule of thumb: resources should never be nested more than 1 level
deep. A collection may need to be scoped by its parent, but a specific
member can always be accessed directly by an id, and shouldn’t need
scoping (unless the id is not unique, for some reason).
- Jamis Buck
Your paths should look like:
/teams/:team_id/invites
/projects/:project_id/invites
This provides all the context needed! Adding more nesting just adds bloat and over-complication and makes a poor API.
To create a reusable controller for a nested polymorphic resource you can use a routing concern:
concerns :inviteable do
resources :invites, shallow: true
end
resources :teams, concerns: :inviteable
resources :projects, concerns: :inviteable
You can then set up a controller for invites which checks what parent param is present:
class InvitesController < ApplicationController
before_action :set_parent, only: [:new, :create, :index]
# GET /teams/:team_id/invites/new
# GET /projects/:team_id/invites/new
def new
#invite = #parent.invites.new
end
# GET /teams/:team_id/invites
# GET /projects/:team_id/invites
def index
#invites = #parent.invites
end
# POST /teams/:team_id/invites
# POST /projects/:team_id/invites
def create
#invite = #parent.invites.new(invite_params)
# ...
end
# ...
private
def parent_class
if params[:team_id]
Team
elsif params[:project_id]
Project
end
end
def parent_param
params[ parent_class.model_name.singular_route_key + "_id" ]
end
def set_parent
#parent = parent_class.find(parent_param)
end
end
When the route is:
/teams/:team_id/invites/new //note that it should be team_id, not :id,
or
/teams/:team_id/projects/:project_id/invites/new
you could always to check the nesting by these params. If
params[:project_id].present?
then you are under /teams/:team_id/projects/:project_id/invites route, the invitable_type should be Project. Otherwise, it should be /teams/:team_id/invites/, and the invitable_type should be Team.

Managing Multiple Hierarchical Rails Models Interlinked to Multiple Levels

I am building a To Do application but with multiple levels of tasks and subtasks. I have been following Ruby on Rails Tutorial by Michael Hartl and almost everything I have learnt till now is from there.
My app has multiple hierarchy models. What I mean is that at the top is the User, a user can have multiple goals, each goal can have multiple tasks, and each task can have multiple subtasks.
Just to give you a more clear idea, I'll list down the hierarchy
User
Goals (multiple goals per user)
Tasks (multiple tasks per goal)
Subtasks (multiple subtasks per task)
Now, when I created the models, I included a dependency (has_many/belongs_to) between goals and users, but I also did the same for tasks and users and subtasks and users. The reason for this is that I wanted an 'id' column in all tables that corresponds to the user table so that I can list down all subtasks/tasks/goals of a user very easily.
Coming to the front end, I need forms that are capable of adding goals/tasks/subtasks.
The relation between users and goals is not problematic at all. The has_many/belongs_to relation allows me to user something like
def create
#goal = current_user.goals.build(path_params)
if #goal.save
redirect_to current_user
else
.
.
end
end
However, the problem arises when I have 3 levels of hierarchy. User, goals and tasks.
My question is that how exactly do I do this for the task controller?
I tried using something like
#task = current_user.goals.tasks.build(task_params)
but that has the obvious flaw apart from probably being syntactically incorrect : there is no way to detect what particular goal this task is being assigned to.
What I can think of is that the front end form must contain a field (perhaps hidden) that contains the goal ID to which the task is being assigned. But not entirely sure is this is correct as well or how if a front end gimmick like that can actually help in case there are even more levels of hierarchy.
Please tell me where I am going wrong / whats a better way to do this.
I am only a beginner right now so I am very confused on how multiple level model hierarchies are managed.
What I can think of is that the front end form must contain a field (perhaps hidden) that contains the goal ID to which the task is being assigned.
More or less. A better way is to have RESTful resources here. So you'll have this structure of nested resources:
# routes.rb
resources :users
resources :goals do
resources :tasks do
resources :subtasks
end
end
This means that to create a task, you have to send a POST request to
/goals/1/tasks
This way, in your TasksController, you'll have params[:goal_id] and now you can do something like this:
class TasksController
before_action :load_goal
def create
#task = #goal.tasks.build(task_params)
...
end
private
def load_goal
#goal ||= Goal.where(id: params[:goal_id]).first
end
end
Repeat for other levels of hierarchy.
As for the propagating user_id down the line, you can do something like this:
class Task
belongs_to :goal
before_save :copy_user_id_from_parent
def copy_user_id_from_parent
self.user_id = goal.user_id
end
end

Rails 4 user roles and permissions

I am writing a rails application for an organization. Every user may have 1 or more roles and can only access certain controller actions depending on those roles.
For example, only admins can create, destroy and update certain fields of Users. Also, there are Teams which each have a team leader, and only the team leader can update certain information about the Team (like the member list, for example). However, Admins are the one who assign the team leader in the first place.
The specific details of my scenario are not important, I merely hope I described the situation where there are many different roles and permissions.
My question is: what gem to use? My first thought was CanCan, but the last commit was almost a year ago and there is no mention of Rails 4 compatibility. Is there a currently maintained alternative?
Your first guess was right, use cancancan and you'll be good with it.
EDIT Jul 24, 2015
I've been using cancancan for a long time now and it was always working great. I've recently started working on a project where Pundit is used for authorization.
It is awesome. It prompts you to define the policy for each resource and it feels more natural than one bloated Ability class.
For bigger projects, I would definitely recommend Pundit.
To control access to actions I'd recommend Action Access, it boils down to this:
class UsersController < ApplicationController
let :admin, :all
let :user, [:index, :show]
# ...
end
This will automatically lock the controller, allowing admins to access every action, users only to show or index users and anyone else will be rejected and redirected with an alert.
If you need more control, you can use not_authorized! inside actions to check and reject access.
It's completely independent of the authentication system and it can work without User models or predefined roles. All you need is to set the clearance level for the current request:
class ApplicationController < ActionController::Base
def current_clearance_level
session[:role] || :guest
end
end
You can return whatever you app needs here, like current_user.role for example.
Although it isn't required, it bundles a set of handy model additions that allow to do things like:
<% if current_user.can? :edit, :team %>
<%= link_to 'Edit team', edit_team_path(#team) %>
<% end %>
Here :team refers to TeamsController, so the link will only be displayed if the current user is authorized to access the edit action in TeamsController. It also supports namespaces.
You can lock controllers by default, customize the redirection path and the alert message, etc.
It's very straightforward and easy, I hope you find it useful.
Something that was suggested to me that we are now using is the petergate gem. Easy to use and very clean looking with a great rails feel.
Works well with devise.
Here is some examples from the readme.
If you're using devise you're in luck, otherwise you'll have to add following methods to your project:
user_signed_in?
current_user
after_sign_in_path_for(current_user)
authenticate_user!
This comes in your User.rb. Adding more roles is as easy as adding them to the array.
petergate(roles: [:admin, :editor], multiple: false)
Instance Methods
user.role => :editor
user.roles => [:editor, :user]
user.roles=(v) #sets roles
user.available_roles => [:admin, :editor]
user.has_roles?(:admin, :editors) # returns true if user is any of roles passed in as params.
Controller access syntax.
access all: [:show, :index], user: {except: [:destroy]}, company_admin: :all

Fat model, thin controller - but what if mixed models?

I am building a Ruby on Rails 4.1 app that has the following models that should make sense when you see the model names:
Domains, Teams, Users, Meetings etc
Now, a Team belongs to a domain and a User to a team, also meetings belong to a user. A domain is simply an organisation or company that is using the software.
After the creation of an admin user that user has to initially create a Domain, then create the first team, then other users can sign up.
As you can probably anticipate, in the process of creating the initial Domain and Team (which is done in the new/create of the Team and Domain controllers) I am having to access and change references in all three. So, for example, creating the first Domain will have to associate the admin user, creating a Team will involve linking it to the user, then also the parent domain.
So, it's all getting a bit mixed up, with the controller needing to access several models.
My question is, where does this logic belong? In the spirit of thin controller you would normally farm it out to the model, but it involves more than one model. Is this where the new Rails 4 concerns become useful or should I just put it in the controller?
I am relatively new to rails, so please keep that in mind if you are kind enough to reply - thanks!
Nested resources
class Domain
has_many :teams
end
class Team
has_many :users
belongs_to :domain
end
class User
has_many :meetings
belongs_to :team
end
class Meeting
belongs_to :user
end
Then in your controllers:
class TeamsController < ApplicationController
def new
#team = Team.new
end
def create
#domain = Domain.find(params[:domain_id])
#team = #domain.teams.build(params[:team])
#team.save
respond_with #team
end
end
That's what we call a "nested" controller, in the routes.rb file:
resources :domain do
resources :team
end
The URL will look like that:
/domains/:domain_id/teams
/domains/:domain_id/teams/:team_id
Same logic apply for other models. This should give you a starting point to build your application.
The following line
#domain.teams.build(params[:team])
automatically link a domain to a team setting the reference (id) for you.
However you should not do deep nesting according to rails guide so that's where the builder design pattern can come in handy.
Builder design pattern
However, if things start to get to messy, I would suggest using a dedicated ruby class to "build" your objects and their relationship. We usually call those classes a "Builder":
class TeamBuilder
attr_reader :domain, :params
def initialize(domain, params = {})
#domain = domain
#params = params
end
def build
domain.teams.build(params)
end
end
Here it's again doing very simple task. For user and meetings for example:
class UserBuilder
attr_reader :team, :params
def initialize(team, params = {})
#team = team
#params = params
end
def build
team.users.build(params).tap do |user|
user.foo = 'foo'
user.meetings.build(...)
user.meetings << MeetingBuilder.new(user, { ... })
end
end
end
class MeetingBuilder
# ...
end
Here we use the MeetingBuilder within the UserBuilder to build a meeting.
Usage:
user = UserBuilder.new(team, { ... }).build
user.save
Ideally, a model shouldn't care about, or even know about, other classes. So between the model and the controller, this kind of logic definitely belongs in the controller.
I would probably go with a third class though, taking care of that messy stuff, like a DomainFactory for example. Which is just a plain old ruby object (poro), no active record or anything, with the sole purpose of creating domains.
A tip is to read up on loose coupling and single responsibility.

Rails RESTful website vs user experience and less clicks

Situation: I have a team model, a user model and a teamate model for the users of a team.
Say I want to have a view that contains the information of a team team/show
and that I wish (to simplify the user's experience) to add a list of the users, an add user to team and the possibility to remove a user from that team.
To be perfectly restful, I would need a controller (let's call it Teamates), it would handle the users of a team.
I would have all the CRUD needed.
Is it clean to have the team/show view call the teamates controller for the following actions: adduser, removeuser, listusers.
What I am trying to achieve is less clicks for the users.
In other words, I would like the user to be able to manage the users of a team from the team view instead if requireing him to navigate even further.
I don't think you need a controller for teamates.
And you really should not have adduser/removeuser/etc actions in your team controller!
You could set up your routes like that:
resources :teams do
scope :module => "team_scope" do
resources :users
end
end
Then you would have a UsersController in app/controllers/team_scope/users_controller.rb
To create a new user for a team, you would post to: /team/1-team-a/users and it would hit the create action in the UsersController above.
When you use scope in your routes, it does not change the route helpers like with namespace. The new action would just be accessible via new_team_user_path(#team).
Hum... so yeah, in this case I would have a TeamatesController, and maybe set up my routes like that:
resources :teams do
resources :teamates, :only => [] do
collection do
get :edit
put :update
end
end
end
And then you could edit the associations between a team and its players...
Your form would post the users id to team_teamates_path(team)...
But I'm really not sure it's the best way, I'd have to think about it. This is not really restful as well.

Resources