roda-phlex 0.2.0 → 1.0.0.beta1

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: d4325bf47a07f07abf3c30722c16bcd2422559e3ae8ef3af32803efe081d640c
4
- data.tar.gz: fe31c12b510c61204b61996b6f22a696a8e82ba1f6a491ffe399c71e30bae365
3
+ metadata.gz: ab83be491f6ff213f1097afe81e6f3af4fba92b87cce26826deae0f2814df623
4
+ data.tar.gz: e874de74010cf0c913df896d356337fc8ed59434a435e037e404de0e650f8268
5
5
  SHA512:
6
- metadata.gz: 3174f3c8d94c44d3876a51fe5f5d531f3feee7e5143d0b98a43e6978537f1b4e070c260f73421fb5bd04e9d8619e83067ab34eef073c7f91836d3c9a2e94df32
7
- data.tar.gz: c6eb86db087726ee22f7cf2d0df7c140c34433606279675bd56c26158b361c9b0b34f8369eb9a390c04193b5b5c8e17e6591bb599553a1ffef06499be901baf3
6
+ metadata.gz: e9b359e8a1c34db8c4f1817358bf3b423c63186515455bef90daad7e3135f014c967d70d0086c5a51af3253796a69da3d8ee0399d57c49237991d9323af0d53c
7
+ data.tar.gz: 6f284a8c8ef79ab27ecc5f3ab370381eb1f4396c727907a49494681317b7db70a11bc797fada70af76f03502660e87835df7568fff8eccb29f95f9a72c57e5d4
data/.rspec CHANGED
@@ -1,3 +1,4 @@
1
1
  --format documentation
2
2
  --color
3
3
  --require spec_helper
4
+ --tag ~isolate
data/.standard.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  # For available configuration options, see:
2
2
  # https://github.com/standardrb/standard
3
- ruby_version: 3.0
3
+ ruby_version: 3.2
data/CHANGELOG.md CHANGED
@@ -4,19 +4,37 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6
6
 
7
- ## [Unreleased]
7
+ ## [1.0.0.beta1] - 2025-01-06
8
8
 
9
9
  ### Added
10
10
 
11
+ - Support for Phlex 2.0 Component context. (`HelloWorld.new.call(context: {some: :data})`).
12
+ + Like layout options, the `:context` plugin option can be used to set a default context.
13
+ The value will be`dup`ed on first use.
14
+ + `#phlex_context` method to access the context.
15
+ + `#set_phlex_context` method to set the context.
16
+ - `:delegate_on` plugin option to specify the object to delegate methods to.
17
+ Defaults to `::Phlex::SGML` but its advised to set it to your own subclass of `::Phlex::SGML`.
18
+ - `:delegate_name` plugin option to specify name of the method that delegates to the Roda app.
19
+ Defaults to `"app"`.
20
+
11
21
  ### Changed
12
22
 
13
- ### Deprecated
23
+ - Update to phlex 2.0.
24
+ - `#phlex_layout`, `#phlex_layout_opts`, `#phlex_layout_handler` no longer accept a value to set their value.
25
+ Use their corresponding `#set_*` methods instead.
14
26
 
15
27
  ### Removed
16
28
 
29
+ - `delegate: :all` plugin option removed. You need to specify the methods to delegate explicitly.
30
+ - `delegate: <Symbol,String>` plugin option removed. Method names need to be specified as an array of
31
+ symbols or strings, even when delegating only one method.
32
+
17
33
  ### Fixed
18
34
 
19
- ### Security
35
+ - Mutating the layout options hash no longer affects subsequent requests.
36
+ The value passed as `:layout_opts` plugin config is now `dup`ed on first use.
37
+ Note, that mutating nested objects will still affect the original hash.
20
38
 
21
39
  ## [0.2.0] - 2024-12-13
22
40
 
data/README.md CHANGED
@@ -30,17 +30,24 @@ gem install roda-phlex
30
30
  * `:layout_opts` (`Object`): Options that are passed to the layout
31
31
  class when it is instantiated. These options can be used to customize
32
32
  the behavior of the layout. Usually, this is a `Hash`.
33
+ To avoid external changes effecting subsequent requests, you should `.freeze` this
34
+ and all nested objects.
33
35
  * `:layout_handler` (`#call`): A custom handler for creating layout
34
36
  instances. This proc receives three arguments: the layout class, the
35
37
  layout options, and the object to be rendered. By default, it runs
36
38
  `layout.new(obj, **layout_opts)`, which instantiates the layout class with the
37
39
  provided view object and options as keyword arguments.
40
+ * `:context` (`Hash`): The context that is passed to the rendering call. (default: `{}`)
41
+ To avoid external changes effecting subsequent requests, you should `.freeze` this
42
+ and all nested objects.
38
43
  * `:delegate`: Define if or which methods should be delegated to the Roda app:
39
44
  + `true` (default): Create a single `app` method that delegates to the Roda app.
40
45
  + `false`: Do not create any delegate methods.
41
- + `:all`: Delegate all methods the Roda app responds to, to it. Be careful with this option.
42
- It can lead to unexpected behavior if the Roda app has methods that conflict with Phlex methods.
43
- + `Symbol`, `String`, `Array<Symbol,String>`: Delegate only the specified methods to the Roda app.
46
+ + `Array<Symbol,String>`: Delegate the named methods to the Roda app.
47
+ * `:delegate_on`: Class or module to define delegation methods on. Defaults to `::Phlex::SGML`.
48
+ + Use this option to limit delegation methods to a application specific class or module
49
+ (like "ApplicationView") to avoid polluting the global namespace.
50
+ * `:delegate_name`: The name of the method that delegates to the Roda app. Defaults to `"app"`.
44
51
 
45
52
  ## Usage
46
53
 
@@ -117,7 +124,7 @@ class Layout < Phlex::HTML
117
124
  # Standard site header and navigation.
118
125
  render Header.new
119
126
 
120
- yield_content(&block)
127
+ yield
121
128
  }
122
129
  }
123
130
  end
@@ -142,16 +149,17 @@ end
142
149
 
143
150
  ```ruby
144
151
  # Define a default layout and layout options for the whole application
145
- plugin :phlex, layout: MyLayout, layout_opts: { title: +"My App" }
152
+ plugin :phlex, layout: MyLayout, layout_opts: { title: "My App".freeze }.freeze
153
+
146
154
  route do |r|
147
155
  r.on "posts" do
148
156
  # redefine the layout and layout options for this route tree
149
- phlex_layout MyPostLayout
150
- phlex_layout_opts[:title] << " - Posts"
157
+ set_phlex_layout MyPostLayout
158
+ set_phlex_layout_opts phlex_layout_opts.merge(title: "#{phlex_layout_opts[:title]} - Posts")
151
159
 
152
160
  r.get 'new' do
153
161
  # Redefine the layout and layout options for this route
154
- phlex_layout_opts[:title] = "Create new post"
162
+ phlex_layout_opts[:title] << " - Create new post"
155
163
  phlex MyView.new
156
164
  end
157
165
  end
data/Rakefile CHANGED
@@ -7,4 +7,9 @@ RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  require "standard/rake"
9
9
 
10
+ Rake::Task["spec"].enhance do
11
+ sh "bundle", "exec", "rspec", "-t", "isolate", "spec/roda/delegation_error_spec.rb"
12
+ sh "bundle", "exec", "rspec", "-t", "isolate", "spec/roda/delegation_spec.rb"
13
+ end
14
+
10
15
  task default: %i[spec standard]
data/lib/roda/phlex.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  class Roda
4
4
  module RodaPlugins
5
5
  module Phlex
6
- VERSION = "0.2.0"
6
+ VERSION = "1.0.0.beta1"
7
7
  end
8
8
  end
9
9
  end
@@ -17,16 +17,16 @@ class Roda
17
17
  # layout options, and the object to be rendered. By default, it uses the
18
18
  # `DEFAULT_LAYOUT_HANDLER`, which instantiates the layout class with the
19
19
  # provided object and options as keyword arguments.
20
+ # - `:context` (+Hash+): The context that is passed to the rendering call. (default: `{}`)
20
21
  # - `:delegate`: Define if or which methods should be delegated to the Roda app:
21
22
  # + `true` (default): Create a single `app` method that delegates to the Roda app.
22
23
  # + `false`: Do not create any delegate methods.
23
- # + `:all`: Delegate all methods the Roda app responds to, to it. Be careful with this option.
24
- # It can lead to unexpected behavior if the Roda app has methods that conflict with Phlex methods.
25
- # + `Symbol`, `String`, `Array<Symbol,String>`: Delegate only the named methods to the Roda app.
24
+ # + `Array<Symbol,String>`: Delegate the named methods to the Roda app.
25
+ # - `:delegate_on`: Class or module to define delegation methods on. Defaults to +::Phlex::SGML+.
26
+ # + Use this option to limit delegation methods to a application specific class or module
27
+ # (like "ApplicationView") to avoid polluting the global namespace.
28
+ # - `:delegate_name`: The name of the method that delegates to the Roda app. Defaults to `"app"`.
26
29
  module Phlex
27
- Undefined = Object.new
28
- private_constant :Undefined
29
-
30
30
  Error = Class.new(StandardError)
31
31
 
32
32
  # Custom TypeError class for Phlex errors.
@@ -53,129 +53,154 @@ class Roda
53
53
  layout_opts ? layout.new(obj, **layout_opts) : layout.new(obj)
54
54
  end
55
55
 
56
+ # @!visibility private
57
+ DELEGATE_ERROR_MESSAGE = "roda-phlex: :delegate is enabled, but :%s is to %s. Delegation will be disabled. Set :delegate to false to suppress this warning."
58
+ private_constant :DELEGATE_ERROR_MESSAGE
59
+
56
60
  # Configures the Phlex plugin for the Roda application.
57
61
  # @param app [Roda] The Roda application.
58
62
  # @param opts [Hash] The options for configuring the Phlex plugin.
59
63
  def self.configure(app, opts = OPTS)
60
- delegate = opts.key?(:delegate) ? opts.delete(:delegate) : true
61
- app.opts[:phlex] = opts
62
- app.opts[:phlex][:layout_handler] ||= DEFAULT_LAYOUT_HANDLER
63
-
64
+ delegate = opts.fetch(:delegate, true)
64
65
  if delegate
65
- overrides = Module.new do
66
- def app
67
- @_view_context
68
- end
66
+ delegate_on = opts.fetch(:delegate_on) { ::Phlex::SGML }
67
+ delegate_name = opts.fetch(:delegate_name, "app")
69
68
 
70
- case delegate
71
- when :all
72
- def method_missing(name, ...)
73
- if app.respond_to?(name)
74
- app.send(name, ...)
75
- else
76
- super
77
- end
78
- end
69
+ warn sprintf(DELEGATE_ERROR_MESSAGE, "delegate_on", delegate_on.inspect) unless delegate_on
70
+ warn sprintf(DELEGATE_ERROR_MESSAGE, "delegate_name", delegate_name.inspect) unless delegate_name
71
+ end
79
72
 
80
- def respond_to_missing?(name, include_private = false)
81
- app.respond_to?(name) || super
73
+ app.opts[:phlex] = opts.dup
74
+ app.opts[:phlex][:layout_handler] ||= DEFAULT_LAYOUT_HANDLER
75
+ app.opts[:phlex][:context] ||= {}
76
+
77
+ if delegate && delegate_on && delegate_name
78
+ delegate_mod = Module.new do
79
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
80
+ def #{delegate_name}
81
+ @_context.view_context
82
82
  end
83
+ RUBY
83
84
 
84
- when Symbol, String, Array
85
- Array(delegate).each do |delegate|
85
+ case delegate
86
+ when Array
87
+ delegate.each do |delegate|
86
88
  class_eval <<~RUBY, __FILE__, __LINE__ + 1
87
89
  def #{delegate}(...)
88
- app.#{delegate}(...)
90
+ #{delegate_name}.#{delegate}(...)
89
91
  end
90
92
  RUBY
91
93
  end
92
94
  end
93
95
  end
94
96
 
95
- ::Phlex::SGML.include(overrides)
97
+ delegate_on.include(delegate_mod)
96
98
  end
97
99
  end
98
100
 
99
101
  module InstanceMethods
100
- # Retrieves or sets the layout.
101
- # When no argument is provided, it returns the current layout.
102
- # Use +nil+ or +false+ to disable layout.
103
- #
104
- # @param layout [Class, nil, false] The layout (a +Phlex::SGML+ class) to be set.
102
+ # Retrieves the layout class.
105
103
  # @return [Class, nil] The current layout (a +Phlex::SGML+ class) or nil if not set.
106
- def phlex_layout(layout = Undefined)
107
- case layout
108
- when Undefined
109
- opts.dig(:phlex, :layout)
110
- when nil, false
111
- opts[:phlex].delete(:layout)
112
- else
113
- if layout <= ::Phlex::SGML
114
- opts[:phlex][:layout] = layout
115
- else
116
- raise TypeError.new(layout)
117
- end
118
- end
104
+ def phlex_layout
105
+ return @_phlex_layout if defined?(@_phlex_layout)
106
+ @_phlex_layout = opts.dig(:phlex, :layout)
119
107
  end
120
108
 
121
- # Retrieves or sets the layout options.
122
- # When no argument is provided, it returns the current layout options.
123
- # Use +nil+ to delete layout options.
109
+ # Sets the layout class.
124
110
  #
125
- # @note The {DEFAULT_LAYOUT_HANDLER} expects +layout_opts+ to be a +Hash+.
126
- # @param layout_opts [Object, nil] The layout options to be set, usually a +Hash+.
127
- # @return [Object, nil] The current layout options or nil if not set.
128
- def phlex_layout_opts(layout_opts = Undefined)
129
- case layout_opts
130
- when Undefined
131
- opts.dig(:phlex, :layout_opts)
132
- when nil
133
- opts[:phlex].delete(:layout_opts)
111
+ # @param layout [Class, nil] The layout class to be set.
112
+ # @return [Class, nil] The layout class that was set.
113
+ def set_phlex_layout(layout)
114
+ if !layout || layout <= ::Phlex::SGML
115
+ @_phlex_layout = layout
134
116
  else
135
- opts[:phlex][:layout_opts] = layout_opts
117
+ raise TypeError.new(layout)
136
118
  end
137
119
  end
138
120
 
139
- # Retrieves or sets the layout handler.
140
- # When no argument is provided, it returns the current layout handler.
141
- # Use +nil+ or +:default: to reset the layout handler to the {DEFAULT_LAYOUT_HANDLER}.
142
- #
143
- # @param handler [#call, nil, :default] The layout handler to be set.
121
+ # Retrieves the layout options hash.
122
+ # @return [Object, nil] The current layout options or nil if not set.
123
+ # @note Layout options set via the plugin configuration will get `dep`ed
124
+ # for the current request. Be aware that this is a shallow copy and
125
+ # changes to nested objects will affect the original object, that is
126
+ # all subsequent requests. This is *unsafe* and should be avoided:
127
+ # ```ruby
128
+ # plugin :phlex, layout_opts: {key: {nested: "value"}}
129
+ # # ...
130
+ # # UNSAFE: Changes to phlex_layout_opts[:key] will affect the plugin config.
131
+ # phlex_layout_opts[:key][:nested] = "other value"
132
+ # ```
133
+ def phlex_layout_opts
134
+ return @_phlex_layout_opts if defined?(@_phlex_layout_opts)
135
+ @_phlex_layout_opts = opts.dig(:phlex, :layout_opts).dup
136
+ end
137
+
138
+ # Sets the layout options.
139
+ # @param layout_opts [Object, nil] The layout options to be set.
140
+ # @return [Object, nil] The layout options that were set.
141
+ # @note The {DEFAULT_LAYOUT_HANDLER} expects +layout_opts+ to be a +Hash+.
142
+ def set_phlex_layout_opts(layout_opts)
143
+ @_phlex_layout_opts = layout_opts
144
+ end
145
+
146
+ # Retrieves the layout handler.
144
147
  # @return [#call] The current layout handler.
145
- def phlex_layout_handler(handler = Undefined)
146
- case handler
147
- when Undefined
148
- opts.dig(:phlex, :layout_handler)
148
+ def phlex_layout_handler
149
+ return @_phlex_layout_handler if defined?(@_phlex_layout_handler)
150
+ @_phlex_layout_handler = opts.dig(:phlex, :layout_handler)
151
+ end
152
+
153
+ # Sets the layout handler.
154
+ # Use +nil+ or +:default: to reset the layout handler to the {DEFAULT_LAYOUT_HANDLER}.
155
+ def set_phlex_layout_handler(handler)
156
+ @_phlex_layout_handler = case handler
149
157
  when nil, :default
150
- opts[:phlex][:layout_handler] = DEFAULT_LAYOUT_HANDLER
158
+ DEFAULT_LAYOUT_HANDLER
151
159
  else
152
- opts[:phlex][:layout_handler] = handler
160
+ handler
153
161
  end
154
162
  end
155
163
 
164
+ # Retrieves the Phlex context.
165
+ # @return [Hash, nil] The current Phlex context.
166
+ def phlex_context
167
+ return @_phlex_context if defined?(@_phlex_context)
168
+ @phlex_context = opts.dig(:phlex, :context).dup
169
+ end
170
+
171
+ # Sets the Phlex context.
172
+ # @param context [Hash] The Phlex context to be set.
173
+ # @return [Hash] The Phlex context that was set.
174
+ def set_phlex_context(context)
175
+ @_phlex_context = context
176
+ end
177
+
156
178
  # Renders a Phlex object.
157
179
  # @param obj [Phlex::SGML] The Phlex object to be rendered.
180
+ # @param layout [Class, nil] The layout to be used for rendering. Defaults to the layout set by {#phlex_layout}.
181
+ # - The layout class will be initialized with the object and layout options from #{phlex_layout_opts} via {#phlex_layout_handler}.
182
+ # - +nil+ or +false+ will disable layout and render the +obj+ directly.
183
+ # @param context [Hash, nil] The Phlex context to be used for rendering. Defaults to the context set by {#phlex_context}.
158
184
  # @param content_type [String, nil] The content type of the response.
159
185
  # @param stream [Boolean] Whether to stream the response or not.
160
- def phlex(obj, content_type: nil, stream: false)
186
+ def phlex(obj, layout: phlex_layout, context: phlex_context, content_type: nil, stream: false)
161
187
  raise TypeError.new(obj) unless obj.is_a?(::Phlex::SGML)
162
188
 
163
189
  content_type ||= "image/svg+xml" if obj.is_a?(::Phlex::SVG)
164
190
  response["Content-Type"] = content_type if content_type
165
191
 
166
- phlex_opts = opts[:phlex]
167
- renderer = if (layout = phlex_opts[:layout])
168
- phlex_layout_handler.call(layout, phlex_opts[:layout_opts], obj)
192
+ renderer = if layout
193
+ phlex_layout_handler.call(layout, phlex_layout_opts, obj)
169
194
  else
170
195
  obj
171
196
  end
172
197
 
173
198
  if stream
174
199
  self.stream do |out|
175
- renderer.call(out, view_context: self)
200
+ renderer.call(out, context: context, view_context: self)
176
201
  end
177
202
  else
178
- renderer.call(view_context: self)
203
+ renderer.call(context: context, view_context: self)
179
204
  end
180
205
  end
181
206
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda-phlex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Schulze
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-12-13 00:00:00.000000000 Z
10
+ date: 2025-01-06 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: phlex
@@ -16,14 +15,20 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: 1.7.0
18
+ version: 2.0.0.rc1
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '3'
20
22
  type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
23
25
  requirements:
24
26
  - - ">="
25
27
  - !ruby/object:Gem::Version
26
- version: 1.7.0
28
+ version: 2.0.0.rc1
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '3'
27
32
  - !ruby/object:Gem::Dependency
28
33
  name: roda
29
34
  requirement: !ruby/object:Gem::Requirement
@@ -65,7 +70,6 @@ metadata:
65
70
  homepage_uri: https://github.com/fnordfish/roda-phlex
66
71
  source_code_uri: https://github.com/fnordfish/roda-phlex
67
72
  changelog_uri: https://github.com/fnordfish/roda-phlex/blob/main/CHANGELOG.md
68
- post_install_message:
69
73
  rdoc_options: []
70
74
  require_paths:
71
75
  - lib
@@ -80,8 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
84
  - !ruby/object:Gem::Version
81
85
  version: '0'
82
86
  requirements: []
83
- rubygems_version: 3.5.23
84
- signing_key:
87
+ rubygems_version: 3.6.2
85
88
  specification_version: 4
86
89
  summary: A Phlex adapter for Roda
87
90
  test_files: []