Skip to content

Instantly share code, notes, and snippets.

@swifthand
Last active December 30, 2015 23:49
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save swifthand/bb9f597da56a39e2ef53 to your computer and use it in GitHub Desktop.
class EventsController < ApplicationController
def show
role_based_action
end
def new
role_based_action
end
def create
role_based_action
end
def edit
role_based_action
end
def update
role_based_action
end
end
class ApplicationController < ActionController::Base
# Loads a module into this instance of the controller, and calls an action based on
# the role of the current user. Defaults the to controller and action of the request.
# Examples:
#
# User.role: 'admin', controller#action: 'orders#show'
# Will extend this class with AdminOrdersController and call admin_show
#
# User.role: 'customer', controller#action: 'characters#new'
# Will extend this class with CustomerCharactersController and call customer_new
# Since it is unlikely we will allow customers to create new characters, this method is unlikely
# to exist, and will spit out an error.
#
# TODO: Have a sane method of redirecting to a default path for missing methods instead of an error.
# TODO: Catch the uninitialized constant error in the call to Kernel.const_get and redirect somewhere sane.
def role_based_action(controller_name = nil, action_name = nil)
controller_name = params[:controller] if controller_name.nil?
action_name = params[:action] if action_name.nil?
user_role = current_user.nil? ? 'public' : current_user.role
extend Kernel.const_get("#{user_role}_#{controller_name}_controller".classify)
self.send("#{user_role}_#{action_name}")
end
end
class PublicRole::EventsController < ApplicationController
def show
# A public/guest user's view of an Event.
end
end
class AdminRole::EventsController < ApplicationController
def {show new create edit update}
# Various admin user's ability to do things with an Event.
end
end
class BusinessRole::EventsController < ApplicationController
def {show edit update}
# A business user's view of an Event.
end
end
class GroupRole::EventsController < ApplicationController
def {show new create}
# A group user's view of an Event.
end
end
class ApplicationController < ActionController::Base
def role_based_action(options = HashWithIndifferentAccess.new)
# Otherwise, let's determine a valid controller, action and role to proceed with.
options[:user_role] ||= current_user.nil? ? 'public' : current_user.role
options[:controller_name] = params[:controller] if options[:controller_name].nil?
options[:action_name] = params[:action] if options[:action_name].nil?
# Bail for the explicit role 'invalid'
root_or_sign_up_redirect(options[:controller_name], options[:action_name]) and return if options[:user_role] == 'invalid'
# Load the desired role-based controller code and try and use the desired action.
if Rails.env.development?
execute_role_action(options)
else
begin
execute_role_action(options)
rescue NoMethodError
root_or_sign_up_redirect(options[:controller_name], options[:action_name]) and return true if options[:user_role] == 'public'
rescue NameError
root_or_sign_up_redirect(options[:controller_name], options[:action_name]) and return true
end
end
end
def role_for_resource(resource)
if current_user.nil?
'public'
elsif current_user.admin?
'admin'
else
current_user.role_for? resource
end
end
def execute_role_action(options)
@role_controller = fetch_role_controller(options[:user_role], options[:controller_name]).new(self)
protected_instance_variables << '@role_controller'
@role_controller.send(options[:action_name])
end
def role_based_action_for(resource, options = HashWithIndifferentAccess.new)
role_based_action(options.merge({user_role: role_for_resource(resource)}))
end
def fetch_role_controller(role_name, controller_name)
"#{role_name.camelize}Role::#{controller_name.camelize}Controller".constantize
end
# A permanent view_assign alias & override is used to pull instance variable
# assignments from our custom role-bound controllers.
alias :rails_view_assigns :view_assigns
def view_assigns
base_assigns = rails_view_assigns
role_assigns = @role_controller ? @role_controller.view_assigns : {}
base_assigns.merge(role_assigns)
end
end
class DelegatingController
include Rails.application.routes.url_helpers
delegate(
# Methods from ActionController::Base methods
:params, :gon, :render, :respond_to, :redirect_to, :current_user,
:request, :send_data, :headers, :flash,
# Methods from ApplicationController
:log_action, :site_disabled?, :first_when_valid, :after_sign_in_path_for,
:root_redirect, :redirect_back, :store_location, :clear_stored_location,
:root_or_sign_up_redirect, :sign_up_redirect, :sign_in_redirect,
:admin_users_only, :nested_merge, :page_context,
# Methods related to Devise
:sign_in,
# Delegate all to the controller we are wrapping.
:to => :@target_controller)
DEFAULT_PROTECTED_VARIABLES = %w(@protected_instance_variables)
def initialize(target_controller)
@target_controller = target_controller
protect_variables('@target_controller')
end
def change_controllers(user_role, controller_name, action_name)
@target_controller.execute_role_action(user_role: user_role, controller_name: controller_name, action_name: action_name)
end
def protect_variables(*variable_names)
variable_names = [*variable_names]
@protected_instance_variables ||= []
@protected_instance_variables += variable_names
end
def protected_instance_variables
DEFAULT_PROTECTED_VARIABLES + @protected_instance_variables
end
# Taken nearly verbatim from AbstractController::Rendering#view_assigns
# Normally located at /rails/actionpack/lib/abstract_controller/rendering.rb
def view_assigns
hash = {}
variables = instance_variable_names
variables -= protected_instance_variables
variables.each { |name| hash[name.to_s[1, name.length]] = instance_variable_get(name) }
hash
end
end
module PublicRole
class EventsController < DelegatingController
def show
@event = Event.find(params[:id])
root_or_sign_up_redirect('events', 'show') and return true unless Event::RSVPABLE_STATES.include? @event.state
gon.map = RestaurantResultsMap.new(@event.business_location).for_gon
@rsvp = @event.rsvps.build
@rsvp.build_proto_user
# Beginning refactor to page viewmodel.
# Code above will be merged in with a larger refactor.
@page = RsvpPage.new(@event)
render 'design/rsvp_page', layout: 'v3'
end
def new
guidelines and return true if params[:step] == 'guidelines'
context_based_signup
end
# private #######################################################################
def context_based_signup
store_location
params[:context_type] = EventContextType.new(params).type
change_controllers('public', 'users', 'new')
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment