Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jdickey/5784340 to your computer and use it in GitHub Desktop.
Save jdickey/5784340 to your computer and use it in GitHub Desktop.

See this StackOverflow question to understand what all this is for.

Thanks for any help.

Attached are:

  • 01_config_routes.rb -- showing how the :comments resource is nested within :articles
  • 02_app_models_article.rb -- showing the (containing) Article model
  • 03_app_models_comment.rb -- showing the (contained) Comment model
  • 04_app_models_ability.rb -- "main" CanCan Ability class; uses three modules (05-07) to manage the specifics of the three different roles (guest, logged-in user and admin);
  • 05_app_models_ability_guest_abilities.rb -- CanCan ability definitions for guest user (nobody logged in);
  • 06_app_models_ability_user_abilities.rb -- CanCan ability definitions for registered, logged-in user;
  • 07_app_models_ability_admin_abilities.rb -- CanCan ability definitions for admin-role users;
  • 08_app_models_user.rb -- simplistic Devise-equipped User model.

IMPORTANT IMPORTANT IMPORTANT

Controllers in this app are derived from Jose Valim's InheritedResources::Base class rather than Rails' ApplicationController. I suspect that the #update call may be coming from there?

  • 09_app_controllers_articles_controller.rb is the Article controller. Adds CanCan's load_and_authorize_resource method;
  • 10_app_controllers_comments_controller.rb is the Comments controller. Does the CanCan bit; declares that it belongs_to :article, and declares the five actions it uses. (It's a value object once created, so no :edit or :update.)
Relativity::Application.routes.draw do
devise_for :users
root to: 'welcome#index'
get "welcome/index"
resources :articles do
resources :comments
end
resources :users
end
# Mishmash of Article business object, Rails persistence and validation.
class Article < ActiveRecord::Base
has_many :comments
accepts_nested_attributes_for :comments
attr_accessible :comments_attributes
attr_accessible :content
attr_accessible :links
attr_accessible :links_text
attr_accessible :status
attr_accessible :title
validate :links_must_be_valid
validate :status_must_be_valid
validates :content, presence: true
### ### ### ###
def author_name
User.find(author_id).name
end
def links_text
LinksValidator.new(links).to_a.join ', '
end
# Why not just use a LinksValidator here, too? The original specs threw
# values such as random whitespace between links at this method and expected
# it to Do The Right Thing, not stand firm on JSON correctness. Hence the
# sore-thumb assignment to `parts`. The Right Thing probably is to eliminate
# the crazy specs and replace this with a one-liner like
# ```ruby
# self[:links] = Yajl.dump LinksValidator.new(value).to_a
# ```
#
def links_text= value
parts = value.respond_to?(:split) ? value.split(/,\s*/) : value
self[:links] = Yajl.dump parts
end
def status_options
Article.legal_status_values
end
### ### ### ###
private
def links_must_be_valid
self.errors[:links].concat LinksVlaidator.new(links).errors
end
def status_must_be_valid
self.errors[:status].concat Article.validate_status status
end
### ### ### ###
def self.collect_errors messages, validations, status
_errors = MessageCollector.new.
add_autofail_if messages[:invalid_type] {not status.is_a? String}
validations.each {|message, proc| _errors.add(message) {proc.call}}
_errors
end
def self.legal_status_values
%w(draft public)
end
def self.validate_status status
is_valid_status = Article.legal_status_values.index status
messages = {
invalid_type: 'is not a string value',
empty_string: 'must not be empty',
blank_string: 'must not be blank',
invalid_string: 'must have one of the valid values'
}
validations = {
messages[:empty_string] => Proc.new {status.empty?},
messages[:blank_string] => Proc.new {status.strip.empty?},
messages[:invalid_string] => Proc.new{not is_valid_status}
}
collect_errors messages, validations, status
end
end
class Comment < ActiveRecord::Base
attr_accessible :article
attr_accessible :article_id
attr_accessible :commenter
attr_accessible :commenter_id
attr_accessible :content
belongs_to :article
belongs_to :commenter, class_name: 'User'
validates_presence_of :article_id
validates_presence_of :content
validates :article_id, numericality: {only_integer: true, greater_than: 0}
def commenter_name
User.find(commenter_id).name
end
end
require_relative 'ability/admin_abilities'
require_relative 'ability/guest_abilities'
require_relative 'ability/user_abilities'
# CanCan-required class to map users to permissions. This is done here by way of
# roles (which are implemented using Rolify).
class Ability
include CanCan::Ability
def initialize user
user ||= User.new.decorate # guest user
extend Ability.ability_module(user)
init user
init_all user
end
private
def init_all user
init_all_for_user_model user
init_all_for_article_model user
init_all_for_comment_model user
end
def init_all_for_article_model user_in
(user_in)
[:index, :read].each do |ability|
can ability, Article, status: 'public'
end
end
def init_all_for_comment_model user_in
(user_in)
[:index, :show].each do |ability|
can ability, Comment do |comment|
art_id = comment[:article_id]
Article.find(art_id).status == 'public'
end
end
end
def init_all_for_user_model user_in
(user_in)
[:index, :read].each {|ability| can ability, User}
end
def self.ability_module user
if user.has_role? :admin
Abilities::AdminAbilities
elsif user.id
Abilities::UserAbilities
else
Abilities::GuestAbilities
end
end
end
class Ability
module Abilities
module GuestAbilities
def init user
(user)
end
end # module Abilities::GuestAbilities
end # module Abilities
end
class Ability
module Abilities
module UserAbilities
def init user
init_for_user_model user
init_for_article_model user
init_for_comment_model user
end
private
def init_for_article_model user_in
can :create, Article
can :manage, Article, author_id: user_in.id
can :update, Article do |article|
puts "*" * 80
puts "DANGER, WILL ROBINSON!"
puts " Abilities::UserAbilities.init_for_article_model hardcoding true!"
puts "*" * 80
true
end
end
def init_for_comment_model user_in
[:create, :update].each do |action|
can action, Comment do |comment|
art_id = comment[:article_id]
Article.find(art_id).status == 'public'
end
end
end
def init_for_user_model user_in
[:edit, :update].each do |action|
can action, User do |user|
user.id == user_in.id
end
end
end
end # module Abilities::UserAbilities
end # module Abilities
end # class Ability
class Ability
module Abilities
module AdminAbilities
def init user
(user)
can :manage, User
can :delete, Article
end
end # module Abilities::AdminAbilites
end # module Abilities
end
# Simple/simplistic User model..
class User < ActiveRecord::Base
rolify
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :bio
attr_accessible :email
attr_accessible :name
attr_accessible :password
attr_accessible :password_confirmation
attr_accessible :remember_me
validates_presence_of :email
validates_presence_of :name
validates_presence_of :password, on: :create
end
class ArticlesController < InheritedResources::Base
respond_to :html
load_and_authorize_resource
end
class CommentsController < InheritedResources::Base
respond_to :html
actions :index, :show, :new, :create, :delete
belongs_to :article
load_and_authorize_resource
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment