guise 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +71 -0
- data/Rakefile +2 -0
- data/guise.gemspec +22 -0
- data/lib/guise/attributes.rb +27 -0
- data/lib/guise/inheritance.rb +35 -0
- data/lib/guise/introspection.rb +32 -0
- data/lib/guise/version.rb +3 -0
- data/lib/guise.rb +22 -0
- data/spec/factories.rb +24 -0
- data/spec/guise/inheritance_spec.rb +22 -0
- data/spec/guise/introspection_spec.rb +43 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/database.rb +39 -0
- metadata +112 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Eduardo Gutierrez
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# guise
|
2
|
+
|
3
|
+
An alternative to storing role resources in the database.
|
4
|
+
|
5
|
+
guise delegates type information to roles table that determines what a resource
|
6
|
+
can be instantiated as. In essence, it's similar to single table inheritance,
|
7
|
+
but with multiple inheritances possible.
|
8
|
+
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'guise'
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
```
|
21
|
+
$ bundle
|
22
|
+
```
|
23
|
+
|
24
|
+
Or install it yourself as:
|
25
|
+
|
26
|
+
```
|
27
|
+
$ gem install guise
|
28
|
+
```
|
29
|
+
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
Create a table to store your type information:
|
34
|
+
|
35
|
+
```
|
36
|
+
rails generate model guises user:references title:string
|
37
|
+
rake db:migrate
|
38
|
+
```
|
39
|
+
|
40
|
+
Then add the `guises` call to your model. If your table name is different or
|
41
|
+
you want a different attribute, supply the `:as` and `:attribute` options.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class User < ActiveRecord::Base
|
45
|
+
has_many :guises
|
46
|
+
has_guises :DeskWorker, :MailFowarder
|
47
|
+
end
|
48
|
+
|
49
|
+
class Person < ActiveRecord::Base
|
50
|
+
has_many :positions
|
51
|
+
has_guises :Admin, :Engineer, :association => :positions, :attribute => :rank
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
This adds the following methods to the `User` class:
|
56
|
+
* `:desk_workers` and `:mail_forwarders` scopes
|
57
|
+
* `:has_role?` that checks if a user is a particular type
|
58
|
+
* `:desk_worker?`, `:mail_forwarder` that proxy to `:has_role?`
|
59
|
+
* `:has_roles?` that checks if a user is of any of the types supplied
|
60
|
+
* Aliases the association method (`:positions`) as `:guises` if association is not called `:guises`
|
61
|
+
|
62
|
+
Additionally, this creates classes `DeskWorker` and `MailForwarder` that:
|
63
|
+
* Inherit from `User`.
|
64
|
+
* Have default scopes for `:desk_workers` and `:mail_forwarders` respectively.
|
65
|
+
* Create users with the right associated occupation.
|
66
|
+
|
67
|
+
|
68
|
+
## Plans
|
69
|
+
|
70
|
+
* Provide generators for roles table
|
71
|
+
* Update `has_guises` method to setup `has_many` association
|
data/Rakefile
ADDED
data/guise.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/guise/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Eduardo Gutierrez"]
|
6
|
+
gem.email = ["edd_d@mit.edu"]
|
7
|
+
gem.description = %q{ Databse-indempotent roles (mostly) }
|
8
|
+
gem.summary = %q{ Guise provides a (hopefully) reasonable paradigm for user roles on top of ActiveRecord }
|
9
|
+
gem.homepage = "https://github.com/ecbypi/guise"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "guise"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Guise::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency "activerecord", "~> 3.0"
|
19
|
+
gem.add_development_dependency "rspec", "~> 2.9"
|
20
|
+
gem.add_development_dependency "sqlite3", "~> 1.3.3"
|
21
|
+
gem.add_development_dependency "factory_girl", "~> 3.2"
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Guise
|
2
|
+
module Attributes
|
3
|
+
def set_attributes(names, options)
|
4
|
+
@@guise_attributes = options.reverse_merge(
|
5
|
+
:association => :guises,
|
6
|
+
:attribute => :title,
|
7
|
+
:names => names
|
8
|
+
)
|
9
|
+
|
10
|
+
class_eval do
|
11
|
+
alias_method :guises, guise_association
|
12
|
+
end if guise_association != :guises
|
13
|
+
end
|
14
|
+
|
15
|
+
def guises
|
16
|
+
@@guise_attributes[:names]
|
17
|
+
end
|
18
|
+
|
19
|
+
def guise_association
|
20
|
+
@@guise_attributes[:association]
|
21
|
+
end
|
22
|
+
|
23
|
+
def guise_attribute
|
24
|
+
@@guise_attributes[:attribute]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Guise
|
2
|
+
module Inheritance
|
3
|
+
def build_guises
|
4
|
+
guises.each do |name|
|
5
|
+
scope_name = name.tableize.to_sym
|
6
|
+
introspective_name = "#{name.underscore}?"
|
7
|
+
|
8
|
+
# Add a scope for this type of resource
|
9
|
+
scope scope_name, joins(guise_association).where(guise_association => { guise_attribute => name })
|
10
|
+
|
11
|
+
# build the class setting it's default scope to limit to those of itself
|
12
|
+
guise_class = Class.new(self) do
|
13
|
+
default_scope { send(scope_name) }
|
14
|
+
|
15
|
+
after_initialize do
|
16
|
+
self.guises.new(self.guise_attribute => name) unless self.has_role?(name)
|
17
|
+
end
|
18
|
+
|
19
|
+
after_create do
|
20
|
+
self.guises.create(self.guise_attribute => name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Object.const_set(name, guise_class)
|
25
|
+
|
26
|
+
# define the introspection method for the type
|
27
|
+
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
28
|
+
def #{introspective_name}
|
29
|
+
has_role?(#{name})
|
30
|
+
end
|
31
|
+
METHOD
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Guise
|
2
|
+
module Introspection
|
3
|
+
|
4
|
+
def has_role?(name)
|
5
|
+
name = name.to_s.classify
|
6
|
+
|
7
|
+
if !self.class.guises.include?(name)
|
8
|
+
raise NameError, "no such guise #{name}"
|
9
|
+
end
|
10
|
+
|
11
|
+
guises.pluck(guise_attribute).include?(name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_any_roles?(*names)
|
15
|
+
names.map(&method(:has_role?)).any?
|
16
|
+
end
|
17
|
+
|
18
|
+
def has_roles?(*names)
|
19
|
+
names.map(&method(:has_role?)).all?
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def guise_attribute
|
25
|
+
self.class.guise_attribute
|
26
|
+
end
|
27
|
+
|
28
|
+
def guise_table
|
29
|
+
self.class.guise_table
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/guise.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'guise/version'
|
2
|
+
require 'guise/attributes'
|
3
|
+
require 'guise/inheritance'
|
4
|
+
require 'guise/introspection'
|
5
|
+
|
6
|
+
module Guise
|
7
|
+
def self.extended(base)
|
8
|
+
base.extend Inheritance
|
9
|
+
base.extend Attributes
|
10
|
+
base.send :include, Introspection
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_guises(*names)
|
14
|
+
options = names.last.is_a?(Hash) ? names.pop : {}
|
15
|
+
class_names = names.map(&:to_s).map(&:classify)
|
16
|
+
|
17
|
+
set_attributes(class_names, options)
|
18
|
+
build_guises
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
ActiveRecord::Base.extend Guise
|
data/spec/factories.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
|
3
|
+
factory :user do
|
4
|
+
name 'Micro Helpline'
|
5
|
+
email 'mrhalp@mit.edu'
|
6
|
+
|
7
|
+
factory :technician do
|
8
|
+
after_create do |user|
|
9
|
+
FactoryGirl.create(:user_role, :name => 'Technician', :user => user)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
factory :supervisor do
|
14
|
+
after_create do |user|
|
15
|
+
FactoryGirl.create(:user_role, :name => 'Supervisor', :user => user)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
factory :user_role do
|
21
|
+
association :user
|
22
|
+
name 'Technician'
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Guise
|
4
|
+
describe Inheritance do
|
5
|
+
let!(:user) { create(:user) }
|
6
|
+
let!(:technician) { create(:technician) }
|
7
|
+
let!(:supervisor) { create(:supervisor) }
|
8
|
+
|
9
|
+
it "builds subclasses of names called in :guise" do
|
10
|
+
Technician.new.should be_a User
|
11
|
+
Technician.new.guises.should_not be_empty
|
12
|
+
end
|
13
|
+
|
14
|
+
it "adds scopes for each type" do
|
15
|
+
User.technicians.should include(technician)
|
16
|
+
User.technicians.should_not include(user)
|
17
|
+
|
18
|
+
User.supervisors.should include(supervisor)
|
19
|
+
User.supervisors.should_not include(user)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Guise
|
4
|
+
describe Introspection do
|
5
|
+
let!(:user) { create(:user) }
|
6
|
+
let!(:technician) { create(:technician) }
|
7
|
+
let!(:supervisor) { create(:supervisor) }
|
8
|
+
|
9
|
+
describe "#has_role?" do
|
10
|
+
it "checks if resource is of the type provided" do
|
11
|
+
user.has_role?(:technician).should be_false
|
12
|
+
technician.has_role?(:Technician).should be_true
|
13
|
+
end
|
14
|
+
|
15
|
+
it "raises an error if type was not added in :guises call" do
|
16
|
+
expect { user.has_role?(:Accountant) }.to raise_error(NameError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#has_roles?" do
|
21
|
+
before :each do
|
22
|
+
create(:user_role, :name => 'Technician', :user => supervisor)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "checks if resource is all of the provided types" do
|
26
|
+
technician.has_roles?(:Supervisor, :Technician).should be_false
|
27
|
+
supervisor.has_roles?('Supervisor', Technician).should be_true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#has_any_roles?" do
|
32
|
+
it "checks if resource is any of the supplied roles" do
|
33
|
+
user.has_any_roles?(:Supervisor, :Technician).should be_false
|
34
|
+
technician.has_any_roles?('supervisor', 'technician').should be_true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it "adds methods that proxy to #has_role? for ease" do
|
39
|
+
user.should respond_to :technician?
|
40
|
+
user.should respond_to :supervisor?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'factory_girl'
|
2
|
+
|
3
|
+
require File.expand_path('../support/database.rb', __FILE__)
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
|
7
|
+
config.before :suite do
|
8
|
+
Database.create
|
9
|
+
FactoryGirl.find_definitions
|
10
|
+
end
|
11
|
+
|
12
|
+
config.include FactoryGirl::Syntax::Methods
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Inspired/borrowed from @ernie's way of building out a database in
|
2
|
+
# ransack and squeel
|
3
|
+
|
4
|
+
require 'active_record'
|
5
|
+
require 'guise'
|
6
|
+
|
7
|
+
ActiveRecord::Base.establish_connection(
|
8
|
+
:adapter => 'sqlite3',
|
9
|
+
:database => ':memory:'
|
10
|
+
)
|
11
|
+
|
12
|
+
class User < ActiveRecord::Base
|
13
|
+
has_many :user_roles
|
14
|
+
has_guises :Technician, :Supervisor, :association => :user_roles, :attribute => :name
|
15
|
+
end
|
16
|
+
|
17
|
+
class UserRole < ActiveRecord::Base
|
18
|
+
belongs_to :user
|
19
|
+
end
|
20
|
+
|
21
|
+
module Database
|
22
|
+
def self.create
|
23
|
+
ActiveRecord::Base.silence do
|
24
|
+
ActiveRecord::Migration.verbose = false
|
25
|
+
|
26
|
+
ActiveRecord::Schema.define do
|
27
|
+
create_table :users, :force => true do |t|
|
28
|
+
t.string :name
|
29
|
+
t.string :email
|
30
|
+
end
|
31
|
+
|
32
|
+
create_table :user_roles, :force => true do |t|
|
33
|
+
t.string :name
|
34
|
+
t.integer :user_id
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: guise
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Eduardo Gutierrez
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: &70099999163140 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70099999163140
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70099999161660 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.9'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70099999161660
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: sqlite3
|
38
|
+
requirement: &70099999160760 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.3.3
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70099999160760
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: factory_girl
|
49
|
+
requirement: &70099999159300 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.2'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70099999159300
|
58
|
+
description: ! ' Databse-indempotent roles (mostly) '
|
59
|
+
email:
|
60
|
+
- edd_d@mit.edu
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- .gitignore
|
66
|
+
- .rspec
|
67
|
+
- Gemfile
|
68
|
+
- LICENSE
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- guise.gemspec
|
72
|
+
- lib/guise.rb
|
73
|
+
- lib/guise/attributes.rb
|
74
|
+
- lib/guise/inheritance.rb
|
75
|
+
- lib/guise/introspection.rb
|
76
|
+
- lib/guise/version.rb
|
77
|
+
- spec/factories.rb
|
78
|
+
- spec/guise/inheritance_spec.rb
|
79
|
+
- spec/guise/introspection_spec.rb
|
80
|
+
- spec/spec_helper.rb
|
81
|
+
- spec/support/database.rb
|
82
|
+
homepage: https://github.com/ecbypi/guise
|
83
|
+
licenses: []
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 1.8.11
|
103
|
+
signing_key:
|
104
|
+
specification_version: 3
|
105
|
+
summary: Guise provides a (hopefully) reasonable paradigm for user roles on top of
|
106
|
+
ActiveRecord
|
107
|
+
test_files:
|
108
|
+
- spec/factories.rb
|
109
|
+
- spec/guise/inheritance_spec.rb
|
110
|
+
- spec/guise/introspection_spec.rb
|
111
|
+
- spec/spec_helper.rb
|
112
|
+
- spec/support/database.rb
|