magic-presenter 0.4.0 → 1.0.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: 8ca84b456b21332f6eeba63a5667ed1c8cf7ac97d59141192f138d1698c504bf
4
- data.tar.gz: c380ea14f2ddd44150ae71095063e895486d33077521fe1067fca81d4c4b36b0
3
+ metadata.gz: ea088d296d049e126830600bfcc99c5265fbecb41e9d5db51c0d27c02c73e6c8
4
+ data.tar.gz: e5932ed2865e3f534d181ee18c9eccf26aefb250b48e5564cd3d56cf8b55040d
5
5
  SHA512:
6
- metadata.gz: 728dbf1b11115069bb58bfb47c81258f358d22c3570c35dc4a5e7e4d9429a2fa5a6dc3237d0ef0e6a0442d88ca7e9b405e481bd5ea2da90c618800593c8f5155
7
- data.tar.gz: f014751ea9cf0470fd1387f25f63b56291a63a434abd177ca0e1aaab27ba1d9a4da4e590354b729fcd12a40fa19f125866daedfba4bd904f37e8d48b3c281ea6
6
+ metadata.gz: 3cd7a2241b4eae01cc861951d165cff4c52a563ee517c0f05f9e90fab79ac6c53f81a0ca561fb09a1fdaf88d9d39ef435953dcc87a3642c3a632735401d0a0b1
7
+ data.tar.gz: b4fb57b18de8d450e2297722ab69b09761fb9c5bb89d3b93dfb418f5f064771c5736f9423a617ddab404fb0193608c2c331f415c6a2bd44daee85321dd635bec
data/README.md CHANGED
@@ -12,7 +12,9 @@
12
12
 
13
13
  A bit of history: this gem was inspired by digging deeper into [Draper](https://github.com/drapergem/draper) with an eye on a refactoring.
14
14
 
15
- Based on [Magic Decorator](https://github.com/Alexander-Senko/magic-decorator), it implements a presenter logic.
15
+ Based on [Magic Decorator](
16
+ https://github.com/Alexander-Senko/magic-decorator
17
+ ), it implements a presenter logic.
16
18
 
17
19
  ## Installation
18
20
 
@@ -85,7 +87,7 @@ end
85
87
  ```
86
88
 
87
89
  A view context must be set to enable helpers.
88
- It’s done automagically [wherever possible](#helpers).
90
+ It’s done automagically [wherever possible](#view-context).
89
91
  However, one can set it explicitly anywhere:
90
92
 
91
93
  ```ruby
@@ -99,9 +101,10 @@ end
99
101
 
100
102
  ## 🧙 Magic
101
103
 
102
- It’s based on [Magic Decorator](
103
- https://github.com/Alexander-Senko/magic-decorator#magic
104
- ), so get familiar with that one as well.
104
+ > [!IMPORTANT]
105
+ > It’s based on [Magic Decorator](
106
+ > https://github.com/Alexander-Senko/magic-decorator#magic
107
+ > ), so get familiar with that one as well.
105
108
 
106
109
  ### Presentable scope
107
110
 
@@ -115,7 +118,7 @@ Presenters provide automatic class inference for any model based on its class na
115
118
  ).
116
119
 
117
120
  For example, `MyNamespace::MyModel.new.decorate` looks for `MyNamespace::MyPresenter` first.
118
- When missing, it further looks for decorators for its ancestor classes, up to `ObjectPresenter`.
121
+ When missing, it further looks for presenters for its ancestor classes, up to `ObjectPresenter`.
119
122
 
120
123
  #### Mapping rules
121
124
 
@@ -132,19 +135,87 @@ When in doubt, one can use `Magic::Presenter.name_for`:
132
135
  Magic::Presenter.name_for Person # => "PersonPresenter"
133
136
  ```
134
137
 
135
- ### In views
138
+ #### Preloading
139
+
140
+ > [!NOTE]
141
+ > Magic Lookup doesn’t try to autoload any classes, it searches among already loaded ones instead.
142
+ > Thus, presenters should be preloaded to be visible via [lookups](#presenter-class-inference).
143
+
144
+ This is done automatically in both _test_ and _production_ environments by Rails.
145
+ All the application’s presenters and models are eagerly loaded before normal and reverse lookups by Magic Presenter as well.
146
+ So, normally one shouldn’t worry about that.
136
147
 
137
148
  > [!IMPORTANT]
149
+ > When developing a Rails engine that defines its own presenters, one should take care of the preloading themselves.
150
+
151
+ That could be done in an initializer with a helper method provided:
152
+
153
+ ```ruby
154
+ Rails.application.config.to_prepare do
155
+ Magic.eager_load :presenters, engine: MyLib::Engine
156
+ end
157
+ ```
158
+
159
+ ### Class methods delegation
160
+
161
+ Missing class methods of a presenter are delegated to a matching model class if the latter can be inferred unambiguously.
162
+ `Magic::Lookup::Error` is raised otherwise.
163
+
164
+ ### In views
165
+
166
+ > [!NOTE]
138
167
  > Every object passed to views is decorated automagically.
139
168
  > This involves both implicit instance variables and `locals` passed explicitly.
140
169
 
141
170
  ### Helpers
142
171
 
172
+ One can call helpers directly without explicit `helper` or `h`:
173
+
174
+ ```ruby
175
+ class PersonPresenter < Magic::Presenter::Base
176
+ def self.links
177
+ [ link_to('All', model_class) ]
178
+ end
179
+
180
+ def link(...) = link_to(name, self, ...)
181
+ end
182
+ ```
183
+
184
+ #### View context
185
+
143
186
  View context is set automagically to enable helpers:
144
187
  - in views,
145
188
  - in controller actions,
146
189
  - in mailer actions.
147
190
 
191
+ ## Generators
192
+
193
+ > [!NOTE]
194
+ > The built-in `helper` generator is overridden with `presenter` one to generate presenters instead of helpers.
195
+
196
+ ## Testing presenters
197
+
198
+ Magic Presenter supports RSpec and Test::Unit.
199
+ The appropriate tests are generated alongside a presenter.
200
+
201
+ Testing presenters is much like [testing Rails helpers](
202
+ https://guides.rubyonrails.org/testing.html#testing-helpers
203
+ ).
204
+ Since the test class inherits from `ActionView::TestCase`, Rails’ helper methods such as `link_to`, `localize` and many others are available in tests.
205
+
206
+ As any presenter is a decorator, see also [how to test decorators](
207
+ https://github.com/Alexander-Senko/magic-decorator#testing-decorators
208
+ ).
209
+
210
+ ### RSpec
211
+
212
+ Presenter specs are expected to live in `spec/presenters`.
213
+ If a different path is used, `type: :presenter` metadata should be set explicitly.
214
+
215
+ ### Test::Unit
216
+
217
+ Tests related to the presenters are located under the `test/presenters` directory and inherit from `Magic::Presenter::TestCase`.
218
+
148
219
  ## Development
149
220
 
150
221
  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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ActiveSupport.on_load :action_view do # rubocop:disable Metrics/BlockLength
4
4
  concerning :DecoratedAssignments, prepend: true do
5
- def assign(assignments, ...)
5
+ def assign assignments
6
6
  decorate assignments
7
7
 
8
8
  super
@@ -24,7 +24,7 @@ ActiveSupport.on_load :action_view do # rubocop:disable Metrics/BlockLength
24
24
 
25
25
  concerning :PresenterContext, prepend: true do
26
26
  def in_rendering_context(...)
27
- Magic::Presenter::Base.with view_context: self do
27
+ Magic::Presenter.with view_context: self do
28
28
  super
29
29
  end
30
30
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined? Rails::Generators
4
+ # HACK: eliminates the following Thor warning:
5
+ #
6
+ # Deprecation warning: Expected boolean default value for '--helper'; got :presenter (string).
7
+ Thor::Option.prepend Module.new {
8
+ def validate_default_type!
9
+ return if @name == 'helper'
10
+
11
+ super
12
+ end
13
+ }
14
+
15
+ Magic.each_engine do |engine|
16
+ engine.config.generators do
17
+ _1.helper = :presenter
18
+ end
19
+ end
20
+ end
@@ -1,7 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- RSpec.configure do |config|
4
- config.include concern(:PresenterExampleGroup) {
5
- included { metadata[:type] = :presenter }
6
- }, file_path: %r'spec/presenters', type: :presenter
7
- end if defined? RSpec
3
+ if defined? RSpec::Core
4
+ RSpec.configure do |config|
5
+ if defined? RSpec::Rails
6
+ require 'rspec/rails/example/presenter_example_group'
7
+
8
+ config.include RSpec::Rails::PresenterExampleGroup, type: :presenter
9
+ end
10
+
11
+ # Tag all groups and examples in the spec/presenters directory with
12
+ # type: :presenter
13
+ config.define_derived_metadata file_path: %r'/spec/presenters/' do |metadata|
14
+ metadata[:type] ||= :presenter
15
+ end
16
+ end
17
+ end
@@ -3,7 +3,7 @@
3
3
  module Magic
4
4
  module Presenter
5
5
  module Generator # :nodoc:
6
- require 'generators/presenter/presenter_generator'
6
+ require 'generators/rails/presenter/presenter_generator'
7
7
 
8
8
  private
9
9
 
@@ -18,7 +18,7 @@ module Magic
18
18
  root / 'presenters' / path
19
19
  end
20
20
 
21
- def presenter_path(*) = file_path(*, root: PresenterGenerator.target_root)
21
+ def presenter_path(*) = file_path(*, root: Rails::PresenterGenerator.target_root)
22
22
  end
23
23
  end
24
24
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ class PresenterGenerator < Generators::NamedBase # :nodoc:
5
+ include Magic::Presenter::Generator
6
+ include Generators::ResourceHelpers
7
+
8
+ source_root File.expand_path('templates', __dir__)
9
+
10
+ check_class_collision suffix: 'Presenter'
11
+
12
+ class_option :parent,
13
+ type: :string,
14
+ default: 'ApplicationPresenter',
15
+ desc: 'The parent class for the generated presenter'
16
+
17
+ cattr_reader :target_root, default: Pathname('app')
18
+
19
+ def create_presenter_file
20
+ template 'presenter.rb', "#{file_path}.rb"
21
+ end
22
+
23
+ hook_for :test_framework do |generator|
24
+ invoke generator, [ name ]
25
+ end
26
+
27
+ private
28
+
29
+ def parent_class_name = options[:parent].classify
30
+ end
31
+ end
@@ -1,9 +1,6 @@
1
1
  require "test_helper"
2
2
 
3
3
  <% module_namespacing do -%>
4
- class <%= class_name %>Test < ActiveSupport::TestCase
5
- # test "the truth" do
6
- # assert true
7
- # end
4
+ class <%= class_name %>Test < Magic::Presenter::TestCase
8
5
  end
9
6
  <% end -%>
@@ -40,6 +40,8 @@ module Magic
40
40
  } for #{self}"
41
41
  end
42
42
 
43
+ delegate_missing_to :model_class
44
+
43
45
  def descendants
44
46
  Magic.eager_load :presenters
45
47
 
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_view/test_case'
4
+
5
+ module Magic
6
+ module Presenter
7
+ # = Magic Presenter test case
8
+ #
9
+ class TestCase < ActiveSupport::TestCase
10
+ module Behavior # :nodoc:
11
+ extend ActiveSupport::Concern
12
+
13
+ include ActionView::TestCase::Behavior
14
+
15
+ included do
16
+ Magic.each_engine { include _1.routes.url_helpers }
17
+ end
18
+ end
19
+
20
+ include Behavior
21
+ end
22
+ end
23
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Magic
4
4
  module Presenter
5
- VERSION = '0.4.0'
5
+ VERSION = '1.0.0'
6
6
  end
7
7
  end
@@ -11,6 +11,7 @@ module Magic # :nodoc:
11
11
  autoload :Base, 'magic/presenter/base'
12
12
  autoload :Helpers, 'magic/presenter/helpers'
13
13
  autoload :GlobalID, 'magic/presenter/global_id'
14
+ autoload :TestCase, 'magic/presenter/test_case'
14
15
  autoload :Generator, 'generators/magic/presenter/generator'
15
16
 
16
17
  singleton_class.delegate *%i[
@@ -19,16 +20,23 @@ module Magic # :nodoc:
19
20
  ], to: Base
20
21
  end
21
22
 
22
- module_function
23
+ module_function # TODO: extract to Magic Support
23
24
 
24
- def eager_load *scopes
25
+ def eager_load *scopes, engine: Rails.application
25
26
  return if Rails.application.config.eager_load
26
27
 
27
28
  scopes
28
29
  .map(&:to_s)
29
30
  .map(&:pluralize)
30
- .map { Rails.root / 'app' / _1 }
31
+ .map { engine.root / 'app' / _1 }
31
32
  .select(&:exist?)
32
33
  .each { Rails.autoloaders.main.eager_load_dir _1 }
33
34
  end
35
+
36
+ def each_engine(&)
37
+ Rails.application
38
+ .then { [ _1, *_1.railties ] }
39
+ .grep(Rails::Engine)
40
+ .each(&)
41
+ end
34
42
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/rails'
4
+
5
+ module RSpec
6
+ module Rails
7
+ # @api public
8
+ # Container module for presenter specs.
9
+ module PresenterExampleGroup
10
+ extend ActiveSupport::Concern
11
+ include HelperExampleGroup
12
+ include Magic::Presenter::TestCase::Behavior
13
+
14
+ included do
15
+ around { Magic::Presenter.with view_context: self, &_1 }
16
+ end
17
+ end
18
+ end
19
+ 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.4.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Senko
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2024-10-29 00:00:00.000000000 Z
10
+ date: 2024-11-23 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -35,14 +35,28 @@ dependencies:
35
35
  requirements:
36
36
  - - "~>"
37
37
  - !ruby/object:Gem::Version
38
- version: '0.3'
38
+ version: '1.0'
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'
45
+ version: '1.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: magic-lookup
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
46
60
  description: Based on Magic Decorator, it’s meant to replace Draper.
47
61
  email:
48
62
  - Alexander.Senko@gmail.com
@@ -56,15 +70,16 @@ files:
56
70
  - app/models/concerns/magic/presentable.rb
57
71
  - config/initializers/action_controller.rb
58
72
  - config/initializers/action_view.rb
73
+ - config/initializers/generators.rb
59
74
  - config/initializers/presentable.rb
60
75
  - config/initializers/rspec.rb
61
76
  - lib/generators/magic/presenter/generator.rb
62
77
  - lib/generators/magic/presenter/install/USAGE
63
78
  - lib/generators/magic/presenter/install/install_generator.rb
64
79
  - lib/generators/magic/presenter/install/templates/application_presenter.rb.tt
65
- - lib/generators/presenter/USAGE
66
- - lib/generators/presenter/presenter_generator.rb
67
- - lib/generators/presenter/templates/presenter.rb.tt
80
+ - lib/generators/rails/presenter/USAGE
81
+ - lib/generators/rails/presenter/presenter_generator.rb
82
+ - lib/generators/rails/presenter/templates/presenter.rb.tt
68
83
  - lib/generators/rspec/presenter/presenter_generator.rb
69
84
  - lib/generators/rspec/presenter/templates/presenter_spec.rb.tt
70
85
  - lib/generators/test_unit/presenter/presenter_generator.rb
@@ -75,7 +90,9 @@ files:
75
90
  - lib/magic/presenter/engine.rb
76
91
  - lib/magic/presenter/global_id.rb
77
92
  - lib/magic/presenter/helpers.rb
93
+ - lib/magic/presenter/test_case.rb
78
94
  - lib/magic/presenter/version.rb
95
+ - lib/rspec/rails/example/presenter_example_group.rb
79
96
  - lib/tasks/magic/presenter_tasks.rake
80
97
  homepage: https://github.com/Alexander-Senko/magic-presenter
81
98
  licenses:
@@ -83,7 +100,7 @@ licenses:
83
100
  metadata:
84
101
  homepage_uri: https://github.com/Alexander-Senko/magic-presenter
85
102
  source_code_uri: https://github.com/Alexander-Senko/magic-presenter
86
- changelog_uri: https://github.com/Alexander-Senko/magic-presenter/blob/v0.4.0/CHANGELOG.md
103
+ changelog_uri: https://github.com/Alexander-Senko/magic-presenter/blob/v1.0.0/CHANGELOG.md
87
104
  rdoc_options: []
88
105
  require_paths:
89
106
  - lib
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class PresenterGenerator < Rails::Generators::NamedBase # :nodoc:
4
- include Magic::Presenter::Generator
5
-
6
- source_root File.expand_path('templates', __dir__)
7
-
8
- check_class_collision suffix: 'Presenter'
9
-
10
- class_option :parent,
11
- type: :string,
12
- default: 'ApplicationPresenter',
13
- desc: 'The parent class for the generated presenter'
14
-
15
- cattr_reader :target_root, default: Pathname('app')
16
-
17
- def create_presenter_file
18
- template 'presenter.rb', "#{file_path}.rb"
19
- end
20
-
21
- hook_for :test_framework
22
-
23
- private
24
-
25
- def parent_class_name = options[:parent].classify
26
- end