caser 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: a75cc231c0399c0f6b15abfe52003f8473a977ab
4
+ data.tar.gz: fc2b008ef54bc368d7071f40befa4cc31444e119
5
+ SHA512:
6
+ metadata.gz: f78d5657c158f0b9f8161254ab4ff74c90ffa8caaa9e4d441babc20e66481c221ff363927b40f7c78a577f2eb322802950253e61e00f88dc1b6a4d0831a54809
7
+ data.tar.gz: 35ef34038154d373822d0c6e17e84be24912ff211a46b7008af6be205eff614c71620d32a669a45ba532d97d759351764724ed271187b3e731561a726f210c9b
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .ruby-gemset
19
+ .ruby-version
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in caser.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Simon Robson
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,76 @@
1
+ # Caser
2
+
3
+ This gem is an extraction of a pattern that I've evolved in a few recent web apps to find a home for our business logic between the controller and the persistence layer.
4
+
5
+ It's somewhat inspired by the ideas in Hexagonal Architecture and Bob Martin's talks on structuring web applications. But it is very light and simple.
6
+
7
+ I've found it gives me an easy way out of the world of Rails (or your framework of choice) and into pure application code for many common cases once CRUD moves beyond simple single model updates. It tends to make business logic easily testable in isolation and usable from scripts and the console.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'caser'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install caser
22
+
23
+ ## Usage
24
+
25
+ To create a basic use case, extend Caser::Action:
26
+
27
+ class Api::CreateContact < Caser::Action
28
+ attr_accessor :user, params
29
+ def after_initialize
30
+ @user, @params = current_user, params
31
+ end
32
+
33
+ def do_process
34
+ if valid?(params)
35
+ contact = SecretSauce::CreateContact(params)
36
+ set_outcome(contact)
37
+ end
38
+ end
39
+
40
+ def valid?(params)
41
+ unless :whatever
42
+ errors << 'No, we're not going to do that'
43
+ return false
44
+ end
45
+ true
46
+ end
47
+
48
+
49
+ Here is typical usage in a controller:
50
+
51
+ def create
52
+ update = Api::CreateContact.new(current_user, params[:contact]).process
53
+ if update.success?
54
+ ... code for success response (update.outcome contains any result object from the use case)
55
+ else
56
+ ... code for failure response (update.errors contains and array of errors)
57
+ end
58
+ end
59
+
60
+ These use cases also support call-back style usage:
61
+
62
+ def create
63
+ Api::CreateContact.new(current_user, params[:contact]) do |on
64
+ on.success {|action| ... code for success case}
65
+ on.failure {|action| ... code for failure case}
66
+ end.process
67
+ end
68
+
69
+
70
+ ## Contributing
71
+
72
+ 1. Fork it
73
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
74
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
75
+ 4. Push to the branch (`git push origin my-new-feature`)
76
+ 5. Create new Pull Request
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ t.ruby_opts = ["-w"]
8
+ t.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'caser/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "caser"
8
+ spec.version = Caser::VERSION
9
+ spec.authors = ["Simon Robson"]
10
+ spec.email = ["shrobson@gmail.com"]
11
+ spec.description = %q{Simple structures for writing actions / use cases}
12
+ spec.summary = %q{Provides a standard approach to plugging the gap between controllers and persistence for the common cases, such as crud, in Ruby web applications.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
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_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,3 @@
1
+ require 'caser/version'
2
+ require 'caser/action'
3
+ require 'caser/callbacks'
@@ -0,0 +1,50 @@
1
+ require 'observer'
2
+
3
+ module Caser
4
+ class Action
5
+ include Observable
6
+
7
+ def initialize(*params)
8
+ @_callbacks = nil
9
+ yield @_callbacks = Callbacks.new if block_given?
10
+ after_initialize(*params)
11
+ end
12
+
13
+ def process
14
+ processed!
15
+ do_process
16
+ success? ? succeed! : fail!
17
+ self
18
+ end
19
+
20
+ def succeed!
21
+ @_callbacks.on_success(self) if @_callbacks
22
+ end
23
+
24
+ def fail!
25
+ @_callbacks.on_failure(self) if @_callbacks
26
+ end
27
+
28
+ def processed!
29
+ @_processed = true
30
+ end
31
+
32
+ def processed?
33
+ @_processed
34
+ end
35
+
36
+ def success?
37
+ processed? && errors.empty?
38
+ end
39
+
40
+ def errors
41
+ @_errors ||= []
42
+ end
43
+
44
+ def emit(event)
45
+ changed
46
+ notify_observers(event)
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,25 @@
1
+ module Caser
2
+ class Callbacks
3
+ def initialize
4
+ @_success_cb = @_failure_cb = nil
5
+ end
6
+
7
+ def on_success(*args)
8
+ return unless @_success_cb
9
+ @_success_cb.call(*args)
10
+ end
11
+
12
+ def on_failure(*args)
13
+ return unless @_failure_cb
14
+ @_failure_cb.call(*args)
15
+ end
16
+
17
+ def success(&cb)
18
+ @_success_cb = cb
19
+ end
20
+
21
+ def failure(&cb)
22
+ @_failure_cb = cb
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Caser
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,106 @@
1
+ require 'minitest/autorun'
2
+ require 'caser'
3
+
4
+ describe Caser::Action do
5
+ class BasicAction < Caser::Action
6
+ attr_accessor :will_fail
7
+ def after_initialize(will_fail = false)
8
+ @will_fail = will_fail
9
+ end
10
+
11
+ def do_process
12
+ errors << "It failed" if will_fail?
13
+ end
14
+
15
+ def will_fail?
16
+ will_fail
17
+ end
18
+ end
19
+
20
+ describe 'A basic action' do
21
+ it 'results in success' do
22
+ @action = BasicAction.new
23
+ @action.process
24
+ @action.success?.must_equal true
25
+ end
26
+
27
+ it 'returns self from process' do
28
+ @action = BasicAction.new
29
+ @action.process.must_be_same_as @action
30
+ end
31
+
32
+ it 'reports failure' do
33
+ @action = BasicAction.new(true).process
34
+ @action.success?.must_equal false
35
+ end
36
+
37
+ it 'gives access to errors in failure case' do
38
+ @action = BasicAction.new(true).process
39
+ @action.errors.length.must_equal 1
40
+ end
41
+ end
42
+
43
+ describe 'callback style' do
44
+ def setup
45
+ @bug = Minitest::Mock.new
46
+ end
47
+
48
+ it 'calls back on success' do
49
+ @bug.expect(:succeeded, true)
50
+ @action = BasicAction.new do |on|
51
+ on.success { @bug.succeeded }
52
+ on.failure { @bug.failed }
53
+ end
54
+ @action.process
55
+ @bug.verify
56
+ end
57
+
58
+ it 'passes the action instance to the callbacks' do
59
+ @passed_in = nil
60
+ @action = BasicAction.new do |on|
61
+ on.success { |a| @passed_in = a }
62
+ end
63
+ @action.process
64
+ @passed_in.must_equal @action
65
+ end
66
+
67
+ it 'calls back on failure' do
68
+ @bug.expect(:failed, true)
69
+ BasicAction.new(true) do |on|
70
+ on.success { @bug.succeeded }
71
+ on.failure { @bug.failed }
72
+ end.process
73
+ @bug.verify
74
+ end
75
+ end
76
+
77
+ describe 'observability' do
78
+ class ObservedAction < Caser::Action
79
+ def after_initialize
80
+ end
81
+
82
+ def do_process
83
+ emit(:working)
84
+ end
85
+ end
86
+
87
+ class Watcher
88
+ def update(message)
89
+ messages << message
90
+ end
91
+
92
+ def messages
93
+ @messages ||= []
94
+ end
95
+ end
96
+
97
+ it 'implements observable and provides and emit method to notify observers' do
98
+ @action = ObservedAction.new
99
+ @observer = Watcher.new
100
+ @action.add_observer(@observer)
101
+ @action.process
102
+ @observer.messages.must_equal [:working]
103
+ end
104
+ end
105
+
106
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: caser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Simon Robson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-06-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Simple structures for writing actions / use cases
42
+ email:
43
+ - shrobson@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - .gitignore
49
+ - Gemfile
50
+ - LICENSE.txt
51
+ - README.md
52
+ - Rakefile
53
+ - caser.gemspec
54
+ - lib/caser.rb
55
+ - lib/caser/action.rb
56
+ - lib/caser/callbacks.rb
57
+ - lib/caser/version.rb
58
+ - test/action_test.rb
59
+ homepage: ''
60
+ licenses:
61
+ - MIT
62
+ metadata: {}
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 2.0.3
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: Provides a standard approach to plugging the gap between controllers and
83
+ persistence for the common cases, such as crud, in Ruby web applications.
84
+ test_files:
85
+ - test/action_test.rb