actionizer 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a63a9ba128ed80a4d5090ddb1934f44a5281387f
4
- data.tar.gz: b8d54516906e1766cefebc20e31c4831f9b95dd0
3
+ metadata.gz: fbc6064d47f58c9d86ac8c9affc380faf93177e8
4
+ data.tar.gz: c5ebe99c56d627b9fbde668dfebb9429323d0fcc
5
5
  SHA512:
6
- metadata.gz: 4aa44e386f9957c7f52177084fb9e60d0ba3b0fe572f5eef09f30be13e62732059489f8e4927319e0554d27ea2b4c4538bc618f5e0df0e26315dff1dcdeb2432
7
- data.tar.gz: ea3037ed552d5d9ae3b5aeb3ce37cef181c9a9cc612b18386e3abf3d2790ece33a3eb31c449c322d61c1c401107e49ba346525341e4a7e13b33c94e2a754a3bf
6
+ metadata.gz: 933e1fa6228e751600e00ac4553038fb8067e29a77f20e12f820668a5ca34e27e374a6896ff1eebe5596629a4b997d347a0cd8da095bf7507d8ceee108e28c42
7
+ data.tar.gz: 63b679a33a98811c45984b4e5ef74e0a35f798e63571047108825364a82fe587526a5d6c9ec18e9278d903c9b81b26b35e60b6e9bd55c8eb4f759cf06a1194d0
data/.rubocop.yml CHANGED
@@ -1,3 +1,6 @@
1
+ Lint/InheritException:
2
+ Enabled: false
3
+
1
4
  Metrics/AbcSize:
2
5
  Max: 25
3
6
  Metrics/LineLength:
@@ -17,5 +20,3 @@ Style/MutableConstant:
17
20
  Enabled: false
18
21
  Style/NegatedIf:
19
22
  Enabled: false
20
- Style/TrivialAccessors:
21
- Enabled: false
data/README.md CHANGED
@@ -1,12 +1,19 @@
1
1
  # Actionizer
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/actionizer.svg)](https://badge.fury.io/rb/actionizer)
3
4
  [![Build Status](https://travis-ci.org/mikenichols/actionizer.svg?branch=master)](https://travis-ci.org/mikenichols/actionizer)
4
5
  [![Test Coverage](https://codeclimate.com/github/mikenichols/actionizer/badges/coverage.svg)](https://codeclimate.com/github/mikenichols/actionizer/coverage)
5
6
  [![Code Climate](https://codeclimate.com/github/mikenichols/actionizer/badges/gpa.svg)](https://codeclimate.com/github/mikenichols/actionizer)
6
7
 
7
8
  ## Turn your classes into small, modular, resuable Actions!
8
9
 
9
- ## Installation
10
+ This gem is an implementation of the Interactor pattern. This pattern has also been called [Ports and Adapters](http://www.dossier-andreas.net/software_architecture/ports_and_adapters.html) or [Hexagonal Architecture](http://victorsavkin.com/post/42542190528/hexagonal-architecture-for-rails-developers) or [DCI (Data-Context-Interaction)](https://en.wikipedia.org/wiki/Data,_context_and_interaction) but the ideas are pretty much the same. Its goal is to provide a simple pattern for writing truly single-purpose classes. These classes are then easy to reason about, easy to test, and easy to change. They aren't coupled to all sorts of other parts of your system; they are self-contained and perform one and only one action.
11
+
12
+ This is not remotely a new or unique idea as there have been [many](http://blog.8thlight.com/uncle-bob/2011/11/22/Clean-Architecture.html) [awesome](http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html) [articles](http://jeffreypalermo.com/blog/the-onion-architecture-part-1/) and previous [projects](https://github.com/collectiveidea/interactor/) written to implement clean, modular classes. Uncle Bob even wrote a [great post](https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html) highlighting similarities between the big architectural ideas of the last decade or so. While you should totally go read that article, I'll summarize it here for you. Your app is not your framework. It's super effective to decouple your business logic from your framework, database, UI, and third-party APIs.
13
+
14
+ I like to think of your business logic as being the "gooey center" of your app. That's where the most critical code lives. You want to keep it isolated from the details of databases, file systems, and networks. This inner layer should be so simple as to be boring because it's crucial to get it correct, so you want to make the code clean and simple, even stupid simple. `Actionizer` will help you write boring, stupid simple code.
15
+
16
+ ### Installation
10
17
 
11
18
  Add this line to your application's Gemfile:
12
19
 
@@ -14,15 +21,11 @@ Add this line to your application's Gemfile:
14
21
  gem 'actionizer'
15
22
  ```
16
23
 
17
- And then execute:
18
-
19
- $ bundle
24
+ And then run:
20
25
 
21
- Or install it yourself as:
26
+ $ bundle install
22
27
 
23
- $ gem install actionizer
24
-
25
- ## Usage
28
+ ### Basic usage
26
29
 
27
30
  Include `Actionizer` in your class and define an instance method. That instance method will be automatically invoked when you call the class method of the same name. Any Action defined with `Actionizer` will automatically return a hash-like result you can check for `success?` or `failure?`.
28
31
 
@@ -39,6 +42,8 @@ class CreateUser
39
42
  end
40
43
  ```
41
44
 
45
+ ### Check result status with `success?` and `failure?`
46
+
42
47
  Actions are successful by default:
43
48
  ```ruby
44
49
  result = SuccessfulAction.call(id: 1234)
@@ -49,6 +54,25 @@ result.failure?
49
54
  #=> false
50
55
  ```
51
56
 
57
+ You can simplify your controllers drastically having them simply check for `success?`
58
+ ```ruby
59
+ class UserController < ApplicationController
60
+
61
+ def create
62
+ result = CreateUser.call(name: params.fetch(:name), email: params.fetch(:email))
63
+
64
+ if result.success?
65
+ render :dashboard, user: result.user
66
+ else
67
+ redirect_to :new, error: "Couldn't create user because #{result.error_reason}"
68
+ end
69
+ end
70
+
71
+ end
72
+ ```
73
+
74
+ ### Signal failure with `fail!`
75
+
52
76
  You can immediately stop execution with the `fail!` method.
53
77
  ```ruby
54
78
  class DeleteAccount
@@ -64,7 +88,7 @@ class DeleteAccount
64
88
  end
65
89
  ```
66
90
 
67
- When an action fails with `fail!`, the result it returns will return false for `success?` and true for `failure?`.
91
+ When you fail an action with `fail!`, the result it returns will return true for `failure?` and false for `success?`.
68
92
  ```ruby
69
93
  result = FailingAction.call(id: 1234)
70
94
 
@@ -74,7 +98,9 @@ result.failure?
74
98
  #=> true
75
99
  ```
76
100
 
77
- The most common way to use Actionizer is to compose small pieces of functionality (which can themselves be Actions) into larger pieces of functionality to give that sequence of Actions a name and simple interface.
101
+ ### Composing Actions
102
+
103
+ The most common way to use `Actionizer` is to compose small pieces of functionality (which can themselves be Actions) into larger pieces of functionality to give that sequence of Actions a name and simple interface. Say you want to create a user and send them a welcome email as part of the onboarding process. Then you might do something like this:
78
104
  ```ruby
79
105
  class OnboardUser
80
106
  include Actionizer
@@ -89,8 +115,11 @@ class OnboardUser
89
115
  end
90
116
  ```
91
117
 
118
+ This code is self-documenting because there's no wiki or comments to read about what's going on here. The code is telling you exactly what's going on.
92
119
 
93
- This pattern is so common, there's a shorthand: `<METHOD>_or_fail`. It works for any instance method defined on the class you specify.
120
+ ### Error-checking shorthand: `*_or_fail`
121
+
122
+ To automatically check for `failure?` and bubble up errors on failure, there's a shorthand: `<METHOD>_or_fail`. It works for any instance method defined on the class you specify.
94
123
  ```ruby
95
124
  class OnboardUser
96
125
  include Actionizer
@@ -103,6 +132,31 @@ class OnboardUser
103
132
  end
104
133
  ```
105
134
 
135
+ ### Explicitly declare your inputs with `inputs_for`
136
+
137
+ To more explicitly document the inputs to your Actions, you can use `inputs_for`.
138
+ ```ruby
139
+ class CreateUser
140
+ include Actionizer
141
+
142
+ inputs_for :call do
143
+ required :name
144
+ required :email
145
+ optional :phone_number
146
+ end
147
+ def call
148
+ result = CreateUser.call(user_params)
149
+ end
150
+
151
+ private
152
+
153
+ def user_params
154
+ { name: input.name, email: input.email, phone_number: input.phone_number }.compact
155
+ end
156
+ end
157
+ ```
158
+
159
+ You'll get an `ArgumentError` if you pass any params not defined in the `inputs_for` block, or if you don't supply all required params. This is completely opt-in so if you don't provide an `inputs_for` block, no checking is performed.
106
160
 
107
161
  ## Development
108
162
 
@@ -1,5 +1,5 @@
1
1
  module Actionizer
2
- class Failure < StandardError
2
+ class Failure < Exception
3
3
  attr_reader :output
4
4
 
5
5
  def initialize(msg, output)
@@ -1,3 +1,3 @@
1
1
  module Actionizer
2
- VERSION = '0.8.0'
2
+ VERSION = '0.9.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Nichols
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-07-10 00:00:00.000000000 Z
11
+ date: 2016-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler