light_operations 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +178 -8
- data/lib/light_operations/core.rb +1 -0
- data/lib/light_operations/version.rb +1 -1
- data/spec/lib/core_spec.rb +33 -9
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f01d43765f156039ef8227aa36ef7fc853bbc722
|
4
|
+
data.tar.gz: 6a28e10184931953b04722b98ad505e100636307
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0709083f10d462b9421924e4313323b38dd077a3908827fef106eb8cfee646a6128cde566399fff48cc431c72cc18d5e1c86a8bfa3044d9f61a59a4ba9219527
|
7
|
+
data.tar.gz: 80297934730c17e80022a7ecb2c843a3882ae14cd6041fab3a8fa08941e446c47aa6dca8c507005dfc2014dd0777fbe884d8793d6354065193282d61087ce6d5
|
data/README.md
CHANGED
@@ -19,6 +19,109 @@ Or install it yourself as:
|
|
19
19
|
|
20
20
|
$ gem install light_operations
|
21
21
|
|
22
|
+
## How it works
|
23
|
+
|
24
|
+
Basicly this is a Container for buissnes logic.
|
25
|
+
|
26
|
+
You can define dependencies during initialization and run with custom parameters.
|
27
|
+
When you define deferred actions on `success` and `fail` before operation execution is finished,
|
28
|
+
after execution one of those action depend for execution result will be executed.
|
29
|
+
Actions could be a block (Proc) or you could delgate execution to method other object,
|
30
|
+
by binding operation with specific object with those methods.
|
31
|
+
You also could use operation as simple execution and check status by `success?` or `fail?` method
|
32
|
+
and then by using `subject` and `errors` method build your own logic to finish your result.
|
33
|
+
There is many possible usecases where and how you could use operations.
|
34
|
+
You can build csacade of opreations, use them one after the other,
|
35
|
+
use them recursively and a lot more.
|
36
|
+
|
37
|
+
Class
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
class MyOperation < LightOperations::Core
|
41
|
+
def execute(_params = nil)
|
42
|
+
dependency(:my_service) # when missing MissingDependency error will be raised
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
```
|
47
|
+
|
48
|
+
Initialization
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
MyOperation.new(my_service: MyService.new)
|
52
|
+
```
|
53
|
+
|
54
|
+
You can add deferred actions for success and fail
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
# 1
|
58
|
+
MyOperation.new.on_success { |model| render :done, locals: { model: model } }
|
59
|
+
# 2
|
60
|
+
MyOperation.new.on(success: -> () { |model| render :done, locals: { model: model } )
|
61
|
+
```
|
62
|
+
|
63
|
+
When you bind operation with other object you could delegate actions to binded object methods
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
# 1
|
67
|
+
MyOperation.new.bind_with(self).on_success(:done)
|
68
|
+
# 2
|
69
|
+
MyOperation.new.bind_with(self).on(success: :done)
|
70
|
+
```
|
71
|
+
|
72
|
+
Execution method `#run` finalize actions execution
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
MyOperation.new.bind_with(self).on(success: :done).run(params)
|
76
|
+
```
|
77
|
+
|
78
|
+
After execution operation hold execution state you could get back all info you need
|
79
|
+
|
80
|
+
- `#success?` => `true/false`
|
81
|
+
- `#fail?` => `true/false`
|
82
|
+
- `#subject?` => `success or fail object`
|
83
|
+
- `#errors` => `errors by default array but you can return any objec tou want`
|
84
|
+
|
85
|
+
Default usage
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
operation.new(dependencies)
|
89
|
+
.on(success: :done, fail: :show_error)
|
90
|
+
.bind_with(self)
|
91
|
+
.run(params)
|
92
|
+
```
|
93
|
+
|
94
|
+
or
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
operation.new(dependencies).tap do |op|
|
98
|
+
return op.run(params).success? ? op.subject : op.errors
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
#### success block or method receive subject as argument
|
103
|
+
`(subject) -> { }`
|
104
|
+
|
105
|
+
or
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
def success_method(subject)
|
109
|
+
...
|
110
|
+
end
|
111
|
+
|
112
|
+
```
|
113
|
+
#### fail block or method receive subject and errors as argument
|
114
|
+
`(subject, errors) -> { }`
|
115
|
+
|
116
|
+
or
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
def fail_method(subject, errors)
|
120
|
+
...
|
121
|
+
end
|
122
|
+
|
123
|
+
```
|
124
|
+
|
22
125
|
## Usage
|
23
126
|
|
24
127
|
|
@@ -37,10 +140,11 @@ class ArticleVoteBumperOperation < LightOperations::Core
|
|
37
140
|
article.vote = article.vote.next
|
38
141
|
article.save
|
39
142
|
end
|
143
|
+
{ success: true }
|
40
144
|
end
|
41
145
|
|
42
146
|
def on_ar_error(_exception)
|
43
|
-
fail!(
|
147
|
+
fail!(vote: 'could not be updated!')
|
44
148
|
end
|
45
149
|
end
|
46
150
|
```
|
@@ -50,14 +154,14 @@ Controller
|
|
50
154
|
```ruby
|
51
155
|
class ArticleVotesController < ApplicationController
|
52
156
|
def up
|
53
|
-
response =
|
157
|
+
response = operation.run.success? ? response.subject : response.errors
|
54
158
|
render :up, json: response
|
55
159
|
end
|
56
160
|
|
57
161
|
private
|
58
162
|
|
59
|
-
def
|
60
|
-
@
|
163
|
+
def operation
|
164
|
+
@operation ||= ArticleVoteBumperOperation.new(article_model: article)
|
61
165
|
end
|
62
166
|
|
63
167
|
def article
|
@@ -66,7 +170,7 @@ class ArticleVotesController < ApplicationController
|
|
66
170
|
end
|
67
171
|
```
|
68
172
|
|
69
|
-
#### Basic
|
173
|
+
#### Basic recursive execution to collect newsfeeds from 2 sources
|
70
174
|
|
71
175
|
Operation
|
72
176
|
|
@@ -92,7 +196,8 @@ class NewsFeedsController < ApplicationController
|
|
92
196
|
BACKUP_NEWS_URL = 'http://rss.not_so_bad_news.pl'
|
93
197
|
def news
|
94
198
|
collect_feeds_op
|
95
|
-
|
199
|
+
.bind_with(self)
|
200
|
+
.on(success: :display_news, fail: :second_attempt)
|
96
201
|
.run(url: DEFAULT_NEWS_URL)
|
97
202
|
end
|
98
203
|
|
@@ -105,7 +210,7 @@ class NewsFeedsController < ApplicationController
|
|
105
210
|
end
|
106
211
|
|
107
212
|
def display_news(news)
|
108
|
-
render :display_news, locals { news: news }
|
213
|
+
render :display_news, locals: { news: news }
|
109
214
|
end
|
110
215
|
|
111
216
|
def display_old_news
|
@@ -121,7 +226,58 @@ class NewsFeedsController < ApplicationController
|
|
121
226
|
end
|
122
227
|
```
|
123
228
|
|
229
|
+
#### Basic with active_model/active_record object
|
230
|
+
|
231
|
+
Operation
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
class AddBookOperation < LightOperations::Core
|
235
|
+
def execute(params = {})
|
236
|
+
dependency(:book_model).new(params).tap do |model|
|
237
|
+
model.valid? # this method automatically provide errors from model.errors
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
```
|
242
|
+
|
243
|
+
Controller
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
class BooksController < ApplicationController
|
247
|
+
def index
|
248
|
+
render :index, locals: { collection: Book.all }
|
249
|
+
end
|
250
|
+
|
251
|
+
def new
|
252
|
+
render_book_form
|
253
|
+
end
|
254
|
+
|
255
|
+
def create
|
256
|
+
add_book_op
|
257
|
+
.bind_with(self)
|
258
|
+
.on(success: :book_created, fail: :render_book_form)
|
259
|
+
.run(permit_book_params)
|
260
|
+
end
|
261
|
+
|
262
|
+
private
|
263
|
+
|
264
|
+
def book_created(book)
|
265
|
+
redirect_to :index, notice: "book #{book.name} created"
|
266
|
+
end
|
267
|
+
|
268
|
+
def render_book_form(book = Book.new, _errors = nil)
|
269
|
+
render :new, locals: { book: book }
|
270
|
+
end
|
124
271
|
|
272
|
+
def add_book_op
|
273
|
+
@add_book_op ||= AddBookOperation.new(book_model: Book)
|
274
|
+
end
|
275
|
+
|
276
|
+
def permit_book_params
|
277
|
+
params.requre(:book)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
```
|
125
281
|
|
126
282
|
#### Simple case when you want have user authorization
|
127
283
|
|
@@ -253,13 +409,27 @@ class AuthController < ApplicationController
|
|
253
409
|
end
|
254
410
|
```
|
255
411
|
|
412
|
+
Register success and fails action is avialable by `#on` like :
|
413
|
+
|
414
|
+
```ruby
|
415
|
+
def create
|
416
|
+
auth_op.bind_with(self).on(success: :dashboard, fail: :show_error).run(params)
|
417
|
+
end
|
418
|
+
```
|
419
|
+
|
420
|
+
Operation have some helper methods (to improve recursive execution)
|
421
|
+
|
422
|
+
- `#clear!` => return operation to init state
|
423
|
+
- `#unbind!` => unbind binded object
|
424
|
+
- `#clear_subject_with_errors!` => clear subject and errors
|
425
|
+
|
256
426
|
When operation status is most importent we can simply use `#success?` or `#fail?` on the executed operation
|
257
427
|
|
258
428
|
Errors are available by `#errors` after operation is executed
|
259
429
|
|
260
430
|
## Contributing
|
261
431
|
|
262
|
-
1. Fork it ( https://github.com/[my-github-username]/
|
432
|
+
1. Fork it ( https://github.com/[my-github-username]/light_operations/fork )
|
263
433
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
264
434
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
265
435
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/spec/lib/core_spec.rb
CHANGED
@@ -5,6 +5,16 @@ describe LightOperations::Core do
|
|
5
5
|
let(:params) { { login: 'pawel', password: 'abc' } }
|
6
6
|
let(:dependencies) { { login_service: login_service } }
|
7
7
|
|
8
|
+
let(:binding_object) do
|
9
|
+
Class.new(Object).tap do |klass|
|
10
|
+
klass.class_eval do
|
11
|
+
def success_action(_subject); end
|
12
|
+
|
13
|
+
def error_action(_subject, _errors); end
|
14
|
+
end
|
15
|
+
end.new
|
16
|
+
end
|
17
|
+
|
8
18
|
def subject_factory(&block)
|
9
19
|
Class.new(described_class).tap do |klass|
|
10
20
|
klass.class_eval(&block)
|
@@ -18,15 +28,6 @@ describe LightOperations::Core do
|
|
18
28
|
end
|
19
29
|
|
20
30
|
context 'use cases' do
|
21
|
-
let(:binding_object) do
|
22
|
-
Class.new(Object).tap do |klass|
|
23
|
-
klass.class_eval do
|
24
|
-
def success_action(_subject); end
|
25
|
-
|
26
|
-
def error_action(_subject, _errors); end
|
27
|
-
end
|
28
|
-
end.new
|
29
|
-
end
|
30
31
|
|
31
32
|
# dependency using
|
32
33
|
|
@@ -234,4 +235,27 @@ describe LightOperations::Core do
|
|
234
235
|
subject.clear!
|
235
236
|
end
|
236
237
|
end
|
238
|
+
|
239
|
+
context 'Operation executed several times' do
|
240
|
+
subject do
|
241
|
+
subject_factory do
|
242
|
+
def execute(params = {})
|
243
|
+
fail!(:missing_result) unless params.key?(:result)
|
244
|
+
params[:result]
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
it 'always start with clean state of subject and errors' do
|
250
|
+
|
251
|
+
subject
|
252
|
+
.bind_with(binding_object)
|
253
|
+
.on(success: :success_action, fail: :error_action)
|
254
|
+
|
255
|
+
expect(binding_object).to receive(:error_action).with(nil, :missing_result)
|
256
|
+
subject.run
|
257
|
+
expect(binding_object).to receive(:success_action).with(:success)
|
258
|
+
subject.run(result: :success)
|
259
|
+
end
|
260
|
+
end
|
237
261
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: light_operations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pawel Niemczyk
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|