Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lookup_chain customizability #162

Merged
merged 1 commit into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added parent_component configuration for field components (#160)
- Added Ruby 3.3 support (#164)
- Add `lookup_chain` customizability (#162)

### Removed
- Drop Ruby 2.7 support (#164)
Expand Down
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ Development of this gem is sponsored by:

## Compatibility

> [!WARNING]
> **This is an early release, and the API is subject to change until `v1.0.0`.**
> [!WARNING] > **This is an early release, and the API is subject to change until `v1.0.0`.**

This gem is tested on:

Expand All @@ -36,6 +35,26 @@ end
| --------------------------- | ----------------------------------------------------- | ----------------------- |
| `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.

## Usage

Add your own form builder.
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 @@ -170,6 +170,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
Loading