actionview-component 1.4.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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