porch 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: a4465a58cd1f427a90f98c0fe4287da4a95a591c
4
+ data.tar.gz: 3c9117fc73b0f17b88e1a6d6a44a3c549269886a
5
+ SHA512:
6
+ metadata.gz: 283ed8cbacd23252b660d4f64cd538851b1f15463f3b3e84534cc8eb260b721018a1cc6a9c6c0eec0816763811c6ce8dfbfbafe64a10c5fa1404b283d0fc2686
7
+ data.tar.gz: 0809d2abafa5631ac04752b8dcc0c21864e364b38be31fe1068a4a33a7a44d2513beb491ce0422bea66325644f882092028c2ac4d6b75111c944a1b8f5b188a2
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at jamie@brilliantfantastic.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,66 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ porch (0.1.0)
5
+ dry-validation (~> 0.10.4)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ concurrent-ruby (1.0.4)
11
+ diff-lcs (1.2.5)
12
+ dry-configurable (0.5.0)
13
+ concurrent-ruby (~> 1.0)
14
+ dry-container (0.6.0)
15
+ concurrent-ruby (~> 1.0)
16
+ dry-configurable (~> 0.1, >= 0.1.3)
17
+ dry-core (0.2.3)
18
+ concurrent-ruby (~> 1.0)
19
+ dry-equalizer (0.2.0)
20
+ dry-logic (0.4.0)
21
+ dry-container (~> 0.2, >= 0.2.6)
22
+ dry-core (~> 0.1)
23
+ dry-equalizer (~> 0.2)
24
+ dry-types (0.9.3)
25
+ concurrent-ruby (~> 1.0)
26
+ dry-configurable (~> 0.1)
27
+ dry-container (~> 0.3)
28
+ dry-core (~> 0.2, >= 0.2.1)
29
+ dry-equalizer (~> 0.2)
30
+ dry-logic (~> 0.4, >= 0.4.0)
31
+ inflecto (~> 0.0.0, >= 0.0.2)
32
+ dry-validation (0.10.4)
33
+ concurrent-ruby (~> 1.0)
34
+ dry-configurable (~> 0.1, >= 0.1.3)
35
+ dry-container (~> 0.2, >= 0.2.8)
36
+ dry-core (~> 0.2, >= 0.2.1)
37
+ dry-equalizer (~> 0.2)
38
+ dry-logic (~> 0.4, >= 0.4.0)
39
+ dry-types (~> 0.9, >= 0.9.0)
40
+ inflecto (0.0.2)
41
+ rake (10.0.4)
42
+ rspec (3.5.0)
43
+ rspec-core (~> 3.5.0)
44
+ rspec-expectations (~> 3.5.0)
45
+ rspec-mocks (~> 3.5.0)
46
+ rspec-core (3.5.4)
47
+ rspec-support (~> 3.5.0)
48
+ rspec-expectations (3.5.0)
49
+ diff-lcs (>= 1.2.0, < 2.0)
50
+ rspec-support (~> 3.5.0)
51
+ rspec-mocks (3.5.0)
52
+ diff-lcs (>= 1.2.0, < 2.0)
53
+ rspec-support (~> 3.5.0)
54
+ rspec-support (3.5.0)
55
+
56
+ PLATFORMS
57
+ ruby
58
+
59
+ DEPENDENCIES
60
+ bundler (~> 1.12.0)
61
+ porch!
62
+ rake (~> 10.0.0)
63
+ rspec (~> 3.5.0)
64
+
65
+ BUNDLED WITH
66
+ 1.12.5
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 Jamie Wright
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,286 @@
1
+ Porch
2
+ ======
3
+
4
+ [ ![Codeship Status for jwright/porch](https://app.codeship.com/projects/8780ad70-b5ae-0134-fb0d-62cbe9f5cc84/status?branch=master)](https://app.codeship.com/projects/194128)
5
+
6
+ A simple service layer pattern for plain old Ruby objects.
7
+
8
+ ## DESCRIPTION
9
+
10
+ Yeah, yeah, yeah. Keep yer controllers skinny they always tell us. This is a lot easier to say than do in many cases. Our controllers are supposed to create that user, send them an invitation email, block them from access until they authenticate their email address, etc.
11
+
12
+ Porch allows you to move the code into a series of steps that execute simple methods on itself or within simple PORO objects.
13
+
14
+ This was inspired by [LightService](https://github.com/adomokos/light-service) and the middleware chain by [Sidekiq](https://github.com/mperham/sidekiq).
15
+
16
+ ## USAGE
17
+
18
+ ### Installation
19
+
20
+ ```
21
+ gem install porch
22
+ ```
23
+
24
+ ### Getting started
25
+
26
+ Your service object is simply a series of steps.
27
+
28
+ ```
29
+ # app/services/registers_user.rb
30
+
31
+ require "porch"
32
+
33
+ module Services
34
+ class RegistersUser
35
+ include Porch::Organizer
36
+
37
+ attr_reader :attributes
38
+
39
+ def initialize(attributes)
40
+ @attributes = attributes
41
+ end
42
+
43
+ def register
44
+ with(attributes) do |chain|
45
+ chain.add CreateUser
46
+ chain.add SendWelcomeEmail
47
+ chain.add CreateBillingCustomer
48
+ end
49
+ end
50
+ end
51
+ end
52
+ ```
53
+
54
+ You can then use your service in your controllers with the result that it returns from the chain.
55
+
56
+ ```
57
+ # app/controllers/users_controller.rb
58
+
59
+ class UsersController < ApplicationController
60
+ def create
61
+ result = Services::RegistersUser.new(params[:user]).register
62
+
63
+ if result.success?
64
+ redirect_to dashboard_path(result.user), notice: "Welcome #{result.user.name}."
65
+ else
66
+ flash[:error] = result.message
67
+ render :new
68
+ end
69
+ end
70
+ end
71
+ ```
72
+
73
+ ### Defining steps or actions
74
+
75
+ You can define steps as classes and include some nice helper methods. (COMING SOON)
76
+
77
+ ```
78
+ # app/services/steps/create_user.rb
79
+
80
+ require "porch"
81
+
82
+ class CreateUser
83
+ include Porch::Step
84
+
85
+ params do
86
+ required(:email).filled(type?: :str, format?: RegEx.email_address)
87
+ required(:password).filled(type?: :str, min_size?: 8)
88
+ end
89
+
90
+ def call(context)
91
+ context.user = User.create email: context.email, password: context.password
92
+ context.fail! context.user.errors unless context.user.valid?
93
+ end
94
+ end
95
+ ```
96
+
97
+ You can define steps as PORO classes that respond to a call method.
98
+
99
+ ```
100
+ # app/services/create_billing_customer.rb
101
+
102
+ require "stripe"
103
+
104
+ class CreateBillingCustomer
105
+ def call(context)
106
+ customer = Stripe::Customer.create email: context.email
107
+ User.find_by_email(context.email).update_attributes(billing_id: customer.id)
108
+ end
109
+ end
110
+ ```
111
+
112
+ You can define steps as blocks on the organizer
113
+
114
+ ```
115
+ # app/services/registers_user.rb
116
+
117
+ require "porch"
118
+
119
+ module Services
120
+ class RegistersUser
121
+ include Porch::Organizer
122
+
123
+ # ...
124
+
125
+ def register
126
+ with(attributes) do |chain|
127
+ # ...
128
+ chain.add :send_welcome_email do |context|
129
+ UserMailer.welcome(context.user).deliver_later
130
+ end
131
+ # ...
132
+ end
133
+ end
134
+ end
135
+ end
136
+ ```
137
+
138
+ You can define steps as methods on the organizer.
139
+
140
+ ```
141
+ # app/services/registers_user.rb
142
+
143
+ require "porch"
144
+
145
+ module Services
146
+ class RegistersUser
147
+ include Porch::Organizer
148
+
149
+ # ...
150
+
151
+ def register
152
+ with(attributes) do |chain|
153
+ # ...
154
+ chain.add :send_welcome_email
155
+ # ...
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ def send_welcome_email(context)
162
+ UserMailer.welcome(context.user).deliver_later
163
+ end
164
+ end
165
+ end
166
+ ```
167
+
168
+ ### Failing the chain
169
+
170
+ At any step, you can set the `Porch::Context` as a failure which will stop processing the remaining steps and set the `Context` as a failed context with an optional message.
171
+
172
+ ```
173
+ class RegistersUser
174
+ include Porch::Organizer
175
+
176
+ # ...
177
+
178
+ def register
179
+ with(attributes) do |chain|
180
+ # ...
181
+ chain.add :send_welcome_email
182
+ # ...
183
+ end
184
+ end
185
+
186
+ private
187
+
188
+ def send_welcome_email(context)
189
+ context.fail! "Better luck next time!" if some_failure_condition?
190
+ UserMailer.welcome(context.user).deliver_later
191
+ end
192
+ end
193
+
194
+ result = RegistersUser.new(email: "test@example.com").register
195
+ if result.failure?
196
+ puts result.message # => "Better luck next time!"
197
+ end
198
+ ```
199
+
200
+ ### Skipping steps
201
+
202
+ At any step, you can skip the remaining actions in the organizer. This stops the running of the remaining actions but the `Porch::Context` will still return a successful `Porch::Context`.
203
+
204
+ ```
205
+ class RegistersUser
206
+ include Porch::Organizer
207
+
208
+ # ...
209
+
210
+ def register
211
+ with(attributes) do |chain|
212
+ # ...
213
+ chain.add :save_user
214
+ # ...
215
+ end
216
+ end
217
+
218
+ private
219
+
220
+ def save_user(context)
221
+ User.create context
222
+ context.skip_remaining! if sending_emails_disabled?
223
+ end
224
+ end
225
+
226
+ result = RegistersUser.new(email: "test@example.com").register
227
+ result.success? # => true
228
+ ```
229
+
230
+ ### Validating the context
231
+
232
+ Porch comes with multiple ways that you can validate each step is setup correctly. It uses the DSL provided by [dry-validation](http://dry-rb.org/gems/dry-validation/).
233
+
234
+ Several helper methods are included in the `Porch::Context` to guard against an invalid `Porch::Context`.
235
+
236
+ `Porch::Context#guard` can be used and if the validation fails, the `Context` will skip the remaining actions.
237
+
238
+ ```
239
+ class SomeStep
240
+ def call(context)
241
+ context.guard { required(:email) }
242
+ # The rest of the action will not be performed and the rest of the actions will be
243
+ # skipped if the guard fails
244
+ end
245
+ end
246
+ ```
247
+
248
+ `Porch::Context#guard!` (with a bang(!)) can be used and if the validation fails, the `Context` will be marked as a failure. The failure message for the `Context` will be set to be a comma-seperated list of the context errors that failed.
249
+
250
+ ```
251
+ class SomeStep
252
+ def call(context)
253
+ context.guard! { required(:email) }
254
+ # The rest of the action will not be performed and the rest of the actions will be
255
+ # skipped and the action will be marked as failed if the guard fails
256
+ end
257
+ end
258
+ ```
259
+
260
+ At any point, you can use the `Porch::GuardRail::Guard` helper method to validate any hash (including the `Porch::Context`).
261
+
262
+ ```
263
+ hash = { email: "test@example" }
264
+ result = Porch::GuardRail::Guard.new(hash).against { required(:email).value(format?: RegEx::Email) }
265
+ result.success? # => false
266
+ result.errors # => { email: ["is invalid"] }
267
+ ```
268
+
269
+ ## CONTRIBUTING
270
+
271
+ 1. Clone the repository `git clone https://github.com/jwright/porch`
272
+ 1. Create a feature branch `git checkout -b my-awesome-feature`
273
+ 1. Codez!
274
+ 1. Commit your changes (small commits please)
275
+ 1. Push your new branch `git push origin my-awesome-feature`
276
+ 1. Create a pull request `hub pull-request -b jwright:master -h jwright:my-awesome-feature`
277
+
278
+ ## RELEASING A NEW GEM
279
+
280
+ 1. Bump the VERSION in `lib/porch/version.rb`
281
+ 1. Commit changes and push to GitHub
282
+ 1. run `bundle exec rake release`
283
+
284
+ ## LICENSE
285
+
286
+ This project is licensed under the [MIT License](LICENSE.md).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,71 @@
1
+ require "porch/guard_rail"
2
+
3
+ module Porch
4
+ class Context < Hash
5
+ include Porch::GuardRail
6
+
7
+ attr_reader :message
8
+
9
+ def initialize(context={}, success=true)
10
+ @message = nil
11
+ @success = success
12
+ @skip_remaining = false
13
+ deep_duplicate(context)
14
+ end
15
+
16
+ def deep_dup
17
+ self.class.new(self, self.success?)
18
+ end
19
+
20
+ def fail!(message="")
21
+ @message = message
22
+ @success = false
23
+ throw :stop_current_step_execution, self
24
+ end
25
+
26
+ def failure?
27
+ !success?
28
+ end
29
+
30
+ def guard(&block)
31
+ result = super
32
+ skip_remaining! if result.failure?
33
+ result
34
+ end
35
+
36
+ def guard!(&block)
37
+ result = guard &block
38
+ fail!(HumanError.new(result.errors).message) if result.failure?
39
+ result
40
+ end
41
+
42
+ def method_missing(name, *args, &block)
43
+ return fetch(name) if key?(name)
44
+ super
45
+ end
46
+
47
+ def skip_remaining?
48
+ !!@skip_remaining
49
+ end
50
+
51
+ def skip_remaining!(skip_current=false)
52
+ @skip_remaining = true
53
+ throw :stop_current_step_execution, self if skip_current
54
+ end
55
+
56
+ def success?
57
+ @success
58
+ end
59
+
60
+ def stop_processing?
61
+ failure? || skip_remaining?
62
+ end
63
+
64
+ private
65
+
66
+ def deep_duplicate(context)
67
+ (context || {}).to_hash.each { |k, v| self[k] = v }
68
+ self
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,55 @@
1
+ module Porch
2
+ module CoreExt
3
+ class String
4
+ CAPITALIZE_SEPARATOR = ' '.freeze
5
+ CLASSIFY_SEPARATOR = '_'.freeze
6
+ NAMESPACE_SEPARATOR = '::'.freeze
7
+ UNDERSCORE_DIVISION_TARGET = '\1_\2'.freeze
8
+ UNDERSCORE_SEPARATOR = '/'.freeze
9
+
10
+ def initialize(string)
11
+ @string = string.to_s
12
+ end
13
+
14
+ def capitalize
15
+ head, *tail = underscore.split(CLASSIFY_SEPARATOR)
16
+
17
+ self.class.new tail.unshift(head.capitalize).join(CAPITALIZE_SEPARATOR)
18
+ end
19
+
20
+ def gsub(pattern, replacement=nil, &blk)
21
+ if block_given?
22
+ string.gsub(pattern, &blk)
23
+ else
24
+ string.gsub(pattern, replacement)
25
+ end
26
+ end
27
+
28
+ def split(pattern, limit=0)
29
+ string.split(pattern, limit)
30
+ end
31
+
32
+ def to_s
33
+ string
34
+ end
35
+
36
+ def underscore
37
+ new_string = gsub(NAMESPACE_SEPARATOR, UNDERSCORE_SEPARATOR)
38
+ new_string.gsub!(/([A-Z\d]+)([A-Z][a-z])/, UNDERSCORE_DIVISION_TARGET)
39
+ new_string.gsub!(/([a-z\d])([A-Z])/, UNDERSCORE_DIVISION_TARGET)
40
+ new_string.gsub!(/[[:space:]]|\-/, UNDERSCORE_DIVISION_TARGET)
41
+ new_string.downcase!
42
+ self.class.new new_string
43
+ end
44
+
45
+ def ==(other)
46
+ to_s == other
47
+ end
48
+ alias eql? ==
49
+
50
+ private
51
+
52
+ attr_reader :string
53
+ end
54
+ end
55
+ end
@@ -0,0 +1 @@
1
+ require_relative "core_ext/string"
@@ -0,0 +1,10 @@
1
+ module Porch
2
+ class InvalidStepTypeError < RuntimeError
3
+ attr_reader :step
4
+
5
+ def initialize(step)
6
+ @step = step
7
+ super "Step type #{step.class.name} is not handled"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1 @@
1
+ require_relative "errors/invalid_step_type_error"
@@ -0,0 +1,35 @@
1
+ require_relative "step_decorators/class_step_decorator"
2
+ require_relative "step_decorators/method_step_decorator"
3
+ require_relative "step_decorators/proc_step_decorator"
4
+
5
+ module Porch
6
+ class ExecutableStepDecorator
7
+ attr_reader :decorated_step
8
+
9
+ def step
10
+ decorated_step.step
11
+ end
12
+
13
+ def initialize(step, organizer)
14
+ @decorated_step = decorate step, organizer
15
+ end
16
+
17
+ def execute(context)
18
+ catch :stop_current_step_execution do
19
+ decorated_step.execute context
20
+ end
21
+ end
22
+
23
+ def self.registered_decorators
24
+ [ClassStepDecorator, MethodStepDecorator, ProcStepDecorator].freeze
25
+ end
26
+
27
+ private
28
+
29
+ def decorate(step, organizer)
30
+ decorator = self.class.registered_decorators.find { |d| d.decorates?(step) }
31
+ raise InvalidStepTypeError.new(step) if decorator.nil?
32
+ decorator.new(step, organizer)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ require "dry-validation"
2
+
3
+ module Porch
4
+ module GuardRail
5
+ class Guard
6
+ attr_reader :guarded_object
7
+
8
+ def initialize(guarded_object)
9
+ @guarded_object = guarded_object
10
+ end
11
+
12
+ def against(&block)
13
+ schema = Dry::Validation.Schema &block
14
+ schema.call guarded_object
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "guard_rail/guard"
2
+
3
+ module Porch
4
+ module GuardRail
5
+ def guard(&block)
6
+ Guard.new(self).against(&block)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,43 @@
1
+ module Porch
2
+ class HumanError
3
+ attr_accessor :seperator
4
+ attr_reader :errors
5
+
6
+ def initialize(errors, seperator=", ")
7
+ @errors = errors
8
+ @seperator = seperator
9
+ end
10
+
11
+ def message
12
+ collect_messages(errors).join(seperator)
13
+ end
14
+
15
+ private
16
+
17
+ def collect_messages(error_hash, attribute_prefix=nil)
18
+ error_hash.collect do |attribute, descriptions|
19
+ if descriptions.is_a? Hash
20
+ collect_messages descriptions, attribute
21
+ else
22
+ append_message attribute_prefix, attribute, descriptions
23
+ end
24
+ end
25
+ end
26
+
27
+ def append_message(attribute_prefix, attribute, descriptions)
28
+ append_descriptions(humanize(attribute_prefix, attribute), descriptions)
29
+ end
30
+
31
+ def append_descriptions(attribute, descriptions)
32
+ descriptions.collect { |description| "#{attribute} #{description}" }.join(seperator)
33
+ end
34
+
35
+ def humanize(attribute_prefix, attribute)
36
+ capitalize("#{attribute_prefix} #{attribute}".lstrip)
37
+ end
38
+
39
+ def capitalize(string)
40
+ CoreExt::String.new(string).capitalize
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,14 @@
1
+ module Porch
2
+ module Organizer
3
+ attr_reader :context
4
+
5
+ def with(parameters={})
6
+ @context = Context.new parameters
7
+
8
+ chain = StepChain.new(self)
9
+ yield chain if block_given?
10
+
11
+ chain.execute context
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,31 @@
1
+ module Porch
2
+ class StepChain
3
+ attr_reader :organizer, :steps
4
+
5
+ def initialize(organizer)
6
+ @organizer = organizer
7
+ @steps = []
8
+ end
9
+
10
+ def add(step=nil, &block)
11
+ step = block if block_given?
12
+ steps << ExecutableStepDecorator.new(step, organizer)
13
+ end
14
+
15
+ def insert(index, step=nil, &block)
16
+ step = block if block_given?
17
+ steps.insert index, ExecutableStepDecorator.new(step, organizer)
18
+ end
19
+
20
+ def remove(step)
21
+ @steps.delete_if { |decorated_step| decorated_step.step == step }
22
+ end
23
+
24
+ def execute(context)
25
+ ctx = Context.new context
26
+ steps.map do |step|
27
+ ctx = step.execute ctx unless ctx.stop_processing?
28
+ end.last || ctx
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ module Porch
2
+ class ClassStepDecorator
3
+ attr_reader :step
4
+
5
+ def initialize(step, _organizer)
6
+ @step = step
7
+ end
8
+
9
+ def execute(context)
10
+ ctx = Context.new(context)
11
+ step.new.call ctx
12
+ ctx
13
+ end
14
+
15
+ def self.decorates?(step)
16
+ step.is_a? Class
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Porch
2
+ class MethodStepDecorator
3
+ attr_reader :organizer, :step
4
+
5
+ def initialize(step, organizer)
6
+ @step = step
7
+ @organizer = organizer
8
+ end
9
+
10
+ def execute(context)
11
+ ctx = Context.new(context)
12
+ organizer.send step, ctx
13
+ ctx
14
+ end
15
+
16
+ def self.decorates?(step)
17
+ step.is_a?(Symbol) || step.is_a?(String)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module Porch
2
+ class ProcStepDecorator
3
+ attr_reader :step
4
+
5
+ def initialize(step, _organizer)
6
+ @step = step
7
+ end
8
+
9
+ def execute(context)
10
+ ctx = Context.new(context)
11
+ step.call ctx
12
+ ctx
13
+ end
14
+
15
+ def self.decorates?(step)
16
+ step.is_a? Proc
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module Porch
2
+ VERSION = "0.1.0"
3
+ end
data/lib/porch.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "porch/context"
2
+ require "porch/core_ext"
3
+ require "porch/errors"
4
+ require "porch/executable_step_decorator"
5
+ require "porch/human_error"
6
+ require "porch/guard_rail"
7
+ require "porch/organizer"
8
+ require "porch/step_chain"
9
+ require "porch/version"
10
+
11
+ module Porch
12
+ end
data/porch.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "porch/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "porch"
8
+ spec.version = Porch::VERSION
9
+ spec.authors = ["Jamie Wright"]
10
+ spec.email = ["jamie@brilliantfantastic.com"]
11
+
12
+ spec.summary = "A simple service layer pattern for plain old Ruby objects."
13
+ spec.description = %q{Porch allows you to move the code into a series of steps that execute simple methods on itself or within simple PORO objects.}
14
+ spec.homepage = "https://github.com/jwright/porch"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "dry-validation", "~> 0.10.4"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.12.0"
25
+ spec.add_development_dependency "rake", "~> 10.0.0"
26
+ spec.add_development_dependency "rspec", "~> 3.5.0"
27
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: porch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jamie Wright
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-01-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-validation
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.10.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.10.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.12.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.12.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 10.0.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 10.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.5.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.5.0
69
+ description: Porch allows you to move the code into a series of steps that execute
70
+ simple methods on itself or within simple PORO objects.
71
+ email:
72
+ - jamie@brilliantfantastic.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".rspec"
78
+ - CODE_OF_CONDUCT.md
79
+ - Gemfile
80
+ - Gemfile.lock
81
+ - LICENSE.md
82
+ - README.md
83
+ - Rakefile
84
+ - lib/porch.rb
85
+ - lib/porch/context.rb
86
+ - lib/porch/core_ext.rb
87
+ - lib/porch/core_ext/string.rb
88
+ - lib/porch/errors.rb
89
+ - lib/porch/errors/invalid_step_type_error.rb
90
+ - lib/porch/executable_step_decorator.rb
91
+ - lib/porch/guard_rail.rb
92
+ - lib/porch/guard_rail/guard.rb
93
+ - lib/porch/human_error.rb
94
+ - lib/porch/organizer.rb
95
+ - lib/porch/step_chain.rb
96
+ - lib/porch/step_decorators/class_step_decorator.rb
97
+ - lib/porch/step_decorators/method_step_decorator.rb
98
+ - lib/porch/step_decorators/proc_step_decorator.rb
99
+ - lib/porch/version.rb
100
+ - porch.gemspec
101
+ homepage: https://github.com/jwright/porch
102
+ licenses:
103
+ - MIT
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 2.5.1
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: A simple service layer pattern for plain old Ruby objects.
125
+ test_files: []