playhouse 0.1.1

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