-
-
Save svenfuchs/b7733d5fea89d2dd37ca to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Vertical application slices | |
The Rails < 2.3 engines plugin defined the term "engine" as a "full vertical | |
application slice". When Rails 2.3 included engines it went a few steps back | |
and only implemented most of the core features, making an "engine" rather a | |
"pimped plugin". Today in Rails 3 we have engines that are way more powerful | |
and flexible than what we had before but (apart from what Piotr's plans are | |
[1]) there's still been one feature in the original engines plugin that I miss | |
today: the ability to mix first-class-citizen code slices from various engines | |
automatically without having one engine know much about other engines. | |
Let's say we have a bunch of engines that contribute small applications (say, | |
a blog and a ticket tracker) and only share a few things, maybe a User model. | |
So, we'd have the engines: user, blog, tickets. Obviously users have many blog | |
posts and many tickets. Now, when the blog engine is installed the User.should | |
have_many(:posts). When the tickets engine is installed the User.should | |
have_many(:tickets). But obviously this needs to be defined in the blog and | |
tickets engines, not in the user engine where the User class is defined. | |
So, the blog and tickets engines somehow need to reopen the User class and add | |
that association: | |
# somewhere in the blog engine | |
User.has_many :posts | |
# somewhere in the tickets engine | |
User.has_many :tickets | |
Traditionally there were two approaches (to my knowledge) to place these code | |
bits in the blog and tickets engines: | |
1. Initializers. It was easy to hack Rails plugins to have their own | |
initializers, so they'd all get loaded at startup. Placing all of these things | |
into tons of initializers smelled a lot and also required to use to_prepare | |
hooks all over the place. | |
2. App folder. One could do something like: | |
# [blog-engine]/app/models/user.rb | |
load 'relative/path/to/user-engine/app/models/user.rb' | |
User.has_many :posts | |
But this obviously requires the blog-engine to preceed the user-engine in the | |
load path. With engines being Rails plugins one could mess with the plugin | |
load order to enforce this but obviously this isn't a nice and reliable | |
option, too. | |
I believe this should be a first-class framework capability. There should be a | |
way to tell the framework: "Here's a file that relates to the User class but | |
it's only a slice of it. You have to look elsewhere to find the file defining | |
the User class. Once you did please load this slice, too." | |
Maybe this could be accomplished introducing an after_constant_load hook in | |
ActiveSupport::Dependencies. Once Dependencies has found the User class in | |
[user-engine]/app/models/user.rb it would call the after_constant_load hook | |
where we could then look for files named something like models/user_slice.rb | |
and load them. Obviously this approach would require a naming convention or | |
special subfolder. | |
Another approach could be to have Dependencies be able to work recursively (I | |
have no idea how much of the current implementation could support that): | |
When const_missing for the User class is triggered Dependencies would first | |
find the file in the blog-engine: | |
# [blog-engine]/app/models/user.rb | |
User.has_many(:posts) | |
... which again uses the not yet defined User constant which again would | |
trigger Dependencies const_missing. Now Dependencies would skip looking at the | |
file [blog-engine]/app/models/user.rb and instead look for the next one in the | |
load path and so forth until it finds: | |
# [user-engine]/app/models/user.rb | |
class User | |
end | |
Dependencies would also need to continue looking for user.rb files once it has | |
found one ... so that engines that come later in the load_path will be able to | |
contribute their stuff, too: | |
# [xyz-engine]/app/models/user.rb | |
User.has_many(:xyzs) | |
So, basically this approach would change Dependencies: | |
- from looking for a single file defining a missing constant | |
- to loading all files according to the naming convention | |
(constant_name.underscore) and configuration (app folders etc). | |
--- | |
[1] http://piotrsarnacki.com/2010/07/06/rsoc-status-briging-engine-closer-to-application |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks Konstantin, that sounds interesting. Will look into that!