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.
@@ -1,5 +1,13 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.3.1
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-Types
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 to use classes beyond models and controllers to hold the app's logic.
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://cb.platan.us/services-commands-y-otros-poderosos-patrones-en-rails) (in spanish).
11
-
12
- 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 and Commands with ease.
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
- ## Usage
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
- ### Generators
36
+ ### Services
19
37
 
20
38
  For generating services we use:
21
39
 
22
- $ rails generate service MagicMakingService foo bar
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
- This will create the MagicMakingService class, inheriting from a base service class:
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
- class MagicMakingService < PowerTypes::Service.new(:foo, bar: nil)
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
- The arguments get available to be used in the service class as instance variables: `@foo` and `@bar`
29
- Default values for arguments are optional, and can't be defined in the generator, but manually after. In this case a `nil` value was given for `bar`.
30
- 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.
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 generator will create its corresponding rspec file.
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
- For generating commands:
156
+ Then, you can use it like this:
37
157
 
38
- $ rails generate command MakeMagic foo bar
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
- Which will generate the corresponding class, with the `perform` method. This method must be implemented, and its called when the command is executed.
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
- class MakeMagic < PowerTypes::Command.new(:foo, bar: nil)
165
+ ### Observers
43
166
 
44
- And in a similar way to services, the command's spec file is also created by this generator
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
- And use any method the service provides
53
-
54
- magic_service.gandalfize(sauron)
55
- magic_service.harry_potterize(voldemort)
169
+ ```
170
+ $ rails generate observer MyModel
171
+ ```
56
172
 
57
- In the case of commands, we are not suposed to store or reuse the object. You just want to run it and keep the result
58
-
59
- result = MakeMagic.for(foo: a_foo, bar: "i'm bar")
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
- def dissappear(object)
73
- #blah blah
74
- end
75
-
76
- def shrink(children)
77
- #bleh bleeh
78
- end
79
-
80
- def shuffle(cards)
81
- #blaah
82
- end
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
- MagicTricks.dissapear(rabbit)
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-Types is © 2016 Platanus, S.p.A. It is free software and may be redistributed under the terms specified in the LICENSE file.
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.
@@ -4,6 +4,7 @@ module PowerTypes
4
4
  def create_folders
5
5
  empty_directory "app/commands/"
6
6
  empty_directory "app/services/"
7
+ empty_directory "app/observers/"
7
8
  empty_directory "app/utils/"
8
9
  empty_directory "app/values/"
9
10
  end
@@ -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
@@ -0,0 +1,8 @@
1
+ class <%= class_name %>Observer < PowerTypes::Observer
2
+ # after_save :run
3
+ # before_create { puts "yes, you can provide a block to work with" }
4
+ #
5
+ # def run
6
+ # p object # object holds an <%= class_name %> instance.
7
+ # end
8
+ end
@@ -0,0 +1,5 @@
1
+ require 'rails_helper'
2
+
3
+ describe <%= class_name %>Observer do
4
+ pending "add some examples to (or delete) #{__FILE__}"
5
+ end
@@ -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