actionview-component 1.0.1 → 1.1.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 +4 -4
- data/CHANGELOG.md +7 -0
- data/CONTRIBUTING.md +1 -1
- data/README.md +7 -5
- data/actionview-component.gemspec +1 -1
- data/lib/action_view/component/base.rb +132 -0
- data/lib/action_view/component/test_helpers.rb +11 -0
- metadata +5 -4
- data/lib/action_view/component.rb +0 -132
- data/lib/action_view/component_test_helpers.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 59f02a9bdd999da01d26a2f7a9c80d0ec6cd1cf96d0195f8d2aa983f504d9908
|
4
|
+
data.tar.gz: ea36b326f6c565cf64acc827449dc11372a2bf10c49a77b5e994d9597234e926
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e87095b78618384e60884166c0a6eb9d024c94afe0f5e4d44b1295dd5fbdf8a84000e304d8c4b56e51e8c6112f6416718fe04667b9e13bca4b2a5283c6346b49
|
7
|
+
data.tar.gz: '08dc5a1408d730948c08239a90edea25cfde9cc144a7e4b24ecfd4ee6817f7a49c19df7acec9fdfd58b26545c8fe182080dc436f3ac9aa14b17479365a40166b'
|
data/CHANGELOG.md
ADDED
data/CONTRIBUTING.md
CHANGED
@@ -39,7 +39,7 @@ If you are the current maintainer of this gem:
|
|
39
39
|
1. Bump the Gemfile and Gemfile.lock versions for an app which relies on this gem
|
40
40
|
1. Install the new gem locally
|
41
41
|
1. Test behavior locally, branch deploy, whatever needs to happen
|
42
|
-
1. Bump gem version in `
|
42
|
+
1. Bump gem version in `actionview-component.gemspec`.
|
43
43
|
1. Make a PR to github/actionview-component.
|
44
44
|
1. Build a local gem: `gem build actionview-component.gemspec`
|
45
45
|
1. Merge github/actionview-component PR
|
data/README.md
CHANGED
@@ -28,7 +28,7 @@ $ bundle
|
|
28
28
|
In `config/application.rb`, add:
|
29
29
|
|
30
30
|
```bash
|
31
|
-
require "action_view/component"
|
31
|
+
require "action_view/component/base"
|
32
32
|
```
|
33
33
|
|
34
34
|
## Guide
|
@@ -89,7 +89,9 @@ Components are most effective in cases where view code is reused or needs to be
|
|
89
89
|
|
90
90
|
### Building components
|
91
91
|
|
92
|
-
Components are subclasses of `ActionView::Component` and live in `app/components`. You may wish to create an `ApplicationComponent` that is a subclass of `ActionView::Component` and inherit from that instead.
|
92
|
+
Components are subclasses of `ActionView::Component::Base` and live in `app/components`. You may wish to create an `ApplicationComponent` that is a subclass of `ActionView::Component::Base` and inherit from that instead.
|
93
|
+
|
94
|
+
Component class names end in -`Component`.
|
93
95
|
|
94
96
|
Components support ActiveModel validations. Components are validated after initialization, but before rendering.
|
95
97
|
|
@@ -101,7 +103,7 @@ An `ActionView::Component` is a Ruby file and corresponding template file (in an
|
|
101
103
|
|
102
104
|
`app/components/test_component.rb`:
|
103
105
|
```ruby
|
104
|
-
class TestComponent < ActionView::Component
|
106
|
+
class TestComponent < ActionView::Component::Base
|
105
107
|
validates :content, :title, presence: true
|
106
108
|
|
107
109
|
def initialize(title:)
|
@@ -152,10 +154,10 @@ An error will be raised:
|
|
152
154
|
Components are unit tested directly. The `render_component` test helper renders a component and wraps the result in `Nokogiri.HTML`, allowing us to test the component above as:
|
153
155
|
|
154
156
|
```ruby
|
155
|
-
require "action_view/
|
157
|
+
require "action_view/component/test_helpers"
|
156
158
|
|
157
159
|
class MyComponentTest < Minitest::Test
|
158
|
-
include ActionView::
|
160
|
+
include ActionView::Component::TestHelpers
|
159
161
|
|
160
162
|
def test_render_component
|
161
163
|
assert_equal(
|
@@ -6,7 +6,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
6
6
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
8
|
spec.name = "actionview-component"
|
9
|
-
spec.version = "1.0
|
9
|
+
spec.version = "1.1.0"
|
10
10
|
spec.authors = ["GitHub Open Source"]
|
11
11
|
spec.email = ["opensource+actionview-component@github.com"]
|
12
12
|
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Monkey patch ActionView::Base#render to support ActionView::Component
|
4
|
+
#
|
5
|
+
# Upstreamed in https://github.com/rails/rails/pull/36388
|
6
|
+
# Necessary for Rails versions < 6.1.0.alpha
|
7
|
+
class ActionView::Base
|
8
|
+
module RenderMonkeyPatch
|
9
|
+
def render(component, _ = nil, &block)
|
10
|
+
return super unless component.respond_to?(:render_in)
|
11
|
+
|
12
|
+
component.render_in(self, &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
prepend RenderMonkeyPatch unless Rails::VERSION::MINOR > 0 && Rails::VERSION::MAJOR == 6
|
17
|
+
end
|
18
|
+
|
19
|
+
module ActionView
|
20
|
+
module Component
|
21
|
+
class Base < ActionView::Base
|
22
|
+
include ActiveModel::Validations
|
23
|
+
|
24
|
+
# Entrypoint for rendering components. Called by ActionView::Base#render.
|
25
|
+
#
|
26
|
+
# view_context: ActionView context from calling view
|
27
|
+
# args(hash): params to be passed to component being rendered
|
28
|
+
# block: optional block to be captured within the view context
|
29
|
+
#
|
30
|
+
# returns HTML that has been escaped by the respective template handler
|
31
|
+
#
|
32
|
+
# Example subclass:
|
33
|
+
#
|
34
|
+
# app/components/my_component.rb:
|
35
|
+
# class MyComponent < ActionView::Component::Base
|
36
|
+
# def initialize(title:)
|
37
|
+
# @title = title
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# app/components/my_component.html.erb
|
42
|
+
# <span title="<%= @title %>">Hello, <%= content %>!</span>
|
43
|
+
#
|
44
|
+
# In use:
|
45
|
+
# <%= render MyComponent.new(title: "greeting") do %>world<% end %>
|
46
|
+
# returns:
|
47
|
+
# <span title="greeting">Hello, world!</span>
|
48
|
+
#
|
49
|
+
def render_in(view_context, *args, &block)
|
50
|
+
self.class.compile
|
51
|
+
@content = view_context.capture(&block) if block_given?
|
52
|
+
validate!
|
53
|
+
call
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(*); end
|
57
|
+
|
58
|
+
class << self
|
59
|
+
def inherited(child)
|
60
|
+
child.include Rails.application.routes.url_helpers unless child < Rails.application.routes.url_helpers
|
61
|
+
|
62
|
+
super
|
63
|
+
end
|
64
|
+
|
65
|
+
# Compile template to #call instance method, assuming it hasn't been compiled already.
|
66
|
+
# We could in theory do this on app boot, at least in production environments.
|
67
|
+
# Right now this just compiles the template the first time the component is rendered.
|
68
|
+
def compile
|
69
|
+
return if @compiled && ActionView::Base.cache_template_loading
|
70
|
+
|
71
|
+
class_eval("def call; @output_buffer = ActionView::OutputBuffer.new; #{compiled_template}; end")
|
72
|
+
|
73
|
+
@compiled = true
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def compiled_template
|
79
|
+
handler = ActionView::Template.handler_for_extension(File.extname(template_file_path).gsub(".", ""))
|
80
|
+
template = File.read(template_file_path)
|
81
|
+
|
82
|
+
if handler.method(:call).parameters.length > 1
|
83
|
+
handler.call(DummyTemplate.new, template)
|
84
|
+
else
|
85
|
+
handler.call(DummyTemplate.new(template))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def template_file_path
|
90
|
+
raise NotImplementedError.new("#{self} must implement #initialize.") unless self.instance_method(:initialize).owner == self
|
91
|
+
|
92
|
+
filename = self.instance_method(:initialize).source_location[0]
|
93
|
+
filename_without_extension = filename[0..-(File.extname(filename).length + 1)]
|
94
|
+
sibling_files = Dir["#{filename_without_extension}.*"] - [filename]
|
95
|
+
|
96
|
+
if sibling_files.length > 1
|
97
|
+
raise StandardError.new("More than one template found for #{self}. There can only be one sidecar template file per component.")
|
98
|
+
end
|
99
|
+
|
100
|
+
if sibling_files.length == 0
|
101
|
+
raise NotImplementedError.new(
|
102
|
+
"Could not find a template file for #{self}."
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
sibling_files[0]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class DummyTemplate
|
111
|
+
attr_reader :source
|
112
|
+
|
113
|
+
def initialize(source = nil)
|
114
|
+
@source = source
|
115
|
+
end
|
116
|
+
|
117
|
+
def identifier
|
118
|
+
""
|
119
|
+
end
|
120
|
+
|
121
|
+
# we'll eventually want to update this to support other types
|
122
|
+
def type
|
123
|
+
"text/html"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
attr_reader :content
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: actionview-component
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Open Source
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-08-
|
11
|
+
date: 2019-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -118,6 +118,7 @@ files:
|
|
118
118
|
- ".github/workflows/ruby_on_rails.yml"
|
119
119
|
- ".gitignore"
|
120
120
|
- ".rubocop.yml"
|
121
|
+
- CHANGELOG.md
|
121
122
|
- CODE_OF_CONDUCT.md
|
122
123
|
- CONTRIBUTING.md
|
123
124
|
- Gemfile
|
@@ -126,8 +127,8 @@ files:
|
|
126
127
|
- README.md
|
127
128
|
- Rakefile
|
128
129
|
- actionview-component.gemspec
|
129
|
-
- lib/action_view/component.rb
|
130
|
-
- lib/action_view/
|
130
|
+
- lib/action_view/component/base.rb
|
131
|
+
- lib/action_view/component/test_helpers.rb
|
131
132
|
- script/bootstrap
|
132
133
|
- script/console
|
133
134
|
- script/install
|
@@ -1,132 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Monkey patch ActionView::Base#render to support ActionView::Component
|
4
|
-
#
|
5
|
-
# Upstreamed in https://github.com/rails/rails/pull/36388
|
6
|
-
# Necessary for Rails versions < 6.1.0.alpha
|
7
|
-
class ActionView::Base
|
8
|
-
module RenderMonkeyPatch
|
9
|
-
def render(component, _ = nil, &block)
|
10
|
-
return super unless component.respond_to?(:render_in)
|
11
|
-
|
12
|
-
component.render_in(self, &block)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
prepend RenderMonkeyPatch unless Rails::VERSION::MINOR > 0 && Rails::VERSION::MAJOR == 6
|
17
|
-
end
|
18
|
-
|
19
|
-
module ActionView
|
20
|
-
class Component < ActionView::Base
|
21
|
-
VERSION = "1.0.0"
|
22
|
-
|
23
|
-
include ActiveModel::Validations
|
24
|
-
|
25
|
-
# Entrypoint for rendering components. Called by ActionView::Base#render.
|
26
|
-
#
|
27
|
-
# view_context: ActionView context from calling view
|
28
|
-
# args(hash): params to be passed to component being rendered
|
29
|
-
# block: optional block to be captured within the view context
|
30
|
-
#
|
31
|
-
# returns HTML that has been escaped by the respective template handler
|
32
|
-
#
|
33
|
-
# Example subclass:
|
34
|
-
#
|
35
|
-
# app/components/my_component.rb:
|
36
|
-
# class MyComponent < ActionView::Component
|
37
|
-
# def initialize(title:)
|
38
|
-
# @title = title
|
39
|
-
# end
|
40
|
-
# end
|
41
|
-
#
|
42
|
-
# app/components/my_component.html.erb
|
43
|
-
# <span title="<%= @title %>">Hello, <%= content %>!</span>
|
44
|
-
#
|
45
|
-
# In use:
|
46
|
-
# <%= render MyComponent.new(title: "greeting") do %>world<% end %>
|
47
|
-
# returns:
|
48
|
-
# <span title="greeting">Hello, world!</span>
|
49
|
-
#
|
50
|
-
def render_in(view_context, *args, &block)
|
51
|
-
self.class.compile
|
52
|
-
@content = view_context.capture(&block) if block_given?
|
53
|
-
validate!
|
54
|
-
call
|
55
|
-
end
|
56
|
-
|
57
|
-
def initialize(*); end
|
58
|
-
|
59
|
-
class << self
|
60
|
-
def inherited(child)
|
61
|
-
child.include Rails.application.routes.url_helpers unless child < Rails.application.routes.url_helpers
|
62
|
-
|
63
|
-
super
|
64
|
-
end
|
65
|
-
|
66
|
-
# Compile template to #call instance method, assuming it hasn't been compiled already.
|
67
|
-
# We could in theory do this on app boot, at least in production environments.
|
68
|
-
# Right now this just compiles the template the first time the component is rendered.
|
69
|
-
def compile
|
70
|
-
return if @compiled && ActionView::Base.cache_template_loading
|
71
|
-
|
72
|
-
class_eval("def call; @output_buffer = ActionView::OutputBuffer.new; #{compiled_template}; end")
|
73
|
-
|
74
|
-
@compiled = true
|
75
|
-
end
|
76
|
-
|
77
|
-
private
|
78
|
-
|
79
|
-
def compiled_template
|
80
|
-
handler = ActionView::Template.handler_for_extension(File.extname(template_file_path).gsub(".", ""))
|
81
|
-
template = File.read(template_file_path)
|
82
|
-
|
83
|
-
if handler.method(:call).parameters.length > 1
|
84
|
-
handler.call(DummyTemplate.new, template)
|
85
|
-
else
|
86
|
-
handler.call(DummyTemplate.new(template))
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def template_file_path
|
91
|
-
raise NotImplementedError.new("#{self} must implement #initialize.") unless self.instance_method(:initialize).owner == self
|
92
|
-
|
93
|
-
filename = self.instance_method(:initialize).source_location[0]
|
94
|
-
filename_without_extension = filename[0..-(File.extname(filename).length + 1)]
|
95
|
-
sibling_files = Dir["#{filename_without_extension}.*"] - [filename]
|
96
|
-
|
97
|
-
if sibling_files.length > 1
|
98
|
-
raise StandardError.new("More than one template found for #{self}. There can only be one sidecar template file per component.")
|
99
|
-
end
|
100
|
-
|
101
|
-
if sibling_files.length == 0
|
102
|
-
raise NotImplementedError.new(
|
103
|
-
"Could not find a template file for #{self}."
|
104
|
-
)
|
105
|
-
end
|
106
|
-
|
107
|
-
sibling_files[0]
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
class DummyTemplate
|
112
|
-
attr_reader :source
|
113
|
-
|
114
|
-
def initialize(source = nil)
|
115
|
-
@source = source
|
116
|
-
end
|
117
|
-
|
118
|
-
def identifier
|
119
|
-
""
|
120
|
-
end
|
121
|
-
|
122
|
-
# we'll eventually want to update this to support other types
|
123
|
-
def type
|
124
|
-
"text/html"
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
private
|
129
|
-
|
130
|
-
attr_reader :content
|
131
|
-
end
|
132
|
-
end
|