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.
@@ -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,9 @@
1
+ module Playhouse
2
+ module Scouts
3
+ class DirectValue
4
+ def actor_for_part(part, params)
5
+ params[part.name]
6
+ end
7
+ end
8
+ end
9
+ 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,7 @@
1
+ def require_all(*path_parts)
2
+ path = File.join(path_parts)
3
+ files = Dir.glob(path)
4
+ files.each do |file|
5
+ require file
6
+ end
7
+ 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