Skip to content
Anuj edited this page Sep 15, 2016 · 6 revisions

This tutorial is an introduction to creating authorization with Banken.

Requirement

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  def index
    @posts = Post.all
  end

  def show
  end

  def new
    @post = Post.new
  end

  def edit
  end

  def create
    @post = Post.new(post_params)
    if @post.save
      redirect_to @post, notice: 'Post was successfully created.'
    else
      render :new
    end
  end

  def update
    if @post.update(post_params)
      redirect_to @post, notice: 'Post was successfully updated.'
    else
      render :edit
    end
  end

  def destroy
    @post.destroy
    redirect_to posts_url, notice: 'Post was successfully destroyed.'
  end

  private
    def set_post
      @post = Post.find(params[:id])
    end

    def post_params
      params.require(:post).permit(:title, :body, :published_at)
    end
end

We try to implement the process of updating the articles that meet the following requirements for the above posts_controller.rb.

  • Can update anyone article in the case of unpublished it.
  • Can't update article in the case of published it.

Authorization design

Banken does not have constraints and conventions for the authority control. You can freely design the logic and role for authorization. This time, it add a admin column to the users table. true means Administrator and 'false' means general user.

> User.find(1).admin?
true
> User.find(2).admin?
false

If you want to have three or more roles, You can use the Enum of Rails 4.1, If you want to have a plurality of types of roles to one user, You can create the roles table. Banken can faithfully control authority even if such as requirements.

Keep Banken

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include Banken
  protect_from_forgery
end
> rails g banken:install
      create  app/loyalties/application_loyalty.rb

Preparation is very simple, two-step.

  1. Bankenmodule include to ApplicationController
  2. rails g banken:install generate app/loyalties/application_loyalty.rb
#app/loyalties/application_loyalty.rb
class ApplicationLoyalty
  attr_reader :user, :record

  def initialize(user, record)
    @user = user
    @record = record
  end

  def index?
    false
  end

  def show?
    false
  end

  def create?
    false
  end

  def new?
    create?
  end

  def update?
    false
  end

  def edit?
    update?
  end

  def destroy?
    false
  end
end

Now you keep Banken in your app. Preparation is now ready.

Loyalty class

It create a loyalty class with the same name as the Controller. So, You need to create PostsLoyalty class with the same name as PostsController class

> rails g banken:loyalty posts
      create  app/loyalties/posts_loyalty.rb
#app/loyalties/posts_loyalty.rb
class PostsLoyalty < ApplicationLoyalty
end

You can create PostsLoyalty that inherits from ApplicationLoyalty in app/loyalties/

Control action

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def update
    authorize! @post

    if @post.update(post_params)
      redirect_to @post, notice: 'Post was successfully updated.'
    else
      render :edit
    end
  end
end

It is possible to determine whether processing after by performing authorize!. authorize! perform the following processing.

  1. It create a instance of PostsLoyalty class with the same name as PostsController.
  2. It execute update? method in a instance of PostsLoyalty class.

In this case, you can imagine that authorize would have done something like this

  def update
    raise "not authorized" unless PostsLoyalty.new(current_user, @post).update?

    if @post.update(post_params)
      redirect_to @post, notice: 'Post was successfully updated.'
    else
      render :edit
    end
  end

As the first argument current_user is executed when you create an instance of PostsLoyalty class. So, You must define the current_user in ApplicationController. @post of the second argument is an object that you passed as first argument to authorize! . First argument of authorize! do not need to pass if it is not necessary.

Implement Loyalty class

# app/loyalties/posts_loyalty.rb
class PostsLoyalty < ApplicationLoyalty
  def update?
    user.admin? || record.unpublished?
  end
end

useriscurrent_userandrecordis@postof first argument of authorize!. Banken raise an exception(Banken::NotAuthorizedError) in case of returning false by update?

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  rescue_from Banken::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authorized(exception)
    loyalty_name = exception.loyalty.class.to_s.underscore

    flash[:error] = t "#{loyalty_name}.#{exception.query}", scope: "banken", default: :default
    redirect_to(request.referrer || root_path)
  end
end
ja:
  banken:
    default: 'You cannot perform this action.'
    posts_loyalty:
      update?: 'You cannot edit this post!'
      create?: 'You cannot create posts!'

Control view

<% if loyalty(@post, :posts).update? %>
  <%= link_to "Edit post", edit_post_path(@post) %>
<% end %>

You can easily get a hold of an instance of the loyalty through the loyalty method in both the view. This is especially useful for conditionally showing links or buttons in the view. In this case, you can imagine that authorize would have done something like this

<% if PostsLoyalty.new(current_user, @post).update? %>
  <%= link_to "Edit post", edit_post_path(@post) %>
<% end %>

It is now complete.

And

As you can see, Banken is a small library. But it can separate the logic of Controller and View to Loyalty layer. And it is very useful as Fat Controller measures. Remember that all of the loyalty class are just plain Ruby classes, which means you can use the same mechanisms you always use to DRY things up. Encapsulate a set of permissions into a module and include them in multiple policies. Banken is very simple internal implementation and is not an extension of Rails. If it is Rails version up. it will not be a problem.