guise 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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