Repository navigation
Create an app/lib directory when generating a new Rails project - #47843
bensheldon wants to merge 1 commit into
Conversation
|
Can you give some examples of the kind of classes you'd be placing here? lib/ is intended to be for non-app specific library code that just happens to live in the app for now (usually pending extraction into open source or whatever). Everything app specific that's part of the domain model should live in app/models (that directory is for POJOs as much as ARs). I don't see the purpose of another default directory here. |
|
Thanks for the questions!
This is an no-longer maintained project that I worked on, but I think it has a representative junk-drawer of objects: https://github.com/codeforamerica/drai/tree/a0442ee5689f09e6ede1b7eec6f29dd0f0c36f26/app/lib
Another common item I found in some projects is a pagination object that is somewhere between data and presentation. Here's a much larger project that I didn't work on, but has a fuller https://github.com/codeforamerica/vita-min/tree/2f5faf06f586d1ea3915af262aab7683240f4727/app/lib Also Mastodon: https://github.com/mastodon/mastodon/tree/c62604b5f69c3ad7f5449e0a7dc26606adebb777/app/lib
https://github.com/search?type=code&auto_enroll=true&q=path%3A%2F%5Eapp%5C%2Flib%5C%2F.*%5C.rb%2F
I get that there is a definition of "Model" that means Domain Models, but I don't think it's consistently followed. The Rails Getting Started Guide offers a narrow definition: "A model is a Ruby class that is used to represent data." ...not identities, events, aggregates, etc. I associate I'm not stuck on the name though. I'd update this PR in a heartbeat if there was an alternative name proposal ( |
|
Appreciate the follow up. Stuff like a generic PhoneNumberFormatter is exactly what lib/ is intended for. And if it's app specific, for some reason, then app/models is fine. I don't think the trade-off here is worth it. Introducing another default directory, so going to close this out. |
|
@dhh, in the past @fxn has actually recommended creating an
I've seen this come up in a few Rails Slack channels over and over since Zeitwerk became the default. Most people think that
The fact that I've seen this question come up so many times makes me think there's got to be some better solution here than just closing the issue, even if that solution is more documentation. But I'd be cautious of documentation being the solution, as having even more words to read as a newbie makes getting started even more challenging. |
|
(BTW, the To be honest, I don't quite like At the same time, This is all backwards, because it is to be expected that people want to have those features in The guide even says:
However, we could perhaps evaluate that again. Zeitwerk ignores anything that does not have a Still, I feel like those subdirectories do not belong in |
|
The fundamental problem here is eager loading, you may have generators, or templates, those are Nowadays, in the general case, users need to do this: config.autoload_paths << "#{Rails.root}/lib"
config.eager_load_paths << "#{Rails.root}/lib"
%w(assets generators tasks templates).each do |subdir|
Rails.autoloaders.main.ignore("#{Rails.root}/lib/#{subdir}")
endThe real fundamental problem is that |
This comment was marked as off-topic.
This comment was marked as off-topic.
Yeah, I agree that I can't help but feel like That section of code you provided there sure does feel like a lot of configuration for a framework whose mantra is "convention over configuration"... I'm not necessarily saying that Rails should include an |
That is not the criteria! In Rails applications, having more folders than the ones generated is normal. Projects have The criteria for code in
|
|
I want to lean into what @fxn wrote of "Smells to a X/Y solution to me." If I approached this from the Y-side, that would mean to me "what would allow autoloading Going by the intended purpose of That simply means
Is there anything else, other than |
|
@jasonfb I've hidden your comment as off-topic. Next time you "realize [you] sound rather X-ist", maybe reconsider writing the rest of the comment. |
This comment was marked as off-topic.
This comment was marked as off-topic.
Agreed, the fact that you need to write that code today, or the emergence of suggesting For autoloading to be round in Rails, I believe this is pending. |
|
I've been thinking a lot about the idea of autoloading I feel like explaining the rules of which lib subfolders autoload and which don't will cause a lot of confusion. And I think moving folders like lib/tasks will cause some migration (and tooling?) problems, especially with gems that generate rake tasks. The current autoloading rule is simple and predictable. If the goal of
then I feel like I think the real question then becomes: I don't think As an aside (which feels relevant to the conversation), I know you mentioned some recent documentation changes about autoloading. Is there a way to view a diff of a rails guide across versions? I don't believe there is, and I think that could help a lot about autoloading confusion and just the framework in general. If I've read all of the rails guides before, I don't particularly have time to read the entire thing again every new minor rails version to try to mentally diff the latest version to what I remember reading last. Ideally I could just see a diff (similar to a git diff) to see what sections changed in all the guides. Sometimes guide changes are subtle and sometimes whole sections are added / reworded. I think this would help make sure that the guide updates are read by people who have already read the guide before. |
|
Hey @natematykiewicz, let me reply to your points summarizing them for brevity:
Agreed. It is also worth noting that If we revisit the definition of
This is no longer the case, more that 400 gems use Zeitwerk today. It is not unsual to prefer the Rails autoloading experience to writing manual
The way I see it, you don't need an ontology that perfectly partitions all your classes and modules. In edge cases that don't quite fit you can relax the rules and throw them in
Yes. In the particular case of the autoloading guide, the Git history of the file is quite readable, since things have converged quite a bit. We are in last-mile polishing phase and there are only a handful of commits. Also, compare with GitHub, or with the command line client: Indeed, documenting in the guide how to properly autoload from Thanks for sharing your thoughts @natematykiewicz! |
|
I am leaning into this interface, which acknowledges config.autoload_libThis does the following:
If you have more stuff to ignore, you'd be able to pass it: config.autoload_lib(ignore: %w(assets generators tasks))That would solve the vast majority of existing apps needs. And if you need something very custom, you can grab |
|
Now that I think, that is another thing I dislike about The subdirectories in Compare |
|
I think you can further bucket code living in For example let's say I make discount code generator. It's a 4 line function that's not super related to my app so I may decide to put that in There's also the case of wanting to refactor these files. If I add a new file and need to modify |
|
@nickjj totally. |
|
@fxn if you can pull it off, I think Having it uncommented for new apps would help encourage people to adopt autoloading of the lib, and having it add it commented in an upgrade would preserve backwards compatibility, while also encouraging adoption on an opt-in basis. |
|
Three quick contributions here.
I will also point out that inconsistent autoloading behavior of the root lib directory alongside the tasks being there already is making that folder even more confusing. I already can't tell my team what that folder is for. If Rails starts partially autoloading that, I would do it with a guideline towards where non-autoloading stuff should transition to like the examples above instead of saying lib will be partially autoloading forever. |
|
@nhorton thanks, let me reply to some:
We all agree it would be less surprising if both But in Rails 3 there was no way to skip subdirectories. So, generators, templates, and all kind of stuff was executed in production due to eager loading. So, reality said The fact that so, in this case, I believe we cannot play the "obvious" card. You have to learn what are the autoload paths by default in Rails.
I normally have them in
Totally agree, the
So, while It it sounds complicated, you are now getting why is |
|
Thanks for the quick reply, @fxn. Let me ground my next remarks in context: I was the technical founder for a company in the Rails 2/3 days that was one of the bigger Rails shops for a while, then went away from the code side of things for a while. I am now back in code, having pivoted my current company from a JS-and-Python-heavy stack over onto Rails. What I find is that the biggest challenge is getting some great engineers who are comfortable in those languages over onto Ruby and Rails. The biggest complaint I hear is folks not understanding Rails magic. The core issue sometimes being legitimate technical stuff, but often just being from Rails being a big beast of a system, and folks feeling like they won't ever get to the expertise they had in other languages / platforms. I find myself constantly trying to explain principles of Ruby and Rails to folks. The best always ends up being the classic of Convention over Configuration. But where it breaks down is when it is more "lore" than anything. This is because it makes engineers feel they won't ever ramp on all the lore. I get that. Python as a good example is a really simple language, and the frameworks there are really simple. It means that it takes you 5x as long to do anything in it, but as an engineer you feel you know what you are doing and are competent. Right now, I can actually explain lib pretty well as "its for the code assets you don't want auto-reloaded like tasks". While not 100% accurate, it is close enough that folks can operate with that. However, there is annoyance that happens when they forget that fact and start coding there and not realize the changes are being picked up. If half of lib starts auto-loading, my best explanation for it at that point is "yeah - it is basically half the stuff you don't want autoloaded and half Thus the reason I mentioned the migration-like behavior is because it addresses the biggest issue - engineers getting surprised when their code does autoload - without any compat or "clarity" implications. The Django comparison here is pretty apt - Django auto-migrates tables as you edit your model file. Rails makes that a separate step, for good reasons, but it is experiencially almost as easy as Django because it tells you visibly as an engineer when you missed a step, and lets you one-click run the migration. The Rails approach here is simple, consistent, and friendly. One more example of a simple approach to this that might help a lot too is add a PORO generator. It could be incredibly basic - But by doing that and landing the file in the But I want to restate the odd core assertion I have: my biggest struggle with Rails as an eng leader today is hearts and minds, and clarity beats functionality on that respect. |
You'll agree Rails doesn't need to be designed after a definition you made up yourself, right?
The previous autoloader did not have the ability to ignore stuff, the current autoloader can do that, so I believe it is feasible today to encasulate autoloading lib in one call, and can be done robustly for the user. Regarding magic or framework size, when I was CTO of a company and someone joined, the first thing they had to do was to spend two weeks reading AWDwR from cover to cover. Once you know the framework, there is no magic, only defaults that act as a catalizer and let you fly. |
|
Hey @fxn can you share what is AWDwR?? |
https://www.pragprog.com/titles/rails7/agile-web-development-with-rails-7/ |
Motivation / Background
This PR changes the Rails App Generator to create an
app/libdirectory with a.keepfile.Why
app/lib?One of the most common problems I encounter consulting on Rails projects is that developers have previously added
libto autoload paths and then twisted themselves into knots with error-prone conventions for subsequently un-autoloading a subset of files also inlib.All of this could have been avoided if there was an existing place under
appwhere developers felt comfortable placing constants that didn’t quite fit anywhere else but would be autoloaded.app/libcan be that place.Why not?
The following are common arguments I've seen against
app/lib, and my response:It is confusing that
app/libis named similarly tolib. I agree, but it is not uncommon to have directories with the same name and similar function nested under different contexts. I believe developers can handle this complexity. Most similarly, Linux haslibandusr/lib. Within a new Rails app, there are many such directories that are manageable:app/assetsandlib/assets(sometimes evenvendor/assetstoo)app/javascriptandvendor/javascriptstorageandtmp/storageconfigandapp/assets/configapp/controllersandapp/javascript/controllersEverything has a place so do better and find it. There is a certain belief that everything within
appshould be organized into functionally-named directories and any files placed inapp/libactually belongs inapp/servicesorapp/interactorsorapp/modelsor someplace if the developers just tried harder. The implication is that developers are bad developers if they don’t yet know what kind of constant they have and where its forever home should be.I reject this. Over the lifespan of an application, there will be constants that have not yet found their functional kin, if those kin ever come to exist at all; sometimes you simply need some code and a place to put it.
app/libcan be the convention for where those constants can live temporarily or as long as necessary. Autoloading is really nice, let’s treat them to it.Really, "lib" is a bad name. I’m open to alternative suggestions, and I think conventionalizing it in Rails avoids pushing an otherwise value-less naming choice onto developers (e.g.
app/miscorapp/extras). The need here is that there should be a default, don’t make me think place for autoloaded constants. I think the differences are easy to explain as:libis where the project library files live, which is in the LOAD_PATH but not autoloaded. The code defined here is used in scripts and utilities that are not part of the application or when code will be used within the application but should be loaded, or loaded conditionally, using an explicitrequireapp/libis where application library files live with the benefit of automatic autoloading. The constants defined in these files are part of the application, but have not (or not yet) been organized into functional groups within their own directory withinappIn conclusion
I think the symmetry of the naming between
libandapp/libwill lead a fresh Rails developer to seek out the answer to “Why are there twolibdirectories?", and they will become illuminated. And it will prevent them from seeking the answer to “How do I autoloadlib?” which will start them on a rough path that leads to me advising them to undo it.Checklist
Before submitting the PR make sure the following are checked:
[Fix #issue-number]