guise 0.2.3 → 0.3.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.
@@ -0,0 +1,104 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/hash/keys'
3
+ require 'active_support/core_ext/hash/indifferent_access'
4
+ require 'active_support/core_ext/string/inflections'
5
+
6
+ module Guise
7
+ module Syntax
8
+ def has_guises(*guises)
9
+ include Introspection
10
+
11
+ options = guises.last.is_a?(Hash) ? guises.pop : {}
12
+
13
+ guises = guises.map(&:to_s)
14
+ association = options.fetch(:association)
15
+ attribute = options.fetch(:attribute)
16
+
17
+ Guise.registry[self.name] = {
18
+ names: guises,
19
+ association: association,
20
+ attribute: attribute
21
+ }
22
+
23
+ guises.each do |guise|
24
+ method_name = guise.underscore
25
+ scope method_name.pluralize, -> { joins(association).where(association => { attribute => guise }) }
26
+
27
+ define_method "#{method_name}?" do
28
+ has_guise?(guise)
29
+ end
30
+ end
31
+
32
+ has_many association, options.except(:association, :attribute)
33
+
34
+ if association != :guises
35
+ association_singular = association.to_s.singularize
36
+
37
+ alias_method :guises, association
38
+ alias_method "has_#{association_singular}?", :has_guise?
39
+ alias_method "has_#{association}?", :has_guises?
40
+ alias_method "has_any_#{association}?", :has_any_guises?
41
+ end
42
+ end
43
+
44
+ def guise_of(class_name)
45
+ options = Guise.registry[class_name]
46
+
47
+ if options.nil?
48
+ raise ArgumentError, "no guises defined on #{class_name}"
49
+ end
50
+
51
+ default_scope -> { send(model_name.plural) }
52
+
53
+ callback = SourceCallback.new(self.name, options[:attribute])
54
+
55
+ after_initialize callback
56
+ after_create callback
57
+ end
58
+
59
+ def guise_for(class_name, options = {})
60
+ guise_options = Guise.registry[class_name]
61
+
62
+ if guise_options.nil?
63
+ raise ArgumentError, "no guises defined on #{class_name}"
64
+ end
65
+
66
+ association = class_name.to_s.underscore.to_sym
67
+ guises = guise_options[:names]
68
+ attribute = guise_options[:attribute]
69
+ foreign_key = options[:foreign_key] || "#{class_name.underscore}_id"
70
+
71
+ belongs_to association, options.except(:validate)
72
+
73
+ guises.each do |guise|
74
+ scope guise.underscore.pluralize, -> { where(attribute => guise) }
75
+ end
76
+
77
+ if options[:validate] != false
78
+ validates attribute, uniqueness: { scope: foreign_key }, presence: true, inclusion: { in: guises }
79
+ end
80
+ end
81
+
82
+ def scoped_guise_for(class_name)
83
+ guise_options = Guise.registry[class_name]
84
+
85
+ if guise_options.nil?
86
+ raise ArgumentError, "no guises defined on #{class_name}"
87
+ end
88
+
89
+ attribute = guise_options[:attribute]
90
+ parent_name = table_name.classify
91
+
92
+ value = guise_options[:names].detect do |guise|
93
+ guise == model_name.to_s.chomp(parent_name)
94
+ end
95
+
96
+ default_scope -> { where(attribute => value) }
97
+
98
+ callback = AssociationCallback.new(value, attribute)
99
+
100
+ after_initialize callback
101
+ before_create callback
102
+ end
103
+ end
104
+ end
data/lib/guise/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Guise
2
- VERSION = "0.2.3"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/guise.rb CHANGED
@@ -1,79 +1,15 @@
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+
1
3
  require 'guise/version'
2
- require 'guise/options'
4
+ require 'guise/callbacks'
5
+ require 'guise/syntax'
3
6
  require 'guise/introspection'
4
7
 
5
8
  module Guise
6
-
7
- def has_guises(*names)
8
- extend Options
9
-
10
- options = names.last.is_a?(Hash) ? names.pop : {}
11
- class_names = names.map(&:to_s).map(&:classify)
12
-
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.underscore}_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
74
- end
9
+ mattr_reader :registry
10
+ @@registry = HashWithIndifferentAccess.new
75
11
  end
76
12
 
77
13
  if defined?(ActiveRecord)
78
- ActiveRecord::Base.extend Guise
14
+ ActiveRecord::Base.extend(Guise::Syntax)
79
15
  end
data/spec/factories.rb CHANGED
@@ -5,20 +5,27 @@ FactoryGirl.define do
5
5
  email 'mrhalp@mit.edu'
6
6
 
7
7
  factory :technician do
8
- after_create do |user|
9
- FactoryGirl.create(:user_role, :name => 'Technician', :user => user)
8
+ after(:create) do |user, proxy|
9
+ create(:user_role, name: 'Technician', user: user)
10
10
  end
11
11
  end
12
12
 
13
13
  factory :supervisor do
14
- after_create do |user|
15
- FactoryGirl.create(:user_role, :name => 'Supervisor', :user => user)
14
+ after(:create) do |user, proxy|
15
+ create(:user_role, name: 'Supervisor', user: user)
16
16
  end
17
17
  end
18
18
  end
19
19
 
20
20
  factory :user_role do
21
- association :user
21
+ user
22
22
  name 'Technician'
23
+
24
+ factory :technician_role do
25
+ end
26
+
27
+ factory :supervisor_role do
28
+ name 'Supervisor'
29
+ end
23
30
  end
24
31
  end
data/spec/guise_spec.rb CHANGED
@@ -1,78 +1,126 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Guise do
4
+ let!(:user) { create(:user) }
5
+ let!(:supervisor) { create(:supervisor) }
6
+ let!(:technician) { create(:technician) }
4
7
 
5
- let(:user) { create(:user) }
6
- let(:supervisor) { create(:supervisor) }
7
- let(:technician) { create(:technician) }
8
+ after do
9
+ User.delete_all
10
+ UserRole.delete_all
11
+ end
8
12
 
9
13
  describe ".has_guises" do
10
- subject { user }
11
-
12
14
  it "sets up has_many association" do
13
- should have_many :user_roles
15
+ expect(user).to have_many :user_roles
14
16
  end
15
17
 
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
18
+ it "adds scopes for each type" do
19
+ technicians = User.technicians
20
+
21
+ expect(technicians).to include technician
22
+ expect(technicians).not_to include user
23
+ expect(technicians).not_to include supervisor
19
24
  end
25
+ end
20
26
 
21
- it "adds scopes for each type" do
22
- User.technicians.should include(technician)
23
- User.technicians.should_not include(user)
27
+ describe "#has_guise?" do
28
+ it "checks if record is of the specified type" do
29
+ expect(user).not_to have_guise :technician
24
30
 
25
- User.supervisors.should include(supervisor)
26
- User.supervisors.should_not include(user)
31
+ expect(technician).to have_guise :technician
32
+ expect(technician).to have_guise :Technician
33
+ expect(technician).to have_guise 'Technician'
34
+ expect(technician).to have_guise 'technician'
35
+ expect(technician).to have_guise Technician
27
36
  end
28
37
 
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
38
+ it "raises an error if type was not specified" do
39
+ expect { user.has_guise?(:Accountant) }.to raise_error ArgumentError
40
+ end
34
41
 
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
42
+ it 'is aliased based on the name of the association' do
43
+ expect(user).not_to have_user_role :technician
44
+ expect(technician).to have_user_role :technician
38
45
  end
39
46
 
40
- describe "#has_roles?" do
41
- before :each do
42
- create(:user_role, :name => 'Technician', :user => supervisor)
43
- end
47
+ it 'is wrapped for each guise specified' do
48
+ expect(user).not_to be_technician
49
+ expect(technician).to be_technician
50
+ end
51
+ end
44
52
 
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
53
+ describe "#has_guises?" do
54
+ before do
55
+ @role = create(:user_role, name: 'Technician', user: supervisor)
49
56
  end
50
57
 
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
58
+ after do
59
+ @role.destroy
60
+ end
61
+
62
+ it "checks if resource is all of the provided types" do
63
+ expect(technician).not_to have_guises :Supervisor, :Technician
64
+ expect(supervisor).to have_guises :Supervisor, :Technician
56
65
  end
57
66
 
58
- it "adds methods that proxy to #has_role? for ease" do
59
- user.should respond_to :technician?
60
- user.should respond_to :supervisor?
67
+ it 'is aliased based on the association name' do
68
+ expect(technician).not_to have_user_roles :Supervisor, :Technician
69
+ expect(supervisor).to have_user_roles :Supervisor, :Technician
70
+ end
71
+ end
61
72
 
62
- user.technician?.should be_false
63
- technician.technician?.should be_true
73
+ describe "#has_any_roles?" do
74
+ it "checks if resource is any of the supplied roles" do
75
+ expect(user).not_to have_any_guises :Supervisor, :Technician
76
+ expect(technician).to have_any_guises 'supervisor', 'technician'
77
+ end
78
+
79
+ it 'is aliased based on the association name' do
80
+ expect(user).not_to have_any_user_roles :Supervisor, :Technician
81
+ expect(technician).to have_any_user_roles 'supervisor', 'Technician'
82
+ end
83
+ end
84
+
85
+ describe '.guise_of' do
86
+ it "sets default scope to limit to records of the class's type" do
87
+ technician_ids = Technician.pluck(:id)
88
+
89
+ expect(technician_ids).to eq [technician.id]
90
+ end
91
+
92
+ it 'sets up lifecycle callbacks to ensure records are initialized and created with the correct associated records' do
93
+ new_record = Technician.new
94
+ expect(new_record).to have_guise :technician
95
+
96
+ created_record = Technician.create!
97
+ expect(created_record).to have_guise :technician
64
98
  end
65
99
  end
66
100
 
67
101
  describe ".guise_for" do
68
- subject { create(:user_role) }
102
+ subject { UserRole.new }
69
103
 
70
104
  it "sets up belongs_to" do
71
105
  should belong_to(:user)
72
106
  end
73
107
 
74
- describe "adds validations to ensure guise attribute is" do
108
+ it 'defines scopes for each guise' do
109
+ technician_role = create(:technician_role)
110
+ supervisor_role = create(:supervisor_role)
111
+
112
+ technician_roles = UserRole.technicians
113
+
114
+ expect(technician_roles).to include technician_role
115
+ expect(technician_roles).not_to include supervisor_role
116
+
117
+ supervisor_roles = UserRole.supervisors
75
118
 
119
+ expect(supervisor_roles).to include supervisor_role
120
+ expect(supervisor_roles).not_to include technician_role
121
+ end
122
+
123
+ describe "adds validations to ensure guise attribute is" do
76
124
  it "present" do
77
125
  should validate_presence_of(:name)
78
126
  end
@@ -82,8 +130,24 @@ describe Guise do
82
130
  end
83
131
 
84
132
  it "is one of the guise names provided" do
85
- expect { create(:user_role, :name => 'Farmer') }.to raise_error ActiveRecord::RecordInvalid
133
+ expect { create(:user_role, name: 'Farmer') }.to raise_error ActiveRecord::RecordInvalid
86
134
  end
87
135
  end
88
136
  end
137
+
138
+ describe '.scoped_guise_of' do
139
+ it 'sets default scope' do
140
+ names = TechnicianUserRole.pluck(:name).uniq
141
+
142
+ expect(names).to eq ['Technician']
143
+ end
144
+
145
+ it 'sets up lifecycle callbacks to ensure the object is in the correct state' do
146
+ new_technician_role = TechnicianUserRole.new
147
+ created_technician_role = TechnicianUserRole.create!
148
+
149
+ expect(new_technician_role.name).to eq 'Technician'
150
+ expect(created_technician_role.name).to eq 'Technician'
151
+ end
152
+ end
89
153
  end
data/spec/spec_helper.rb CHANGED
@@ -1,17 +1,54 @@
1
+ require 'pry'
1
2
  require 'active_record'
2
3
  require 'factory_girl'
3
4
  require 'shoulda-matchers'
4
5
  require 'guise'
5
6
 
6
- require File.expand_path('../support/database.rb', __FILE__)
7
+ I18n.enforce_available_locales = false
7
8
 
8
- RSpec.configure do |config|
9
+ ActiveRecord::Base.establish_connection(
10
+ adapter: 'sqlite3',
11
+ database: ':memory:'
12
+ )
13
+
14
+ ActiveRecord::Migration.verbose = false
9
15
 
10
- config.before :suite do
11
- Database.create
12
- FactoryGirl.find_definitions
16
+ ActiveRecord::Schema.define do
17
+ create_table :users, force: true do |t|
18
+ t.string :name
19
+ t.string :email
13
20
  end
14
21
 
15
- config.include FactoryGirl::Syntax::Methods
22
+ create_table :user_roles, force: true do |t|
23
+ t.string :name
24
+ t.integer :person_id
25
+ end
26
+ end
27
+
28
+ class User < ActiveRecord::Base
29
+ has_guises :Technician, :Supervisor, association: :user_roles, attribute: :name, foreign_key: :person_id
30
+ end
31
+
32
+ class Technician < User
33
+ guise_of :User
34
+ end
35
+
36
+ class Supervisor < User
37
+ guise_of :User
38
+ end
39
+
40
+ class UserRole < ActiveRecord::Base
41
+ guise_for :User,
42
+ foreign_key: :person_id
43
+ end
44
+
45
+ class TechnicianUserRole < UserRole
46
+ scoped_guise_for :User
16
47
  end
17
48
 
49
+
50
+ FactoryGirl.find_definitions
51
+ RSpec.configure do |config|
52
+ config.order = 'random'
53
+ config.include FactoryGirl::Syntax::Methods
54
+ end