playhouse 0.1.1
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +69 -0
- data/README.md +207 -0
- data/Rakefile +16 -0
- data/lib/playhouse/context.rb +120 -0
- data/lib/playhouse/part.rb +29 -0
- data/lib/playhouse/play.rb +59 -0
- data/lib/playhouse/production.rb +33 -0
- data/lib/playhouse/role.rb +55 -0
- data/lib/playhouse/scouts/build_with_composer.rb +33 -0
- data/lib/playhouse/scouts/can_construct_object.rb +15 -0
- data/lib/playhouse/scouts/direct_value.rb +9 -0
- data/lib/playhouse/scouts/entity_from_repository.rb +23 -0
- data/lib/playhouse/support/default_hash_values.rb +23 -0
- data/lib/playhouse/support/files.rb +7 -0
- data/lib/playhouse/talent_scout.rb +42 -0
- data/lib/playhouse/theatre.rb +62 -0
- data/lib/playhouse/validation/actors_validator.rb +23 -0
- data/lib/playhouse/validation/required_actor_validator.rb +17 -0
- data/lib/playhouse/validation/validation_errors.rb +76 -0
- data/playhouse.gemspec +20 -0
- data/spec/playhouse/context_spec.rb +111 -0
- data/spec/playhouse/part_spec.rb +19 -0
- data/spec/playhouse/play_spec.rb +64 -0
- data/spec/playhouse/production_spec.rb +47 -0
- data/spec/playhouse/role_spec.rb +72 -0
- data/spec/playhouse/support/default_hash_values_spec.rb +34 -0
- data/spec/playhouse/talent_scout_spec.rb +95 -0
- data/spec/playhouse/theatre_spec.rb +34 -0
- data/spec/playhouse/validation/actors_validator_spec.rb +38 -0
- data/spec/spec_helper.rb +5 -0
- metadata +133 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
module Playhouse
|
2
|
+
class ContextUsageError < ArgumentError
|
3
|
+
end
|
4
|
+
|
5
|
+
class ActorKeyError < ContextUsageError
|
6
|
+
def initialize(context_name, actor_name)
|
7
|
+
@context_name = context_name
|
8
|
+
@actor_name = actor_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def description
|
12
|
+
message
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class InvalidActorKeyError < ActorKeyError
|
17
|
+
def message
|
18
|
+
"Actor key #{@actor_name.inspect} is not a symbol, for #{@context_name}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class UnknownActorKeyError < ActorKeyError
|
23
|
+
def message
|
24
|
+
"Unknown actor #{@actor_name.inspect} for #{@context_name}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class ContextValidationError < Exception
|
29
|
+
def initialize(*params)
|
30
|
+
@part_errors = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def for_part(part_name)
|
34
|
+
@part_errors[part_name] || []
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_to_part(part_name, error)
|
38
|
+
@part_errors[part_name] ||= []
|
39
|
+
@part_errors[part_name] << error
|
40
|
+
end
|
41
|
+
|
42
|
+
def part_errors
|
43
|
+
@part_errors
|
44
|
+
end
|
45
|
+
|
46
|
+
def message
|
47
|
+
part_messages.join("\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def part_messages
|
53
|
+
@part_errors.map do |part_name, errors|
|
54
|
+
[part_name, errors.map(&:message).join(', ')].join(': ')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class ActorValidationError < Exception
|
60
|
+
def initialize(options)
|
61
|
+
@part_name = options[:part_name]
|
62
|
+
end
|
63
|
+
|
64
|
+
attr_reader :part_name
|
65
|
+
end
|
66
|
+
|
67
|
+
class RequiredActorMissing < ActorValidationError
|
68
|
+
def message
|
69
|
+
"Missing actor #{@part_name}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def description
|
73
|
+
"You must specify one of these"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/playhouse.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "playhouse"
|
3
|
+
s.version = '0.1.1'
|
4
|
+
s.authors = ["Craig Ambrose", "Joshua Vial"]
|
5
|
+
s.email = ["craig@enspiral.com", "joshua@enspiral.com"]
|
6
|
+
s.homepage = "https://github.com/enspiral/economatic"
|
7
|
+
s.summary = "A DCI framework"
|
8
|
+
s.description = "Provides one possible way of implementing a DCI architecture in a ruby app"
|
9
|
+
|
10
|
+
s.require_paths = %w(lib)
|
11
|
+
|
12
|
+
s.files = `git ls-files`.split($/)
|
13
|
+
s.test_files = s.files.grep(%r{^(spec)/})
|
14
|
+
|
15
|
+
s.required_ruby_version = '>= 1.9.2'
|
16
|
+
|
17
|
+
s.add_dependency 'rake'
|
18
|
+
s.add_dependency 'activesupport'
|
19
|
+
s.add_dependency 'activerecord'
|
20
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'playhouse/context'
|
3
|
+
|
4
|
+
module Playhouse
|
5
|
+
describe Context do
|
6
|
+
before do
|
7
|
+
class ExampleContext < Context
|
8
|
+
attr_accessor :performed
|
9
|
+
|
10
|
+
def perform
|
11
|
+
self.performed = true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
Playhouse.class_eval{remove_const :ExampleContext}
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'constructing' do
|
21
|
+
it 'does not allow an unspecified actor to be stored' do
|
22
|
+
expect { ExampleContext.new(foobar: 'value') }.to raise_error(ArgumentError)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '.actor' do
|
27
|
+
let(:role) { double(:role) }
|
28
|
+
|
29
|
+
it 'allows the actor to be passed into the constructor and stored' do
|
30
|
+
ExampleContext.actor :foobar
|
31
|
+
subject = ExampleContext.new(foobar: 'value')
|
32
|
+
subject.foobar.should == 'value'
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'extends the actor by the specified role' do
|
36
|
+
ExampleContext.actor :foobar, role: role
|
37
|
+
role.should_receive(:cast_actor).with('value').and_return('cast value')
|
38
|
+
subject = ExampleContext.new(foobar: 'value')
|
39
|
+
subject.call
|
40
|
+
subject.foobar.should == 'cast value'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#inherit_actors_from' do
|
45
|
+
before do
|
46
|
+
class ParentContext < Context
|
47
|
+
actor :current_user
|
48
|
+
actor :other_actor
|
49
|
+
end
|
50
|
+
@parent = ParentContext.new current_user: 'user', other_actor: 'other'
|
51
|
+
ExampleContext.actor :current_user
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'loads actors that are unset' do
|
55
|
+
subject = ExampleContext.new
|
56
|
+
subject.inherit_actors_from @parent
|
57
|
+
expect(subject.current_user).to eq 'user'
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'does not load actors that are already set' do
|
61
|
+
subject = ExampleContext.new current_user: 'user override'
|
62
|
+
subject.inherit_actors_from @parent
|
63
|
+
expect(subject.current_user).to eq 'user override'
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'does not load actors for which it has no part' do
|
67
|
+
subject = ExampleContext.new
|
68
|
+
subject.inherit_actors_from @parent
|
69
|
+
expect{subject.other_actor}.to raise_error
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#call' do
|
74
|
+
it 'calls perform' do
|
75
|
+
subject = ExampleContext.new
|
76
|
+
subject.call
|
77
|
+
|
78
|
+
subject.performed.should be_true
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'does not raise an error if an optional actor is not supplied' do
|
82
|
+
ExampleContext.actor :foobar, optional: true
|
83
|
+
subject = ExampleContext.new
|
84
|
+
subject.call
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'raises an error if a required actor is not supplied' do
|
88
|
+
ExampleContext.actor :foobar
|
89
|
+
subject = ExampleContext.new
|
90
|
+
expect {
|
91
|
+
subject.call
|
92
|
+
}.to raise_error
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe 'http_method' do
|
97
|
+
it 'defaults to :get' do
|
98
|
+
expect(ExampleContext.http_methods).to include(:get)
|
99
|
+
end
|
100
|
+
it 'can be set one at a time' do
|
101
|
+
ExampleContext.http_method :post
|
102
|
+
expect(ExampleContext.http_methods).to include(:post)
|
103
|
+
end
|
104
|
+
it 'can be set with an array' do
|
105
|
+
ExampleContext.http_method [:get, :post]
|
106
|
+
expect(ExampleContext.http_methods).to include(:get)
|
107
|
+
expect(ExampleContext.http_methods).to include(:post)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'playhouse/part'
|
3
|
+
|
4
|
+
module Playhouse
|
5
|
+
describe Part do
|
6
|
+
subject { Part.new(:amount) }
|
7
|
+
|
8
|
+
describe "#validators" do
|
9
|
+
it "is empty for an optional part" do
|
10
|
+
subject.optional = true
|
11
|
+
subject.validators.should == []
|
12
|
+
end
|
13
|
+
|
14
|
+
it "includes a required actor validator if not optional" do
|
15
|
+
subject.validators.first.should be_a(RequiredActorValidator)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'playhouse/context'
|
3
|
+
require 'playhouse/play'
|
4
|
+
|
5
|
+
module Playhouse
|
6
|
+
describe Play do
|
7
|
+
before do
|
8
|
+
class CalculateTax < Context
|
9
|
+
end
|
10
|
+
|
11
|
+
class ExampleAPI < Play
|
12
|
+
context CalculateTax
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
Playhouse.class_eval{remove_const :ExampleAPI}
|
18
|
+
Playhouse.class_eval{remove_const :CalculateTax}
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'when instantiated' do
|
22
|
+
subject { ExampleAPI.new }
|
23
|
+
let(:context) { double(:context) }
|
24
|
+
|
25
|
+
it 'presents contexts as callable methods' do
|
26
|
+
CalculateTax.actor :taxable_income
|
27
|
+
CalculateTax.should_receive(:new).with(taxable_income: 123).and_return(context)
|
28
|
+
context.should_receive(:call)
|
29
|
+
|
30
|
+
#subject.respond_to?(:calculate_tax).should be_true
|
31
|
+
subject.calculate_tax taxable_income: 123
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'presents callable methods with parent' do
|
35
|
+
parent = double(:context)
|
36
|
+
expect(subject).to receive(:execute_context_with_parent)
|
37
|
+
|
38
|
+
subject.calculate_tax_with_parent parent, {}
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'has a name' do
|
42
|
+
expect(subject.name).to eq('example_api')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'resource' do
|
47
|
+
before do
|
48
|
+
module ExampleResource
|
49
|
+
class Context1 < Context
|
50
|
+
end
|
51
|
+
class Context2 < Context
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
it 'loads all contexts inside a module' do
|
56
|
+
ExampleAPI.stub(:context)
|
57
|
+
expect(ExampleAPI).to receive(:context).with(ExampleResource::Context1)
|
58
|
+
expect(ExampleAPI).to receive(:context).with(ExampleResource::Context2)
|
59
|
+
ExampleAPI.contexts_for ExampleResource
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'playhouse/production'
|
3
|
+
|
4
|
+
module Playhouse
|
5
|
+
describe Production do
|
6
|
+
let(:theatre) { double(:theatre) }
|
7
|
+
let(:interface) { double(:interface, build: instance) }
|
8
|
+
let(:instance) { double(:interface_instance) }
|
9
|
+
|
10
|
+
describe "#run" do
|
11
|
+
it "requires a theatre and an interface" do
|
12
|
+
expect { subject.run(theatre: theatre) }.to raise_error(ArgumentError)
|
13
|
+
expect { subject.run(interface: interface) }.to raise_error(ArgumentError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "opens the theatre" do
|
17
|
+
theatre.should_receive :while_open
|
18
|
+
subject.run(theatre: theatre, interface: interface)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "runs the interface" do
|
22
|
+
theatre.stub(:while_open).and_yield
|
23
|
+
interface.should_receive(:build).with(subject).and_return(instance)
|
24
|
+
instance.should_receive(:run)
|
25
|
+
|
26
|
+
subject.run(theatre: theatre, interface: interface)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "passes optional interface_args parameter through to interface" do
|
30
|
+
theatre.stub(:while_open).and_yield
|
31
|
+
instance.should_receive(:run).with("args")
|
32
|
+
|
33
|
+
subject.run(theatre: theatre, interface: interface, interface_args: "args")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#plays" do
|
38
|
+
let(:play_class) { double(:play_class, new: play) }
|
39
|
+
let(:play) { double(:play) }
|
40
|
+
|
41
|
+
it "has a collection of plays which can be added" do
|
42
|
+
subject.add_play play_class
|
43
|
+
subject.plays.should == [play]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'playhouse/role'
|
3
|
+
|
4
|
+
module Playhouse
|
5
|
+
describe Role do
|
6
|
+
module ExampleRole
|
7
|
+
include Role
|
8
|
+
|
9
|
+
actor_dependency :dingbat
|
10
|
+
|
11
|
+
def foobar
|
12
|
+
'awesome!'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class DingbatActor
|
17
|
+
def dingbat
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
subject { ExampleRole }
|
22
|
+
let(:valid_actor) { DingbatActor.new }
|
23
|
+
let(:invalid_actor) { Object.new }
|
24
|
+
|
25
|
+
context "when casting as" do
|
26
|
+
it "adds foobar method to actor" do
|
27
|
+
player = subject.cast_actor(valid_actor)
|
28
|
+
player.foobar.should == 'awesome!'
|
29
|
+
end
|
30
|
+
|
31
|
+
context "without a default role" do
|
32
|
+
it "raises exception if an actor dependency is not met" do
|
33
|
+
expect {
|
34
|
+
subject.cast_actor(invalid_actor)
|
35
|
+
}.to raise_error
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "with a default role" do
|
40
|
+
subject { ExampleRoleWithDefault }
|
41
|
+
|
42
|
+
module DingbatProviderRole
|
43
|
+
include Role
|
44
|
+
def dingbat
|
45
|
+
end
|
46
|
+
end
|
47
|
+
module ExampleRoleWithDefault
|
48
|
+
include Role
|
49
|
+
|
50
|
+
actor_dependency :dingbat, default_role: DingbatProviderRole
|
51
|
+
|
52
|
+
def foobar
|
53
|
+
'awesome!'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it "auto casts actor with default role" do
|
58
|
+
player = subject.cast_actor(invalid_actor)
|
59
|
+
player.foobar
|
60
|
+
player.dingbat
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when casting an enumerable as" do
|
66
|
+
it "extends each member of the enumerable" do
|
67
|
+
players = subject.cast_all([valid_actor])
|
68
|
+
players.first.foobar.should == 'awesome!'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'playhouse/support/default_hash_values'
|
3
|
+
|
4
|
+
module Playhouse
|
5
|
+
module Support
|
6
|
+
describe DefaultHashValues do
|
7
|
+
def subject_with(data)
|
8
|
+
data.extend(DefaultHashValues)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#value_or_default" do
|
12
|
+
it "returns the value if it is not nil" do
|
13
|
+
subject_with(key: 1).value_or_default(:key, 'fish').should == 1
|
14
|
+
subject_with(key: false).value_or_default(:key, 'fish').should == false
|
15
|
+
end
|
16
|
+
|
17
|
+
it "returns the default if the value is nil" do
|
18
|
+
subject_with(key: nil).value_or_default(:key, 'fish').should == 'fish'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#value_or_error" do
|
23
|
+
it "returns the value if it is not nil" do
|
24
|
+
subject_with(key: 1).value_or_error(:key, 'fish').should == 1
|
25
|
+
subject_with(key: false).value_or_error(:key, 'fish').should == false
|
26
|
+
end
|
27
|
+
|
28
|
+
it "raises the error if the value is nil" do
|
29
|
+
expect { subject_with(key: nil).value_or_error(:key, 'fish') }.to raise_error('fish')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|