actionview-component 1.4.0 → 1.6.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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -0
  3. data/Gemfile.lock +15 -4
  4. data/README.md +103 -2
  5. data/actionview-component.gemspec +1 -0
  6. data/lib/action_view/component.rb +6 -0
  7. data/lib/action_view/component/active_model_conversion_monkey_patch.rb +9 -0
  8. data/lib/action_view/component/base.rb +26 -56
  9. data/lib/action_view/component/monkey_patch.rb +29 -0
  10. data/lib/action_view/component/preview.rb +106 -0
  11. data/lib/action_view/component/railtie.rb +51 -0
  12. data/lib/action_view/component/test_helpers.rb +1 -1
  13. data/lib/action_view/component/version.rb +1 -1
  14. data/lib/rails/generators/component/USAGE +13 -0
  15. data/lib/rails/generators/component/component_generator.rb +50 -0
  16. data/lib/rails/generators/component/templates/component.html.erb.tt +5 -0
  17. data/lib/rails/generators/component/templates/component.rb.tt +9 -0
  18. data/lib/rails/generators/rspec/component_generator.rb +19 -0
  19. data/lib/rails/generators/rspec/templates/component_spec.rb.tt +13 -0
  20. data/lib/rails/generators/test_unit/component_generator.rb +20 -0
  21. data/lib/rails/generators/test_unit/templates/component_test.rb.tt +12 -0
  22. data/lib/railties/lib/rails.rb +6 -0
  23. data/lib/railties/lib/rails/component_examples_controller.rb +9 -0
  24. data/lib/railties/lib/rails/components_controller.rb +55 -0
  25. data/lib/railties/lib/rails/templates/rails/components/index.html.erb +8 -0
  26. data/lib/railties/lib/rails/templates/rails/components/preview.html.erb +1 -0
  27. data/lib/railties/lib/rails/templates/rails/components/previews.html.erb +6 -0
  28. data/lib/railties/lib/rails/templates/rails/examples/show.html.erb +1 -0
  29. metadata +36 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30f9a7b6c2e7f0549743bf979ce3db726539f9c1f9391cb4f2f34e6d955b8689
4
- data.tar.gz: 6db7efd3c77edbdacfa376d78729c8092848fc48176378a0677b50e6cd110433
3
+ metadata.gz: e0d1497b59c35721a9d9c48906ca5b1a64eb30f72e9bfd17d0da2437820e7e87
4
+ data.tar.gz: e703a27db8cac4a589f6d8f78e3ffc2bb7783af16d7c49c8986d06db7155bcd4
5
5
  SHA512:
6
- metadata.gz: bd86da9ed9d6be7b8397711cde1da635d657630b1864da03e297e87596395476d84bbafb4c84447b4518207ead5cbe2f905b6b7fc95f10b96cfe0b2bd5e9fcc5
7
- data.tar.gz: d42213b009dfbd75489be053daab29fa0c74614f8657d308a08254c72ba31327933d0683d5c4ad6df43d1cdf63b86236ef3cda9e6b23b9b883ba987c1692b719
6
+ metadata.gz: 481e35fdf095d48a1c3a33795d8e1d8b0be6a6b6944b149e5a6320add88f8748f4ea8e19282cd90bb62f9858291cd07084e9b1221051fcfb89f70b7dd6471281
7
+ data.tar.gz: 45fd087cc394799a8f21044c58de91a3aeee93dc29c8fbd8ff8c4a1979977bb86bc47d38341f51a37bbe93038c81b77908ec8d634d4d41b064a11585a684ae44
@@ -1,3 +1,75 @@
1
+ # v1.6.0
2
+
3
+ * Avoid dropping elements in the render_inline test helper.
4
+
5
+ *@dark-panda*
6
+
7
+ * Add test for helpers.asset_url.
8
+
9
+ *Christopher Coleman*
10
+
11
+ * Add rudimentary compatibility with better_html.
12
+
13
+ *Joel Hawksley*
14
+
15
+ * Template-less variants fall back to default template.
16
+
17
+ *Asger Behncke Jacobsen*, *Cesario Uy*
18
+
19
+ * Generated tests use new naming convention.
20
+
21
+ *Simon Træls Ravn*
22
+
23
+ * Eliminate sqlite dependency.
24
+
25
+ *Simon Dawson*
26
+
27
+ * Add support for rendering components via #to_component_class
28
+
29
+ *Vinicius Stock*
30
+
31
+ # v1.5.3
32
+
33
+ * Add support for RSpec to generators.
34
+
35
+ *Dylan Clark, Ryan Workman*
36
+
37
+ * Require controllers as part of setting autoload paths.
38
+
39
+ *Joel Hawksley*
40
+
41
+ # v1.5.2
42
+
43
+ * Disable eager loading initializer.
44
+
45
+ *Kasper Meyer*
46
+
47
+ # v1.5.1
48
+
49
+ * Update railties class to work with Rails 6.
50
+
51
+ *Juan Manuel Ramallo*
52
+
53
+ # v1.5.0
54
+
55
+ Note: `actionview-component` is now loaded by requiring `actionview/component`, not `actionview/component/base`.
56
+
57
+ * Fix issue with generating component method signatures.
58
+
59
+ *Ryan Workman, Dylan Clark*
60
+
61
+ * Create component generator.
62
+
63
+ *Vinicius Stock*
64
+
65
+ * Add helpers proxy.
66
+
67
+ *Kasper Meyer*
68
+
69
+ * Introduce ActionView::Component::Previews.
70
+
71
+ *Juan Manuel Ramallo*
72
+
1
73
  # v1.4.0
2
74
 
3
75
  * Fix bug where components broke in application paths with periods.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- actionview-component (1.4.0)
4
+ actionview-component (1.6.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -62,19 +62,28 @@ GEM
62
62
  tzinfo (~> 1.1)
63
63
  zeitwerk (~> 2.1, >= 2.1.8)
64
64
  ast (2.4.0)
65
+ better_html (1.0.14)
66
+ actionview (>= 4.0)
67
+ activesupport (>= 4.0)
68
+ ast (~> 2.0)
69
+ erubi (~> 1.4)
70
+ html_tokenizer (~> 0.0.6)
71
+ parser (>= 2.4)
72
+ smart_properties
65
73
  builder (3.2.3)
66
74
  concurrent-ruby (1.1.5)
67
- crass (1.0.4)
75
+ crass (1.0.5)
68
76
  erubi (1.8.0)
69
77
  globalid (0.4.2)
70
78
  activesupport (>= 4.2.0)
71
79
  haml (5.1.2)
72
80
  temple (>= 0.8.0)
73
81
  tilt
82
+ html_tokenizer (0.0.7)
74
83
  i18n (1.6.0)
75
84
  concurrent-ruby (~> 1.0)
76
85
  jaro_winkler (1.5.3)
77
- loofah (2.2.3)
86
+ loofah (2.3.1)
78
87
  crass (~> 1.0.2)
79
88
  nokogiri (>= 1.5.9)
80
89
  mail (2.7.1)
@@ -87,7 +96,7 @@ GEM
87
96
  mini_portile2 (2.4.0)
88
97
  minitest (5.1.0)
89
98
  nio4r (2.5.2)
90
- nokogiri (1.10.4)
99
+ nokogiri (1.10.5)
91
100
  mini_portile2 (~> 2.4.0)
92
101
  parallel (1.17.0)
93
102
  parser (2.6.3.0)
@@ -139,6 +148,7 @@ GEM
139
148
  slim (4.0.1)
140
149
  temple (>= 0.7.6, < 0.9)
141
150
  tilt (>= 2.0.6, < 2.1)
151
+ smart_properties (1.15.0)
142
152
  sprockets (3.7.2)
143
153
  concurrent-ruby (~> 1.0)
144
154
  rack (> 1, < 3)
@@ -163,6 +173,7 @@ PLATFORMS
163
173
 
164
174
  DEPENDENCIES
165
175
  actionview-component!
176
+ better_html (~> 1)
166
177
  bundler (>= 1.14)
167
178
  haml (~> 5)
168
179
  minitest (= 5.1.0)
data/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  This gem is meant to serve as a precursor to upstreaming the `ActionView::Component` class into Rails. It also serves to enable the usage of `ActionView::Component` in older versions of Rails.
9
9
 
10
- Preliminary support for rendering components was merged into Rails `6.1.0.alpha` in https://github.com/rails/rails/pull/36388. Assuming `ActionView::Component` makes it into Rails `6.1`, this gem will then exist to serve as a backport.
10
+ Preliminary support for rendering components was merged into Rails `6.1.0.alpha` in https://github.com/rails/rails/pull/36388. Assuming `ActionView::Component` makes it into Rails, this gem will then exist to serve as a backport.
11
11
 
12
12
  ## Design philosophy
13
13
 
@@ -32,7 +32,7 @@ $ bundle
32
32
  In `config/application.rb`, add:
33
33
 
34
34
  ```bash
35
- require "action_view/component/base"
35
+ require "action_view/component"
36
36
  ```
37
37
 
38
38
  ## Guide
@@ -97,10 +97,26 @@ Components are subclasses of `ActionView::Component::Base` and live in `app/comp
97
97
 
98
98
  Component class names end in -`Component`.
99
99
 
100
+ Component module names are plural, as they are for controllers. (`Users::AvatarComponent`)
101
+
100
102
  Components support ActiveModel validations. Components are validated after initialization, but before rendering.
101
103
 
102
104
  Content passed to an `ActionView::Component` as a block is captured and assigned to the `content` accessor.
103
105
 
106
+ #### Quick start
107
+
108
+ Use the component generator to create a new `ActionView::Component`.
109
+
110
+ The generator accepts the component name and the list of accepted properties as arguments:
111
+
112
+ ```bash
113
+ bin/rails generate component Example title content
114
+ invoke test_unit
115
+ create test/components/example_component_test.rb
116
+ create app/components/example_component.rb
117
+ create app/components/example_component.html.erb
118
+ ```
119
+
104
120
  #### Implementation
105
121
 
106
122
  An `ActionView::Component` is a Ruby file and corresponding template file (in any format supported by Rails) with the same base name:
@@ -147,6 +163,24 @@ Components can be rendered via:
147
163
 
148
164
  `render(component: TestComponent, locals: { foo: :bar })`
149
165
 
166
+ **Rendering components through models**
167
+
168
+ Passing model instances will cause `render` to look for its respective component class.
169
+
170
+ The component is instantiated with the rendered model instance.
171
+
172
+ Example for a `Post` model:
173
+
174
+ `render(@post)`
175
+
176
+ ```ruby
177
+ class PostComponent < ActionView::Component
178
+ def initialize(post)
179
+ @post = post
180
+ end
181
+ end
182
+ ```
183
+
150
184
  The following syntax has been deprecated and will be removed in v2.0.0:
151
185
 
152
186
  `render(TestComponent.new(foo: :bar))`
@@ -201,6 +235,73 @@ def test_render_component_for_tablet
201
235
  end
202
236
  ```
203
237
 
238
+ ### Previewing Components
239
+ `ActionView::Component::Preview`s provide a way to see how components look by visiting a special URL that renders them.
240
+ In the previous example, the preview class for `TestComponent` would be called `TestComponentPreview` and located in `test/components/previews/test_component_preview.rb`.
241
+ To see the preview of the component with a given title, implement a method that renders the component.
242
+ You can define as many examples as you want:
243
+
244
+ ```ruby
245
+ # test/components/previews/test_component_preview.rb
246
+
247
+ class TestComponentPreview < ActionView::Component::Preview
248
+ def with_default_title
249
+ render(TestComponent, title: "Test component default")
250
+ end
251
+
252
+ def with_long_title
253
+ render(TestComponent, title: "This is a really long title to see how the component renders this")
254
+ end
255
+ end
256
+ ```
257
+
258
+ The previews will be available in <http://localhost:3000/rails/components/test_component/with_default_title>
259
+ and <http://localhost:3000/rails/components/test_component/with_long_title>.
260
+
261
+ Previews use the application layout by default, but you can also use other layouts from your app:
262
+
263
+ ```ruby
264
+ # test/components/previews/test_component_preview.rb
265
+
266
+ class TestComponentPreview < ActionView::Component::Preview
267
+ layout "admin"
268
+
269
+ ...
270
+ end
271
+ ```
272
+
273
+ By default, the preview classes live in `test/components/previews`.
274
+ This can be configured using the `preview_path` option.
275
+ For example, if you want to use `lib/component_previews`, set the following in `config/application.rb`:
276
+
277
+ ```ruby
278
+ config.action_view_component.preview_path = "#{Rails.root}/lib/component_previews"
279
+ ```
280
+
281
+ ### Setting up RSpec
282
+
283
+ If you're using RSpec, you can configure component specs to have access to test helpers. Add the following to
284
+ `spec/rails_helper.rb`:
285
+
286
+ ```ruby
287
+ require "action_view/component/test_helpers"
288
+
289
+ RSpec.configure do |config|
290
+ # ...
291
+
292
+ # Ensure that the test helpers are available in component specs
293
+ config.include ActionView::Component::TestHelpers, type: :component
294
+ end
295
+ ```
296
+
297
+ Specs created by the generator should now have access to test helpers like `render_inline`.
298
+
299
+ To use component previews, set the following in `config/application.rb`:
300
+
301
+ ```ruby
302
+ config.action_view_component.preview_path = "#{Rails.root}/spec/components/previews"
303
+ ```
304
+
204
305
  ## Frequently Asked Questions
205
306
 
206
307
  ### Can I use other templating languages besides ERB?
@@ -39,6 +39,7 @@ Gem::Specification.new do |spec|
39
39
  spec.add_development_dependency "minitest", "= 5.1.0"
40
40
  spec.add_development_dependency "haml", "~> 5"
41
41
  spec.add_development_dependency "slim", "~> 4.0"
42
+ spec.add_development_dependency "better_html", "~> 1"
42
43
  spec.add_development_dependency "rubocop", "= 0.74"
43
44
  spec.add_development_dependency "rubocop-github", "~> 0.13.0"
44
45
  end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/component/monkey_patch"
4
+ require "action_view/component/active_model_conversion_monkey_patch"
5
+ require "action_view/component/base"
6
+ require "action_view/component/railtie"
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Conversion
5
+ def to_component_class
6
+ "#{self.class.name}Component".safe_constantize
7
+ end
8
+ end
9
+ end
@@ -1,39 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Monkey patch ActionView::Base#render to support ActionView::Component
4
- #
5
- # A version of this monkey patch was upstreamed in https://github.com/rails/rails/pull/36388
6
- # We'll need to upstream an updated version of this eventually.
7
- class ActionView::Base
8
- module RenderMonkeyPatch
9
- def render(options = {}, args = {}, &block)
10
- if options.respond_to?(:render_in)
11
- ActiveSupport::Deprecation.warn(
12
- "passing component instances (`render MyComponent.new(foo: :bar)`) has been deprecated and will be removed in v2.0.0. Use `render MyComponent, foo: :bar` instead."
13
- )
14
-
15
- options.render_in(self, &block)
16
- elsif options.is_a?(Class) && options < ActionView::Component::Base
17
- options.new(args).render_in(self, &block)
18
- elsif options.is_a?(Hash) && options.has_key?(:component)
19
- options[:component].new(options[:locals]).render_in(self, &block)
20
- else
21
- super
22
- end
23
- end
24
- end
25
-
26
- prepend RenderMonkeyPatch
27
- end
3
+ require "active_model"
4
+ require "action_view"
5
+ require "active_support/configurable"
6
+ require_relative "preview"
28
7
 
29
8
  module ActionView
30
9
  module Component
31
10
  class Base < ActionView::Base
32
11
  include ActiveModel::Validations
33
12
  include ActiveSupport::Configurable
34
- include ActionController::RequestForgeryProtection
13
+ include ActionView::Component::Previews
35
14
 
36
- validate :variant_exists
15
+ delegate :form_authenticity_token, :protect_against_forgery?, to: :helpers
37
16
 
38
17
  # Entrypoint for rendering components. Called by ActionView::Base#render.
39
18
  #
@@ -93,10 +72,14 @@ module ActionView
93
72
  @controller ||= view_context.controller
94
73
  end
95
74
 
96
- # Looks for the source file path of the initialize method of the instance's class.
75
+ # Provides a proxy to access helper methods through
76
+ def helpers
77
+ @helpers ||= view_context
78
+ end
79
+
97
80
  # Removes the first part of the path and the extension.
98
81
  def virtual_path
99
- self.class.source_location.gsub(%r{(.*app/)|(\.rb)}, "")
82
+ self.class.source_location.gsub(%r{(.*app/components)|(\.rb)}, "")
100
83
  end
101
84
 
102
85
  def view_cache_dependencies
@@ -109,12 +92,6 @@ module ActionView
109
92
 
110
93
  private
111
94
 
112
- def variant_exists
113
- return if self.class.variants.include?(@variant) || @variant.nil?
114
-
115
- errors.add(:variant, "'#{@variant}' has no template defined")
116
- end
117
-
118
95
  def request
119
96
  @request ||= controller.request
120
97
  end
@@ -129,7 +106,7 @@ module ActionView
129
106
  end
130
107
 
131
108
  def call_method_name(variant)
132
- if variant.present?
109
+ if variant.present? && variants.include?(variant)
133
110
  "call_#{variant}"
134
111
  else
135
112
  "call"
@@ -173,6 +150,15 @@ module ActionView
173
150
  templates.map { |template| template[:variant] }
174
151
  end
175
152
 
153
+ # we'll eventually want to update this to support other types
154
+ def type
155
+ "text/html"
156
+ end
157
+
158
+ def identifier
159
+ source_location
160
+ end
161
+
176
162
  private
177
163
 
178
164
  def templates
@@ -206,31 +192,15 @@ module ActionView
206
192
  handler = ActionView::Template.handler_for_extension(File.extname(file_path).gsub(".", ""))
207
193
  template = File.read(file_path)
208
194
 
209
- # This can be removed once this code is merged into Rails
210
195
  if handler.method(:call).parameters.length > 1
211
- handler.call(DummyTemplate.new, template)
212
- else
213
- handler.call(DummyTemplate.new(template))
196
+ handler.call(self, template)
197
+ else # remove before upstreaming into Rails
198
+ handler.call(OpenStruct.new(source: template, identifier: identifier, type: type))
214
199
  end
215
200
  end
216
201
  end
217
202
 
218
- class DummyTemplate
219
- attr_reader :source
220
-
221
- def initialize(source = nil)
222
- @source = source
223
- end
224
-
225
- def identifier
226
- ""
227
- end
228
-
229
- # we'll eventually want to update this to support other types
230
- def type
231
- "text/html"
232
- end
233
- end
203
+ ActiveSupport.run_load_hooks(:action_view_component, self)
234
204
  end
235
205
  end
236
206
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Monkey patch ActionView::Base#render to support ActionView::Component
4
+ #
5
+ # A version of this monkey patch was upstreamed in https://github.com/rails/rails/pull/36388
6
+ # We'll need to upstream an updated version of this eventually.
7
+ class ActionView::Base
8
+ module RenderMonkeyPatch
9
+ def render(options = {}, args = {}, &block)
10
+ if options.respond_to?(:render_in)
11
+ ActiveSupport::Deprecation.warn(
12
+ "passing component instances (`render MyComponent.new(foo: :bar)`) has been deprecated and will be removed in v2.0.0. Use `render MyComponent, foo: :bar` instead."
13
+ )
14
+
15
+ options.render_in(self, &block)
16
+ elsif options.is_a?(Class) && options < ActionView::Component::Base
17
+ options.new(args).render_in(self, &block)
18
+ elsif options.is_a?(Hash) && options.has_key?(:component)
19
+ options[:component].new(options[:locals]).render_in(self, &block)
20
+ elsif options.respond_to?(:to_component_class) && !options.to_component_class.nil?
21
+ options.to_component_class.new(options).render_in(self, &block)
22
+ else
23
+ super
24
+ end
25
+ end
26
+ end
27
+
28
+ prepend RenderMonkeyPatch
29
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+ require "active_support/descendants_tracker"
5
+ require_relative "test_helpers"
6
+
7
+ module ActionView
8
+ module Component #:nodoc:
9
+ module Previews
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ # Set the location of component previews through app configuration:
14
+ #
15
+ # config.action_view_component.preview_path = "#{Rails.root}/lib/component_previews"
16
+ #
17
+ mattr_accessor :preview_path, instance_writer: false
18
+
19
+ # Enable or disable component previews through app configuration:
20
+ #
21
+ # config.action_view_component.show_previews = true
22
+ #
23
+ # Defaults to +true+ for development environment
24
+ #
25
+ mattr_accessor :show_previews, instance_writer: false
26
+ end
27
+ end
28
+
29
+ class Preview
30
+ extend ActiveSupport::DescendantsTracker
31
+ include ActionView::Component::TestHelpers
32
+
33
+ def render(component, *locals)
34
+ render_inline(component, *locals)
35
+ end
36
+
37
+ class << self
38
+ # Returns all component preview classes.
39
+ def all
40
+ load_previews if descendants.empty?
41
+ descendants
42
+ end
43
+
44
+ # Returns the html of the component in its layout
45
+ def call(example)
46
+ example_html = new.public_send(example)
47
+
48
+ Rails::ComponentExamplesController.render(template: "examples/show",
49
+ layout: @layout || "layouts/application",
50
+ assigns: { example: example_html })
51
+ end
52
+
53
+ # Returns the component object class associated to the preview.
54
+ def component
55
+ self.name.sub(%r{Preview$}, "").constantize
56
+ end
57
+
58
+ # Returns all of the available examples for the component preview.
59
+ def examples
60
+ public_instance_methods(false).map(&:to_s).sort
61
+ end
62
+
63
+ # Returns +true+ if the example of the component preview exists.
64
+ def example_exists?(example)
65
+ examples.include?(example)
66
+ end
67
+
68
+ # Returns +true+ if the preview exists.
69
+ def exists?(preview)
70
+ all.any? { |p| p.preview_name == preview }
71
+ end
72
+
73
+ # Find a component preview by its underscored class name.
74
+ def find(preview)
75
+ all.find { |p| p.preview_name == preview }
76
+ end
77
+
78
+ # Returns the underscored name of the component preview without the suffix.
79
+ def preview_name
80
+ name.sub(/Preview$/, "").underscore
81
+ end
82
+
83
+ # Setter for layout name.
84
+ def layout(layout_name)
85
+ @layout = layout_name
86
+ end
87
+
88
+ private
89
+
90
+ def load_previews
91
+ if preview_path
92
+ Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file }
93
+ end
94
+ end
95
+
96
+ def preview_path
97
+ Base.preview_path
98
+ end
99
+
100
+ def show_previews
101
+ Base.show_previews
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Component
5
+ class Railtie < Rails::Railtie # :nodoc:
6
+ config.action_view_component = ActiveSupport::OrderedOptions.new
7
+
8
+ initializer "action_view_component.set_configs" do |app|
9
+ options = app.config.action_view_component
10
+
11
+ options.show_previews = Rails.env.development? if options.show_previews.nil?
12
+
13
+ if options.show_previews
14
+ options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/components/previews" : nil
15
+ end
16
+
17
+ ActiveSupport.on_load(:action_view_component) do
18
+ options.each { |k, v| send("#{k}=", v) }
19
+ end
20
+ end
21
+
22
+ initializer "action_view_component.set_autoload_paths" do |app|
23
+ require "railties/lib/rails/components_controller"
24
+ require "railties/lib/rails/component_examples_controller"
25
+
26
+ options = app.config.action_view_component
27
+
28
+ if options.show_previews && options.preview_path
29
+ ActiveSupport::Dependencies.autoload_paths << options.preview_path
30
+ end
31
+ end
32
+
33
+ initializer "action_view_component.compile_config_methods" do
34
+ ActiveSupport.on_load(:action_view_component) do
35
+ config.compile_methods! if config.respond_to?(:compile_methods!)
36
+ end
37
+ end
38
+
39
+ config.after_initialize do |app|
40
+ options = app.config.action_view_component
41
+
42
+ if options.show_previews
43
+ app.routes.prepend do
44
+ get "/rails/components" => "rails/components#index", :internal => true
45
+ get "/rails/components/*path" => "rails/components#previews", :internal => true
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -4,7 +4,7 @@ module ActionView
4
4
  module Component
5
5
  module TestHelpers
6
6
  def render_inline(component, **args, &block)
7
- Nokogiri::HTML(controller.view_context.render(component, args, &block)).css("body > *")
7
+ Nokogiri::HTML.fragment(controller.view_context.render(component, args, &block))
8
8
  end
9
9
 
10
10
  def controller
@@ -4,7 +4,7 @@ module ActionView
4
4
  module Component
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 4
7
+ MINOR = 6
8
8
  PATCH = 0
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -0,0 +1,13 @@
1
+ Description:
2
+ ============
3
+ Creates a new component and test.
4
+ Pass the component name, either CamelCased or under_scored, and an optional list of attributes as arguments.
5
+
6
+ Example:
7
+ ========
8
+ bin/rails generate component Profile name age
9
+
10
+ creates a Profile component and test:
11
+ Component: app/components/profile_component.rb
12
+ Template: app/components/profile_component.html.erb
13
+ Test: test/components/profile_component_test.rb
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Generators
5
+ class ComponentGenerator < Rails::Generators::NamedBase
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ argument :attributes, type: :array, default: [], banner: "attribute"
9
+ hook_for :test_framework
10
+ check_class_collision suffix: "Component"
11
+
12
+ def create_component_file
13
+ template "component.rb", File.join("app/components", "#{file_name}_component.rb")
14
+ end
15
+
16
+ def create_template_file
17
+ template "component.html.erb", File.join("app/components", "#{file_name}_component.html.erb")
18
+ end
19
+
20
+ private
21
+
22
+ def file_name
23
+ @_file_name ||= super.sub(/_component\z/i, "")
24
+ end
25
+
26
+ def requires_content?
27
+ return @requires_content if @asked
28
+
29
+ @asked = true
30
+ @requires_content = ask("Would you like #{class_name} to require content? (Y/n)").downcase == "y"
31
+ end
32
+
33
+ def parent_class
34
+ defined?(ApplicationComponent) ? "ApplicationComponent" : "ActionView::Component::Base"
35
+ end
36
+
37
+ def initialize_signature
38
+ if attributes.present?
39
+ attributes.map { |attr| "#{attr.name}:" }.join(", ")
40
+ else
41
+ "*"
42
+ end
43
+ end
44
+
45
+ def initialize_body
46
+ attributes.map { |attr| "@#{attr.name} = #{attr.name}" }.join("\n ")
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ <%- if requires_content? -%>
2
+ <%= "<%= content %%>" %>
3
+ <%- else -%>
4
+ <div>Add <%= class_name %> template here</div>
5
+ <%- end -%>
@@ -0,0 +1,9 @@
1
+ class <%= class_name %>Component < <%= parent_class %>
2
+ <%- if requires_content? -%>
3
+ validates :content, presence: true
4
+ <%- end -%>
5
+
6
+ def initialize(<%= initialize_signature %>)
7
+ <%= initialize_body %>
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rspec
4
+ module Generators
5
+ class ComponentGenerator < ::Rails::Generators::NamedBase
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ def create_test_file
9
+ template "component_spec.rb", File.join("spec/components", "#{file_name}_component_spec.rb")
10
+ end
11
+
12
+ private
13
+
14
+ def file_name
15
+ @_file_name ||= super.sub(/_component\z/i, "")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ require "rails_helper"
2
+
3
+ RSpec.describe <%= class_name %>Component, type: :component do
4
+ pending "add some examples to (or delete) #{__FILE__}"
5
+
6
+ # it "renders something useful" do
7
+ # expect(
8
+ # render_inline(described_class, attr: "value") { "Hello, components!" }.css("p").to_html
9
+ # ).to include(
10
+ # "Hello, components!"
11
+ # )
12
+ # end
13
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TestUnit
4
+ module Generators
5
+ class ComponentGenerator < ::Rails::Generators::NamedBase
6
+ source_root File.expand_path("templates", __dir__)
7
+ check_class_collision suffix: "ComponentTest"
8
+
9
+ def create_test_file
10
+ template "component_test.rb", File.join("test/components", "#{file_name}_component_test.rb")
11
+ end
12
+
13
+ private
14
+
15
+ def file_name
16
+ @_file_name ||= super.sub(/_component\z/i, "")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ require "test_helper"
2
+
3
+ class <%= class_name %>ComponentTest < ActiveSupport::TestCase
4
+ include ActionView::Component::TestHelpers
5
+
6
+ test "component renders something useful" do
7
+ # assert_equal(
8
+ # %(<span title="my title">Hello, components!</span>),
9
+ # render_inline(<%= class_name %>Component, attr: "value") { "Hello, components!" }.css("span").to_html
10
+ # )
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ autoload :ComponentsController
5
+ autoload :ComponentExamplesController
6
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/application_controller"
4
+ load "config/application.rb" unless Rails.root
5
+
6
+ class Rails::ComponentExamplesController < ActionController::Base # :nodoc:
7
+ prepend_view_path File.expand_path("templates/rails", __dir__)
8
+ append_view_path Rails.root.join("app/views")
9
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/application_controller"
4
+
5
+ class Rails::ComponentsController < Rails::ApplicationController # :nodoc:
6
+ prepend_view_path File.expand_path("templates/rails", __dir__)
7
+
8
+ around_action :set_locale, only: :previews
9
+ before_action :find_preview, only: :previews
10
+ before_action :require_local!, unless: :show_previews?
11
+
12
+ if respond_to?(:content_security_policy)
13
+ content_security_policy(false)
14
+ end
15
+
16
+ def index
17
+ @previews = ActionView::Component::Preview.all
18
+ @page_title = "Component Previews"
19
+ render template: "components/index"
20
+ end
21
+
22
+ def previews
23
+ if params[:path] == @preview.preview_name
24
+ @page_title = "Component Previews for #{@preview.preview_name}"
25
+ render template: "components/previews"
26
+ else
27
+ @example_name = File.basename(params[:path])
28
+ render template: "components/preview", layout: false
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def show_previews? # :doc:
35
+ ActionView::Component::Base.show_previews
36
+ end
37
+
38
+ def find_preview # :doc:
39
+ candidates = []
40
+ params[:path].to_s.scan(%r{/|$}) { candidates << $` }
41
+ preview = candidates.detect { |candidate| ActionView::Component::Preview.exists?(candidate) }
42
+
43
+ if preview
44
+ @preview = ActionView::Component::Preview.find(preview)
45
+ else
46
+ raise AbstractController::ActionNotFound, "Component preview '#{params[:path]}' not found"
47
+ end
48
+ end
49
+
50
+ def set_locale
51
+ I18n.with_locale(params[:locale] || I18n.default_locale) do
52
+ yield
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,8 @@
1
+ <% @previews.each do |preview| %>
2
+ <h3><%= link_to preview.preview_name.titleize, "/rails/components/#{preview.preview_name}" %></h3>
3
+ <ul>
4
+ <% preview.examples.each do |preview_example| %>
5
+ <li><%= link_to preview_example, "/rails/components/#{preview.preview_name}/#{preview_example}" %></li>
6
+ <% end %>
7
+ </ul>
8
+ <% end %>
@@ -0,0 +1 @@
1
+ <%= raw @preview.call(@example_name) %>
@@ -0,0 +1,6 @@
1
+ <h3><%= @preview.preview_name.titleize %></h3>
2
+ <ul>
3
+ <% @preview.examples.each do |example| %>
4
+ <li><%= link_to example, "/rails/components/#{@preview.preview_name}/#{example}" %></li>
5
+ <% end %>
6
+ </ul>
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.4.0
4
+ version: 1.6.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-11-07 00:00:00.000000000 Z
11
+ date: 2019-12-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '4.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: better_html
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: rubocop
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -127,9 +141,29 @@ files:
127
141
  - README.md
128
142
  - Rakefile
129
143
  - actionview-component.gemspec
144
+ - lib/action_view/component.rb
145
+ - lib/action_view/component/active_model_conversion_monkey_patch.rb
130
146
  - lib/action_view/component/base.rb
147
+ - lib/action_view/component/monkey_patch.rb
148
+ - lib/action_view/component/preview.rb
149
+ - lib/action_view/component/railtie.rb
131
150
  - lib/action_view/component/test_helpers.rb
132
151
  - lib/action_view/component/version.rb
152
+ - lib/rails/generators/component/USAGE
153
+ - lib/rails/generators/component/component_generator.rb
154
+ - lib/rails/generators/component/templates/component.html.erb.tt
155
+ - lib/rails/generators/component/templates/component.rb.tt
156
+ - lib/rails/generators/rspec/component_generator.rb
157
+ - lib/rails/generators/rspec/templates/component_spec.rb.tt
158
+ - lib/rails/generators/test_unit/component_generator.rb
159
+ - lib/rails/generators/test_unit/templates/component_test.rb.tt
160
+ - lib/railties/lib/rails.rb
161
+ - lib/railties/lib/rails/component_examples_controller.rb
162
+ - lib/railties/lib/rails/components_controller.rb
163
+ - lib/railties/lib/rails/templates/rails/components/index.html.erb
164
+ - lib/railties/lib/rails/templates/rails/components/preview.html.erb
165
+ - lib/railties/lib/rails/templates/rails/components/previews.html.erb
166
+ - lib/railties/lib/rails/templates/rails/examples/show.html.erb
133
167
  - script/bootstrap
134
168
  - script/console
135
169
  - script/install