magic-presenter 0.2.0 → 0.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: 04a88e0f222913a774946299ef036231cb1628baaa980111b5bb058cb77f69a4
4
- data.tar.gz: 9a5d28343913ebae389a941153d49d6c3d04acb2e6e61891ada49404a2e1d49f
3
+ metadata.gz: 8ca84b456b21332f6eeba63a5667ed1c8cf7ac97d59141192f138d1698c504bf
4
+ data.tar.gz: c380ea14f2ddd44150ae71095063e895486d33077521fe1067fca81d4c4b36b0
5
5
  SHA512:
6
- metadata.gz: 0a572d9c94e5b75cb65c23e029d524f19d48457d1ea57bf6185d4ed463757296c2917d77e7210ab67021347b111136e34c1cbcdb15ae47c6d67445623ff13508
7
- data.tar.gz: 41c4b6afee8ffb1b89aedbba4f619d94ddc04d3c4201afa6ae7c34fec7d1fe52a746eb3fc29463d316fc446c4e1a7b9458395c473c18af02b388caae0f2dabaa
6
+ metadata.gz: 728dbf1b11115069bb58bfb47c81258f358d22c3570c35dc4a5e7e4d9429a2fa5a6dc3237d0ef0e6a0442d88ca7e9b405e481bd5ea2da90c618800593c8f5155
7
+ data.tar.gz: f014751ea9cf0470fd1387f25f63b56291a63a434abd177ca0e1aaab27ba1d9a4da4e590354b729fcd12a40fa19f125866daedfba4bd904f37e8d48b3c281ea6
data/README.md CHANGED
@@ -68,6 +68,35 @@ See the help for more info:
68
68
 
69
69
  $ bin/rails generate presenter --help
70
70
 
71
+ ### View helpers
72
+
73
+ A presenter can use any helpers via `#helpers` (aliased as `#h`) both in class and instance methods:
74
+
75
+ ```ruby
76
+ class PersonPresenter < Magic::Presenter::Base
77
+ def self.links
78
+ [ h.link_to('All', model_class) ]
79
+ end
80
+
81
+ def link(...)
82
+ helpers.link_to(name, self, ...)
83
+ end
84
+ end
85
+ ```
86
+
87
+ A view context must be set to enable helpers.
88
+ It’s done automagically [wherever possible](#helpers).
89
+ However, one can set it explicitly anywhere:
90
+
91
+ ```ruby
92
+ Magic::Presenter.with view_context: ApplicationController.new.view_context do
93
+ # put the code that uses helpers within presenters here
94
+ end
95
+ ```
96
+
97
+ > [!NOTE]
98
+ > A valid `request` may be needed for URL helpers to get host info.
99
+
71
100
  ## 🧙 Magic
72
101
 
73
102
  It’s based on [Magic Decorator](
@@ -103,6 +132,19 @@ When in doubt, one can use `Magic::Presenter.name_for`:
103
132
  Magic::Presenter.name_for Person # => "PersonPresenter"
104
133
  ```
105
134
 
135
+ ### In views
136
+
137
+ > [!IMPORTANT]
138
+ > Every object passed to views is decorated automagically.
139
+ > This involves both implicit instance variables and `locals` passed explicitly.
140
+
141
+ ### Helpers
142
+
143
+ View context is set automagically to enable helpers:
144
+ - in views,
145
+ - in controller actions,
146
+ - in mailer actions.
147
+
106
148
  ## Development
107
149
 
108
150
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -7,6 +7,14 @@ module Magic
7
7
  module Presentable
8
8
  include Decoratable
9
9
 
10
+ class << self
11
+ def classes
12
+ Magic.eager_load :models
13
+
14
+ super
15
+ end
16
+ end
17
+
10
18
  private
11
19
 
12
20
  def decorator_base = Presenter
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ presenter_context = proc do
4
+ around_action :set_presenter_context
5
+
6
+ private
7
+
8
+ def set_presenter_context(&)
9
+ Magic::Presenter.with view_context:, &
10
+ end
11
+ end
12
+
13
+ ActiveSupport.on_load :action_controller, &presenter_context
14
+ ActiveSupport.on_load :action_mailer, &presenter_context
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveSupport.on_load :action_view do # rubocop:disable Metrics/BlockLength
4
+ concerning :DecoratedAssignments, prepend: true do
5
+ def assign(assignments, ...)
6
+ decorate assignments
7
+
8
+ super
9
+ end
10
+
11
+ def _run(method, template, locals, ...)
12
+ decorate locals
13
+
14
+ super
15
+ end
16
+
17
+ private
18
+
19
+ def decorate objects
20
+ objects
21
+ .transform_values!(&:decorated)
22
+ end
23
+ end
24
+
25
+ concerning :PresenterContext, prepend: true do
26
+ def in_rendering_context(...)
27
+ Magic::Presenter::Base.with view_context: self do
28
+ super
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def decorate(...)
35
+ super
36
+ .each_value
37
+ .grep(Magic::Presenter::Base)
38
+ .each { _1.view_context = self }
39
+ end
40
+ end
41
+ end
@@ -17,6 +17,9 @@ module Magic
17
17
  # If not found, it looks for presenters of its ancestor classes,
18
18
  # up to `ObjectPresenter`.
19
19
  class Base < Decorator::Base
20
+ include GlobalID if defined? ::GlobalID
21
+ prepend Helpers if defined? ::ActionView
22
+
20
23
  class << self
21
24
  def name_for object_class
22
25
  object_class
@@ -25,6 +28,23 @@ module Magic
25
28
  .delete_suffix('Record')
26
29
  .then { "#{_1}Presenter" }
27
30
  end
31
+
32
+ def model_class
33
+ Presentable.classes
34
+ .select { Presenter.for(_1) == self }
35
+ .sole
36
+ rescue Enumerable::SoleItemExpectedError => error
37
+ raise Lookup::Error, "#{error.message
38
+ .sub('items', 'model classes')
39
+ .sub('item', 'model class')
40
+ } for #{self}"
41
+ end
42
+
43
+ def descendants
44
+ Magic.eager_load :presenters
45
+
46
+ super
47
+ end
28
48
  end
29
49
  end
30
50
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Magic
4
+ module Presenter
5
+ # = Active Job integration
6
+ #
7
+ # Active Job [accepts Active Record objects](
8
+ # http://edgeguides.rubyonrails.org/active_job_basics.html#globalid
9
+ # ).
10
+ # An object passed to a background job must implement [Global ID](
11
+ # https://github.com/rails/globalid
12
+ # ).
13
+ #
14
+ # This module implements Global ID for decorated models by defining
15
+ # `.find` method that uses the one from a model class and decorates
16
+ # the result.
17
+ # This means one can pass decorated objects to background jobs and
18
+ # get them decorated when deserialized.
19
+ module GlobalID
20
+ extend ActiveSupport::Concern
21
+
22
+ included do
23
+ include ::GlobalID::Identification
24
+ end
25
+
26
+ class_methods do
27
+ def find(...)
28
+ model_class
29
+ .find(...)
30
+ .decorated
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Magic
4
+ module Presenter
5
+ module Helpers # :nodoc:
6
+ extend ActiveSupport::Concern
7
+
8
+ # The Magic::Presenter::Helpers::MissingContext exception is
9
+ # raised when no view context to run helpers in has been set.
10
+ class MissingContext < RuntimeError
11
+ def message
12
+ <<~TEXT
13
+ missing view context
14
+ You should set Magic::Presenter.view_context first
15
+ TEXT
16
+ end
17
+ end
18
+
19
+ prepended do
20
+ class_attribute :view_context
21
+ end
22
+
23
+ class_methods do
24
+ include Helpers
25
+
26
+ alias __raise__ raise
27
+ end
28
+
29
+ private
30
+
31
+ def helpers
32
+ view_context or
33
+ __raise__ MissingContext
34
+ end
35
+
36
+ alias_method :h, :helpers
37
+
38
+ def method_missing(method, ...)
39
+ super
40
+ rescue NoMethodError
41
+ __raise__ unless view_context
42
+ __raise__ unless helpers.respond_to? method
43
+
44
+ helpers.send(method, ...)
45
+ end
46
+
47
+ def respond_to_missing?(method, ...)
48
+ super or
49
+ (helpers if view_context).respond_to?(method, ...)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Magic
4
4
  module Presenter
5
- VERSION = '0.2.0'
5
+ VERSION = '0.4.0'
6
6
  end
7
7
  end
@@ -6,16 +6,29 @@ require_relative 'presenter/version'
6
6
  require_relative 'presenter/engine'
7
7
 
8
8
  module Magic # :nodoc:
9
- autoload :Presentable, 'magic/presentable'
10
-
11
9
  # Presentation layer for Rails models
12
10
  module Presenter
13
11
  autoload :Base, 'magic/presenter/base'
12
+ autoload :Helpers, 'magic/presenter/helpers'
13
+ autoload :GlobalID, 'magic/presenter/global_id'
14
14
  autoload :Generator, 'generators/magic/presenter/generator'
15
15
 
16
- module_function
16
+ singleton_class.delegate *%i[
17
+ for name_for
18
+ view_context view_context=
19
+ ], to: Base
20
+ end
21
+
22
+ module_function
23
+
24
+ def eager_load *scopes
25
+ return if Rails.application.config.eager_load
17
26
 
18
- def for(...) = Base.for(...)
19
- def name_for(...) = Base.name_for(...)
27
+ scopes
28
+ .map(&:to_s)
29
+ .map(&:pluralize)
30
+ .map { Rails.root / 'app' / _1 }
31
+ .select(&:exist?)
32
+ .each { Rails.autoloaders.main.eager_load_dir _1 }
20
33
  end
21
34
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: magic-presenter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Senko
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2024-10-26 00:00:00.000000000 Z
10
+ date: 2024-10-29 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -33,16 +33,16 @@ dependencies:
33
33
  name: magic-decorator
34
34
  requirement: !ruby/object:Gem::Requirement
35
35
  requirements:
36
- - - ">="
36
+ - - "~>"
37
37
  - !ruby/object:Gem::Version
38
- version: 0.3.0.alpha
38
+ version: '0.3'
39
39
  type: :runtime
40
40
  prerelease: false
41
41
  version_requirements: !ruby/object:Gem::Requirement
42
42
  requirements:
43
- - - ">="
43
+ - - "~>"
44
44
  - !ruby/object:Gem::Version
45
- version: 0.3.0.alpha
45
+ version: '0.3'
46
46
  description: Based on Magic Decorator, it’s meant to replace Draper.
47
47
  email:
48
48
  - Alexander.Senko@gmail.com
@@ -53,7 +53,9 @@ files:
53
53
  - MIT-LICENSE
54
54
  - README.md
55
55
  - Rakefile
56
- - config/initializers/loading.rb
56
+ - app/models/concerns/magic/presentable.rb
57
+ - config/initializers/action_controller.rb
58
+ - config/initializers/action_view.rb
57
59
  - config/initializers/presentable.rb
58
60
  - config/initializers/rspec.rb
59
61
  - lib/generators/magic/presenter/generator.rb
@@ -67,11 +69,12 @@ files:
67
69
  - lib/generators/rspec/presenter/templates/presenter_spec.rb.tt
68
70
  - lib/generators/test_unit/presenter/presenter_generator.rb
69
71
  - lib/generators/test_unit/presenter/templates/presenter_test.rb.tt
70
- - lib/magic/presentable.rb
71
72
  - lib/magic/presenter.rb
72
73
  - lib/magic/presenter/authors.rb
73
74
  - lib/magic/presenter/base.rb
74
75
  - lib/magic/presenter/engine.rb
76
+ - lib/magic/presenter/global_id.rb
77
+ - lib/magic/presenter/helpers.rb
75
78
  - lib/magic/presenter/version.rb
76
79
  - lib/tasks/magic/presenter_tasks.rake
77
80
  homepage: https://github.com/Alexander-Senko/magic-presenter
@@ -80,7 +83,7 @@ licenses:
80
83
  metadata:
81
84
  homepage_uri: https://github.com/Alexander-Senko/magic-presenter
82
85
  source_code_uri: https://github.com/Alexander-Senko/magic-presenter
83
- changelog_uri: https://github.com/Alexander-Senko/magic-presenter/blob/v0.2.0/CHANGELOG.md
86
+ changelog_uri: https://github.com/Alexander-Senko/magic-presenter/blob/v0.4.0/CHANGELOG.md
84
87
  rdoc_options: []
85
88
  require_paths:
86
89
  - lib
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- Rails.application.config.to_prepare do
4
- [ Rails.root / 'app/presenters' ]
5
- .select(&:exist?)
6
- .each { Rails.autoloaders.main.eager_load_dir _1 }
7
- end unless Rails.application.config.eager_load