This message was imported from the Ruby/Rails Modularity Slack server. Find more info in the import thread.
Message originally sent by slack user U712YWCKK8T
Does anyone use packs-rails with a structured app/public folder? I have a package that provides both ViewComponents and helpers, and I want to put them under app/public/components and app/public/helpers, but AIUI that will mess up Zeitwerk loading.
I currently have a custom setup not using packs-rails which has the app and public folders as siblings instead of nested, then adds both of them to config.paths["app"], but I was hoping to switch to packs-rails so I can build on the community’s work and not reimplement everything myself.
I was looking at this literally yesterday - I ended up just using the “type” as a module name so the paths are more logical, so Package::Models::MyThing in app/public/models/my_thing.rb (We use automatic_namespaces to add the namespace to the class name)
It didn’t feel like a great solution, but I think it’s more because rails magic tends to hide those things, so it’s a bit unusual from that lens.
I was also trying to get “a public folder per type folder” to put Package::MyThing in app/model/public/my_thing.rb to work, but couldn’t get that to pick up correctly either, and not sure it’s the better option to put more time into it.
I’ve just got a proof-of-concept working using my non-packs-rails custom solution, turns out it was fairly straightforward.
Add app/public path to your app with config.paths.add("app/public", eager_load: true, glob: "{**,**/concerns}")
Add packages’ paths to it (like packa-rails does with other paths) config.paths["app/public"] << "packs/foo/app/public"
Adding those directly to my application.rb successfully loads the classes under packs/foo/app/public, Since we’re not yet using packs-rails I haven’t tried using this pattern with that, but I see no reason why it wouldn’t work. The main question is figuring out the cleanest way to configure packs-rails to make it work, I don’t have time to look into that today.
@AlexEvanczuk it would be interesting to get your view on this.
Can this be configured in packs-rails already? What do you do at Gusto when a component has a fairly complex public folder?
Or maybe just the fact this happens is considered a code smell?
Thanks for the great question and conversation folks. @iMacTia I don’t believe packs-rails can configure organizational, non-autoloaded subdirectories in the public folder. However, since it’s built on top of zeitwerk, which supports “collapsing directories,” I could see this as a capability of packs-rails.
We haven’t seen too many instances at Gusto with large complex public folder. I think calling it a code smell might be right, since public APIs are intended to be simple and hide implementation details. I think exposing, for example, models in public API might be an example of exposing implementation details.
Actually, yeah we are not exposing the ActiveRecord models as public, but I was experimenting with having a read-only version with explicit fields exposed, essentially because we have a lot existing relationships that if we fully decouple leaves us with a ton of N+1 if we use PORO value objects that the db can’t leverage.
Probably a whole new tangent than the root of this thread.
Ah, definitely a tangent, but a great tangent! Folks over at Gusto have done a lot of experimentation with exposing read-only versions of AR models. Would be a great topic for a new thread if you were interested in starting one
To give specifics on why we have this large package and why I don’t think it’s a code smell - our monolith hosts multiple web apps, each of which we want to have in a separate pack. The pack where I want this structure contains common code for the web apps, hence ViewComponents and helpers. It also has view templates which might be nice to be in the public folder, but since packwerk can’t enforce privacy for them it’s less of an issue.
I guess I could just disable the privacy check for this package or set public_path: ., it does currently have some private stuff for setting up Lookbook, with the components, but I don’t think it would be a big deal for that to be “publicly accessible”.
I also wonder whether having helpers be defined outside of a path in config.paths["app/helpers"] would be an issue, but I don’t really know why Rails has that path configured and couldn’t find any reference in the code.
I finally got around to getting packs-rails to work with our code, so thought I’d close the loop on this with my solution. Add this to your application.rb:
Packs.all.each do |pack|
config.paths["app"] << pack.path.join("app/public")
config.paths.keys.filter { |path| path.start_with? "app/" }.each do |path|
config.paths[path] << pack.path.join("app/public", path.delete_prefix("app/"))
end
end
If you only want to do it for specific packages then you can use a specific list instead of Packs.all . This also assumes your public path is always app/public, if it isn’t then you’ll need to find a way to get the actual path (e.g. pack.raw_hash["public_path"] || "app/public").