dry-view 0.5.1 → 0.7.1
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 +4 -4
- data/CHANGELOG.md +143 -18
- data/LICENSE +20 -0
- data/README.md +22 -14
- data/dry-view.gemspec +29 -21
- data/lib/dry-view.rb +3 -1
- data/lib/dry/view.rb +514 -2
- data/lib/dry/view/context.rb +80 -0
- data/lib/dry/view/decorated_attributes.rb +82 -0
- data/lib/dry/view/errors.rb +29 -0
- data/lib/dry/view/exposure.rb +35 -14
- data/lib/dry/view/exposures.rb +18 -6
- data/lib/dry/view/part.rb +166 -53
- data/lib/dry/view/part_builder.rb +140 -0
- data/lib/dry/view/path.rb +35 -7
- data/lib/dry/view/render_environment.rb +62 -0
- data/lib/dry/view/render_environment_missing.rb +44 -0
- data/lib/dry/view/rendered.rb +55 -0
- data/lib/dry/view/renderer.rb +36 -29
- data/lib/dry/view/scope.rb +160 -14
- data/lib/dry/view/scope_builder.rb +98 -0
- data/lib/dry/view/tilt.rb +78 -0
- data/lib/dry/view/tilt/erb.rb +26 -0
- data/lib/dry/view/tilt/erbse.rb +21 -0
- data/lib/dry/view/tilt/haml.rb +26 -0
- data/lib/dry/view/version.rb +5 -2
- metadata +78 -115
- data/.gitignore +0 -26
- data/.rspec +0 -2
- data/.travis.yml +0 -23
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -22
- data/LICENSE.md +0 -10
- data/Rakefile +0 -6
- data/benchmarks/templates/button.html.erb +0 -1
- data/benchmarks/view.rb +0 -24
- data/bin/console +0 -7
- data/lib/dry/view/controller.rb +0 -155
- data/lib/dry/view/decorator.rb +0 -45
- data/lib/dry/view/missing_renderer.rb +0 -15
- data/spec/fixtures/templates/_hello.html.slim +0 -1
- data/spec/fixtures/templates/decorated_parts.html.slim +0 -4
- data/spec/fixtures/templates/edit.html.slim +0 -11
- data/spec/fixtures/templates/empty.html.slim +0 -1
- data/spec/fixtures/templates/greeting.html.slim +0 -2
- data/spec/fixtures/templates/hello.html.slim +0 -1
- data/spec/fixtures/templates/layouts/app.html.slim +0 -6
- data/spec/fixtures/templates/layouts/app.txt.erb +0 -3
- data/spec/fixtures/templates/parts_with_args.html.slim +0 -3
- data/spec/fixtures/templates/parts_with_args/_box.html.slim +0 -3
- data/spec/fixtures/templates/shared/_index_table.html.slim +0 -2
- data/spec/fixtures/templates/shared/_shared_hello.html.slim +0 -1
- data/spec/fixtures/templates/tasks.html.slim +0 -3
- data/spec/fixtures/templates/user.html.slim +0 -2
- data/spec/fixtures/templates/users.html.slim +0 -5
- data/spec/fixtures/templates/users.txt.erb +0 -3
- data/spec/fixtures/templates/users/_row.html.slim +0 -2
- data/spec/fixtures/templates/users/_tbody.html.slim +0 -5
- data/spec/fixtures/templates/users_with_count.html.slim +0 -5
- data/spec/fixtures/templates/users_with_count_inherit.html.slim +0 -6
- data/spec/fixtures/templates_override/_hello.html.slim +0 -1
- data/spec/fixtures/templates_override/users.html.slim +0 -5
- data/spec/integration/decorator_spec.rb +0 -80
- data/spec/integration/exposures_spec.rb +0 -392
- data/spec/integration/part/decorated_attributes_spec.rb +0 -157
- data/spec/integration/view_spec.rb +0 -133
- data/spec/spec_helper.rb +0 -46
- data/spec/unit/controller_spec.rb +0 -37
- data/spec/unit/decorator_spec.rb +0 -61
- data/spec/unit/exposure_spec.rb +0 -227
- data/spec/unit/exposures_spec.rb +0 -103
- data/spec/unit/part_spec.rb +0 -90
- data/spec/unit/renderer_spec.rb +0 -57
- data/spec/unit/scope_spec.rb +0 -53
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/equalizer"
|
4
|
+
require_relative "decorated_attributes"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
class View
|
8
|
+
# Provides a baseline environment across all the templates, parts and scopes
|
9
|
+
# in a given rendering.
|
10
|
+
#
|
11
|
+
# @abstract Subclass this and add your own methods (along with a custom
|
12
|
+
# `#initialize` if you wish to inject dependencies)
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
class Context
|
16
|
+
include Dry::Equalizer(:_options)
|
17
|
+
include DecoratedAttributes
|
18
|
+
|
19
|
+
attr_reader :_render_env, :_options
|
20
|
+
|
21
|
+
# Returns a new instance of Context
|
22
|
+
#
|
23
|
+
# In subclasses, you should include an `**options` parameter and pass _all
|
24
|
+
# arguments_ to `super`. This allows Context to make copies of itself
|
25
|
+
# while preserving your dependencies.
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# class MyContext < Dry::View::Context
|
29
|
+
# # Injected dependency
|
30
|
+
# attr_reader :assets
|
31
|
+
#
|
32
|
+
# def initialize(assets:, **options)
|
33
|
+
# @assets = assets
|
34
|
+
# super
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
def initialize(render_env: nil, **options)
|
40
|
+
@_render_env = render_env
|
41
|
+
@_options = options
|
42
|
+
end
|
43
|
+
|
44
|
+
# @api private
|
45
|
+
def for_render_env(render_env)
|
46
|
+
return self if render_env == _render_env
|
47
|
+
|
48
|
+
self.class.new(**_options.merge(render_env: render_env))
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns a copy of the Context with new options merged in.
|
52
|
+
#
|
53
|
+
# This may be useful to supply values for dependencies that are _optional_
|
54
|
+
# when initializing your custom Context subclass.
|
55
|
+
#
|
56
|
+
# @example
|
57
|
+
# class MyContext < Dry::View::Context
|
58
|
+
# # Injected dependencies (request is optional)
|
59
|
+
# attr_reader :assets, :request
|
60
|
+
#
|
61
|
+
# def initialize(assets:, request: nil, **options)
|
62
|
+
# @assets = assets
|
63
|
+
# @request = reuqest
|
64
|
+
# super
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# my_context = MyContext.new(assets: assets)
|
69
|
+
# my_context_with_request = my_context.with(request: request)
|
70
|
+
#
|
71
|
+
# @api public
|
72
|
+
def with(**new_options)
|
73
|
+
self.class.new(
|
74
|
+
render_env: _render_env,
|
75
|
+
**_options.merge(new_options)
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
class View
|
7
|
+
# Decorates attributes in Parts.
|
8
|
+
module DecoratedAttributes
|
9
|
+
# @api private
|
10
|
+
def self.included(klass)
|
11
|
+
klass.extend ClassInterface
|
12
|
+
end
|
13
|
+
|
14
|
+
# Decorated attributes class-level interface.
|
15
|
+
module ClassInterface
|
16
|
+
# @api private
|
17
|
+
MODULE_NAME = :DecoratedAttributes
|
18
|
+
|
19
|
+
# Decorates the provided attributes, wrapping them in Parts using the
|
20
|
+
# current render environment.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# class Article < Dry::View::Part
|
24
|
+
# decorate :feature_image
|
25
|
+
# decorate :author as: :person
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# @param names [Array<Symbol>] the attribute names
|
29
|
+
# @param options [Hash] the options to pass to the Part Builder
|
30
|
+
# @option options [Symbol, Class] :as an alternative name or class to use when finding a
|
31
|
+
# matching Part
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
def decorate(*names, **options)
|
35
|
+
decorated_attributes.decorate(*names, **options)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def decorated_attributes
|
41
|
+
if const_defined?(MODULE_NAME, false)
|
42
|
+
const_get(MODULE_NAME)
|
43
|
+
else
|
44
|
+
const_set(MODULE_NAME, Attributes.new).tap do |mod|
|
45
|
+
prepend mod
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @api private
|
52
|
+
class Attributes < Module
|
53
|
+
def initialize(*)
|
54
|
+
@names = Set.new
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
def decorate(*names, **options)
|
59
|
+
@names += names
|
60
|
+
|
61
|
+
class_eval do
|
62
|
+
names.each do |name|
|
63
|
+
define_method name do
|
64
|
+
attribute = super()
|
65
|
+
|
66
|
+
if _render_env && attribute
|
67
|
+
_render_env.part(name, attribute, **options)
|
68
|
+
else
|
69
|
+
attribute
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def inspect
|
77
|
+
%(#<#{self.class.name}#{@names.to_a.sort.inspect}>)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
class View
|
5
|
+
# Error raised when critical settings are not configured
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class UndefinedConfigError < StandardError
|
9
|
+
def initialize(key)
|
10
|
+
super("no +#{key}+ configured")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Error raised when template could not be found within a view's configured
|
15
|
+
# paths
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
class TemplateNotFoundError < StandardError
|
19
|
+
def initialize(template_name, lookup_paths)
|
20
|
+
msg = [
|
21
|
+
"Template +#{template_name}+ could not be found in paths:",
|
22
|
+
lookup_paths.map { |path| " - #{path}" }
|
23
|
+
].join("\n\n")
|
24
|
+
|
25
|
+
super(msg)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/dry/view/exposure.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/equalizer"
|
2
4
|
|
3
5
|
module Dry
|
4
|
-
|
6
|
+
class View
|
7
|
+
# An exposure defined on a view
|
8
|
+
#
|
9
|
+
# @api private
|
5
10
|
class Exposure
|
6
11
|
include Dry::Equalizer(:name, :proc, :object, :options)
|
7
12
|
|
8
|
-
EXPOSURE_DEPENDENCY_PARAMETER_TYPES = [
|
9
|
-
INPUT_PARAMETER_TYPES = [
|
13
|
+
EXPOSURE_DEPENDENCY_PARAMETER_TYPES = %i[req opt].freeze
|
14
|
+
INPUT_PARAMETER_TYPES = %i[key keyreq keyrest].freeze
|
10
15
|
|
11
16
|
attr_reader :name
|
12
17
|
attr_reader :proc
|
@@ -21,7 +26,7 @@ module Dry
|
|
21
26
|
end
|
22
27
|
|
23
28
|
def bind(obj)
|
24
|
-
self.class.new(name, proc, obj, options)
|
29
|
+
self.class.new(name, proc, obj, **options)
|
25
30
|
end
|
26
31
|
|
27
32
|
def dependency_names
|
@@ -44,6 +49,14 @@ module Dry
|
|
44
49
|
end
|
45
50
|
end
|
46
51
|
|
52
|
+
def for_layout?
|
53
|
+
options.fetch(:layout) { false }
|
54
|
+
end
|
55
|
+
|
56
|
+
def decorate?
|
57
|
+
options.fetch(:decorate) { true }
|
58
|
+
end
|
59
|
+
|
47
60
|
def private?
|
48
61
|
options.fetch(:private) { false }
|
49
62
|
end
|
@@ -63,23 +76,31 @@ module Dry
|
|
63
76
|
private
|
64
77
|
|
65
78
|
def call_proc(input, locals)
|
66
|
-
args = proc_args(input, locals)
|
67
|
-
|
68
|
-
if
|
69
|
-
proc.(
|
79
|
+
args, keywords = proc_args(input, locals)
|
80
|
+
|
81
|
+
if keywords.empty?
|
82
|
+
if proc.is_a?(Method)
|
83
|
+
proc.(*args)
|
84
|
+
else
|
85
|
+
object.instance_exec(*args, &proc)
|
86
|
+
end
|
70
87
|
else
|
71
|
-
|
88
|
+
if proc.is_a?(Method)
|
89
|
+
proc.(*args, **keywords)
|
90
|
+
else
|
91
|
+
object.instance_exec(*args, **keywords, &proc)
|
92
|
+
end
|
72
93
|
end
|
73
94
|
end
|
74
95
|
|
75
96
|
def proc_args(input, locals)
|
76
97
|
dependency_args = proc_dependency_args(locals)
|
77
|
-
|
98
|
+
keywords = proc_input_args(input)
|
78
99
|
|
79
|
-
if
|
80
|
-
dependency_args
|
100
|
+
if keywords.empty?
|
101
|
+
[dependency_args, {}]
|
81
102
|
else
|
82
|
-
dependency_args
|
103
|
+
[dependency_args, keywords]
|
83
104
|
end
|
84
105
|
end
|
85
106
|
|
data/lib/dry/view/exposures.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "tsort"
|
4
|
+
require "dry/core/equalizer"
|
2
5
|
require "dry/view/exposure"
|
3
6
|
|
4
7
|
module Dry
|
5
|
-
|
8
|
+
class View
|
9
|
+
# @api private
|
6
10
|
class Exposures
|
11
|
+
include Dry::Equalizer(:exposures)
|
7
12
|
include TSort
|
8
13
|
|
9
14
|
attr_reader :exposures
|
@@ -25,7 +30,7 @@ module Dry
|
|
25
30
|
end
|
26
31
|
|
27
32
|
def add(name, proc = nil, **options)
|
28
|
-
exposures[name] = Exposure.new(name, proc, options)
|
33
|
+
exposures[name] = Exposure.new(name, proc, **options)
|
29
34
|
end
|
30
35
|
|
31
36
|
def import(name, exposure)
|
@@ -40,12 +45,19 @@ module Dry
|
|
40
45
|
self.class.new(bound_exposures)
|
41
46
|
end
|
42
47
|
|
43
|
-
def
|
48
|
+
def call(input)
|
49
|
+
# rubocop:disable Style/MultilineBlockChain
|
44
50
|
tsort.each_with_object({}) { |name, memo|
|
45
|
-
|
46
|
-
|
47
|
-
|
51
|
+
next unless (exposure = self[name])
|
52
|
+
|
53
|
+
value = exposure.(input, memo)
|
54
|
+
value = yield(value, exposure) if block_given?
|
55
|
+
|
56
|
+
memo[name] = value
|
57
|
+
}.each_with_object({}) { |(name, value), memo|
|
58
|
+
memo[name] = value unless self[name].private?
|
48
59
|
}
|
60
|
+
# rubocop:enable Style/MultilineBlockChain
|
49
61
|
end
|
50
62
|
|
51
63
|
private
|
data/lib/dry/view/part.rb
CHANGED
@@ -1,77 +1,205 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/equalizer"
|
4
|
+
require_relative "decorated_attributes"
|
5
|
+
require_relative "render_environment_missing"
|
4
6
|
|
5
7
|
module Dry
|
6
|
-
|
8
|
+
class View
|
9
|
+
# Decorates an exposure value and provides a place to encapsulate
|
10
|
+
# view-specific behavior alongside your application's domain objects.
|
11
|
+
#
|
12
|
+
# @abstract Subclass this and provide your own methods adding view-specific
|
13
|
+
# behavior. You should not override `#initialize`.
|
14
|
+
#
|
15
|
+
# @see https://dry-rb.org/gems/dry-view/parts/
|
16
|
+
#
|
17
|
+
# @api public
|
7
18
|
class Part
|
19
|
+
# @api private
|
8
20
|
CONVENIENCE_METHODS = %i[
|
21
|
+
format
|
9
22
|
context
|
10
23
|
render
|
24
|
+
scope
|
11
25
|
value
|
12
26
|
].freeze
|
13
27
|
|
14
|
-
include Dry::Equalizer(:_name, :_value, :
|
28
|
+
include Dry::Equalizer(:_name, :_value, :_render_env)
|
29
|
+
include DecoratedAttributes
|
15
30
|
|
31
|
+
# The part's name. This comes from the exposure supplying the value.
|
32
|
+
#
|
33
|
+
# @return [Symbol] name
|
34
|
+
#
|
35
|
+
# @api public
|
16
36
|
attr_reader :_name
|
17
37
|
|
38
|
+
# The decorated value. This is the value returned from the exposure.
|
39
|
+
#
|
40
|
+
# @overload _value
|
41
|
+
# Returns the value.
|
42
|
+
# @overload value
|
43
|
+
# A convenience alias for `_value`. Is available unless the value itself
|
44
|
+
# responds to `#value`.
|
45
|
+
#
|
46
|
+
# @return [Object] value
|
47
|
+
#
|
48
|
+
# @api public
|
18
49
|
attr_reader :_value
|
19
50
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
51
|
+
# The current render environment
|
52
|
+
#
|
53
|
+
# @return [RenderEnvironment] render environment
|
54
|
+
#
|
55
|
+
# @api private
|
56
|
+
attr_reader :_render_env
|
25
57
|
|
26
|
-
|
58
|
+
# Determins a part name (when initialized without one). Intended for use
|
59
|
+
# only while unit testing Parts.
|
60
|
+
#
|
61
|
+
# @api private
|
62
|
+
def self.part_name(inflector)
|
63
|
+
name ? inflector.underscore(inflector.demodulize(name)) : "part"
|
64
|
+
end
|
27
65
|
|
66
|
+
# Returns a new Part instance
|
67
|
+
#
|
68
|
+
# @param name [Symbol] part name
|
69
|
+
# @param value [Object] the value to decorate
|
70
|
+
# @param render_env [RenderEnvironment] render environment
|
71
|
+
#
|
28
72
|
# @api public
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
73
|
+
def initialize(
|
74
|
+
render_env: RenderEnvironmentMissing.new,
|
75
|
+
name: self.class.part_name(render_env.inflector),
|
76
|
+
value:
|
77
|
+
)
|
78
|
+
@_name = name
|
79
|
+
@_value = value
|
80
|
+
@_render_env = render_env
|
33
81
|
end
|
34
82
|
|
35
|
-
#
|
36
|
-
|
37
|
-
|
83
|
+
# The template format for the current render environment.
|
84
|
+
#
|
85
|
+
# @overload _format
|
86
|
+
# Returns the format.
|
87
|
+
# @overload format
|
88
|
+
# A convenience alias for `#_format.` Is available unless the value
|
89
|
+
# itself responds to `#format`.
|
90
|
+
#
|
91
|
+
# @return [Symbol] format
|
92
|
+
#
|
93
|
+
# @api public
|
94
|
+
def _format
|
95
|
+
_render_env.format
|
38
96
|
end
|
39
97
|
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
98
|
+
# The context object for the current render environment
|
99
|
+
#
|
100
|
+
# @overload _context
|
101
|
+
# Returns the context.
|
102
|
+
# @overload context
|
103
|
+
# A convenience alias for `#_context`. Is available unless the value
|
104
|
+
# itself responds to `#context`.
|
105
|
+
#
|
106
|
+
# @return [Context] context
|
107
|
+
#
|
108
|
+
# @api public
|
109
|
+
def _context
|
110
|
+
_render_env.context
|
48
111
|
end
|
49
112
|
|
113
|
+
# Renders a new partial with the part included in its locals.
|
114
|
+
#
|
115
|
+
# @overload _render(partial_name, as: _name, **locals, &block)
|
116
|
+
# Renders the partial.
|
117
|
+
# @overload render(partial_name, as: _name, **locals, &block)
|
118
|
+
# A convenience alias for `#_render`. Is available unless the value
|
119
|
+
# itself responds to `#render`.
|
120
|
+
#
|
121
|
+
# @param partial_name [Symbol, String] partial name
|
122
|
+
# @param as [Symbol] the name for the Part to assume in the partial's locals. Defaults to
|
123
|
+
# the Part's `_name`.
|
124
|
+
# @param locals [Hash<Symbol, Object>] other locals to provide the partial
|
125
|
+
#
|
126
|
+
# @return [String] rendered partial
|
127
|
+
#
|
128
|
+
# @api public
|
129
|
+
# rubocop:disable Naming/UncommunicativeMethodParamName
|
50
130
|
def _render(partial_name, as: _name, **locals, &block)
|
51
|
-
|
131
|
+
_render_env.partial(partial_name, _render_env.scope({as => self}.merge(locals)), &block)
|
132
|
+
end
|
133
|
+
# rubocop:enable Naming/UncommunicativeMethodParamName
|
134
|
+
|
135
|
+
# Builds a new scope with the part included in its locals.
|
136
|
+
#
|
137
|
+
# @overload _scope(scope_name = nil, **locals)
|
138
|
+
# Builds the scope.
|
139
|
+
# @overload scope(scope_name = nil, **locals)
|
140
|
+
# A convenience alias for `#_scope`. Is available unless the value
|
141
|
+
# itself responds to `#scope`.
|
142
|
+
#
|
143
|
+
# @param scope_name [Symbol, nil] scope name, used by the scope builder to determine the
|
144
|
+
# scope class
|
145
|
+
# @param locals [Hash<Symbol, Object>] other locals to provide the partial
|
146
|
+
#
|
147
|
+
# @return [Dry::View::Scope] scope
|
148
|
+
#
|
149
|
+
# @api public
|
150
|
+
def _scope(scope_name = nil, **locals)
|
151
|
+
_render_env.scope(scope_name, {_name => self}.merge(locals))
|
52
152
|
end
|
53
153
|
|
154
|
+
# Returns a string representation of the value
|
155
|
+
#
|
156
|
+
# @return [String]
|
157
|
+
#
|
158
|
+
# @api public
|
54
159
|
def to_s
|
55
160
|
_value.to_s
|
56
161
|
end
|
57
162
|
|
58
|
-
|
163
|
+
# Builds a new a part with the given parameters
|
164
|
+
#
|
165
|
+
# This is helpful for manually constructing a new part object that
|
166
|
+
# maintains the current render environment.
|
167
|
+
#
|
168
|
+
# However, using `.decorate` is preferred for declaring attributes that
|
169
|
+
# should also be decorated as parts.
|
170
|
+
#
|
171
|
+
# @see DecoratedAttributes::ClassInterface#decorate
|
172
|
+
#
|
173
|
+
# @param klass [Class] part class to use (defaults to the part's class)
|
174
|
+
# @param name [Symbol] part name (defaults to the part's name)
|
175
|
+
# @param value [Object] value to decorate (defaults to the part's value)
|
176
|
+
# @param options[Hash<Symbol, Object>] other options to provide when initializing the new part
|
177
|
+
#
|
178
|
+
# @api public
|
179
|
+
def new(klass = self.class, name: _name, value: _value, **options)
|
59
180
|
klass.new(
|
60
181
|
name: name,
|
61
182
|
value: value,
|
62
|
-
|
63
|
-
|
64
|
-
decorator: _decorator,
|
65
|
-
**options,
|
183
|
+
render_env: _render_env,
|
184
|
+
**options
|
66
185
|
)
|
67
186
|
end
|
68
187
|
|
188
|
+
# Returns a string representation of the part
|
189
|
+
#
|
190
|
+
# @return [String]
|
191
|
+
#
|
192
|
+
# @api public
|
193
|
+
def inspect
|
194
|
+
%(#<#{self.class.name} name=#{_name.inspect} value=#{_value.inspect}>)
|
195
|
+
end
|
196
|
+
|
69
197
|
private
|
70
198
|
|
199
|
+
# Handles missing methods. If the `_value` responds to the method, then
|
200
|
+
# the method will be sent to the value.
|
71
201
|
def method_missing(name, *args, &block)
|
72
|
-
if
|
73
|
-
_resolve_decorated_attribute(name)
|
74
|
-
elsif _value.respond_to?(name)
|
202
|
+
if _value.respond_to?(name)
|
75
203
|
_value.public_send(name, *args, &block)
|
76
204
|
elsif CONVENIENCE_METHODS.include?(name)
|
77
205
|
__send__(:"_#{name}", *args, &block)
|
@@ -79,25 +207,10 @@ module Dry
|
|
79
207
|
super
|
80
208
|
end
|
81
209
|
end
|
210
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
82
211
|
|
83
|
-
def
|
84
|
-
|
85
|
-
locals: locals.merge(name => self),
|
86
|
-
context: _context,
|
87
|
-
renderer: _renderer,
|
88
|
-
)
|
89
|
-
end
|
90
|
-
|
91
|
-
def _resolve_decorated_attribute(name)
|
92
|
-
_decorated_attributes.fetch(name) {
|
93
|
-
_decorated_attributes[name] = _decorator.(
|
94
|
-
name,
|
95
|
-
_value.__send__(name),
|
96
|
-
renderer: _renderer,
|
97
|
-
context: _context,
|
98
|
-
**self.class.decorated_attributes[name],
|
99
|
-
)
|
100
|
-
}
|
212
|
+
def respond_to_missing?(name, include_private = false)
|
213
|
+
CONVENIENCE_METHODS.include?(name) || _value.respond_to?(name, include_private) || super
|
101
214
|
end
|
102
215
|
end
|
103
216
|
end
|