Skip to content

Commit

Permalink
Add lookup_chain customizability
Browse files Browse the repository at this point in the history
  • Loading branch information
joelzwarrington committed May 24, 2024
1 parent c617ee4 commit a3ccc10
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 6 deletions.
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,30 @@ We'll add more use cases to the documentation soon.

### Configuration

| Attribute | Purpose | Default |
| ------------------ | ----------------------------------------------- | --------------------- |
| Attribute | Purpose | Default |
| --------------------------- | ----------------------------------------------------- | ----------------------- |
| `parent_component` (string) | Parent class for all `ViewComponent::Form` components | `"ViewComponent::Base"` |

#### Configuring component lookup

`ViewComponent::Form` will automatically infer the component class with a `Component` suffix. You can customize the lookup using the `lookup_chain`:

```rb
# config/initializers/vcf.rb

ViewComponent::Form.configure do |config|
without_component_suffix = lambda do |component_name, namespaces: []|
namespaces.lazy.map do |namespace|
"#{namespace}::#{component_name.to_s.camelize}".safe_constantize
end.find(&:itself)
end

config.lookup_chain.prepend(without_component_suffix)
end
```

`ViewComponent::Form` will iterate through the `lookup_chain` until a value is returned. By using `prepend` we can fallback on the default `ViewComponent::Form` lookup.

### Building your own components

When building your own ViewComponents for using in forms, it's recommended to inherit from `ViewComponent::Form::FieldComponent`, so you get access to the following helpers:
Expand Down
9 changes: 8 additions & 1 deletion lib/view_component/form/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@
module ViewComponent
module Form
class Configuration
attr_accessor :parent_component
attr_accessor :parent_component, :lookup_chain

def initialize
@parent_component = "ViewComponent::Base"
@lookup_chain = [
lambda do |component_name, namespaces: []|
namespaces.lazy.map do |namespace|
"#{namespace}::#{component_name.to_s.camelize}Component".safe_constantize
end.find(&:itself)
end
]
end
end
end
Expand Down
6 changes: 3 additions & 3 deletions lib/view_component/form/renderer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ def objectify_options(options)

def component_klass(component_name)
@__component_klass_cache[component_name] ||= begin
component_klass = self.class.lookup_namespaces.filter_map do |namespace|
"#{namespace}::#{component_name.to_s.camelize}Component".safe_constantize || false
end.first
component_klass = ViewComponent::Form.configuration.lookup_chain.lazy.map do |lookup|
lookup.call(component_name, namespaces: lookup_namespaces)
end.find(&:itself)

unless component_klass.is_a?(Class) && component_klass < ViewComponent::Base
raise NotImplementedComponentError, "Component named #{component_name} doesn't exist " \
Expand Down
9 changes: 9 additions & 0 deletions spec/internal/app/components/form/text_field.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module Form
class TextField < ViewComponent::Form::LabelComponent
def call
"my custom text_field"
end
end
end
21 changes: 21 additions & 0 deletions spec/view_component/form/builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,27 @@
it { expect(builder.send(:component_klass, :text_field)).to eq(Form::TextFieldComponent) }
it { expect(builder.send(:component_klass, :submit)).to eq(ViewComponent::Form::SubmitComponent) }
end

context "with a custom lookup_chain" do
let(:builder) { CustomFormBuilder.new(object_name, object, template, options) }

around do |example|
original = ViewComponent::Form.configuration.lookup_chain
ViewComponent::Form.configuration.lookup_chain.prepend(lambda do |component_name, namespaces: []|
namespaces.lazy.map do |namespace|
"#{namespace}::#{component_name.to_s.camelize}".safe_constantize
end.find(&:itself)
end)

example.run

ViewComponent::Form.configuration.lookup_chain = original
end

it { expect(builder.send(:component_klass, :label)).to eq(Form::LabelComponent) }
it { expect(builder.send(:component_klass, :text_field)).to eq(Form::TextField) }
it { expect(builder.send(:component_klass, :submit)).to eq(ViewComponent::Form::SubmitComponent) }
end
end

describe "#field_id" do
Expand Down
26 changes: 26 additions & 0 deletions spec/view_component/form/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,31 @@
it do
expect(configuration).to have_attributes(parent_component: "ViewComponent::Base")
end

describe "#lookup_chain" do
subject(:lookup_chain) { described_class.new.lookup_chain }

it "by default implements one lookup lambda" do
expect(lookup_chain.length).to be(1)
end

it "uses Component suffix" do
expect(
lookup_chain.first.call(:text_field, namespaces: [ViewComponent::Form])
).to be(ViewComponent::Form::TextFieldComponent)
end

it "finds the first klass that exists when given a list of namespaces" do # rubocop:disable RSpec/ExampleLength
expect(
lookup_chain.first.call(
:text_field,
namespaces: [
Form,
ViewComponent::Form
]
)
).to be(Form::TextFieldComponent)
end
end
end
end

0 comments on commit a3ccc10

Please sign in to comment.