magic-presenter 1.2.0 → 1.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5fe98ce37280316a18f2600366cd5fbcde0b12f447bf6a00821a356f140e061
4
- data.tar.gz: e9b90001111944c9f353ebc82851233a24d93ad316d45e5535cdc1d968e86c11
3
+ metadata.gz: 018f8d173f204bef93fdaf99a35417b1018a81517231f673a0dd44a1644ee61b
4
+ data.tar.gz: e0ca0e5a3b439b40e20d429f7f7ba35c48390f51debb30510445d4e1d682b948
5
5
  SHA512:
6
- metadata.gz: 3416439e712b63b70cbabce8c74806489023fe5335f29a2a4efca7edca94324dbb966b834f9fc3e8f6a20022c5fc6acf45b7fe45219c1e50edc80226378ff3e2
7
- data.tar.gz: bdfe8668cfec9f90267617c82da4cda1afecff36223e3312243fd850a865b6979350c3bc94f44e0a0646c825e29731de527a9e50b37e4bc866ab559a040555d6
6
+ metadata.gz: 3c2913873efd49741bf0991eb34cfd30e32ceede34a84eef752b86c951625cd173db3b4069473f16a0ecfc6e1ea8d26c9a75a8d8525a4e35c5d8e81e38346841
7
+ data.tar.gz: e28356ed613cc0f68f46618ba989956fd3dd467d91ec0bf81f1d9aced9f6504208a7dedff8ebd05bdfcb2bf7214d5dadc97ba961738928ee2fe09578a7b66eb5
data/README.md CHANGED
@@ -55,6 +55,8 @@ person = Person.new(first_name: 'John', last_name: 'Smith').decorate
55
55
  person.name # => "John Smith"
56
56
  ```
57
57
 
58
+ Use [load hooks](#load-hooks) to extend the base presenter functionality.
59
+
58
60
  ### `Magic::Presentable`
59
61
 
60
62
  This module includes `Magic::Decoratable` and allows a descendant to be decorated by presenters only.
@@ -64,6 +66,26 @@ When no presenter is found,
64
66
  - `#decorate!` raises `Magic::Lookup::Error`,
65
67
  - `#decorated` returns the original object.
66
68
 
69
+ Use [load hooks](#load-hooks) to extend the functionality.
70
+
71
+ #### Collections
72
+
73
+ Active Record relations are presentable as collections. Define a collection presenter inside the matching presenter to customize collection behavior:
74
+
75
+ ```ruby
76
+ class PersonPresenter < Magic::Presenter::Base
77
+ collection_presenter do
78
+ def names = map(&:name).to_sentence
79
+ end
80
+ end
81
+
82
+ Person.all.decorated.names
83
+ ```
84
+
85
+ Under the hood, `.collection_presenter` defines `PersonPresenter::Collection < Magic::Presenter::Collection` in the above example and evaluates the block within that class’ context.
86
+ It looks for a collection presenter class in ancestor classes to be inherited from, up to `Magic::Presenter::Collection`.
87
+ One can also define a collection presenter class explicitly, following the same pattern.
88
+
67
89
  ### Generators
68
90
 
69
91
  A generator can be used to generate a presenter:
@@ -103,6 +125,25 @@ end
103
125
  > [!NOTE]
104
126
  > A valid `request` may be needed for URL helpers to get host data.
105
127
 
128
+ ### Load hooks
129
+
130
+ Load hooks are triggered when `Magic::Presenter::Base` or `Magic::Presentable` are loaded.
131
+ They allow you to run custom initialization code for configuration or to extend the functionality.
132
+
133
+ ```ruby
134
+ ActiveSupport.on_load :presenter do
135
+ # Put the code to be executed when Magic::Presenter is loaded here.
136
+ # It runs in the context of Magic::Presenter::Base class.
137
+ end
138
+ ```
139
+
140
+ ```ruby
141
+ ActiveSupport.on_load :presentable do
142
+ # Put the code to be executed when Magic::Presentable is loaded here.
143
+ # It runs in the context of Magic::Presentable module.
144
+ end
145
+ ```
146
+
106
147
  ## 🧙 Magic
107
148
 
108
149
  > [!IMPORTANT]
@@ -110,6 +151,8 @@ end
110
151
  > https://github.com/Alexander-Senko/magic-decorator#magic
111
152
  > ), so get familiar with that one as well.
112
153
 
154
+ In short, the phylosophy is: _**define presenters you need and expect them to be applied automagically to all the corresponding objects** in every appropriate (view) context_.
155
+
113
156
  ### Presentable scope
114
157
 
115
158
  `Magic::Presentable` is mixed into `ActiveModel::API` by default.
@@ -149,6 +192,7 @@ Missing class methods of a presenter are delegated to a matching model class if
149
192
  > [!NOTE]
150
193
  > Every object passed to views is decorated automagically.
151
194
  > This involves both implicit instance variables and `locals` passed explicitly.
195
+ > Controller helper methods are also decorated when called from views.
152
196
 
153
197
  ### Helpers
154
198
 
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Magic
4
+ module Presentable
5
+ # = Presentable Active Record relation
6
+ #
7
+ # A tiny wrapper around `Presentable::Collection` for Active Record
8
+ # relations.
9
+ module ActiveRecordCollection
10
+ include Collection
11
+
12
+ def model_class = model
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Magic
4
+ module Presentable
5
+ # = Presentable collection
6
+ #
7
+ # This module includes `Magic::Decoratable` and allows a descendant
8
+ # to be decorated by collection presenters only. Presenter class is
9
+ # being inferred automatically from a collection’s model class.
10
+ module Collection
11
+ include Decoratable
12
+
13
+ private
14
+
15
+ def decorator = decorator_base.for model_class
16
+ def decorator_base = Presenter::Collection
17
+ end
18
+ end
19
+ end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Magic
4
+ # = Presentable object
5
+ #
4
6
  # This module includes `Magic::Decoratable` and allows a descendant
5
7
  # to be decorated by presenters only. Presenter class is being
6
8
  # inferred automatically.
@@ -12,4 +14,6 @@ module Magic
12
14
 
13
15
  def decorator_base = Presenter::Base
14
16
  end
17
+
18
+ ActiveSupport.run_load_hooks :presentable, Presentable
15
19
  end
@@ -22,6 +22,16 @@ ActiveSupport.on_load :action_view do # rubocop:disable Metrics/BlockLength
22
22
  end
23
23
  end
24
24
 
25
+ concerning :DecoratedHelpers, prepend: true do
26
+ require 'magic/decorator/core_ext/module'
27
+
28
+ def assign_controller controller
29
+ super
30
+
31
+ self.class.decorated_method controller._helper_methods
32
+ end
33
+ end
34
+
25
35
  concerning :PresenterContext, prepend: true do
26
36
  def in_rendering_context(...)
27
37
  Magic::Presenter.with view_context: self do
@@ -3,3 +3,7 @@
3
3
  Rails.application.config.to_prepare do
4
4
  ActiveModel::API.include Magic::Presentable
5
5
  end
6
+
7
+ ActiveSupport.on_load :active_record do
8
+ ActiveRecord::Relation.include Magic::Presentable::ActiveRecordCollection
9
+ end
@@ -18,6 +18,8 @@ module Magic
18
18
  include GlobalID if defined? ::GlobalID
19
19
  prepend Helpers if defined? ::ActionView
20
20
 
21
+ Collection = Collection # for lookup
22
+
21
23
  class << self
22
24
  def name_for object_class
23
25
  object_class
@@ -30,7 +32,35 @@ module Magic
30
32
  def model_class = Presentable.for self
31
33
 
32
34
  delegate_missing_to :model_class
35
+
36
+ private
37
+
38
+ # Define collection presenter from the given block.
39
+ #
40
+ # class MyPresenter < Magic::Presenter::Base
41
+ # collection_presenter do
42
+ # def count = "#{super} items"
43
+ # end
44
+ # end
45
+ #
46
+ # class MyModel < ApplicationRecord
47
+ # end
48
+ #
49
+ # MyModel.all.count # => 42
50
+ # MyModel.all.decorated.count # => "42 items"
51
+ #
52
+ # It looks for a collection presenter class in ancestor classes
53
+ # to be inherited from, up to `Magic::Presenter::Collection`.
54
+ def collection_presenter(&)
55
+ if const_defined? :Collection, false
56
+ const_get :Collection
57
+ else
58
+ const_set :Collection, Class.new(const_get :Collection)
59
+ end.class_eval(&)
60
+ end
33
61
  end
34
62
  end
63
+
64
+ ActiveSupport.run_load_hooks :presenter, Base
35
65
  end
36
66
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Magic
4
+ module Presenter
5
+ # = Collection presenter
6
+ #
7
+ # This presenter is used as a base class to decorate typed
8
+ # collections (e.g., Active Record relations).
9
+ #
10
+ # == Presenter class inference
11
+ #
12
+ # Collection presenters provide automatic class inference for any
13
+ # typed collection based on the class name of its elements powered
14
+ # by Magic Lookup.
15
+ # For example, a collection of `MyRecord`s (e.g., `MyRecord.all`)
16
+ # looks for `MyPresenter::Collection` first.
17
+ # If not found, it looks for collection presenters of its ancestor
18
+ # classes, up to `ObjectPresenter::Collection`.
19
+ class Collection < EnumerableDecorator
20
+ prepend Helpers if defined? ::ActionView
21
+
22
+ class << self
23
+ def name_for(...) = Base.name_for(...)
24
+ .concat('::Collection')
25
+ end
26
+
27
+ def item_presenter = model_class
28
+ &.then { Presenter.for it }
29
+
30
+ def model_class = __getobj__
31
+ .try :model_class
32
+
33
+ def to_a(...) = self.class.new super
34
+ end
35
+ end
36
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Magic
4
4
  module Presenter
5
- class Engine < ::Rails::Engine # :nodoc:
5
+ class Engine < ::Rails::Engine
6
6
  isolate_namespace Magic::Presenter
7
7
 
8
8
  config.generators do
@@ -44,7 +44,8 @@ module Magic
44
44
 
45
45
  def respond_to_missing?(method, ...)
46
46
  super or
47
- (helpers if view_context).respond_to?(method, ...)
47
+ (helpers if view_context)
48
+ &.respond_to?(method, ...) # rubocop:disable Lint/RedundantSafeNavigation
48
49
  end
49
50
  end
50
51
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Magic
4
4
  module Presenter
5
- VERSION = '1.2.0'
5
+ VERSION = '1.4.0'
6
6
  end
7
7
  end
@@ -5,14 +5,15 @@ require 'magic/decorator'
5
5
  require_relative 'presenter/version'
6
6
  require_relative 'presenter/engine'
7
7
 
8
- module Magic # :nodoc:
8
+ module Magic
9
9
  # Presentation layer for Rails models
10
10
  module Presenter
11
- autoload :Base, 'magic/presenter/base'
12
- autoload :Helpers, 'magic/presenter/helpers'
13
- autoload :GlobalID, 'magic/presenter/global_id'
14
- autoload :TestCase, 'magic/presenter/test_case'
15
- autoload :Generator, 'generators/magic/presenter/generator'
11
+ autoload :Base, 'magic/presenter/base'
12
+ autoload :Collection, 'magic/presenter/collection'
13
+ autoload :Helpers, 'magic/presenter/helpers'
14
+ autoload :GlobalID, 'magic/presenter/global_id'
15
+ autoload :TestCase, 'magic/presenter/test_case'
16
+ autoload :Generator, 'generators/magic/presenter/generator'
16
17
 
17
18
  singleton_class.delegate *%i[
18
19
  for name_for
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: magic-presenter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Senko
@@ -40,7 +40,7 @@ dependencies:
40
40
  -
41
41
  - ~>
42
42
  - !ruby/object:Gem::Version
43
- version: "1.0"
43
+ version: "1.2"
44
44
  type: :runtime
45
45
  prerelease: false
46
46
  version_requirements: !ruby/object:Gem::Requirement
@@ -48,7 +48,7 @@ dependencies:
48
48
  -
49
49
  - ~>
50
50
  - !ruby/object:Gem::Version
51
- version: "1.0"
51
+ version: "1.2"
52
52
  - !ruby/object:Gem::Dependency
53
53
  name: magic-lookup
54
54
  requirement: !ruby/object:Gem::Requirement
@@ -76,6 +76,8 @@ files:
76
76
  - README.md
77
77
  - Rakefile
78
78
  - app/models/concerns/magic/presentable.rb
79
+ - app/models/concerns/magic/presentable/active_record_collection.rb
80
+ - app/models/concerns/magic/presentable/collection.rb
79
81
  - config/initializers/action_controller.rb
80
82
  - config/initializers/action_view.rb
81
83
  - config/initializers/generators.rb
@@ -95,6 +97,7 @@ files:
95
97
  - lib/magic/presenter.rb
96
98
  - lib/magic/presenter/authors.rb
97
99
  - lib/magic/presenter/base.rb
100
+ - lib/magic/presenter/collection.rb
98
101
  - lib/magic/presenter/engine.rb
99
102
  - lib/magic/presenter/global_id.rb
100
103
  - lib/magic/presenter/helpers.rb
@@ -108,7 +111,7 @@ licenses:
108
111
  metadata:
109
112
  homepage_uri: "https://github.com/Alexander-Senko/magic-presenter"
110
113
  source_code_uri: "https://github.com/Alexander-Senko/magic-presenter"
111
- changelog_uri: "https://github.com/Alexander-Senko/magic-presenter/blob/v1.2.0/CHANGELOG.md"
114
+ changelog_uri: "https://github.com/Alexander-Senko/magic-presenter/blob/v1.4.0/CHANGELOG.md"
112
115
  rdoc_options: []
113
116
  require_paths:
114
117
  - lib