guise 0.2.3 → 0.3.0

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