actionview-component 1.0.1 → 1.1.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: c48fe5574dd67f62c3552735fc62e4dc324fe21b604e52b7d0bf1b20c0f5a041
4
- data.tar.gz: e91a31acd0dc6df545ddfb8c0f58beabc401e55937b850830eeac204cecfa66f
3
+ metadata.gz: 59f02a9bdd999da01d26a2f7a9c80d0ec6cd1cf96d0195f8d2aa983f504d9908
4
+ data.tar.gz: ea36b326f6c565cf64acc827449dc11372a2bf10c49a77b5e994d9597234e926
5
5
  SHA512:
6
- metadata.gz: 359d329d3126da4f07b655da1f057a2bf9920534039fa7767d2f8d208bd7e535cd6de6fda3ef0a63c5976f6a4c81aabc2515db121ea3b6370572b3543adc3c41
7
- data.tar.gz: c141f0bd9c595bf6bdb7079b91dd193af8483d5c74fd29ab4ab94cc9b444ea76a1f19ae625d1be3395c19bade5f39b650d03db318539b3eea13a97aa989863ca
6
+ metadata.gz: e87095b78618384e60884166c0a6eb9d024c94afe0f5e4d44b1295dd5fbdf8a84000e304d8c4b56e51e8c6112f6416718fe04667b9e13bca4b2a5283c6346b49
7
+ data.tar.gz: '08dc5a1408d730948c08239a90edea25cfde9cc144a7e4b24ecfd4ee6817f7a49c19df7acec9fdfd58b26545c8fe182080dc436f3ac9aa14b17479365a40166b'
@@ -0,0 +1,7 @@
1
+ * Components now inherit from ActionView::Component::base
2
+
3
+ *Joel Hawksley*
4
+
5
+ * Always recompile component templates outside production.
6
+
7
+ *Joel Hawksley, John Hawthorn*
@@ -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 `action_view/component`.
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/component_test_helpers"
157
+ require "action_view/component/test_helpers"
156
158
 
157
159
  class MyComponentTest < Minitest::Test
158
- include ActionView::ComponentTestHelpers
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.1"
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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Component
5
+ module TestHelpers
6
+ def render_component(component, &block)
7
+ Nokogiri::HTML(component.render_in(ApplicationController.new.view_context, &block))
8
+ end
9
+ end
10
+ end
11
+ 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.1
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-23 00:00:00.000000000 Z
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/component_test_helpers.rb
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
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionView
4
- module ComponentTestHelpers
5
- def render_component(component, &block)
6
- Nokogiri::HTML(component.render_in(ApplicationController.new.view_context, &block))
7
- end
8
- end
9
- end