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

Particular usage of collapse method #111

Closed
nicolas-brousse opened this issue Mar 4, 2020 · 12 comments
Closed

Particular usage of collapse method #111

nicolas-brousse opened this issue Mar 4, 2020 · 12 comments

Comments

@nicolas-brousse
Copy link

Thanks for the 2.3.0 release that add #collapse. (#109)

I tried to used it for a similar concern but I'm not sure how to configure it for.

I did the following in an initializer:

Rails.application.config.paths.add "app/components", eager_load: true

Rails.autoloaders.each do |autoloader|
  autoloader.collapse("app/components/*")
  autoloader.collapse("app/components/form/*")
end

I expect the following

app/components/button/button_component.rb               => ButtonComponent
app/components/form/text_field/text_field_component.rb  => Form::TextFieldComponent

But I've got this

app/components/button/button_component.rb               => ButtonComponent
app/components/form/text_field/text_field_component.rb  => TextFieldComponent

By doing the following it works, but this mean reload server for each new component creation or renaming.

Rails.autoloaders.each do |autoloader|
  autoloader.collapse("app/components/button")
  autoloader.collapse("app/components/form/text_field")
  autoloader.collapse("app/components/form/text_area")
  autoloader.collapse("app/components/form/datetime_field")
  autoloader.collapse("app/components/form/datetime_select")
  autoloader.collapse("app/components/form/group")
  autoloader.collapse("app/components/form/errors_helper")
  autoloader.collapse("app/components/form/actions")
  autoloader.collapse("app/components/form/submit")
end

Here my demo app nicolas-brousse/avc_form_builder#1.

@fxn
Copy link
Owner

fxn commented Mar 4, 2020

The directory app/components is automatically added by Rails to the autoload paths and it is going to be eager loaded. That happens with any subdirectory of app except for the ones related to assets.

In your setup, you are collapsing form and also collapsing text_field, therefore you get TextFieldComponent. Why is form collapsed? Because it matches app/components/*. You should collapse only the subdirectories of app/components that you actually want to collapse.

@fxn
Copy link
Owner

fxn commented Mar 4, 2020

For example, you could just configure

autoloader = Rails.autoloaders.main
autoloader.collapse("app/components/button")
autoloader.collapse("app/components/form/*")

If the first level (button) is the norm and second level (form) the exception, you could loop over the subdirectories of app/components and collapse one way or another depending on a hard-coded list of exceptions.

You see.

@fxn
Copy link
Owner

fxn commented Mar 6, 2020

Hey! Closing here, please if you have any further questions about the setup do not hesitate to follow up.

@fxn fxn closed this as completed Mar 6, 2020
@nicolas-brousse
Copy link
Author

Hey sorry for the delay.

The directory app/components is automatically added by Rails to the autoload paths and it is going to be eager loaded. That happens with any subdirectory of app except for the ones related to assets.

Thanks for the info, I looked on Rails engine configuration but wasn't sure since models, controllers, ..., are specified. (https://github.com/rails/rails/blob/758e4f8406e680a6cbf21b170749202c537a2576/railties/lib/rails/engine/configuration.rb#L38). I cleaned my repository for this part.

In your setup, you are collapsing form and also collapsing text_field, therefore you get TextFieldComponent. Why is form collapsed? Because it matches app/components/*. You should collapse only the subdirectories of app/components that you actually want to collapse.

Okay. I have a better understanding of how #collapse works. Thanks.

For example, you could just configure

autoloader = Rails.autoloaders.main
autoloader.collapse("app/components/button")
autoloader.collapse("app/components/form/*")

If the first level (button) is the norm and second level (form) the exception, you could loop over the subdirectories of app/components and collapse one way or another depending on a hard-coded list of exceptions.

You see.

I see the idea.

I guess my demo app was too empty to have a good preview. I added a new component named AlertComponent in app/components/alert/alert_components.rb.

The idea in this demo, is that the latest folder should always repeat the name of the class. So ModalComponent should be in app/components/modal/modal_component.rb. And if we need an admin module, we may have Admin::ButtonComponent in app/components/admin/button/button_component.rb.

I now it's not really a conventional pattern in Rails applications. And I guess what I'm looking for is not really what #collapse is for.

@fxn
Copy link
Owner

fxn commented Mar 6, 2020

But are those component directories, like button, going to contain more files within?

@nicolas-brousse
Copy link
Author

The components goal is to isolate a view, a class and some assets (like css, js, images). It currently only includes one ruby file.

app/components
└── alert
    ├── alert.css
    ├── alert.html.erb
    ├── alert_component.rb
    ├── alert_controller.js
    └── assets
        ├── alert-icon.svg
        ├── close.svg
        └── notice-icon.svg

But I guess with some case like this one, may be annoying

app/components/form
├── form.css
├── form.html.erb
├── form_component.rb                => FormComponent
│   └── text_area_controller.js
└── text_field
    ├── text_field.css
    ├── text_field.html.erb
    ├── text_field_component.rb      => Form::TextFieldComponent
    └── text_field_controller.js

Maybe it should be better to do it that way

app/components/form
├── form
│   ├── form.css
│   ├── form.html.erb
│   ├── form_component.rb            => Form::FormComponent
│   └── text_area_controller.js
└── text_field
    ├── text_field.css
    ├── text_field.html.erb
    ├── text_field_component.rb      => Form::TextFieldComponent
    └── text_field_controller.js

But I also think about app/components/concerns, what may happen to them 🤔. Or what if there is some kind of abstract classes.

The pattern is [NAMESPACE]/COMPONENT_NAME/COMPONENT_NAME_component.rb who may results to [NAMESPACE]::COMPONENT_NAMEComponent class. It is always the last class name that is repeated.
So admin/dashboard/form/input/input_component.rb => Admin::Dasboard::Form::InputComponent.

I imagine 3 way to manage it:

  1. Be able to manage this kind of pattern, with the component name repeatition, directly with Zeitwerk.
  2. Keep ruby component class file at root and having other files (css, js, ...) in a folder.
  3. Isolate ruby class, so use Button::ButtonComponent class name for button/button_compoent.rb.

I understand that is not a common pattern in ruby ecosystem.

P.S.: Sorry if my explanations are not really clear. I've difficult to explain myself corretly on this subject 😅

@fxn
Copy link
Owner

fxn commented Mar 8, 2020

Regarding concerns, app/components and app/components/concerns are both added to the autoload paths automatically by Rails if they exist when the application boots. Therefore, app/components/concerns/foo.rb has to define Foo.

I can't really recommend you an option, it really depends on personal preferences and how willing are you to use a custom schema at the price of configuration. The good thing about defaults is that if you follow them, all flows. The good thing about flexibility is that if the defaults do not suit you, or you want to experiment, at least you can do it with the necessary amount of configuration, and collapse makes that possible (it was not before).

@alexpapworth
Copy link

Just wondering, is there any way to set the autoloaders settings as part of the config in application.rb?

@fxn
Copy link
Owner

fxn commented Mar 17, 2021

@alexpapworth Right now, in config you have config.eager_load_paths, config.autoload_paths, etc. Features not supported by classic like collapsing do not have an equivalent in config.

If you want to use Zeitwerk API, you need to reach for Rails.autoloaders.main, and you can do that in config/application.rb. That is public API.

@alexpapworth
Copy link

Awesome, good to know. I've gone with the following code, as per the current recommendations ☺️

module MyApp
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 6.0

    config.eager_load_paths << Rails.root.join("lib/components")
  end
end

Rails.autoloaders.main.collapse("lib/components/concerns")

@fxn
Copy link
Owner

fxn commented Mar 18, 2021

Looks good! If you want, you can have both lines together:

module MyApp
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 6.0

    config.eager_load_paths << Rails.root.join("lib/components")
    Rails.autoloaders.main.collapse("lib/components/concerns")
  end
end

your choice :).

@alexpapworth
Copy link

Ooh, that is definitely nicer! Thanks for feedback 😁

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants