Skip to content

Instantly share code, notes, and snippets.

@soynog
Created June 28, 2017 14:39
Show Gist options
  • Save soynog/39a8457718a4d8709e875d521296f59f to your computer and use it in GitHub Desktop.
Save soynog/39a8457718a4d8709e875d521296f59f to your computer and use it in GitHub Desktop.
Roles Helpers
.form-group
%label.col-xs-2.control-label Roles
.col-xs-10
.form-inline
.panel.roles-wrapper.panel-default
.row.panel-body
.col-sm-12
%span.roles-container
.btn.add-role
%span.text-muted add role
%span.text-muted.glyphicon.glyphicon-plus
-# var rh = new RoleHelper(
-# '#{user_roles.to_json.html_safe}',
-# $('.roles-container'),
-# '#{escape_javascript render partial: "admin/users/role_form", locals: {f: f, role: @new_role}}'
-# );
-#
-# // Click handler for adding new roles
-# $('.add-role').click(function() {
-# rh.addRole().trigger('change');
-# });
-#
-# // Have the FormHandler watch the roles for changes, plus trigger change event for the new role
-# fh.watch($('.roles-wrapper'));
-#
-# // Reset the schedule divs on form reset
-# fh.onReset(function() {
-# rh.reset();
-# });
-puts "ROLES FORM", can?(:manage, Role)
=f.simple_fields_for :roles, role,
wrapper: :inline_input_group,
include_id: false do |role_form|
.role-body
=role_form.input :name,
collection: current_user.accessible_role_names,
wrapper_html: {class: 'role-form-group'},
required: true,
include_blank: "role",
input_html: {name: input_name(f, :roles, :name), class: 'role-input name'}
%span.role-fields.hidden{ data: { role: "admin"} }
%span.role-fields.hidden{ data: {role: "staff"} }
for
=role_form.input :resource_id,
collection: current_user.accessible_agencies,
wrapper_html: {class: 'role-form-group'},
required: true,
include_blank: 'select an agency',
input_html: { name: input_name(f, :roles, :resource_id), class: 'role-input resource-id' }
=role_form.input :resource_type,
wrapper_html: {class: 'hidden'},
input_html: { name: input_name(f, :roles, :resource_type),
class: 'resource-type' }
=role_form.input :id,
wrapper_html: {class: 'hidden'},
input_html: {name: input_name(f, :roles, :id), class: 'id'}
=role_form.input :_destroy,
wrapper_html: {class: 'hidden'},
input_html: {class: 'destroy', name: input_name(f, :roles, :_destroy), value: false}
.btn.btn-link.delete-role
%span.glyphicon.glyphicon-remove
# Module to extend users, etc. with role helper functions
module RoleHelper
### SCOPES & CLASS METHODS ###
def self.included(base)
base.extend(ClassMethods)
# SCOPES
base.scope :admins, -> { base.querify(base.with_role(:admin, :any)) }
base.scope :any_role, -> do
base.querify(base.with_any_role(
{name: :admin, resource: :any},
{name: :staff, resource: :any}
))
end
base.scope :guests, -> { base.travelers.where(GuestUserHelper.new.query_str) }
base.scope :registered, -> { base.where.not(GuestUserHelper.new.query_str) }
base.scope :registered_travelers, -> { base.travelers.registered }
base.scope :staff, -> { base.querify(base.with_role(:staff, :any)) }
base.scope :travelers, -> { base.where.not(id: base.any_role.pluck(:id)) }
base.scope :staff_for, -> (agency) { base.with_role(:staff, agency) }
base.scope :staff_for_any, -> (agencies) do # Returns staff for any of the agencies in the passed collection
base.querify( base.with_any_role(*agencies.map{|ag| { :name => :staff, :resource => ag }}) )
end
base.scope :except_user, -> (user) { where.not(id: user.id) }
# ASSOCIATIONS
base.has_many :transportation_agencies,
through: :roles,
source: :resource,
source_type: "TransportationAgency"
base.has_many :partner_agencies,
through: :roles,
source: :resource,
source_type: "PartnerAgency"
# base.accepts_nested_attributes_for :roles, allow_destroy: true
end
# CLASS METHOD DEFINITIONS
module ClassMethods
end
### INSTANCE METHODS ###
def has_no_roles?
!has_any_role?
end
# Check to see if the user is an Admin, any scope
def admin?
has_role? :admin, :any
end
# Check to see if the user is a guest (i.e. not registered)
def guest?
GuestUserHelper.new.is_guest_email?(email)
end
# Check to see if the user registered (i.e. not a guest)
def registered?
!guest?
end
# Check to see if the user is a registered traveler (i.e. not a guest, no roles)
def registered_traveler?
registered? && traveler?
end
# Check to see if the user is a staff, any scope
def staff?
has_role? :staff, :any
end
# Check to see if the user is a TransportationAgency staff
def transportation_staff?
staff? && agencies.any? { |a| a.transportation? }
end
# Check to see if the user is a PartnerAgency staff
def partner_staff?
staff? && agencies.any? { |a| a.partner? }
end
# Check to see if the user is a traveler (i.e. has no roles)
def traveler?
has_no_roles?
end
# Returns the agencies that the user is staff for
def agencies
Agency.where(id: transportation_agencies.pluck(:id) + partner_agencies.pluck(:id))
end
# Returns the last of the user's staffing agencies (of which there are hopefully just one)
def staff_agency
agencies.last
end
# Returns a collection of the user's transportation agency's services
def services
Service.where(transportation_agency: transportation_agencies)
end
# Replaces the user's staff agency role with the passed agency
# wraps in a transaction so changes will be rolled back on error
def set_staff_role(agency)
self.class.transaction do
self.remove_role(:staff)
self.add_role(:staff, agency) if agency
end
end
# Adds or removes the user's admin permissions based on passed boolean
# wraps in a transaction so changes will be rolled back on error
def set_admin(bool)
self.class.transaction do
bool ? self.add_role(:admin) : self.remove_role(:admin)
end
end
# Replaces all roles with the passed roles, wrapping it in a transaction
# and rolling back to the original roles if there's an error
def update_roles(roles)
self.class.transaction do
self.roles.destroy_all
self.add_roles(roles)
end
end
# Takes a collection of roles objects or an array of roles attributes hashes,
# and adds those roles to the rolified object
def add_roles(roles)
roles.each do |role|
self.add_role(role[:name], find_resource(role[:resource_type], role[:resource_id]))
end
end
# Finds a resource based on type and id, allowing for nil params
def find_resource(type, id)
if type.present? && id.present?
resource_id = id.to_i
base_resource_class = type.constantize.base_class
return base_resource_class.find(resource_id)
else
return nil
end
end
# Returns the role names that the user may manage
def accessible_role_names
ability = Ability.new(self)
Role::ROLES.select { |role| ability.can?(:manage, role) }
end
# Returns the agencies that the user may manage
def accessible_agencies
Agency.accessible_by(Ability.new(self))
end
# Returns a list of users who are staff for any of the agencies this user is staff for
def fellow_staff
User.staff_for_any(agencies)
end
# Returns a list of the staff that the user has permissions to access
def accessible_staff
return User.any_role if admin?
return fellow_staff if staff?
return User.none
end
end
// Helper for Rendering User Role Forms
// Takes JSON of the roles, a JQuery reference to the containing div, and HTML for a blank role
function RoleHelper(rolesJSON, containerDiv, roleTemplate, options) {
this.originalRoles = JSON.parse(rolesJSON);
this.container = containerDiv;
this.template = roleTemplate;
this.options = options || {};
this.defaultRole = this.options.defaultRole || {
resource_type: "Agency"
};
this._init();
}
RoleHelper.prototype = {
// Renders all roles as divs
_init: function() {
var rh = this;
this.originalRoles.forEach(function(role) {
rh._render(role);
});
},
// Clears all roles
_clear: function() {
this.container.empty();
},
// Renders a role object in its appropriate container.
// Returns a JQuery reference to the new role.
_render: function(role) {
// Render the a role template into the roles container
this.container.append(this.template);
// Update it with appropriate values
var roleDiv = this.container.find('.role-body').last();
roleDiv.find('select.name').val(role.name);
roleDiv.find('select.resource-id').val(role.resource_id);
roleDiv.find('input.resource-type').val(role.resource_type);
roleDiv.find('input.id').val(role.id);
// Set it up with click handlers
roleDiv.find('.delete-role').click(this.deleteRole);
roleDiv.find('.role-input.name').change(this.updateResourceSelectCallback());
this.updateResourceSelect(roleDiv);
return roleDiv;
},
// Delete role method for click handlers.
// Hides the div and sets "_destroy" value to true
deleteRole: function() {
var parent = $(this).parents('.role-body');
parent.find('input.destroy').val(true).trigger('change');
parent.addClass('hidden');
},
// Adds a new schedule to the passed day, and populates it with default values
addRole: function() {
var rh = this;
return this._render(rh.defaultRole);
},
// Resets the roles form by clearing out the containers and rebuilding from the original roles JSON
reset: function() {
this._clear();
this._init();
},
// Updates appropriate Resource (i.e. agency) selection field based on the role
updateResourceSelect: function(parentDiv) {
// Identify the new role name
var role = parentDiv.find(':input.name').val();
// Hide and disable all selectors
parentDiv.find('.role-fields').addClass('hidden')
parentDiv.find(':input.resource-id').prop('disabled', true);
// Show and enable the appropriate one
parentDiv.find(".role-fields[data-role='" + role + "']").removeClass('hidden')
.find(":input.resource-id").prop('disabled', false);
return parentDiv;
},
// Returns a callback function to update the resource selection field
updateResourceSelectCallback: function() {
var rh = this;
return function() {
rh.updateResourceSelect($(this).parents('.role-body'));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment