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.
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2016 Ignacio Baixas
3
+ Copyright 2022 Platanus
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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) [![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
+ [![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
- - [Services](#services)
32
- - [Commands](#commands)
33
- - [Observers](#observers)
34
- - [Values and Utils](#values-and-utils)
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 villian attributes) and observer:
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} have killed #{object.villian}"
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", villian: "Sauron") #=> This action will trigger the method kill_villian defined in the WizardObserver's after_create callback.
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
- ### Values and Utils
242
+ You can trigger multiple methods on the same callback. For example:
231
243
 
232
- This two types do not have generators.
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
- 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.
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
- module MagicTricks
240
- extend self
284
+ require 'rails_helper'
241
285
 
242
- def dissappear(object)
243
- #blah blah
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
- def shrink(children)
247
- #bleh bleeh
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
- def shuffle(cards)
251
- #blaah
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
- Example of calling a Util function:
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
- MagicTricks.dissapear(rabbit)
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
@@ -18,7 +18,7 @@ module Rails
18
18
  private
19
19
 
20
20
  def active_record_klass
21
- if :Rails::VERSION::MAJOR > 4
21
+ if ::Rails::VERSION::MAJOR > 4
22
22
  ApplicationRecord
23
23
  else
24
24
  ActiveRecord::Base
@@ -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,2 @@
1
+ class <%= class_name %>Presenter < PowerTypes::BasePresenter
2
+ end
@@ -0,0 +1,5 @@
1
+ require 'rails_helper'
2
+
3
+ describe <%= class_name %>Presenter do
4
+ pending "add some examples to (or delete) #{__FILE__}"
5
+ end
@@ -0,0 +1,7 @@
1
+ class <%= class_name %>Util < PowerTypes::BaseUtil
2
+ <% attributes_names.each do |method_name| %>
3
+ def self.<%= method_name %>
4
+ # Method code goes here
5
+ end
6
+ <% end %>
7
+ end
@@ -0,0 +1,9 @@
1
+ require 'rails_helper'
2
+
3
+ describe <%= class_name %>Util do
4
+ <% attributes_names.each do |method_name| %>
5
+ describe '#<%= method_name %>' do
6
+ pending 'describe what the util method <%= method_name %> does here'
7
+ end
8
+ <% end %>
9
+ 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,3 @@
1
+ class PowerTypes::Error < RuntimeError; end
2
+ class PowerTypes::PresenterError < PowerTypes::Error; end
3
+ class PowerTypes::UtilError < PowerTypes::Error; end
@@ -0,0 +1,7 @@
1
+ module PowerTypes
2
+ class BaseUtil
3
+ def initialize
4
+ raise PowerTypes::UtilError.new('a util cannot be instantiated')
5
+ end
6
+ end
7
+ end
@@ -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
@@ -1,6 +1,6 @@
1
1
  module PowerTypes
2
2
  module Util
3
- OBSERVABLE_EVENTS = [:create, :update, :save, :destroy]
3
+ OBSERVABLE_EVENTS = [:create, :update, :save, :destroy, :commit]
4
4
  OBSERVABLE_TYPES = [:before, :after]
5
5
  end
6
6
  end
@@ -1,3 +1,3 @@
1
1
  module PowerTypes
2
- VERSION = '0.3.0'
2
+ VERSION = '0.5.0'
3
3
  end
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.add_development_dependency "bundler", "~> 1.6"
23
- spec.add_development_dependency "rake", "~> 10.4"
24
- spec.add_development_dependency "rspec", "~> 3.1"
25
- spec.add_development_dependency "rspec-nc", "~> 0.2"
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 "pry-remote", "~> 0.1"
31
- spec.add_development_dependency "pry-byebug", "~> 3.2"
32
- spec.add_development_dependency "pry-nav", "~> 0.2"
33
- spec.add_development_dependency "coveralls"
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