hexagonal 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0361915ddc3f4bcb10417c531a291b3f8857c164
4
+ data.tar.gz: 1389a24a48796d96a529b2fe84fbf17c7ccbc834
5
+ SHA512:
6
+ metadata.gz: 883666b54d38b6033bede7a5baf99f5603e9e2b5d482d819edc92a635b0ece9e9f8a505d719268bae96b02d91b5df8e6ae7e4eca0cf23a09df995246efb51c3b
7
+ data.tar.gz: 1ba10dcff07734983eebc13dcfd566f00e2d7ba0a691cf5eaed332c9032490628bf7d764e1449dc14f5935a1cdf4d250494f26faf8b50b1252ce06a823abb3d8
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hexagonal.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Matt Beedle
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,367 @@
1
+ # Hexagonal
2
+
3
+ A simple gem to provide structure and guidance for writing hexagonal ruby
4
+ applications.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'hexagonal'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install hexagonal
21
+
22
+ ## Why?!?!
23
+
24
+ Rails applications are usually really fast to build at the beginning, but due to
25
+ high coupling, as they mature they begin to calcify. Eventually adding any new
26
+ features and fixing bugs becomes a pain. Developers get caught in callback hell.
27
+ Nothing can be tested in isolation. Test suites take >10 minutes to run. This is
28
+ also sometimes true of non-Rails Ruby apps. Some people will suggest that Rails
29
+ Engines are a better solution for breaking up complexity. I say that engines and
30
+ Hexagonal can be used together. Engines don't solve the problem of domain
31
+ objects being tightly coupled to the database and often to each other through
32
+ callbacks for example.
33
+
34
+ Hexagonal is an abstraction of the way that I've been building my latest Ruby
35
+ applications. It's inspired by [Matt Wynne](http://www.confreaks.com/videos/977-goruco2012-hexagonal-rails),
36
+ [Brandur](https://brandur.org/mediator), [grouper](http://eng.joingrouper.com/blog/2014/03/03/rails-the-missing-parts-interactors),
37
+ [agileplanner](https://www.agileplannerapp.com/blog/building-agile-planner/refactoring-with-hexagonal-rails),
38
+ [Victor Savkins](http://victorsavkin.com/post/42542190528/hexagonal-architecture-for-rails-developers)
39
+ and many discussions with [@soulim](https://github.com/soulim).
40
+ Hexagonal provides base classes for everything required to build a small
41
+ modular, Ruby application. See Structure section below.
42
+
43
+ ## Usage
44
+
45
+ When using Rails, the following generators are available. When not using Rails,
46
+ please see the examples folder for how to extend the provided classes
47
+ correctly.
48
+
49
+ ### Generate a resource
50
+ This will generate a repository, policy, mediators and runners for all CRUD
51
+ actions for a specified resource.
52
+ ```
53
+ rails generate hexagonal:resource [RESOURCE_NAME]
54
+ ```
55
+
56
+ ### Generate a repository
57
+ ```
58
+ rails generate hexagonal:repository [REPOSITORY_NAME]
59
+ ```
60
+
61
+ ### Generate a runner
62
+ ```
63
+ rails generate hexagonal:runner [RUNNER_NAME]
64
+ ```
65
+
66
+ ### Generate a mediator
67
+ ```
68
+ rails generate hexagonal:mediator [MEDIATOR_NAME]
69
+ ```
70
+
71
+ ### Generate a policy
72
+ ```
73
+ rails generate hexaganal:policy [POLICY_NAME]
74
+ ```
75
+
76
+ ### Generate a worker
77
+ ```
78
+ rails generate hexagonal:worker [WORKER_NAME]
79
+ ```
80
+
81
+ ### Generate a job
82
+ ```
83
+ rails generate hexagonal:job [JOB_NAME]
84
+ ```
85
+
86
+ ### Generate a decorator
87
+ ```
88
+ rails generate hexagonal:decorator [MODEL_NAME]
89
+ ```
90
+
91
+ ## Structure
92
+
93
+ Here is the basic app structure along with some implementation examples.
94
+ Not all of these objects need to inherited/extended from Hexagonal. Services,
95
+ Jobs and Workers are not planned to be part of the gem.
96
+
97
+ ### Runners (app/runners)
98
+ These are my own creation. They are responsible for model materialization,
99
+ authorization (authentication still happens in the controller) and running
100
+ parameter validation
101
+
102
+ ```ruby
103
+ class CreateJobRunner < Hexagonal::Runners::CreateRunner
104
+ private
105
+
106
+ def form
107
+ @form ||= JobForm.new(attributes)
108
+ end
109
+
110
+ def mediator
111
+ @mediator ||= CreateJobMediator.new(user, form.attributes)
112
+ end
113
+ end
114
+ ```
115
+
116
+ ### Mediators (app/mediators)
117
+ A [Mediator](http://en.wikipedia.org/wiki/Mediator_pattern) is a design
118
+ pattern encapsulating how a set of objects interact
119
+ The mediators take care of saving/updating/deleting/etc and calling out
120
+ to workers (for longer jobs, like looking up social media data)
121
+ or jobs (for shorter jobs, like sending email)
122
+
123
+ ```ruby
124
+ class CreateJobMediator < Hexagonal::Mediators::CreateMediator
125
+ def target
126
+ @target ||= Job.new(attributes)
127
+ end
128
+
129
+ private
130
+
131
+ def default_attributes
132
+ { created_by_id: user.id, account_id: user.account_id }
133
+ end
134
+
135
+ def repository
136
+ @repository ||= JobRepository.new
137
+ end
138
+ end
139
+ ```
140
+
141
+ ### Forms (app/forms)
142
+ These contain parameter validation logic.
143
+
144
+ ```ruby
145
+ class JobForm
146
+ include Hexagonal::Form
147
+
148
+ attribute :title, String
149
+ attribute :remote_working_allowed, Boolean, default: true
150
+
151
+ validates :title, presence: true
152
+ end
153
+ ```
154
+
155
+ ### Decorators (app/decorators)
156
+ These are used to add an object-oriented presentation layer. Decorators use the
157
+ [draper gem](https://github.com/drapergem/draper).
158
+
159
+ ```ruby
160
+ class JobDecorator < Draper::Decorator
161
+ decorates :job
162
+
163
+ delegate_all
164
+
165
+ def address
166
+ [street, city, country].compact.join(', ')
167
+ end
168
+ end
169
+ ```
170
+
171
+ ### Workers (app/workers)
172
+ Sidekiq workers to handle longer running tasks (to avoid slow requests).
173
+ The workers themselves have barely any code inside. They just materialize any
174
+ models required and then call the required service.
175
+
176
+ ```ruby
177
+ class ContactImportWorker
178
+ include Sidekiq::Worker
179
+
180
+ def perform(user_id)
181
+ User.find(user_id).tap do |user|
182
+ ContactImportService.new(user).call
183
+ end
184
+ end
185
+ end
186
+ ```
187
+
188
+ ### Jobs (app/jobs)
189
+ Sucker Punch jobs. Sucker punch handles background tasks in a single process
190
+ using asynchronous Ruby. It's good for keeping costs down on heroku.
191
+ I find sidekiq to [generally be overkill](http://brandonhilkert.com/blog/why-i-wrote-the-sucker-punch-gem/)
192
+ for most tasks (email sending for example). Sucker Punch workers are the same as
193
+ Sidekiq workers. Just materialize models and call the correct service
194
+
195
+ ```ruby
196
+ class SignupConfirmationJob
197
+ include SuckerPunch::Job
198
+
199
+ def perform(user)
200
+ UserMailer.signup_confirmation(user).deliver
201
+ end
202
+ end
203
+ ```
204
+
205
+ ### Services (app/services)
206
+ When the app needs to interact with any third party service then a service
207
+ object is used. They are called either from workers or mediators. They handle
208
+ the details of things like email sending, lookup up social media data,
209
+ importing/syncing contacts, polling IMAP, etc.
210
+
211
+ ```ruby
212
+ class ContactImportService
213
+ def initialize(user)
214
+ @user = user
215
+ end
216
+
217
+ def call
218
+ # some complex logic to pull contacts from social media
219
+ end
220
+ end
221
+ ```
222
+
223
+ ### Responses (app/responses)
224
+ These handle responding to the client. They are almost all just simple
225
+ delegators that help me to avoid duplicating code in controllers.
226
+
227
+ ```ruby
228
+ class CreateResponse < SimpleDelegator
229
+ def created_successfully(object)
230
+ respond_with object
231
+ end
232
+
233
+ def creation_failed(object)
234
+ render :errors, object.errors.as_json
235
+ end
236
+ end
237
+ ```
238
+
239
+ ### Repositories (app/repositories)
240
+ Used to access the database. I'm trying to gradually decouple the app completely
241
+ from ActiveRecord. It keeps the queries private instead of leaking storage API
242
+ details into the app.
243
+
244
+ ```ruby
245
+ class JobRepository
246
+ include Hexagonal::Repository
247
+
248
+ def find_by_creator_id(creator_id)
249
+ adapter.where(creator_id: creator_id)
250
+ end
251
+ end
252
+ ```
253
+
254
+ ### Adapters (app/adapters)
255
+ Adapters communicate between specific storage implementations and repositories.
256
+ So far there is only an ActiveRecordAdapter. When I comes time to switch to
257
+ something else, perhaps sequel, then I will just need to define a new adapter
258
+ and plug it into the base repository. Adapters also need to define a Unit Of
259
+ Work in order to be able to roll back groups of changes. With SQL this is just a
260
+ wrapper around a Transaction.
261
+
262
+ ### Errors (app/errors)
263
+ These define business specific errors rather than just using the standard ones.
264
+ Also map database specific errors to business ones so that the database can be
265
+ switched out easily.
266
+
267
+ ### Policies (app/policies)
268
+ These handle authorization.
269
+
270
+ ```ruby
271
+ class JobPolicy
272
+ def initialize(user, job)
273
+ @user = user
274
+ @job = job
275
+ end
276
+
277
+ def delete?
278
+ job.created_by == user
279
+ end
280
+
281
+ private
282
+
283
+ attr_reader :user, :job
284
+ end
285
+ ```
286
+
287
+ ## Example
288
+
289
+ Here is an example Rails API controller using hexagonal
290
+ ```ruby
291
+ class JobsController < ApplicationController::Base
292
+ before_filter :authenticate_user!
293
+
294
+ def index
295
+ filter_runner.run
296
+ end
297
+
298
+ def show
299
+ find_runner.run
300
+ end
301
+
302
+ def create
303
+ create_runner.run
304
+ end
305
+
306
+ def update
307
+ update_runner.run
308
+ end
309
+
310
+ def destroy
311
+ delete_runner.run
312
+ end
313
+
314
+ private
315
+
316
+ def find_runner
317
+ FindJobRunner.new(find_one_response, current_user, params[:id])
318
+ end
319
+
320
+ def find_one_response
321
+ FindOneResponse.new(self)
322
+ end
323
+
324
+ def filter_runner
325
+ FilterJobsRunner.new(find_all_response, current_user, params)
326
+ end
327
+
328
+ def create_runner
329
+ CreateJobRunner.new(create_response, current_user, params[:job])
330
+ end
331
+
332
+ def create_response
333
+ CreateResponse.new(self)
334
+ end
335
+
336
+ def update_runner
337
+ UpdateJobRunner
338
+ .new(update_response, current_user, params[:id], params[:job])
339
+ end
340
+
341
+ def update_response
342
+ UpdateResponse.new(self)
343
+ end
344
+
345
+ def delete_runner
346
+ DeleteRunner.new(delete_response, current_user, params[:id])
347
+ end
348
+
349
+ def delete_response
350
+ DeleteResponse.new(self)
351
+ end
352
+ end
353
+ ```
354
+
355
+ ## Supported Rubies
356
+ 2.0.x, 2.1.x, JRuby 1.7.x
357
+
358
+ ## Contributing
359
+
360
+ 1. Fork it ( https://github.com/[my-github-username]/hexagonal/fork )
361
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
362
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
363
+ 4. Push to the branch (`git push origin my-new-feature`)
364
+ 5. Create a new Pull Request
365
+
366
+ ## Alternatives
367
+ - [hexx](https://github.com/nepalez/hexx)
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -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 'hexagonal/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "hexagonal"
8
+ spec.version = Hexagonal::VERSION
9
+ spec.authors = ["Matt Beedle"]
10
+ spec.email = ["mattbeedle@googlemail.com"]
11
+ spec.summary = %q{A simple gem for building hexagonal Ruby applications}
12
+ spec.description = %q{A simple gem for building hexagonal Ruby applications}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency 'activerecord'
22
+ spec.add_runtime_dependency 'activesupport'
23
+
24
+ spec.add_development_dependency 'bogus'
25
+ spec.add_development_dependency "bundler", "~> 1.7"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ end
@@ -0,0 +1,11 @@
1
+ require 'hexagonal/version'
2
+
3
+ require 'hexagonal/adapters'
4
+ require 'hexagonal/errors'
5
+ require 'hexagonal/mediators'
6
+ require 'hexagonal/repository'
7
+ require 'hexagonal/runners'
8
+
9
+ module Hexagonal
10
+ # Your code goes here...
11
+ end
@@ -0,0 +1 @@
1
+ require_relative 'adapters/active_record_adapter'
@@ -0,0 +1,65 @@
1
+ module Hexagonal
2
+ module Adapters
3
+ class ActiveRecordAdapter
4
+ include ActiveSupport::Rescuable
5
+
6
+ pattr_initialize :persistence
7
+
8
+ rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
9
+ rescue_from ActiveRecord::StatementInvalid, with: :statement_invalid
10
+
11
+ def find(id)
12
+ persistence.find id
13
+ rescue ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid => e
14
+ rescue_with_handler(e)
15
+ end
16
+
17
+ def save(object)
18
+ object.save
19
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::StatementInvalid => e
20
+ rescue_with_handler(e)
21
+ end
22
+
23
+ def save!(object)
24
+ object.save!
25
+ object
26
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::StatementInvalid => e
27
+ rescue_with_handler(e)
28
+ end
29
+
30
+ def destroy(object)
31
+ object.destroy
32
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::StatementInvalid => e
33
+ rescue_with_handler(e)
34
+ end
35
+
36
+ def query(&block)
37
+ yield(persistence)
38
+ rescue ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid => e
39
+ rescue_with_handler(e)
40
+ end
41
+
42
+ def unit_of_work
43
+ @unit_of_work ||=
44
+ Hexagonal::Adapters::ActiveRecordAdapter::UnitOfWork.new
45
+ end
46
+
47
+ def method_missing(method_sym, *arguments, &block)
48
+ persistence.send(method_sym, *arguments, &block)
49
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::StatementInvalid => e
50
+ rescue_with_handler(e)
51
+ end
52
+
53
+ private
54
+
55
+ def record_not_found(exception)
56
+ fail Hexagonal::Errors::RecordNotFoundException, exception, caller
57
+ end
58
+
59
+ def statement_invalid(exception)
60
+ fail Hexagonal::Errors::StatementInvalidException, exception, caller
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,11 @@
1
+ module Hexagonal
2
+ module Adapters
3
+ module ActiveRecordAdapter
4
+ class UnitOfWork
5
+ def run(&block)
6
+ ActiveRecord::Base.transaction { yield }
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'errors/record_invalid_exception'
2
+ require_relative 'errors/unauthorized_exception'
@@ -0,0 +1,6 @@
1
+ module Hexagonal
2
+ module Errors
3
+ class RecordInvalidException < ActiveRecord::RecordInvalid
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Hexagonal
2
+ module Errors
3
+ class UnauthorizedException < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'mediators/create_mediator'
2
+ require_relative 'mediators/delete_mediator'
@@ -0,0 +1,26 @@
1
+ module Hexagonal
2
+ module Mediators
3
+ class CreateMediator
4
+ pattr_initialize :user, :attributes
5
+
6
+ attr_writer :repository
7
+
8
+ def initialize(user, attributes)
9
+ @user = user
10
+ @attributes = attributes.merge(default_attributes)
11
+ end
12
+
13
+ def call
14
+ repository.save! target
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :attributes, :repository
20
+
21
+ def default_attributes
22
+ {}
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ module Hexagonal
2
+ module Mediators
3
+ class DeleteMediator
4
+ pattr_initialize :user, :target
5
+
6
+ attr_writer :repository
7
+
8
+ def call
9
+ repository.destroy target
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader :repository
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ module Hexagonal
2
+ class Repository
3
+ attr_writer :database_klass, :adapter
4
+
5
+ def all
6
+ adapter.all
7
+ end
8
+
9
+ def find(id)
10
+ adapter.find id
11
+ end
12
+
13
+ def save(object)
14
+ adapter.save(object)
15
+ end
16
+
17
+ def save!(object)
18
+ adapter.save!(object)
19
+ end
20
+
21
+ def destroy(object)
22
+ adapter.destroy(object)
23
+ end
24
+
25
+ def unit_of_work
26
+ adapter.unit_of_work
27
+ end
28
+
29
+ private
30
+
31
+ def adapter
32
+ @adapter ||= Hexagonal::Adapters::ActiveRecordAdapter.new(database_klass)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'responses/create_response'
2
+ require_relative 'responses/delete_response'
3
+ require_relative 'responses/find_all_response'
@@ -0,0 +1,13 @@
1
+ module Hexagonal
2
+ module Responses
3
+ class CreateResponse
4
+ def created_successfully(object)
5
+ present object.class.to_s.underscore.to_sym, object
6
+ end
7
+
8
+ def creation_failed(exception)
9
+ error!({ errors: exception.record.errors }, 422)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Hexagonal
2
+ module Responses
3
+ class DeleteResponse < SimpleDelegator
4
+ def deleted_successfully(object)
5
+ status 204
6
+ end
7
+
8
+ def unauthorized(exception)
9
+ error!({ message: exception.message }, 401)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ module Hexagonal
2
+ module Responses
3
+ class FindAllResponse
4
+ delegate :error!, :garner, :pagination, :present, to: :target
5
+
6
+ def initialize(target, key, paginated: false, cache_method: nil)
7
+ @target = target
8
+ @key = key
9
+ @paginated = paginated
10
+ @cache_method = cache_method
11
+ end
12
+
13
+ def found(objects)
14
+ garner.bind(cache_key(objects)) do
15
+ present(key, objects).as_json
16
+ present(:meta, pagination(objects)).as_json if paginated
17
+ end
18
+ end
19
+
20
+ def invalid(exception)
21
+ error!({ errors: exception.record.errors }, 422)
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :cache_method, :key, :paginated, :target
27
+
28
+ def cache_key(objects)
29
+ cache_method ? objects.send(cache_method) : objects
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'runners/create_runner'
2
+ require_relative 'runners/delete_runner'
3
+ require_relative 'runners/filter_runner'
@@ -0,0 +1,32 @@
1
+ module Hexagonal
2
+ module Runners
3
+ class Hexagonal::Runners::CreateRunner
4
+ pattr_initialize :listener, :user, :attributes
5
+
6
+ delegate :created_successfully, :creation_failed, to: :listener
7
+ delegate :target, to: :mediator
8
+
9
+ attr_writer :form
10
+
11
+ def run
12
+ validate!
13
+ create!
14
+ rescue Hexagonal::Errors::RecordInvalidException => ex
15
+ creation_failed(ex)
16
+ end
17
+
18
+ private
19
+
20
+ def create!
21
+ mediator.call
22
+ created_successfully(target)
23
+ end
24
+
25
+ def validate!
26
+ unless form.valid?
27
+ fail Hexagonal::Errors::RecordInvalidException, form, caller
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ module Hexagonal
2
+ module Runners
3
+ class DeleteRunner
4
+ pattr_initialize :listener, :user, :id
5
+
6
+ delegate :unauthorized, :deleted_successfully, to: :listener
7
+
8
+ attr_writer :policy, :repository
9
+
10
+ def run
11
+ authorize!
12
+ delete!
13
+ deleted_successfully target
14
+ rescue Hexagonal::Errors::UnauthorizedException => ex
15
+ unauthorized(ex)
16
+ end
17
+
18
+ private
19
+
20
+ def authorize!
21
+ fail! unless policy.delete?
22
+ end
23
+
24
+ def fail!
25
+ fail Hexagonal::Errors::UnauthorizedException, 'Unauthorized', caller
26
+ end
27
+
28
+ def delete!
29
+ mediator.call
30
+ end
31
+
32
+ def target
33
+ @target ||= repository.find id
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,36 @@
1
+ module Hexagonal
2
+ module Runners
3
+ class FilterRunner
4
+ attr_writer :form, :repository
5
+
6
+ delegate :found, :invalid, to: :listener
7
+
8
+ def initialize(listener, user, attributes = nil)
9
+ @listener = listener
10
+ @user = user
11
+ @attributes = attributes
12
+ end
13
+
14
+ def run
15
+ validate! if attributes
16
+ found items
17
+ rescue Hexagonal::Errors::RecordInvalidException => ex
18
+ invalid ex
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :listener, :user, :attributes
24
+
25
+ def items
26
+ @items ||= repository.filter_for_user(user, form.attributes)
27
+ end
28
+
29
+ def validate!
30
+ unless form.valid?
31
+ fail Hexagonal::Errors::RecordInvalidException, form, caller
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module Hexagonal
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hexagonal
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Matt Beedle
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bogus
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.7'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ description: A simple gem for building hexagonal Ruby applications
84
+ email:
85
+ - mattbeedle@googlemail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - Gemfile
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - hexagonal.gemspec
96
+ - lib/hexagonal.rb
97
+ - lib/hexagonal/adapters.rb
98
+ - lib/hexagonal/adapters/active_record_adapter.rb
99
+ - lib/hexagonal/adapters/active_record_adapter/unit_of_work.rb
100
+ - lib/hexagonal/errors.rb
101
+ - lib/hexagonal/errors/record_invalid_exception.rb
102
+ - lib/hexagonal/errors/unauthorized_exception.rb
103
+ - lib/hexagonal/mediators.rb
104
+ - lib/hexagonal/mediators/create_mediator.rb
105
+ - lib/hexagonal/mediators/delete_mediator.rb
106
+ - lib/hexagonal/repository.rb
107
+ - lib/hexagonal/responses.rb
108
+ - lib/hexagonal/responses/create_response.rb
109
+ - lib/hexagonal/responses/delete_response.rb
110
+ - lib/hexagonal/responses/find_all_response.rb
111
+ - lib/hexagonal/runners.rb
112
+ - lib/hexagonal/runners/create_runner.rb
113
+ - lib/hexagonal/runners/delete_runner.rb
114
+ - lib/hexagonal/runners/filter_runner.rb
115
+ - lib/hexagonal/version.rb
116
+ homepage: ''
117
+ licenses:
118
+ - MIT
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.2.2
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: A simple gem for building hexagonal Ruby applications
140
+ test_files: []