power-types 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +103 -0
- data/.circleci/setup-rubygems.sh +3 -0
- data/.rubocop.yml +49 -591
- data/.ruby-version +1 -0
- data/LICENSE.txt +1 -1
- data/README.md +192 -23
- data/lib/generators/power_types/init_generator.rb +10 -0
- data/lib/generators/rails/observer_generator.rb +1 -1
- data/lib/generators/rails/presenter_generator.rb +12 -0
- data/lib/generators/rails/templates/presenter.rb +2 -0
- data/lib/generators/rails/templates/presenter_spec.rb +5 -0
- data/lib/generators/rails/templates/util.rb +7 -0
- data/lib/generators/rails/templates/util_spec.rb +9 -0
- data/lib/generators/rails/util_generator.rb +14 -0
- data/lib/power-types.rb +1 -1
- data/lib/power_types/errors.rb +3 -0
- data/lib/power_types/patterns/base_util.rb +7 -0
- data/lib/power_types/patterns/presenter/base_presenter.rb +28 -0
- data/lib/power_types/patterns/presenter/presentable.rb +20 -0
- data/lib/power_types/util.rb +1 -1
- data/lib/power_types/version.rb +1 -1
- data/lib/power_types.rb +6 -0
- data/power-types.gemspec +9 -10
- metadata +50 -60
- data/.hound.yml +0 -4
- data/.travis.yml +0 -13
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.7
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Power Types
|
2
|
-
[![Gem Version](https://badge.fury.io/rb/power-types.svg)](https://badge.fury.io/rb/power-types)
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/power-types.svg)](https://badge.fury.io/rb/power-types)
|
3
|
+
[![CircleCI](https://circleci.com/gh/platanus/local_resource.svg?style=shield)](https://app.circleci.com/pipelines/github/platanus/local_resource)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/github/platanus/power-types/badge.svg)](https://coveralls.io/github/platanus/power-types)
|
3
5
|
|
4
6
|
Rails pattern enforcing types used by the Platanus team.
|
5
7
|
|
@@ -8,7 +10,7 @@ Rails pattern enforcing types used by the Platanus team.
|
|
8
10
|
In Rails projects, Platanus encourages to use classes beyond models and controllers to hold the app's logic.
|
9
11
|
These powerful types proposed are Services, Commands, Observers, Utils and Values.
|
10
12
|
|
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).
|
13
|
+
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-27c2d3aa7c2e) (in spanish).
|
12
14
|
|
13
15
|
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.
|
14
16
|
|
@@ -28,10 +30,20 @@ bundle install
|
|
28
30
|
|
29
31
|
## Power types
|
30
32
|
|
31
|
-
- [
|
32
|
-
- [
|
33
|
-
- [
|
34
|
-
- [
|
33
|
+
- [Power Types](#power-types)
|
34
|
+
- [Introduction](#introduction)
|
35
|
+
- [Installation](#installation)
|
36
|
+
- [Power types](#power-types-1)
|
37
|
+
- [Services](#services)
|
38
|
+
- [Commands](#commands)
|
39
|
+
- [Observers](#observers)
|
40
|
+
- [Values](#values)
|
41
|
+
- [Presenters](#presenters)
|
42
|
+
- [Utils](#utils)
|
43
|
+
- [Publishing](#publishing)
|
44
|
+
- [Contributing](#contributing)
|
45
|
+
- [Credits](#credits)
|
46
|
+
- [License](#license)
|
35
47
|
|
36
48
|
### Services
|
37
49
|
|
@@ -201,7 +213,7 @@ describe MyModelObserver do
|
|
201
213
|
end
|
202
214
|
```
|
203
215
|
|
204
|
-
Now, suppose you have defined the following model (with name and
|
216
|
+
Now, suppose you have defined the following model (with name and villain attributes) and observer:
|
205
217
|
|
206
218
|
```ruby
|
207
219
|
class Wizard < ActiveRecord::Base
|
@@ -214,7 +226,7 @@ class WizardObserver < PowerTypes::Observer
|
|
214
226
|
after_create :kill_villain
|
215
227
|
|
216
228
|
def kill_villain
|
217
|
-
p "#{object.name}
|
229
|
+
p "#{object.name} has killed #{object.villain}"
|
218
230
|
end
|
219
231
|
end
|
220
232
|
```
|
@@ -222,43 +234,200 @@ end
|
|
222
234
|
Then, you can use it like this:
|
223
235
|
|
224
236
|
```ruby
|
225
|
-
Wizard.create!(name: "Gandalf",
|
237
|
+
Wizard.create!(name: "Gandalf", villain: "Sauron") #=> This action will trigger the method kill_villain defined in the WizardObserver's after_create callback.
|
226
238
|
```
|
227
239
|
|
228
240
|
> As you can guess, `object` holds the Wizard instance.
|
229
241
|
|
230
|
-
|
242
|
+
You can trigger multiple methods on the same callback. For example:
|
231
243
|
|
232
|
-
|
244
|
+
```ruby
|
245
|
+
class WizardObserver < PowerTypes::Observer
|
246
|
+
after_create :kill_villain
|
247
|
+
after_create :bury_villains_corpse
|
248
|
+
|
249
|
+
def kill_villain
|
250
|
+
p "#{object.name} has killed #{object.villain}"
|
251
|
+
end
|
252
|
+
|
253
|
+
def bury_villains_corpse
|
254
|
+
p "#{object.name} has buried #{object.villain}'s corpse"
|
255
|
+
end
|
256
|
+
end
|
257
|
+
```
|
258
|
+
Note: Triggering the event will preserve the order of the methods, so in the example `kill_villain` will be called before `bury_villains_corpse`.
|
259
|
+
|
260
|
+
### Values
|
261
|
+
|
262
|
+
This pattern doesn't have a generator.
|
233
263
|
|
234
264
|
Values are just simple Ruby classes, but watch out to keep them in the Values directory!
|
235
265
|
|
236
|
-
|
266
|
+
### Presenters
|
267
|
+
|
268
|
+
For generating presenters we use:
|
269
|
+
|
270
|
+
```
|
271
|
+
$ rails generate presenter users_show
|
272
|
+
```
|
273
|
+
|
274
|
+
This will create the `UsersShowPresenter` class, inheriting from a base class:
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
class UsersShowPresenter < PowerTypes::PresenterBase
|
278
|
+
end
|
279
|
+
```
|
280
|
+
|
281
|
+
And its corresponding rspec file:
|
237
282
|
|
238
283
|
```ruby
|
239
|
-
|
240
|
-
extend self
|
284
|
+
require 'rails_helper'
|
241
285
|
|
242
|
-
|
243
|
-
|
286
|
+
describe UsersShowPresenter do
|
287
|
+
pending "add some examples to (or delete) #{__FILE__}"
|
288
|
+
end
|
289
|
+
```
|
290
|
+
|
291
|
+
To initialize a presenter inside your controller action you should execute the `present_with` method with valid params:
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
class UsersController < InheritedResources::Base
|
295
|
+
def show
|
296
|
+
presenter_params = { param1: 1, param2: 2 }
|
297
|
+
@presenter = present_with(:users_show, presenter_params)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
```
|
301
|
+
|
302
|
+
You can access view helper methods through the `h` method:
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
class UsersShowPresenter < PowerTypes::PresenterBase
|
306
|
+
def platanus_link
|
307
|
+
h.link_to "Hi Platanus!", "https://platan.us"
|
244
308
|
end
|
309
|
+
end
|
310
|
+
```
|
311
|
+
|
312
|
+
You can access `presenter_params` inside the presenter as an `attr_reader`
|
245
313
|
|
246
|
-
|
247
|
-
|
314
|
+
```ruby
|
315
|
+
class UsersController < InheritedResources::Base
|
316
|
+
def show
|
317
|
+
presenter_params = { platanus_url: "https://platan.us" }
|
318
|
+
@presenter = present_with(:users_show, presenter_params)
|
248
319
|
end
|
320
|
+
end
|
321
|
+
```
|
249
322
|
|
250
|
-
|
251
|
-
|
323
|
+
```ruby
|
324
|
+
class UsersShowPresenter < PowerTypes::PresenterBase
|
325
|
+
def platanus_link
|
326
|
+
h.link_to "Hi Platanus!", platanus_url
|
252
327
|
end
|
253
|
-
end
|
328
|
+
end
|
254
329
|
```
|
255
330
|
|
256
|
-
|
331
|
+
If the presenter param has a [decorator](https://github.com/drapergem/draper), the `attr_reader` will be decorated.
|
257
332
|
|
258
333
|
```ruby
|
259
|
-
|
334
|
+
class UsersController < InheritedResources::Base
|
335
|
+
def show
|
336
|
+
presenter_params = { user: user }
|
337
|
+
@presenter = present_with(:users_show, presenter_params)
|
338
|
+
end
|
339
|
+
|
340
|
+
private
|
341
|
+
|
342
|
+
def user
|
343
|
+
@user ||= User.find!(params[:id])
|
344
|
+
end
|
345
|
+
end
|
260
346
|
```
|
261
347
|
|
348
|
+
```ruby
|
349
|
+
class UserDecorator < Draper::Decorator
|
350
|
+
delegate_all
|
351
|
+
|
352
|
+
def cool_view_name
|
353
|
+
"~º#{name}º~"
|
354
|
+
end
|
355
|
+
end
|
356
|
+
```
|
357
|
+
|
358
|
+
```ruby
|
359
|
+
class UsersShowPresenter < PowerTypes::PresenterBase
|
360
|
+
def platanus_link
|
361
|
+
h.link_to "Hi #{user.cool_view_name}!", platanus_url
|
362
|
+
end
|
363
|
+
end
|
364
|
+
```
|
365
|
+
|
366
|
+
In the view, you can use it like this:
|
367
|
+
|
368
|
+
```
|
369
|
+
<div><%= @presenter.platanus_link %></div>
|
370
|
+
```
|
371
|
+
|
372
|
+
### Utils
|
373
|
+
|
374
|
+
To generate a util we use:
|
375
|
+
|
376
|
+
```
|
377
|
+
$ bundle exec rails g util Numbers clean double
|
378
|
+
```
|
379
|
+
|
380
|
+
This will generate the `NumbersUtil` class in the `app/utils` directory, as follows:
|
381
|
+
|
382
|
+
```ruby
|
383
|
+
class NumbersUtil < PowerTypes::BaseUtil
|
384
|
+
|
385
|
+
def self.clean
|
386
|
+
# Method code goes here
|
387
|
+
end
|
388
|
+
|
389
|
+
def self.double
|
390
|
+
# Method code goes here
|
391
|
+
end
|
392
|
+
|
393
|
+
end
|
394
|
+
```
|
395
|
+
|
396
|
+
And it will generate the spec file as well, in the `spec/utils` directory:
|
397
|
+
|
398
|
+
```ruby
|
399
|
+
require 'rails_helper'
|
400
|
+
|
401
|
+
describe NumbersUtil do
|
402
|
+
describe '#clean' do
|
403
|
+
pending 'describe what the util method clean does here'
|
404
|
+
end
|
405
|
+
|
406
|
+
describe '#double' do
|
407
|
+
pending 'describe what the util method double does here'
|
408
|
+
end
|
409
|
+
|
410
|
+
end
|
411
|
+
```
|
412
|
+
|
413
|
+
Every util will inherit from the class `PowerTypes::BaseUtil` which raises an error when the initialize method is called. The purpose of this is to ensure that all the utils methods work as class methods. Thus, there is no need to create an instance of the util to use its methods. For instance, we could use the `NumbersUtil` as follows:
|
414
|
+
|
415
|
+
```ruby
|
416
|
+
NumbersUtil.clean('5.000') # -> 5000
|
417
|
+
NumbersUtil.double(100) # -> 200
|
418
|
+
```
|
419
|
+
|
420
|
+
## Publishing
|
421
|
+
|
422
|
+
On master/main branch...
|
423
|
+
|
424
|
+
1. Change `VERSION` in `lib/power-types/version.rb`.
|
425
|
+
2. Change `Unreleased` title to current version in `CHANGELOG.md`.
|
426
|
+
3. Run `bundle install`.
|
427
|
+
4. Commit new release. For example: `Releasing v0.1.0`.
|
428
|
+
5. Create tag. For example: `git tag v0.1.0`.
|
429
|
+
6. Push tag. For example: `git push origin v0.1.0`.
|
430
|
+
|
262
431
|
## Contributing
|
263
432
|
|
264
433
|
1. Fork it
|
@@ -1,12 +1,22 @@
|
|
1
1
|
module PowerTypes
|
2
2
|
class InitGenerator < Rails::Generators::Base
|
3
3
|
desc "This generator creates the folder structure for the power-types gem"
|
4
|
+
|
4
5
|
def create_folders
|
5
6
|
empty_directory "app/commands/"
|
6
7
|
empty_directory "app/services/"
|
7
8
|
empty_directory "app/observers/"
|
9
|
+
empty_directory "app/presenters/"
|
8
10
|
empty_directory "app/utils/"
|
9
11
|
empty_directory "app/values/"
|
10
12
|
end
|
13
|
+
|
14
|
+
def config_presenters
|
15
|
+
insert_into_file(
|
16
|
+
"app/controllers/application_controller.rb",
|
17
|
+
"\n include PowerTypes::Presentable",
|
18
|
+
after: "ActionController::Base"
|
19
|
+
)
|
20
|
+
end
|
11
21
|
end
|
12
22
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Rails
|
2
|
+
class PresenterGenerator < Rails::Generators::NamedBase
|
3
|
+
source_root File.expand_path("../templates", __FILE__)
|
4
|
+
|
5
|
+
desc "This generator creates a new presenter at app/presenters"
|
6
|
+
|
7
|
+
def create_presenter
|
8
|
+
template('presenter.rb', "app/presenters/#{file_name.underscore}_presenter.rb")
|
9
|
+
template('presenter_spec.rb', "spec/presenters/#{file_name.underscore}_presenter_spec.rb")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Rails
|
2
|
+
class UtilGenerator < Rails::Generators::NamedBase
|
3
|
+
source_root File.expand_path('../templates', __FILE__)
|
4
|
+
|
5
|
+
argument :attributes, type: :array, default: [], banner: 'method method'
|
6
|
+
|
7
|
+
desc 'This generator creates a new util at app/utils'
|
8
|
+
|
9
|
+
def create_util
|
10
|
+
template('util.rb', "app/utils/#{file_name.underscore}_util.rb")
|
11
|
+
template('util_spec.rb', "spec/utils/#{file_name.underscore}_util_spec.rb")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/power-types.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require 'power_types'
|
1
|
+
require 'power_types'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module PowerTypes
|
2
|
+
class BasePresenter
|
3
|
+
def initialize(view, params = {})
|
4
|
+
@h = view
|
5
|
+
|
6
|
+
params.each_pair do |attribute, value|
|
7
|
+
if respond_to?(attribute, true)
|
8
|
+
raise PowerTypes::PresenterError.new(
|
9
|
+
"attribute #{attribute} already defined in presenter"
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
singleton_class.send(:attr_accessor, attribute)
|
14
|
+
instance_variable_set("@#{attribute}", decorated_value(value))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :h
|
21
|
+
|
22
|
+
def decorated_value(value)
|
23
|
+
return value unless value.respond_to?(:decorate)
|
24
|
+
|
25
|
+
value.decorate
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module PowerTypes
|
2
|
+
module Presentable
|
3
|
+
def present_with(presenter_name, data = {})
|
4
|
+
presenter_class_by_name(presenter_name).new(view_context, data)
|
5
|
+
end
|
6
|
+
|
7
|
+
def presenter_class_by_name(presenter_name)
|
8
|
+
class_name = presenter_name.to_s.classify
|
9
|
+
class_constant = class_name.safe_constantize
|
10
|
+
|
11
|
+
if class_constant.blank?
|
12
|
+
raise PowerTypes::PresenterError.new(
|
13
|
+
"missing #{class_name} presenter class"
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
class_constant
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/power_types/util.rb
CHANGED
data/lib/power_types/version.rb
CHANGED
data/lib/power_types.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
|
+
require "active_support/all"
|
2
|
+
|
1
3
|
require "power_types/version"
|
2
4
|
require "power_types/util"
|
5
|
+
require "power_types/errors"
|
3
6
|
require "power_types/patterns/service"
|
4
7
|
require "power_types/patterns/command"
|
5
8
|
require "power_types/patterns/observer/observable"
|
6
9
|
require "power_types/patterns/observer/observer"
|
7
10
|
require "power_types/patterns/observer/trigger"
|
11
|
+
require "power_types/patterns/presenter/base_presenter"
|
12
|
+
require "power_types/patterns/presenter/presentable"
|
13
|
+
require "power_types/patterns/base_util"
|
8
14
|
|
9
15
|
module PowerTypes
|
10
16
|
end
|
data/power-types.gemspec
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
lib = File.expand_path('../lib', __FILE__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'power_types/version'
|
@@ -19,16 +18,16 @@ Gem::Specification.new do |spec|
|
|
19
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
19
|
spec.require_paths = ["lib"]
|
21
20
|
|
22
|
-
spec.
|
23
|
-
|
24
|
-
spec.add_development_dependency "
|
25
|
-
spec.add_development_dependency "
|
21
|
+
spec.add_dependency "activesupport"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 2.2.15"
|
24
|
+
spec.add_development_dependency "coveralls"
|
26
25
|
spec.add_development_dependency "guard", "~> 2.11"
|
27
26
|
spec.add_development_dependency "guard-rspec", "~> 4.5"
|
28
|
-
spec.add_development_dependency "terminal-notifier-guard", "~> 1.6", ">= 1.6.1"
|
29
27
|
spec.add_development_dependency "pry", "~> 0.10"
|
30
|
-
spec.add_development_dependency "
|
31
|
-
spec.add_development_dependency "
|
32
|
-
spec.add_development_dependency "
|
33
|
-
spec.add_development_dependency "
|
28
|
+
spec.add_development_dependency "rake", "~> 10.4"
|
29
|
+
spec.add_development_dependency "rspec", "~> 3.1"
|
30
|
+
spec.add_development_dependency "rspec_junit_formatter"
|
31
|
+
spec.add_development_dependency "rubocop", "0.66"
|
32
|
+
spec.add_development_dependency "rubocop-rspec"
|
34
33
|
end
|