payload 0.1.0

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: c9e68c664efe4b32e13624aa82380db246b9774e
4
+ data.tar.gz: 4cf13b1014a69ec3d3faeb9d1e0fea439c33bb83
5
+ SHA512:
6
+ metadata.gz: 6ca9dcd639d3c26d9f9e0598343dc0f83ed702ac303206788bbee9505240e5a4b357823a9395f1cb84ada129c4125117cfb0c3f89fd66238ca9999864d2c8876
7
+ data.tar.gz: 88b884efc4ebd0555b7eda0edaea9a2310d01f184ffa9a57c90a3803f9be557840926dbcb2c4b34579be973882d2853e5e3766eac3186b7f181f0d615605e0ba
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .bundle
2
+ .yardoc
3
+ doc
4
+ pkg
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,10 @@
1
+ If you'd like to contribute:
2
+
3
+ 1. Fork the [official repository](https://github.com/thoughtbot/payload).
4
+ 2. Make your changes in a topic branch.
5
+ 3. Submit a pull request.
6
+
7
+ Notes:
8
+
9
+ * Contributions without tests won't be accepted.
10
+ * Please don't update the Gem version.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ payload (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.2.5)
10
+ rack (1.5.2)
11
+ rake (10.3.2)
12
+ rspec (2.14.1)
13
+ rspec-core (~> 2.14.0)
14
+ rspec-expectations (~> 2.14.0)
15
+ rspec-mocks (~> 2.14.0)
16
+ rspec-core (2.14.8)
17
+ rspec-expectations (2.14.5)
18
+ diff-lcs (>= 1.1.3, < 2.0)
19
+ rspec-mocks (2.14.6)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ payload!
26
+ rack
27
+ rake
28
+ rspec (~> 2.14.1)
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014 Joe Ferris and thoughtbot, inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,242 @@
1
+ # Payload
2
+
3
+ Payload is a lightweight framework for specifying, injecting, and using
4
+ dependencies. It facilitates run-time assembly of dependencies and makes it
5
+ plausible to use inversion of control beyond the controller level. It also
6
+ attempts to remove boilerplate code for common patterns such as defining
7
+ factories and applying decorators.
8
+
9
+ Overview
10
+ --------
11
+
12
+ The framework makes it easy to define dependencies from application code without
13
+ resorting to singletons, constants, or globals. It also won't cause any
14
+ class-reloading issues during development.
15
+
16
+ Define simple dependencies in `config/dependencies.rb` using services:
17
+
18
+ service :payment_client do |container|
19
+ PaymentClient.new(ENV['PAYMENT_HOST'])
20
+ end
21
+
22
+ You can inject dependencies into controllers.
23
+
24
+ For example, in `app/controllers/payments_controller.rb`:
25
+
26
+ class PaymentsController < ApplicationController
27
+ def create
28
+ payment = Payment.new(params[:payment], dependencies[:payment_client])
29
+ receipt = payment.process
30
+ redirect_to receipt
31
+ end
32
+ end
33
+
34
+ You can easily test this dependency in a controller spec:
35
+
36
+ describe PaymentsController do
37
+ describe '#create' do
38
+ it 'processes a payment' do
39
+ payment_params = :product_id => '123', amount: '25'
40
+ client = stub_service(:payment_client)
41
+ payment = double('payment', process: true)
42
+ Payment.stub(:new).with(payment_params, client).and_return(payment)
43
+
44
+ post :create, payment_params
45
+
46
+ expect(payment).to have_received(:process)
47
+ end
48
+ end
49
+ end
50
+
51
+ You can further invert control and use factories to hide low-level dependencies
52
+ from controllers entirely.
53
+
54
+ In `config/dependencies.rb`:
55
+
56
+ factory :payment do |container|
57
+ Payment.new(container[:attributes], container[:payment_client])
58
+ end
59
+
60
+ In `app/controllers/payments_controller.rb`:
61
+
62
+ class PaymentsController < ApplicationController
63
+ def create
64
+ payment = dependencies[:payment].new(attributes: params[:payment])
65
+ payment.process
66
+ redirect_to payment
67
+ end
68
+ end
69
+
70
+ You can also stub factories in tests:
71
+
72
+ describe PaymentsController do
73
+ describe '#create' do
74
+ it 'processes a payment' do
75
+ payment_params = :product_id => '123', amount: '25'
76
+ payment = stub_factory_instance(:payment, attributes: payment_params)
77
+
78
+ post :create, payment_params
79
+
80
+ expect(payment).to have_received(:process)
81
+ end
82
+ end
83
+ end
84
+
85
+ The controller and its tests are now completely ignorant of `payment_client`,
86
+ and deal only with the collaborator they need: `payment`.
87
+
88
+ Setup
89
+ -----
90
+
91
+ Add payload to your Gemfile:
92
+
93
+ gem 'payload'
94
+
95
+ To access dependencies from controllers, include the `Controller` module:
96
+
97
+ class ApplicationController < ActionController::Base
98
+ include Payload::Controller
99
+ end
100
+
101
+ Specifying Dependencies
102
+ -----------------------
103
+
104
+ Edit `config/dependencies.rb` to specify dependencies.
105
+
106
+ Use the `service` method to define dependencies which can be fully instantiated
107
+ during application bootup:
108
+
109
+ service :payment_client do |container|
110
+ PaymentClient.new(ENV['PAYMENT_HOST'])
111
+ end
112
+
113
+ Other dependencies are accessible from the container:
114
+
115
+ service :payment_notifier do |container|
116
+ PaymentNotifier.new(container[:mailer])
117
+ end
118
+
119
+ Use the `factory` method to define dependencies which require dependencies from
120
+ the container as well as runtime state which varies per-request:
121
+
122
+ factory :payment do |container|
123
+ Payment.new(container[:attributes], container[:payment_client])
124
+ end
125
+
126
+ The container for factory definitions contains all dependencies defined on the
127
+ container as well as dependencies provided when instantiating the factory.
128
+
129
+ Use the `decorate` method to extend or replace a previously defined dependency:
130
+
131
+ decorate :payment do |payment, container|
132
+ NotifyingPayment.new(payment, container[:payment_notifier])
133
+ end
134
+
135
+ Decorated dependencies have access to other dependencies through the container,
136
+ as well as the current definition for that dependency.
137
+
138
+ Using Dependencies
139
+ ------------------
140
+
141
+ The Railtie inserts middleware into the stack which will inject a container into
142
+ the Rack environment for each request. This is available as `dependencies` in
143
+ controllers and `env[:dependencies]` in the Rack stack.
144
+
145
+ Use `[]` to access services:
146
+
147
+ class PaymentsController < ApplicationController
148
+ def create
149
+ dependencies[:payment_client].charge(params[:amount])
150
+ redirect_to payments_path
151
+ end
152
+ end
153
+
154
+ Use `new` to instantiate dependencies from factories:
155
+
156
+ class PaymentsController < ApplicationController
157
+ def create
158
+ payment = dependencies[:payment].new(attributes: params[:payment])
159
+ payment.process
160
+ redirect_to payment
161
+ end
162
+ end
163
+
164
+ The `new` method accepts a `Hash`. Each element of the `Hash` will be accessible
165
+ from the container in `factory` definitions.
166
+
167
+ Grouping Dependencies
168
+ ---------------------
169
+
170
+ You can enforce simplicity in your dependency graph by grouping dependencies and
171
+ explicitly exporting only the dependencies you need to expose to the application
172
+ layer.
173
+
174
+ For example, you can specify payment dependencies in
175
+ `config/dependencies/payments.rb`:
176
+
177
+ service :payment_client do |container|
178
+ PaymentClient.new(ENV['PAYMENT_HOST'])
179
+ end
180
+
181
+ service :payment_notifier do |container|
182
+ PaymentNotifier.new(container[:mailer])
183
+ end
184
+
185
+ factory :payment do |container|
186
+ Payment.new(container[:attributes], container[:payment_client])
187
+ end
188
+
189
+ decorate :payment do |payment, container|
190
+ NotifyingPayment.new(payment, container[:payment_notifier])
191
+ end
192
+
193
+ export :payment
194
+
195
+ In this example, the final, decorated `:payment` dependency will be available in
196
+ controllers, but `:payment_client` and `:payment_notifier` will not.
197
+
198
+ You can use this approach to hide low-level dependencies behind a facade and
199
+ only expose the facade to the application layer.
200
+
201
+ Testing
202
+ -------
203
+
204
+ To activate testing support, require and mix in the `Testing` module:
205
+
206
+ require 'dependencies/testing'
207
+
208
+ RSpec.configure do |config|
209
+ config.include Payload::Testing
210
+ end
211
+
212
+ During integration tests, the fully configured container will be used. During
213
+ controller tests, an empty container will be initialized for each test. Tests
214
+ can inject the dependencies they need for each interaction.
215
+
216
+ This module provides two useful methods:
217
+
218
+ * `stub_service`: Injects a stubbed service into the test container and returns
219
+ it.
220
+ * `stub_factory_instance`: Finds or injects a stubbed factory into the test
221
+ container and expects an instance to be created with the given attributes.
222
+
223
+ Contributing
224
+ ------------
225
+
226
+ Please see the [contribution guidelines].
227
+
228
+ [contribution guidelines]: https://github.com/thoughtbot/payload/blob/master/CONTRIBUTING.md
229
+
230
+ License
231
+ -------
232
+
233
+ ![thoughtbot](http://thoughtbot.com/assets/tm/logo.png)
234
+
235
+ Payload is Copyright © 2014 Joe Ferris and thoughtbot. It is free software, and
236
+ may be redistributed under the terms specified in the [LICENSE] file.
237
+
238
+ [LICENSE]: https://github.com/thoughtbot/payload/blob/master/LICENSE
239
+
240
+ Payload is maintained and funded by [thoughtbot, inc](http://thoughtbot.com/).
241
+
242
+ The names and logos for thoughtbot are trademarks of thoughtbot, inc.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'bundler/gem_tasks'
4
+ require 'rake'
5
+ require 'rspec/core/rake_task'
6
+
7
+ desc 'Default: run specs'
8
+ task :default => [:spec]
9
+
10
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,96 @@
1
+ require 'payload/definition_list'
2
+ require 'payload/factory_definition'
3
+ require 'payload/service_definition'
4
+
5
+ module Payload
6
+ # Used for configuring and resolving dependencies.
7
+ #
8
+ # @see Railtie Railtie to configure and use dependencies in Rails
9
+ # applications.
10
+ # @see RackContainer RackContainer to inject a container into Rack requests.
11
+ # @see MutableContainer MutableContainer to define dependencies in
12
+ # configuration files.
13
+ class Container
14
+ # Used internally by {RailsLoader}.
15
+ #
16
+ # @api private
17
+ # @param [DefinitionList] definitions previously defined definitions.
18
+ def initialize(definitions = DefinitionList.new)
19
+ @definitions = definitions
20
+ end
21
+
22
+ # Extends or replaces an existing dependency definition.
23
+ #
24
+ # @param dependency [Symbol] the name of the dependency to decorate.
25
+ # @yield [Container] the resolved container.
26
+ # @yieldreturn the decorated instance.
27
+ def decorate(dependency, &block)
28
+ decorated = @definitions.find(dependency).decorate(block)
29
+ define dependency, decorated
30
+ end
31
+
32
+ # Defines a factory which can be used to instantiate the dependency. Useful
33
+ # if some dependencies come from the container but others come from runtime
34
+ # state.
35
+ #
36
+ # Resolving the dependency will return an object which responds to `new`.
37
+ # The `new` method will accept remaining dependencies and return the fully
38
+ # resolved dependency from the given block.
39
+ #
40
+ # @param dependency [Symbol] the name of the dependency to define.
41
+ # @yield [Container] the resolved container; any arguments to `new` will be
42
+ # added to the container.
43
+ # @yieldreturn an instance of your dependency.
44
+ def factory(dependency, &block)
45
+ define dependency, FactoryDefinition.new(block)
46
+ end
47
+
48
+ # Defines a service which can be fully resolved from the container.
49
+ #
50
+ # @param dependency [Symbol] the name of the dependency to define.
51
+ # @yield [Container] the resolved container.
52
+ # @yieldreturn the instantiated service.
53
+ def service(dependency, &block)
54
+ define dependency, ServiceDefinition.new(block)
55
+ end
56
+
57
+ # Resolves and returns dependency.
58
+ #
59
+ # @param dependency [Symbol] the name of the dependency to resolve.
60
+ # @raise [UndefinedDependencyError] for undefined dependencies.
61
+ def [](dependency)
62
+ @definitions.find(dependency).resolve(self)
63
+ end
64
+
65
+ # Exports dependencies which can be imported into another container.
66
+ #
67
+ # Used internally by {MutableContainer}. Use {MutableContainer#export}.
68
+ #
69
+ # @api private
70
+ # @param names [Array<Symbol>] dependencies to export.
71
+ # @return [DependencyList] exported dependencies.
72
+ def export(*names)
73
+ @definitions.export(names)
74
+ end
75
+
76
+ # Import dependencies which were exported from another container.
77
+ #
78
+ # Used internally by {RailsLoader}.
79
+ #
80
+ # @api private
81
+ # @param definitions [DependencyList] definitions to import.
82
+ # @return [Container] a new container with the imported definitions.
83
+ def import(definitions)
84
+ self.class.new @definitions.import(definitions)
85
+ end
86
+
87
+ private
88
+
89
+ # Duplicates this object with the definition at the end of the list.
90
+ #
91
+ # @api private
92
+ def define(dependency, definition)
93
+ self.class.new(@definitions.add(dependency, definition))
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,12 @@
1
+ module Payload
2
+ # Mixin to provide access to a {Container}.
3
+ #
4
+ # Include this mixin to access dependencies in controllers. The {Container}
5
+ # will be injected using {RackContainer}.
6
+ module Controller
7
+ # @return [Container] dependencies injected from {RackContainer}.
8
+ def dependencies
9
+ request.env[:dependencies]
10
+ end
11
+ end
12
+ end