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,29 @@
|
|
1
|
+
require 'playhouse/validation/required_actor_validator'
|
2
|
+
|
3
|
+
module Playhouse
|
4
|
+
class Part
|
5
|
+
attr_accessor :name, :repository, :role, :composer, :optional
|
6
|
+
|
7
|
+
def initialize(name, options = {})
|
8
|
+
@name = name
|
9
|
+
|
10
|
+
options.each do |key, value|
|
11
|
+
self.send("#{key}=", value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def cast(actor)
|
16
|
+
@role ? @role.cast_actor(actor) : actor
|
17
|
+
end
|
18
|
+
|
19
|
+
def required
|
20
|
+
!optional
|
21
|
+
end
|
22
|
+
|
23
|
+
def validators
|
24
|
+
result = []
|
25
|
+
result << RequiredActorValidator.new(part_name: name) if required
|
26
|
+
result
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
2
|
+
require 'playhouse/talent_scout'
|
3
|
+
|
4
|
+
module Playhouse
|
5
|
+
# A Play is a cohesive collection of application logic (in the form of Contexts).
|
6
|
+
# Your production might just contain one play, or you might prefer to have several
|
7
|
+
# plays each with their own theme. Contexts are typically called through a play rather
|
8
|
+
# that directly, and the Play will use it's TalentScout to ensure that the correct
|
9
|
+
# Actors are found.
|
10
|
+
#
|
11
|
+
# Contexts within your play can call each other, but should not call Contexts in other
|
12
|
+
# Plays directly, but can do so be calling the public interface on another Play.
|
13
|
+
class Play
|
14
|
+
class << self
|
15
|
+
def context(context_class)
|
16
|
+
context_classes << context_class
|
17
|
+
|
18
|
+
define_method context_class.method_name do |params|
|
19
|
+
execute_context(context_class, params)
|
20
|
+
end
|
21
|
+
|
22
|
+
define_method "#{context_class.method_name}_with_parent" do |parent, params|
|
23
|
+
execute_context_with_parent(parent, context_class, params)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def context_classes
|
28
|
+
@context_classes ||= []
|
29
|
+
end
|
30
|
+
|
31
|
+
def contexts_for(resource_module)
|
32
|
+
resource_module.constants.each do |constant|
|
33
|
+
context resource_module.const_get(constant)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(talent_scout = TalentScout.new)
|
39
|
+
@talent_scout = talent_scout
|
40
|
+
end
|
41
|
+
|
42
|
+
def commands
|
43
|
+
self.class.context_classes
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute_context(context_class, params)
|
47
|
+
@talent_scout.build_context(context_class, params).call
|
48
|
+
end
|
49
|
+
|
50
|
+
def execute_context_with_parent(parent, context_class, params)
|
51
|
+
@talent_scout.build_context_with_parent(parent, context_class, params)
|
52
|
+
end
|
53
|
+
|
54
|
+
def name
|
55
|
+
self.class.name.split('::').last.underscore
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'playhouse/support/default_hash_values'
|
2
|
+
|
3
|
+
module Playhouse
|
4
|
+
# A production is the complete collection of your application logic,
|
5
|
+
# and is nearly an application ready to run. You just need to supply
|
6
|
+
# a theatre and an interface and you can have a running app.
|
7
|
+
class Production
|
8
|
+
def initialize
|
9
|
+
@plays = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def run(options = {})
|
13
|
+
options.extend Support::DefaultHashValues
|
14
|
+
theatre = options.required_value :theatre
|
15
|
+
interface = options.required_value :interface
|
16
|
+
interface_instance = interface.build(self)
|
17
|
+
|
18
|
+
theatre.while_open do
|
19
|
+
interface_instance.run options[:interface_args]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Your production should inherit from this class and call this method
|
24
|
+
# in it's initialize method. This method is public just in case you
|
25
|
+
# want to build your production from the outside, but that's not really
|
26
|
+
# necessary or recommended.
|
27
|
+
def add_play(play_class)
|
28
|
+
@plays << play_class.new
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :plays
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Playhouse
|
2
|
+
module Role
|
3
|
+
class CastingException < Exception; end
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def cast_actor(actor)
|
11
|
+
check_depencies(actor)
|
12
|
+
actor.extend(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def cast_all(actor_collection)
|
16
|
+
actor_collection.map do |actor|
|
17
|
+
cast_actor(actor)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
class Dependency
|
24
|
+
def initialize(method_name, options)
|
25
|
+
@method_name = method_name
|
26
|
+
@default_role = options[:default_role]
|
27
|
+
end
|
28
|
+
|
29
|
+
def check(actor)
|
30
|
+
unless actor.respond_to?(@method_name)
|
31
|
+
if @default_role
|
32
|
+
@default_role.cast_actor(actor)
|
33
|
+
else
|
34
|
+
raise CastingException.new("Trying to cast #{actor.class.name} but it doesn't support method #{@method_name}")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def check_depencies(actor)
|
41
|
+
dependencies.each do |dependency|
|
42
|
+
dependency.check(actor)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def dependencies
|
47
|
+
@dependencies ||= []
|
48
|
+
end
|
49
|
+
|
50
|
+
def actor_dependency(method_name, options = {})
|
51
|
+
dependencies << Dependency.new(method_name, options)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'playhouse/scouts/can_construct_object'
|
2
|
+
|
3
|
+
module Playhouse
|
4
|
+
module Scouts
|
5
|
+
class BuildWithComposer
|
6
|
+
def actor_for_part(part, params)
|
7
|
+
if part.composer
|
8
|
+
param_value = params[part.name]
|
9
|
+
if param_value
|
10
|
+
part.composer.compose(param_value)
|
11
|
+
else
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
else
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def object_builder(part)
|
22
|
+
end
|
23
|
+
|
24
|
+
def param_suffix
|
25
|
+
"attributes"
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_method
|
29
|
+
:compose
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Playhouse
|
2
|
+
module Scouts
|
3
|
+
module CanConstructObject
|
4
|
+
def actor_for_part(part, params)
|
5
|
+
if object_builder(part)
|
6
|
+
param_key = "#{part.name}_#{param_suffix}".to_sym
|
7
|
+
param_value = params[param_key]
|
8
|
+
object_builder(part).send(build_method, param_value) if param_value
|
9
|
+
else
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'playhouse/scouts/can_construct_object'
|
2
|
+
|
3
|
+
module Playhouse
|
4
|
+
module Scouts
|
5
|
+
class EntityFromRepository
|
6
|
+
include CanConstructObject
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def object_builder(part)
|
11
|
+
part.repository
|
12
|
+
end
|
13
|
+
|
14
|
+
def param_suffix
|
15
|
+
"id"
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_method
|
19
|
+
:find
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Playhouse
|
2
|
+
module Support
|
3
|
+
module DefaultHashValues
|
4
|
+
def value_or_default(key, default)
|
5
|
+
value_or_do(key) { default }
|
6
|
+
end
|
7
|
+
|
8
|
+
def value_or_error(key, error)
|
9
|
+
value_or_do(key) { raise(error) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def required_value(key)
|
13
|
+
value_or_error(key, ArgumentError.new("Missing required argument #{key}"))
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def value_or_do(key)
|
19
|
+
self[key].nil? ? yield : self[key]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'playhouse/scouts/build_with_composer'
|
2
|
+
require 'playhouse/scouts/direct_value'
|
3
|
+
require 'playhouse/scouts/entity_from_repository'
|
4
|
+
|
5
|
+
# Finds the actors for a context. The name of this class
|
6
|
+
# might be a bit creative since I felt the need to explain it.
|
7
|
+
module Playhouse
|
8
|
+
class TalentScout
|
9
|
+
def build_context(context_class, params)
|
10
|
+
context_class.new(actors_for_params(context_class, params))
|
11
|
+
end
|
12
|
+
|
13
|
+
def build_context_with_parent(parent, context_class, params)
|
14
|
+
context = build_context context_class, params
|
15
|
+
context.inherit_actors_from parent
|
16
|
+
context
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def actors_for_params(context_class, params)
|
22
|
+
actors = {}
|
23
|
+
context_class.parts.each do |part|
|
24
|
+
actor = actor_for_part(part, params)
|
25
|
+
actors[part.name] = actor if actor
|
26
|
+
end
|
27
|
+
actors
|
28
|
+
end
|
29
|
+
|
30
|
+
def scouts
|
31
|
+
[Scouts::BuildWithComposer, Scouts::DirectValue, Scouts::EntityFromRepository].map(&:new)
|
32
|
+
end
|
33
|
+
|
34
|
+
def actor_for_part(part, params)
|
35
|
+
scouts.each do |scout|
|
36
|
+
actor = scout.actor_for_part(part, params)
|
37
|
+
return actor if actor
|
38
|
+
end
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'active_record'
|
3
|
+
require 'playhouse/support/default_hash_values'
|
4
|
+
|
5
|
+
module Playhouse
|
6
|
+
class Theatre
|
7
|
+
class DoubleBookedError < Exception; end
|
8
|
+
|
9
|
+
attr_reader :root_path
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_reader :current
|
13
|
+
|
14
|
+
def current=(value)
|
15
|
+
if @current
|
16
|
+
raise DoubleBookedError.new("You already have #{@current.inspect} staged")
|
17
|
+
end
|
18
|
+
@current = value
|
19
|
+
end
|
20
|
+
|
21
|
+
def clear_current
|
22
|
+
@current = nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(options = {})
|
27
|
+
options.extend Support::DefaultHashValues
|
28
|
+
|
29
|
+
@environment = options.value_or_error :environment, ArgumentError.new("environment option is required")
|
30
|
+
@root_path = options.value_or_default :root, Dir.pwd
|
31
|
+
@load_db = options.value_or_default :load_db, true
|
32
|
+
end
|
33
|
+
|
34
|
+
def while_open
|
35
|
+
open
|
36
|
+
begin
|
37
|
+
yield
|
38
|
+
ensure
|
39
|
+
close
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def open
|
44
|
+
Dir.chdir @root_path
|
45
|
+
connect_to_database
|
46
|
+
self.class.current = self
|
47
|
+
end
|
48
|
+
|
49
|
+
def close
|
50
|
+
self.class.clear_current
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def connect_to_database
|
56
|
+
if @load_db
|
57
|
+
db_params = YAML.load(File.read(root_path + "/config/database.yml"))[@environment.to_s]
|
58
|
+
ActiveRecord::Base.establish_connection db_params
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'playhouse/validation/validation_errors'
|
2
|
+
|
3
|
+
module Playhouse
|
4
|
+
class ActorsValidator
|
5
|
+
def validate_actors(parts, actors)
|
6
|
+
error = ContextValidationError.new
|
7
|
+
raise_error = false
|
8
|
+
|
9
|
+
parts.each do |part|
|
10
|
+
part.validators.each do |validator|
|
11
|
+
begin
|
12
|
+
validator.validate_actor(actors[part.name])
|
13
|
+
rescue ActorValidationError => actor_error
|
14
|
+
raise_error = true
|
15
|
+
error.add_to_part(part.name, actor_error)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
raise error if raise_error
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Playhouse
|
2
|
+
class ActorValidator
|
3
|
+
def initialize(options)
|
4
|
+
@part_name = options[:part_name]
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
attr_accessor :part_name
|
10
|
+
end
|
11
|
+
|
12
|
+
class RequiredActorValidator < ActorValidator
|
13
|
+
def validate_actor(actor)
|
14
|
+
raise RequiredActorMissing.new(part_name: part_name) if actor.nil?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|