power-types 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -0
- data/.hound.yml +4 -0
- data/.rubocop.yml +1038 -0
- data/.travis.yml +9 -1
- data/README.md +230 -49
- data/lib/generators/power_types/init_generator.rb +1 -0
- data/lib/generators/rails/observer_generator.rb +18 -0
- data/lib/generators/rails/templates/observer.rb +8 -0
- data/lib/generators/rails/templates/observer_spec.rb +5 -0
- data/lib/power_types.rb +4 -0
- data/lib/power_types/patterns/observer/observable.rb +39 -0
- data/lib/power_types/patterns/observer/observer.rb +38 -0
- data/lib/power_types/patterns/observer/trigger.rb +31 -0
- data/lib/power_types/util.rb +6 -0
- data/lib/power_types/version.rb +2 -2
- data/{power_types.gemspec → power-types.gemspec} +3 -2
- metadata +28 -5
data/.travis.yml
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
sudo: false
|
2
2
|
language: ruby
|
3
3
|
rvm:
|
4
|
-
|
4
|
+
- 2.3.1
|
5
5
|
before_install: gem install bundler -v 1.12.4
|
6
|
+
deploy:
|
7
|
+
provider: rubygems
|
8
|
+
api_key:
|
9
|
+
secure: e5mtRDYciM2I5IxsMJDKKNSHgyQsfmAQEMCgbf1tN+SEmxeJTif0FXfA1M4vZ2y6XL7sza0Pf1hTMIqmEIh4L8tv/JxTOl6ZWkD2l8x+nn/2Hlu5mVsztsBh+qDPWPg29hbvTptOfcLHfAqIboB4L4f5nNuHeeLZQjGD7irwaVil7IntV3oG+XfD1uJzkqO9x7Xw+cN+HzACwZQ3u+XhmedCnMWJdCwai5QZAYlytxqKirYoGgS9pi4ZgAs0ptIT+aG3dG8ckCRvhDiu2iGDrv7AnG+K2MrI7O06g0676Iao1O0xTcW2Yo9uArkVXRo9L3Ghk1T5O5zCPoiF7oLiNlBkHeRGOw/+H+88NxziNaOi9BZswn2ROpFylVPrg8hDvIoOqRZfxqgnCtM2vTdOdyCcPOJgm3nhZl9OZ9aPbE3x/Fh5zXVBFT5Vwz3RV0s9uDuKa6pjphxOGb/be+ZYyPJ/uOU7Wo6Qj+lQ+/YBwbZ2wPJ2qAqb69gRBliYpSFbMsLptKw7YUYNW22yknZHGrhHlok7YDDpotUMMDJmSKUrXvBIPdqq9Y61gXLXE5QxxQkWqAFyhluDDMdIDs0PMkDBl45LvHfPJQZxp8sX+FK5ashmMPgdsDcb8DRK+LYrCHrinMkPcwIME5JeuAnb7bT0ErrtBSq8iAD8sPeI+/Q=
|
10
|
+
gem: power-types
|
11
|
+
on:
|
12
|
+
tags: true
|
13
|
+
repo: platanus/power-types
|
data/README.md
CHANGED
@@ -1,62 +1,231 @@
|
|
1
|
-
# Power
|
1
|
+
# Power Types
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/power-types.svg)](https://badge.fury.io/rb/power-types) [![Build Status](https://travis-ci.org/platanus/power-types.svg?branch=master)](https://travis-ci.org/platanus/power-types) [![Coverage Status](https://coveralls.io/repos/github/platanus/power-types/badge.svg)](https://coveralls.io/github/platanus/power-types)
|
2
3
|
|
3
|
-
Rails pattern enforcing types used by the Platanus team
|
4
|
+
Rails pattern enforcing types used by the Platanus team.
|
4
5
|
|
5
6
|
## Introduction
|
6
7
|
|
7
|
-
In Rails projects, Platanus encourages
|
8
|
-
These powerful types proposed are Services, Commands, Utils and Values.
|
8
|
+
In Rails projects, Platanus encourages to use classes beyond models and controllers to hold the app's logic.
|
9
|
+
These powerful types proposed are Services, Commands, Observers, Utils and Values.
|
9
10
|
|
10
|
-
For a deeper understanding about the usage of these patterns, feel welcome to read the [related post in Platanus Blog](https://
|
11
|
-
|
12
|
-
The goal aimed with this gem is to go further, and not just apply this patterns over POROs (plain simple ruby classes).
|
11
|
+
For a deeper understanding about the usage of these patterns, feel welcome to read the [related post in Platanus Blog](https://blog.platan.us/services-commands-y-otros-poderosos-patrones-en-rails) (in spanish).
|
12
|
+
|
13
|
+
The goal aimed with this gem is to go further, and not just apply this patterns over POROs (plain simple ruby classes). The gem provides an special structure and syntax to create and run services, commands and more, with ease.
|
13
14
|
|
14
15
|
It also creates the directory for each type, and provides generators.
|
15
16
|
|
16
|
-
##
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add to your Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem "power-types"
|
23
|
+
```
|
24
|
+
|
25
|
+
```bash
|
26
|
+
bundle install
|
27
|
+
```
|
28
|
+
|
29
|
+
## Power types
|
30
|
+
|
31
|
+
- [Services](#services)
|
32
|
+
- [Commands](#commands)
|
33
|
+
- [Observers](#observers)
|
34
|
+
- [Values and Utils](#values-and-utils)
|
17
35
|
|
18
|
-
###
|
36
|
+
### Services
|
19
37
|
|
20
38
|
For generating services we use:
|
21
39
|
|
22
|
-
|
40
|
+
```
|
41
|
+
$ rails generate service MyService foo bar
|
42
|
+
```
|
43
|
+
|
44
|
+
This will create the MyService class, inheriting from a base service class:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
class MyService < PowerTypes::Service.new(:foo, :bar)
|
48
|
+
# Service code goes here
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
And its corresponding rspec file:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
require 'rails_helper'
|
56
|
+
|
57
|
+
describe MyService do
|
58
|
+
def build(*_args)
|
59
|
+
described_class.new(*_args)
|
60
|
+
end
|
61
|
+
|
62
|
+
pending "describe what your service does here"
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
The arguments get available to be used in the service class as instance variables: `@foo` and `@bar`.
|
67
|
+
Default values for arguments are optional, and can't be defined in the generator, but manually after like this:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
class MyService < PowerTypes::Service.new(foo: "X", bar: nil)
|
71
|
+
# Service code goes here
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
This is a way to make the argument optional. If no default value is assigned, the argument will be required, and an error raised if missing.
|
76
|
+
|
77
|
+
Now, suppose you have defined the following service:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class MagicMakingService < PowerTypes::Service.new(wizard: "Harry Potter")
|
81
|
+
def gandalfize(who)
|
82
|
+
"#{@wizard} gandalfized #{who}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def harrypotterize(who)
|
86
|
+
"#{@wizard} harrypotterized #{who}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
Then, you can use it like this:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
magic_service = MagicMakingService.new(wizard: "Gandalf")
|
95
|
+
magic_service.gandalfize("Sauron") #=> "Gandalf gandalfized Sauron"
|
96
|
+
|
97
|
+
magic_service = MagicMakingService.new
|
98
|
+
magic_service.harrypotterize("Voldemort") #=> "Harry Potter harrypotterize Voldemort"
|
99
|
+
```
|
100
|
+
|
101
|
+
### Commands
|
102
|
+
|
103
|
+
For generating commands we use:
|
104
|
+
|
105
|
+
```
|
106
|
+
$ rails generate command ExecuteSomeAction foo bar
|
107
|
+
```
|
108
|
+
|
109
|
+
This will create the ExecuteSomeAction class, inheriting from a base command class:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
class ExecuteSomeAction < PowerTypes::Command.new(:foo, :bar)
|
113
|
+
def perform
|
114
|
+
# Command code goes here
|
115
|
+
end
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
And its corresponding rspec file:
|
23
120
|
|
24
|
-
|
121
|
+
```ruby
|
122
|
+
require 'rails_helper'
|
123
|
+
|
124
|
+
describe ExecuteSomeAction do
|
125
|
+
def perform(*_args)
|
126
|
+
described_class.for(*_args)
|
127
|
+
end
|
128
|
+
|
129
|
+
pending "describe what perform does here"
|
130
|
+
end
|
131
|
+
```
|
25
132
|
|
26
|
-
|
133
|
+
The arguments get available to be used in the command class as instance variables: `@foo` and `@bar`.
|
134
|
+
Default values for arguments are optional, and can't be defined in the generator, but manually after like this:
|
27
135
|
|
28
|
-
|
29
|
-
|
30
|
-
|
136
|
+
```ruby
|
137
|
+
class ExecuteSomeAction < PowerTypes::Command.new(foo: "X", bar: nil)
|
138
|
+
def perform
|
139
|
+
# Command code goes here
|
140
|
+
end
|
141
|
+
end
|
142
|
+
```
|
31
143
|
|
32
|
-
This
|
144
|
+
This is a way to make the argument optional. If no default value is assigned, the argument will be required, and an error raised if missing.
|
33
145
|
|
146
|
+
Now, suppose you have defined the following command:
|
34
147
|
|
148
|
+
```ruby
|
149
|
+
class MakeMagicTrick < PowerTypes::Command.new(:wizard, receiver: "Sauron")
|
150
|
+
def perform
|
151
|
+
"#{@wizard} enchanted #{@receiver}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
```
|
35
155
|
|
36
|
-
|
156
|
+
Then, you can use it like this:
|
37
157
|
|
38
|
-
|
158
|
+
```ruby
|
159
|
+
MakeMagicTrick.for(wizard: "Gandalf") #=> "Gandalf enchanted Sauron"
|
160
|
+
MakeMagicTrick.for(wizard: "Harry Potter", receiver: "Voldemor") #=> "Harry Portter enchanted Voldemor"
|
161
|
+
```
|
39
162
|
|
40
|
-
|
163
|
+
> In the case of commands, we are not supposed to store or reuse the object. You just want to run it and keep the result.
|
41
164
|
|
42
|
-
|
165
|
+
### Observers
|
43
166
|
|
44
|
-
|
45
|
-
|
46
|
-
### Instantiate and Run
|
47
|
-
|
48
|
-
We can create service objects like this
|
49
|
-
|
50
|
-
magic_service = MagicMakingService.new(foo: my_foo, bar: "a bar")
|
167
|
+
For generating observers we use:
|
51
168
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
magic_service.harry_potterize(voldemort)
|
169
|
+
```
|
170
|
+
$ rails generate observer MyModel
|
171
|
+
```
|
56
172
|
|
57
|
-
|
58
|
-
|
59
|
-
|
173
|
+
This will create the MyModelObserver class, inheriting from a base observer class:
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
class MyModelObserver < PowerTypes::Observer
|
177
|
+
# after_save :run
|
178
|
+
# before_create { puts "yes, you can provide a block to work with" }
|
179
|
+
#
|
180
|
+
# def run
|
181
|
+
# p object # object holds an MyModel instance.
|
182
|
+
# end
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
It will also include the `PowerTypes::Observable` mixin in `MyModel` class:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
class MyModel < ActiveRecord::Base
|
190
|
+
include PowerTypes::Observable
|
191
|
+
end
|
192
|
+
```
|
193
|
+
|
194
|
+
And the corresponding rspec file:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
require 'rails_helper'
|
198
|
+
|
199
|
+
describe MyModelObserver do
|
200
|
+
pending "add some examples to (or delete) #{__FILE__}"
|
201
|
+
end
|
202
|
+
```
|
203
|
+
|
204
|
+
Now, suppose you have defined the following model (with name and villian attributes) and observer:
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
class Wizard < ActiveRecord::Base
|
208
|
+
include PowerTypes::Observable
|
209
|
+
end
|
210
|
+
```
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
class WizardObserver < PowerTypes::Observer
|
214
|
+
after_create :kill_villain
|
215
|
+
|
216
|
+
def kill_villain
|
217
|
+
p "#{object.name} have killed #{object.villian}"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
222
|
+
Then, you can use it like this:
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
Wizard.create!(name: "Gandalf", villian: "Sauron") #=> This action will trigger the method kill_villian defined in the WizardObserver's after_create callback.
|
226
|
+
```
|
227
|
+
|
228
|
+
> As you can guess, `object` holds the Wizard instance.
|
60
229
|
|
61
230
|
### Values and Utils
|
62
231
|
|
@@ -65,26 +234,38 @@ This two types do not have generators.
|
|
65
234
|
Values are just simple Ruby classes, but watch out to keep them in the Values directory!
|
66
235
|
|
67
236
|
Utils should be defined as a module. There you define the independent but related functions. Use the extend self pattern to call them directly after the module name.
|
237
|
+
|
68
238
|
```ruby
|
69
239
|
module MagicTricks
|
70
|
-
extend self
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
240
|
+
extend self
|
241
|
+
|
242
|
+
def dissappear(object)
|
243
|
+
#blah blah
|
244
|
+
end
|
245
|
+
|
246
|
+
def shrink(children)
|
247
|
+
#bleh bleeh
|
248
|
+
end
|
249
|
+
|
250
|
+
def shuffle(cards)
|
251
|
+
#blaah
|
252
|
+
end
|
253
|
+
end
|
83
254
|
```
|
255
|
+
|
84
256
|
Example of calling a Util function:
|
85
257
|
|
86
|
-
|
258
|
+
```ruby
|
259
|
+
MagicTricks.dissapear(rabbit)
|
260
|
+
```
|
261
|
+
|
262
|
+
## Contributing
|
87
263
|
|
264
|
+
1. Fork it
|
265
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
266
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
267
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
268
|
+
5. Create new Pull Request
|
88
269
|
|
89
270
|
## Credits
|
90
271
|
|
@@ -96,4 +277,4 @@ Power-Types is maintained by [platanus](http://platan.us).
|
|
96
277
|
|
97
278
|
## License
|
98
279
|
|
99
|
-
Power
|
280
|
+
Power Types is © 2016 Platanus, S.p.A. It is free software and may be redistributed under the terms specified in the LICENSE file.
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rails
|
2
|
+
class ObserverGenerator < Rails::Generators::NamedBase
|
3
|
+
source_root File.expand_path("../templates", __FILE__)
|
4
|
+
|
5
|
+
desc "This generator creates a new observer at app/observers"
|
6
|
+
def create_observer
|
7
|
+
template('observer.rb', "app/observers/#{file_name.underscore}_observer.rb")
|
8
|
+
template('observer_spec.rb', "spec/observers/#{file_name.underscore}_observer_spec.rb")
|
9
|
+
end
|
10
|
+
|
11
|
+
def include_observable_mixin
|
12
|
+
line = "class #{class_name} < ActiveRecord::Base"
|
13
|
+
gsub_file "app/models/#{file_name.underscore}.rb", /(#{Regexp.escape(line)})/mi do |match|
|
14
|
+
"#{match}\n include PowerTypes::Observable"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/power_types.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
require "power_types/version"
|
2
|
+
require "power_types/util"
|
2
3
|
require "power_types/patterns/service"
|
3
4
|
require "power_types/patterns/command"
|
5
|
+
require "power_types/patterns/observer/observable"
|
6
|
+
require "power_types/patterns/observer/observer"
|
7
|
+
require "power_types/patterns/observer/trigger"
|
4
8
|
|
5
9
|
module PowerTypes
|
6
10
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module PowerTypes
|
2
|
+
module Observable
|
3
|
+
@@observable_disabled = false
|
4
|
+
|
5
|
+
def self.observable_disabled=(_value)
|
6
|
+
@@observable_disabled = _value
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.observable_disabled?
|
10
|
+
@@observable_disabled
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.included(_klass)
|
14
|
+
_klass.extend ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
PowerTypes::Util::OBSERVABLE_EVENTS.each do |event|
|
18
|
+
define_method("_run_#{event}_callbacks") do |&_block|
|
19
|
+
self.class.observers.each { |o| o.trigger(:before, event, self) }
|
20
|
+
result = super &_block
|
21
|
+
self.class.observers.each { |o| o.trigger(:after, event, self) }
|
22
|
+
result
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
def observers
|
28
|
+
return [] if PowerTypes::Observable.observable_disabled?
|
29
|
+
@observers ||= [].tap do |array|
|
30
|
+
begin
|
31
|
+
array << Kernel.const_get("#{self}Observer")
|
32
|
+
rescue NameError
|
33
|
+
# could not find observer
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module PowerTypes
|
2
|
+
class Observer
|
3
|
+
attr_reader :object
|
4
|
+
|
5
|
+
PowerTypes::Util::OBSERVABLE_EVENTS.each do |event|
|
6
|
+
PowerTypes::Util::OBSERVABLE_TYPES.each do |type|
|
7
|
+
define_singleton_method("#{type}_#{event}") do |args = nil, &_block|
|
8
|
+
add_trigger(type, event, *args, &_block)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.trigger(_type, _event, _object)
|
14
|
+
triggers.select { |t| t.type == _type && t.event == _event }.each do |trigger|
|
15
|
+
trigger.call(new(_object))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.add_trigger(_type, _event, _handler = nil, _options = {}, &_block)
|
20
|
+
triggers << PowerTypes::Trigger.new(
|
21
|
+
_type,
|
22
|
+
_event,
|
23
|
+
(_handler || _block),
|
24
|
+
_options
|
25
|
+
)
|
26
|
+
|
27
|
+
triggers.last
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.triggers
|
31
|
+
@triggers ||= []
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(_object)
|
35
|
+
@object = _object
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|