magic-presenter 1.2.0 → 1.3.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: 0336dc17e983771f6f96385c14429c66e961614ee20f1c99202ab92e970e8663
4
+ data.tar.gz: c923d4f65d8772b8fa52dd62c5d26a0d7b5e96f9d1b3ac12af28296dfb80547b
5
5
  SHA512:
6
- metadata.gz: 3416439e712b63b70cbabce8c74806489023fe5335f29a2a4efca7edca94324dbb966b834f9fc3e8f6a20022c5fc6acf45b7fe45219c1e50edc80226378ff3e2
7
- data.tar.gz: bdfe8668cfec9f90267617c82da4cda1afecff36223e3312243fd850a865b6979350c3bc94f44e0a0646c825e29731de527a9e50b37e4bc866ab559a040555d6
6
+ metadata.gz: 94be4b679d87564d79169e919f2e5abb400971b82c0ad78d5456aedbde969e1b4574b27569dee9011861fb899637e3f5394cebfe84488a68063254b70eb7a5a7
7
+ data.tar.gz: 32b20c180e447a2164013d595256c12b8019a5143595d547373c82b157757bea3cf5867248d7068d1d36786e79cd0b6873d6cd814f8f760b53a990c25ed3a924
data/README.md CHANGED
@@ -64,6 +64,24 @@ When no presenter is found,
64
64
  - `#decorate!` raises `Magic::Lookup::Error`,
65
65
  - `#decorated` returns the original object.
66
66
 
67
+ #### Collections
68
+
69
+ Active Record relations are presentable as collections. Define a collection presenter inside the matching presenter to customize collection behavior:
70
+
71
+ ```ruby
72
+ class PersonPresenter < Magic::Presenter::Base
73
+ collection_presenter do
74
+ def names = map(&:name).to_sentence
75
+ end
76
+ end
77
+
78
+ Person.all.decorated.names
79
+ ```
80
+
81
+ Under the hood, `.collection_presenter` defines `PersonPresenter::Collection < Magic::Presenter::Collection` in the above example and evaluates the block within that class’ context.
82
+ It looks for a collection presenter class in ancestor classes to be inherited from, up to `Magic::Presenter::Collection`.
83
+ One can also define a collection presenter class explicitly, following the same pattern.
84
+
67
85
  ### Generators
68
86
 
69
87
  A generator can be used to generate a presenter:
@@ -110,6 +128,8 @@ end
110
128
  > https://github.com/Alexander-Senko/magic-decorator#magic
111
129
  > ), so get familiar with that one as well.
112
130
 
131
+ 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_.
132
+
113
133
  ### Presentable scope
114
134
 
115
135
  `Magic::Presentable` is mixed into `ActiveModel::API` by default.
@@ -149,6 +169,7 @@ Missing class methods of a presenter are delegated to a matching model class if
149
169
  > [!NOTE]
150
170
  > Every object passed to views is decorated automagically.
151
171
  > This involves both implicit instance variables and `locals` passed explicitly.
172
+ > Controller helper methods are also decorated when called from views.
152
173
 
153
174
  ### Helpers
154
175
 
@@ -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.
@@ -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,6 +32,32 @@ 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
35
63
  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
@@ -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.3.0'
6
6
  end
7
7
  end
@@ -8,11 +8,12 @@ require_relative 'presenter/engine'
8
8
  module Magic # :nodoc:
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.3.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.3.0/CHANGELOG.md"
112
115
  rdoc_options: []
113
116
  require_paths:
114
117
  - lib