magic-presenter 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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