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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7ee92d35c5cc99200df7de30aa77d0936c7a6b6f
4
+ data.tar.gz: 1bf5df285920713b73c523645869286351ee6440
5
+ SHA512:
6
+ metadata.gz: e17951ca5e9cbdc74c41b2a9d1a663943c7b92aa9c8d9e7c2614762d69576156c3e8dbbca453de3015a3a48ac6e120c9b64e0eb719239d7af976c586cb15a15c
7
+ data.tar.gz: 9b907d2714c1462d8210fb1c3e07d712fffbd212b5961389020c91264b2e2a8bd5aadcdd79b5727c837377b79aa1128b0d6eb938079c1a7bdcd91df942e3cd4c
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .idea
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.0.0-p195
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ rvm:
2
+ - "2.0.0"
3
+ script: "rake ci"
4
+ before_script: "rake setup_ci"
5
+ notifications:
6
+ hipchat:
7
+ rooms:
8
+ - 3d318fc3e1f401238a50171784b534@Craftworks General
9
+ template: '%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message} (<a href="%{build_url}">Details</a>/<a href="%{compare_url}">Change view</a>)'
10
+ format: html
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.1.1 - Feb 25
2
+
3
+ Adding the ability for contexts to inherit actors from a parent context. All methods on a play have a new [context]_with_parent method and the talent scout is now parent aware.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'rake'
4
+ gem 'playhouse', path: '.'
5
+ gem 'rspec', '2.14.1'
6
+ gem 'coveralls'
data/Gemfile.lock ADDED
@@ -0,0 +1,69 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ playhouse (0.1.1)
5
+ activerecord
6
+ activesupport
7
+ rake
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ activemodel (4.0.2)
13
+ activesupport (= 4.0.2)
14
+ builder (~> 3.1.0)
15
+ activerecord (4.0.2)
16
+ activemodel (= 4.0.2)
17
+ activerecord-deprecated_finders (~> 1.0.2)
18
+ activesupport (= 4.0.2)
19
+ arel (~> 4.0.0)
20
+ activerecord-deprecated_finders (1.0.3)
21
+ activesupport (4.0.2)
22
+ i18n (~> 0.6, >= 0.6.4)
23
+ minitest (~> 4.2)
24
+ multi_json (~> 1.3)
25
+ thread_safe (~> 0.1)
26
+ tzinfo (~> 0.3.37)
27
+ arel (4.0.1)
28
+ atomic (1.1.14)
29
+ builder (3.1.4)
30
+ colorize (0.5.8)
31
+ coveralls (0.6.3)
32
+ colorize
33
+ multi_json (~> 1.3)
34
+ rest-client
35
+ simplecov (>= 0.7)
36
+ thor
37
+ diff-lcs (1.2.5)
38
+ i18n (0.6.9)
39
+ mime-types (1.21)
40
+ minitest (4.7.5)
41
+ multi_json (1.7.3)
42
+ rake (10.0.4)
43
+ rest-client (1.6.7)
44
+ mime-types (>= 1.16)
45
+ rspec (2.14.1)
46
+ rspec-core (~> 2.14.0)
47
+ rspec-expectations (~> 2.14.0)
48
+ rspec-mocks (~> 2.14.0)
49
+ rspec-core (2.14.7)
50
+ rspec-expectations (2.14.4)
51
+ diff-lcs (>= 1.1.3, < 2.0)
52
+ rspec-mocks (2.14.4)
53
+ simplecov (0.7.1)
54
+ multi_json (~> 1.0)
55
+ simplecov-html (~> 0.7.1)
56
+ simplecov-html (0.7.1)
57
+ thor (0.18.0)
58
+ thread_safe (0.1.3)
59
+ atomic
60
+ tzinfo (0.3.38)
61
+
62
+ PLATFORMS
63
+ ruby
64
+
65
+ DEPENDENCIES
66
+ coveralls
67
+ playhouse!
68
+ rake
69
+ rspec (= 2.14.1)
data/README.md ADDED
@@ -0,0 +1,207 @@
1
+ #Playhouse
2
+
3
+ A framework for structing a ruby application using the DCI (Data, Context and Interaction)
4
+ pattern. Playhouse makes no assumptions about whether it's a web app (or any other sort),
5
+ it just helps you to structure your application logic. Playhouse is not used to structure
6
+ presentation logic, it is typically connected to some sort of delivery layer.
7
+
8
+ [![Code Climate](https://codeclimate.com/github/enspiral/playhouse.png)](https://codeclimate.com/github/enspiral/playhouse)
9
+ [![Build Status](https://travis-ci.org/enspiral/playhouse.png)](https://travis-ci.org/enspiral/playhouse)
10
+ [![Coverage Status](https://coveralls.io/repos/enspiral/playhouse/badge.png?branch=master)](https://coveralls.io/r/enspiral/playhouse)
11
+
12
+ ##Status
13
+
14
+ Playhouse is not yet at version 1.0.
15
+
16
+ It is being used for its first production apps now, but its interface may change rapidly and
17
+ at any point, so doing so is not advised unless you are actively involved in Playhouse
18
+ development.
19
+
20
+ ##Installation
21
+
22
+ ```ruby
23
+ gem 'playhouse', git: 'git://github.com/enspiral/playhouse.git'
24
+ ```
25
+
26
+ You may wish to organise your app such that the three main parts of the DCI pattern have their
27
+ own folders. We are currently using:
28
+
29
+ ```
30
+ lib/entities
31
+ lib/roles
32
+ lib/context
33
+ ```
34
+
35
+ ##Getting Started
36
+
37
+ There are three main parts of a Playhouse app, Entities, Roles and Contexts. Additionally,
38
+ there is some overall structure that makes it easy to create an entry point to the
39
+ application logic.
40
+
41
+ ###Entities
42
+
43
+ Entities are the "Data" part of DCI. They represent your Domain models that you probably
44
+ want to persist to a data store of some sort. To avoid the sort of complexity that often
45
+ occurs in models in Rails apps, Playhouse entities should have no functionality other than
46
+ defining their data structure and connecting to the persistance layer.
47
+
48
+ Playhouse does not care what persistance library you use. ActiveRecord works fine, just add
49
+ the gem to your app and start using it. We recommend you don't use validations (Contexts do
50
+ validations in Playhouse), keep relationships to necessary ones only, and don't use scopes
51
+ (queries go in Roles).
52
+
53
+ Playhouse actually has no Entity class. This is just a concept that you need to create yourself.
54
+
55
+ Entities are often used as Actors by Contexts. Actors can also be other basic types (or indeed
56
+ any object).
57
+
58
+ ### Roles
59
+
60
+ Roles are modules that are mixed into to Actors at runtime. Specifically note that they are
61
+ used to extend objects, not classes. If you're not familiar with this, go read up on DCI.
62
+
63
+ Playhouse defines a Role module to provide this behaviour, although it is implemented just
64
+ using Ruby's `extend` method. A role in your Playhouse app looks as follows:
65
+
66
+ ```ruby
67
+ require 'playhouse/role'
68
+
69
+ module YourApp
70
+ module TransferSource
71
+ include Playhouse::Role
72
+
73
+ actor_dependency :minimum_balance
74
+ actor_dependency :bank
75
+
76
+ def some_method
77
+ # do something
78
+ end
79
+ end
80
+ end
81
+ ```
82
+
83
+ Using a role is as simple as:
84
+
85
+ ```ruby
86
+ TransferSource.cast_actor(my_account)
87
+ ```
88
+
89
+ Although Contexts will do this for you automatically. Specifying actor dependencies on your
90
+ role is a good way of documenting the duck type that the role expects to extend. When you
91
+ call cast_actor, then it will raise an exception if the actor you supply does not support
92
+ the methods specified (minimum_balance and bank in the above example).
93
+
94
+ ###Contexts
95
+
96
+ Each of your contexts is a command that your app performs, which you could also think of as
97
+ a use case. In essence, a context is supplied with Actors, "casts" them in various Roles and
98
+ then executes some behaviour. In keeping with conventions of most people using DCI in ruby,
99
+ executing a context is done by calling its `call` method.
100
+
101
+ Playhouse provides a base Context class for you to derive from. Rather than implementing
102
+ `call` directly though, please override our `perform` method so that we can perform some
103
+ checks before your code executes. Here's an example.
104
+
105
+ ```ruby
106
+ require 'playhouse/context'
107
+ require 'economatic/roles/account_transaction_collection'
108
+ require 'economatic/entities/account'
109
+
110
+ module Economatic
111
+ class AccountBalanceEnquiry < Playhouse::Context
112
+ actor :account, role: AccountTransactionCollection, repository: Account
113
+
114
+ def perform
115
+ account.balance
116
+ end
117
+ end
118
+ end
119
+ ```
120
+ This Balance enquiry context is fairly simple. Your context perform method might have more
121
+ lines than this, and it might be good if it lists the main high level steps for
122
+ performing this feature. However, the serious application logic goes into your roles.
123
+
124
+ To calculate a balance, this context just needs one actor, an account, and it casts it
125
+ as a role (AccountTransactionCollection) which actually knows how to calculate a balance
126
+ by summing transactions. Actors are all required by default (unless you specify the
127
+ `optional: true` option), and so building this context without an account will raise an
128
+ exception. Specifying the Account repository can be used to find accounts, allows other
129
+ parts of Playhouse to build this Context by asking Account to fetch an account given an
130
+ id. Remember as well that the AccountTransactionCollection role will check that the account
131
+ has the methods it is dependent on.
132
+
133
+ The return value from your context is returned to the code calling your application
134
+ (which is often your delivery layer or another application), and we suggest that this
135
+ should be a fairly dumb object. Context should return data, you shouldn't use their return value
136
+ in ways that transform it, save data, etc.
137
+
138
+ ##An Interface to Your Application
139
+
140
+ The external interface of your application is essentially the Contexts that are available
141
+ to be called, although some Contexts might be just for calling from other Contexts. To
142
+ organise these a bit to present to the outside world, you can group these into an API
143
+ object which Playhouse calls a Play.
144
+
145
+ ```ruby
146
+ require 'playhouse/play'
147
+
148
+ module Economatic
149
+ class Play < Playhouse::Play
150
+ context AccountBalanceEnquiry
151
+ context ApproveTransfer
152
+ context BankBalanceEnquiry
153
+ context TransferMoney
154
+ end
155
+ end
156
+ ```
157
+
158
+ Contexts can be called via the play just as methods:
159
+
160
+ ```ruby
161
+ play = Economatic::Play.new
162
+ play.account_balance_enquiry(account: some_account_object)
163
+ ```
164
+
165
+ If you call a context this way, we also use our TalentScout to process the parameters you
166
+ supply and find actors if given ids, or build actors that are composed of several parts,
167
+ for example, this will work if calling via the play (but wouldn't work if you construct
168
+ the Context manually):
169
+
170
+ ```ruby
171
+ play.account_balance_enquiry(account_id: 1)
172
+ ```
173
+
174
+ The other advantage of a Play is that you can ask it about the context that it supports,
175
+ and the parts available for Actors in that context. This allows you to present structured
176
+ information about your API, such as auto-generating documentation.
177
+
178
+ ###A Delivery Layer
179
+
180
+ While you can call methods on a Play directly, often this will be done from some user input
181
+ of some sort. This layer knows about how you are delivering your app (as a JSON web service,
182
+ a console app, a GUI app, etc), and it knows about your application somewhat (often by
183
+ interrogating your Plays). However, your core application should never know about your
184
+ delivery layer(s). Even if you're expecting to build a web app, don't put web concepts
185
+ into your app, make it generic.
186
+
187
+ Playhouse doesn't do delivery layers for you, but it provides a known structure to allow
188
+ other gems to help you out with this. We suggest you first try out our playhouse-console
189
+ gem which provides you with a simple console app with one command for each Context.
190
+
191
+ For a web app, it's quite possible to use Sinatra or Rails as your delivery layer.
192
+
193
+ ##Licence
194
+
195
+ Playhouse is licenced under the MIT licence. Copyright 2013 Enspiral Services Ltd.
196
+
197
+ ##Contributing
198
+
199
+ Your contributions are welcome. Send us a pull request, or start a discussion in the github
200
+ issues first.
201
+
202
+ ##Credits
203
+
204
+ From Enspiral Craftworks:
205
+
206
+ * Craig Ambrose (@craigambrose)
207
+ * Joshua Vial (@joshuavial)
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ desc "Run specs"
4
+ RSpec::Core::RakeTask.new do |t|
5
+ end
6
+
7
+ desc "Setup this library to perform ci task"
8
+ task :setup_ci do
9
+ puts "nothing to setup"
10
+ end
11
+
12
+ desc "Test this console interface"
13
+ task :ci => [:spec] do
14
+ end
15
+
16
+ task default: :ci
@@ -0,0 +1,120 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+ require 'playhouse/part'
3
+ require 'playhouse/validation/actors_validator'
4
+
5
+ module Playhouse
6
+ class Context
7
+ class << self
8
+
9
+ def parts
10
+ @actor_definitions ||= []
11
+ end
12
+
13
+ def actor(name, options = {})
14
+ raise InvalidActorKeyError.new(self.class.name, name) unless name.is_a?(Symbol)
15
+
16
+ parts << Part.new(name, options)
17
+
18
+ define_method name do
19
+ @actors[name]
20
+ end
21
+ end
22
+
23
+ def part_for(name)
24
+ raise InvalidActorKeyError.new(self.class.name, name) unless name.is_a?(Symbol)
25
+ parts.detect {|definition| definition.name == name}
26
+ end
27
+
28
+ def method_name
29
+ context_name_parts.join('').underscore.to_sym
30
+ end
31
+
32
+ def http_method(method=:post)
33
+ @http_methods ||= []
34
+ if method.is_a?(Array)
35
+ @http_methods.concat method
36
+ else
37
+ @http_methods << method
38
+ end
39
+ end
40
+
41
+ def http_methods
42
+ return [:get] if @http_methods.nil?
43
+ @http_methods.uniq
44
+ end
45
+
46
+ private
47
+
48
+ def context_name_parts
49
+ name.split('::')[1..-1].reverse
50
+ end
51
+ end
52
+
53
+ def initialize(actors = {})
54
+ store_expected_actors(actors)
55
+ end
56
+
57
+ def inherit_actors_from(parent)
58
+ parent.send(:actors).each do |name, actor|
59
+ if actors[name].nil? && self.class.part_for(name)
60
+ store_actor name, actor
61
+ end
62
+ end
63
+ end
64
+
65
+ def call
66
+ validate_actors
67
+ cast_actors
68
+ perform
69
+ end
70
+
71
+ def perform
72
+ raise NotImplementedError.new("Context #{self.class.name} needs to override the perform method")
73
+ end
74
+
75
+ protected
76
+
77
+ def validator
78
+ ActorsValidator.new
79
+ end
80
+
81
+ private
82
+
83
+ def validate_actors
84
+ validator.validate_actors(self.class.parts, @actors)
85
+ end
86
+
87
+ def store_expected_actors(actors)
88
+ @actors = {}
89
+ actors.each do |name, actor|
90
+ store_actor name, actor
91
+ end
92
+ end
93
+
94
+ def store_actor(name, actor)
95
+ part = self.class.part_for(name)
96
+ if part
97
+ @actors[name] = actor
98
+ else
99
+ raise UnknownActorKeyError.new(self.class.name, name)
100
+ end
101
+ end
102
+
103
+ def cast_actors
104
+ @actors.each do |name, actor|
105
+ part = self.class.part_for(name)
106
+ @actors[name] = part.cast(actor)
107
+ end
108
+ end
109
+
110
+ def actors
111
+ @actors
112
+ end
113
+
114
+ def actors_except(*exceptions)
115
+ actors.reject do |key, value|
116
+ exceptions.include?(key)
117
+ end
118
+ end
119
+ end
120
+ end