guise 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -37,35 +37,65 @@ rails generate model guises user:references title:string
37
37
  rake db:migrate
38
38
  ```
39
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.
40
+ Then add call the `has_guises` method in your model. This will setup the
41
+ `has_many` association for you.
42
42
 
43
43
  ```ruby
44
44
  class User < ActiveRecord::Base
45
- has_many :guises
46
45
  has_guises :DeskWorker, :MailFowarder
47
46
  end
48
-
49
- class Person < ActiveRecord::Base
50
- has_many :positions
51
- has_guises :Admin, :Engineer, :association => :positions, :attribute => :rank
52
- end
53
47
  ```
54
48
 
55
49
  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`
50
+ * `:desk_workers` and `:mail_forwarders` scopes.
51
+ * `:has_role?` that checks if a user is a particular type.
52
+ * `:desk_worker?`, `:mail_forwarder` that proxy to `:has_role?`.
53
+ * `:has_roles?` that checks if a user is of any of the types supplied.
61
54
 
62
- Additionally, this creates classes `DeskWorker` and `MailForwarder` that:
55
+ And creates classes `DeskWorker` and `MailForwarder` that:
63
56
  * Inherit from `User`.
64
57
  * Have default scopes for `:desk_workers` and `:mail_forwarders` respectively.
65
58
  * Create users with the right associated occupation.
66
59
 
67
60
 
68
- ## Plans
61
+ To configure the other end of the association, add `guise_for`:
62
+
63
+ ```ruby
64
+ class Guise < ActiveRecord::Base
65
+ guise_for :user
66
+ end
67
+ ```
68
+
69
+ This method does the following:
70
+ * Sets up `belongs_to` association and accepts the standard options.
71
+ * Validates the column storing the name of the guise in the list supplied is
72
+ unique to the resource it belongs to and is one of the provided names.
73
+
74
+
75
+ ### Customization
76
+
77
+ If the association doesn't fit what is assumed, you can pass in the options for
78
+ `has_many` into `has_guises`. The same applies to `guise_for` with the addition that you can specify not to validate attributes
79
+
80
+ ```ruby
81
+ class Person < ActiveRecord::Base
82
+ has_guises :Admin, :Engineer,
83
+ :association => :positions,
84
+ :attribute => :rank,
85
+ :foreign_key => :employee_id,
86
+ :class_name => :JobTitle
87
+ end
88
+
89
+ class JobTitle < ActiveRecord::Base
90
+ guise_for :person,
91
+ :foreign_key => :employee_id,
92
+ :validate => false # skip setting up validations
93
+ end
94
+ ```
95
+
96
+
97
+ ## The Future
69
98
 
70
99
  * Provide generators for roles table
71
100
  * Update `has_guises` method to setup `has_many` association
101
+ * Adding validations on `guise_attribute` column into association class
data/guise.gemspec CHANGED
@@ -4,7 +4,7 @@ require File.expand_path('../lib/guise/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["Eduardo Gutierrez"]
6
6
  gem.email = ["edd_d@mit.edu"]
7
- gem.description = %q{ Databse-indempotent roles (mostly) }
7
+ gem.description = %q{ Database-indempotent roles (mostly) }
8
8
  gem.summary = %q{ Guise provides a (hopefully) reasonable paradigm for user roles on top of ActiveRecord }
9
9
  gem.homepage = "https://github.com/ecbypi/guise"
10
10
 
@@ -15,8 +15,9 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Guise::VERSION
17
17
 
18
- gem.add_dependency "activerecord", "~> 3.0"
18
+ gem.add_dependency "activerecord", "~> 3.2"
19
19
  gem.add_development_dependency "rspec", "~> 2.9"
20
20
  gem.add_development_dependency "sqlite3", "~> 1.3.3"
21
21
  gem.add_development_dependency "factory_girl", "~> 3.2"
22
+ gem.add_development_dependency "shoulda-matchers", "~> 1.1"
22
23
  end
@@ -0,0 +1,25 @@
1
+ module Guise
2
+ module Options
3
+ def extract_guise_options(names, options)
4
+ @@guise_options = {
5
+ :association => options.delete(:association) || :guises,
6
+ :attribute => options.delete(:attribute) || :title,
7
+ :names => names
8
+ }
9
+
10
+ return @@guise_options, options
11
+ end
12
+
13
+ def guises
14
+ @@guise_options[:names]
15
+ end
16
+
17
+ def guise_association
18
+ @@guise_options[:association]
19
+ end
20
+
21
+ def guise_attribute
22
+ @@guise_options[:attribute]
23
+ end
24
+ end
25
+ end
data/lib/guise/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Guise
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/guise.rb CHANGED
@@ -1,21 +1,76 @@
1
1
  require 'guise/version'
2
- require 'guise/attributes'
3
- require 'guise/inheritance'
2
+ require 'guise/options'
4
3
  require 'guise/introspection'
5
4
 
6
5
  module Guise
7
- def self.extended(base)
8
- base.extend Inheritance
9
- base.extend Attributes
10
- base.send :include, Introspection
11
- end
12
6
 
13
7
  def has_guises(*names)
8
+ extend Options
9
+
14
10
  options = names.last.is_a?(Hash) ? names.pop : {}
15
11
  class_names = names.map(&:to_s).map(&:classify)
16
12
 
17
- set_attributes(class_names, options)
18
- build_guises
13
+ guise_options, association_options = extract_guise_options(class_names, options)
14
+
15
+ build_guises(class_names, guise_options)
16
+ introspect_guises(class_names)
17
+
18
+ has_many guise_association, association_options
19
+
20
+ if guise_association != :guises
21
+ alias_method :guises, guise_association
22
+ end
23
+ end
24
+
25
+ def guise_for(name, options = {})
26
+ association = Object.const_get(name.to_s.classify)
27
+ foreign_key = options[:foreign_key] || "#{association.name}_id"
28
+
29
+ belongs_to name, options
30
+
31
+ if options[:validate] != false
32
+ validates association.guise_attribute,
33
+ :uniqueness => { :scope => foreign_key },
34
+ :presence => true,
35
+ :inclusion => { :in => association.guises }
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def build_guises(names, options)
42
+ names.each do |name|
43
+ scope_name = name.tableize.to_sym
44
+
45
+ # Add a scope for this type of resource
46
+ scope scope_name, joins(guise_association).where(guise_association => { guise_attribute => name })
47
+
48
+ # build the class setting it's default scope to limit to those of itself
49
+ guise_class = Class.new(self) do
50
+ default_scope { send(scope_name) }
51
+
52
+ after_initialize do
53
+ self.guises.new(self.guise_attribute => name) unless self.has_role?(name)
54
+ end
55
+
56
+ after_create do
57
+ self.guises.create(self.guise_attribute => name)
58
+ end
59
+ end
60
+
61
+ Object.const_set(name, guise_class)
62
+ end
63
+ end
64
+
65
+ def introspect_guises(names)
66
+ include Introspection
67
+
68
+ names.each do |name|
69
+ method_name = "#{name.underscore}?"
70
+ define_method method_name do
71
+ has_role?(name)
72
+ end
73
+ end
19
74
  end
20
75
  end
21
76
 
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe Guise do
4
+
5
+ let(:user) { create(:user) }
6
+ let(:supervisor) { create(:supervisor) }
7
+ let(:technician) { create(:technician) }
8
+
9
+ describe ".has_guises" do
10
+ subject { user }
11
+
12
+ it "sets up has_many association" do
13
+ should have_many :user_roles
14
+ end
15
+
16
+ it "builds subclasses of names called in :guise" do
17
+ Technician.new.should be_a User
18
+ Technician.new.guises.should_not be_empty
19
+ end
20
+
21
+ it "adds scopes for each type" do
22
+ User.technicians.should include(technician)
23
+ User.technicians.should_not include(user)
24
+
25
+ User.supervisors.should include(supervisor)
26
+ User.supervisors.should_not include(user)
27
+ end
28
+
29
+ describe "#has_role?" do
30
+ it "checks if resource is of the type provided" do
31
+ user.has_role?(:technician).should be_false
32
+ technician.has_role?(:Technician).should be_true
33
+ end
34
+
35
+ it "raises an error if type was not added in :guises call" do
36
+ expect { user.has_role?(:Accountant) }.to raise_error(NameError)
37
+ end
38
+ end
39
+
40
+ describe "#has_roles?" do
41
+ before :each do
42
+ create(:user_role, :name => 'Technician', :user => supervisor)
43
+ end
44
+
45
+ it "checks if resource is all of the provided types" do
46
+ technician.has_roles?(:Supervisor, :Technician).should be_false
47
+ supervisor.has_roles?('Supervisor', Technician).should be_true
48
+ end
49
+ end
50
+
51
+ describe "#has_any_roles?" do
52
+ it "checks if resource is any of the supplied roles" do
53
+ user.has_any_roles?(:Supervisor, :Technician).should be_false
54
+ technician.has_any_roles?('supervisor', 'technician').should be_true
55
+ end
56
+ end
57
+
58
+ it "adds methods that proxy to #has_role? for ease" do
59
+ user.should respond_to :technician?
60
+ user.should respond_to :supervisor?
61
+
62
+ user.technician?.should be_false
63
+ technician.technician?.should be_true
64
+ end
65
+ end
66
+
67
+ describe ".guise_for" do
68
+ subject { create(:user_role) }
69
+
70
+ it "sets up belongs_to" do
71
+ should belong_to(:user)
72
+ end
73
+
74
+ describe "adds validations to ensure guise attribute is" do
75
+
76
+ it "present" do
77
+ should validate_presence_of(:name)
78
+ end
79
+
80
+ it "unique per resource" do
81
+ should validate_uniqueness_of(:name).scoped_to(:person_id)
82
+ end
83
+
84
+ it "is one of the guise names provided" do
85
+ expect { create(:user_role, :name => 'Farmer') }.to raise_error ActiveRecord::RecordInvalid
86
+ end
87
+ end
88
+ end
89
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,7 @@
1
+ require 'active_record'
1
2
  require 'factory_girl'
3
+ require 'shoulda-matchers'
4
+ require 'guise'
2
5
 
3
6
  require File.expand_path('../support/database.rb', __FILE__)
4
7
 
@@ -1,23 +1,24 @@
1
1
  # Inspired/borrowed from @ernie's way of building out a database in
2
2
  # ransack and squeel
3
3
 
4
- require 'active_record'
5
- require 'guise'
6
-
7
4
  ActiveRecord::Base.establish_connection(
8
5
  :adapter => 'sqlite3',
9
6
  :database => ':memory:'
10
7
  )
11
8
 
12
9
  class User < ActiveRecord::Base
13
- has_many :user_roles
14
- has_guises :Technician, :Supervisor, :association => :user_roles, :attribute => :name
10
+ has_guises :Technician, :Supervisor,
11
+ :association => :user_roles,
12
+ :attribute => :name,
13
+ :foreign_key => :person_id
15
14
  end
16
15
 
17
16
  class UserRole < ActiveRecord::Base
18
- belongs_to :user
17
+ guise_for :user,
18
+ :foreign_key => :person_id
19
19
  end
20
20
 
21
+
21
22
  module Database
22
23
  def self.create
23
24
  ActiveRecord::Base.silence do
@@ -31,7 +32,7 @@ module Database
31
32
 
32
33
  create_table :user_roles, :force => true do |t|
33
34
  t.string :name
34
- t.integer :user_id
35
+ t.integer :person_id
35
36
  end
36
37
  end
37
38
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: guise
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,22 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-04 00:00:00.000000000 Z
12
+ date: 2012-05-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
16
- requirement: &70099999163140 !ruby/object:Gem::Requirement
16
+ requirement: &70304078344960 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: '3.0'
21
+ version: '3.2'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70099999163140
24
+ version_requirements: *70304078344960
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70099999161660 !ruby/object:Gem::Requirement
27
+ requirement: &70304078344320 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '2.9'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70099999161660
35
+ version_requirements: *70304078344320
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: sqlite3
38
- requirement: &70099999160760 !ruby/object:Gem::Requirement
38
+ requirement: &70304078343680 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 1.3.3
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70099999160760
46
+ version_requirements: *70304078343680
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: factory_girl
49
- requirement: &70099999159300 !ruby/object:Gem::Requirement
49
+ requirement: &70304078342460 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,8 +54,19 @@ dependencies:
54
54
  version: '3.2'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70099999159300
58
- description: ! ' Databse-indempotent roles (mostly) '
57
+ version_requirements: *70304078342460
58
+ - !ruby/object:Gem::Dependency
59
+ name: shoulda-matchers
60
+ requirement: &70304078341900 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: '1.1'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70304078341900
69
+ description: ! ' Database-indempotent roles (mostly) '
59
70
  email:
60
71
  - edd_d@mit.edu
61
72
  executables: []
@@ -70,13 +81,11 @@ files:
70
81
  - Rakefile
71
82
  - guise.gemspec
72
83
  - lib/guise.rb
73
- - lib/guise/attributes.rb
74
- - lib/guise/inheritance.rb
75
84
  - lib/guise/introspection.rb
85
+ - lib/guise/options.rb
76
86
  - lib/guise/version.rb
77
87
  - spec/factories.rb
78
- - spec/guise/inheritance_spec.rb
79
- - spec/guise/introspection_spec.rb
88
+ - spec/guise_spec.rb
80
89
  - spec/spec_helper.rb
81
90
  - spec/support/database.rb
82
91
  homepage: https://github.com/ecbypi/guise
@@ -106,7 +115,6 @@ summary: Guise provides a (hopefully) reasonable paradigm for user roles on top
106
115
  ActiveRecord
107
116
  test_files:
108
117
  - spec/factories.rb
109
- - spec/guise/inheritance_spec.rb
110
- - spec/guise/introspection_spec.rb
118
+ - spec/guise_spec.rb
111
119
  - spec/spec_helper.rb
112
120
  - spec/support/database.rb
@@ -1,27 +0,0 @@
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
@@ -1,35 +0,0 @@
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
@@ -1,22 +0,0 @@
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
@@ -1,43 +0,0 @@
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