#277 Mountable Engines
Engines are receiving a major update in Rails 3.1. You can mount them at any path, embed assets, run generators and more. See how in this episode.
- Download:
- source codeProject Files in Zip (56.6 KB)
- mp4Full Size H.264 Video (19.6 MB)
- m4vSmaller H.264 Video (12.7 MB)
- webmFull Size VP8 Video (18.7 MB)
- ogvFull Size Theora Video (23.3 MB)
I have always been a big fan on Engines and use them religiously. All the love Engines are being shown in Rails 3.1 is getting me very excited for the final release.
Thank you Ryan!
Is there a memory consumption overhead when bundling a plugin as an engine compared to, say, a gem? Has anyone benchmarked this?
Great tutorial, as always! Anybody knows of any good engines that can be mounted inside an rails app to handle the common stuff such as marketing pages / helpdesk?
Engines look promising in Rails 3.1!
Wondering why you didn't use something like
in the application controller. Is it so that the error is raised like it normally is?
In that case you could reraise it in record_error I think.
So, are these engines intended to be used as gems or they will live in the plugins directory?
Hi Peco,
You can put them in
vendor/plugins
or as gems, as long as they are in a position where the class inheriting from Rails::Engine is loaded then they should work as well either way.I would personally recommend using gems though, as they have versioning support where plugins do not
Thanks Ryan for this screencast!
If people are interested to see what a bigger Rails 3.1 engine looks like, a few friends and I have built a forum system called forem: http://github.com/radar/forem. We would be happy to answer any questions anyone has about building an engine.
Ryan, I looked at the engine you guys made: say I make an engine, how much work does it then take to make this engine compatible with refinery? ... at first glance it seems your engine just blends right in with refinery, but I'm guessing there is more to it... can you say something bout this or is there anywhere I can read up on this?
Thanks in advance man... thanks for the example and thanks to the ryan that made this screencast... beauty as ever!
Another good example of engines is Spree Commerce. The engines are not mountable but you can see how powerful the concept is even without mounting and namespaces (which we plan to support.)
Interesting. I wonder why Rails is able to load assets and initializers and such, but not able to pick up the migrations... one would think it could just append the migration path to the list of places to search for and run from.
It's because the semantics don't work quite right. Migrations these days are timestamped, so you can roll back and forward in the order the migrations were applied, right? The timestamp is encoded in the migration file name.
If you just use migrations from the engine... they'll be timestamped with the time the developer of the engine created them, not the time your app actually first included/applied them.
The best thing I've found to do is have a generator which generates your engine/gem's migrations directly into the local rails app, with a filename with timestamp based on time-of-generation-into-local-app, and only doing this generation if a migration with the same name (not counting timestamp) does not already exist in local app (so the generation step is idempotent, and can be run for later versions of engine/gem and only add new migrations, not duplicate ones already generated).
Quick question.
Let's say that the author of the main app that uses the engine, decides to mount it under a different path.
For instance:
How would this influence the assets used by the engine.
I imagine that the image at the top of the Uhoh failures page would fail, as the engine is no longer in the /uhoh path?
Or is this handled automatically by Rails?
@Thomas, if I understood correctly, that
uhoh
under"uhoh/alert.png"
is independent from the route path, it refers touhoh/app/assets/images/uhoh/alert.png
, notice the extrauhoh
folder afterimages/
(minute 11:40 to 11:50). So yes, it is "automatically" handled by Rails.Ok, whatever...
I think it won't be a problem. In the assets directory of the engine, the path of the image is still "uhoh/alert.png", no matter what route the engine is mounted at.
You should always refresh a page before replying, just in case someone has already posted a more thorough explanation than yours :)
If you're planning to build gems from your engines, it may be worthwhile to start with the latest from 3-1-stable instead of RC5. The generated gemspec file is now much more complete, including details such as author, email and homepage. Also, it now contains dependencies which are included in the Gemfile via the gemspec command (similar to the "bundle gem" approach).
Hi Ryan - nice 'Casts!
A suggestion/request for Railscasts: Any way you can add a link in each episode's "show notes" page to that episode's git source code page? That would make what is already an awesome resource even better!
Wow soooo awesome i always neglected engines but from now ill see how i can apply them to my apps
Isn't that should be "Episode 149: Rails Engines" instead of "Episode 249: Notifications in Rails 3" in show notes ??
#249 deals with ActiveSupport Notification subscriptions, which this episode uses to catch exceptions.
#149 is relevant though, so I've added a link to it. Thanks.
This is my first two comments so I don't want to be too intrusive :), but the episode
104
looks at least as relevant as249
and149
:)I see it's referred to inside the episode, so I've also added a link to the show notes. Thanks.
Bravo!! Always been a big fan of Railscasts!
I'm wondering if there's an easy way to mount an engine into a subdomain, say, http://uhoh.example.com in this case?
Not tested, but I think this should work:
Anyone try this?
Just did, it works :)
What if i have a main app that would like say multiple blogs provided through some mountable blog engine. Do I then have to mount the blog engine each time for each blog and with a different path each time?
Someone correct me if I'm wrong but I'd assume since you have to fetch the migrations from the engine and run them on the consumer / main app it's likely they both would share the same data store (and thus this wouldn't work).
Not sure if there is a way to pass arbitrary data to the mounts to allow it to store data associated with a particular id (ie: blog 1, 2, 3 and then the data being associated with those). I assume you could somehow (although it sounds dirty) inspect the request and then use that to determine which instance the blog is and store / fetch its data using that means.
My gut tells me the best way to do this would rather to:
1. Have one engine for the blog
2. Have that engine understand the idea of there being more than one blog ... so there would be a blog model that has 0 to many posts, etc and then key the posts off blog so they are associated with that blog
3. Have the routes somehow handle pointing to the correct blog -- whether you want that to be /blog/1-Name-of-Blog/ etc or something else is your call.
Ryan, thanks for a great episode. I am curious, too, about the ins and outs of mounting the engine multiple times. I am not sure what the use case would be, but the question is an interesting one.
Mountable engines was totally new to me before I watched this screencast. Therefor I have a couple of questions..
Would a use case example be to create a authentication and profile engine, then a blog engine, a forum engine etc and combine them all together under a "umbrella" app?
Is the mentioned use case a good way to create better scalable apps?
I guess you can create a engine within a engine as well?
that's pretty much the way a lot of people are doing things now - although I dunno if authentication is something for which I'd use an engine - maybe you would though
if you can afford it, I'd recommend buying and reading "Crafting Rails Applications" by Jose Valim, from where much of rails3.1's updated engine code was extracted (see the enginex gem if you're on older rails)
ryan bigg posted a link to this nice forum engine he made http://github.com/radar/forem
and here's my favourite engine atm (called rails admin) which handles all your back-end stuff (== no more django-envy): https://github.com/sferik/rails_admin
Good screencast. Can you please explain why we need engine? Can I keep creating apps without engine or do I have to do it with?
Engines are just a way to package up part of a rails app, and then use it in multiple projects (either your own, or open-source, etc.)
For example, let's say you made a nice e-commerce system. If you package it up as an engine, other people can simple mount it into their rails app (say at the "/store" path), and voila, they now have a store! Very elegant.
How do I test mountable engine with RSpec? If I follow instructions from episode #275, generators use Test:Unit instead of RSpec, even though I have rails-rspec gem installed.
Same problem here. Have you figured this out?
Ditto, And I'm using Rails 3.1.1. I'm guessing the Rspec folks are going to have to do something one their end. I also suspect that Ryan was aware of this when he filmed this one, because his preferred method for testing is Rspec and yet he didn't mention it in this cast.
I got testing with rspec working here, note that I am using mongo(oid) and not ActiveRecord (same principle though):
https://github.com/RobertBrewitz/EngineRspecMongoid
NOTE: I think I found some bug in the rails plugin new generator with camel casing. More here: https://github.com/rails/rails/issues/3684
Using it for my puppy project, flows smoothly now after some tinkering :-)
https://github.com/RobertBrewitz/articulated
Hi,
I'm trying to test specific extensions to an engine I mounted in my app.
I wanted to have a look at the puppy project you mention, but the repo is not there anymore. Do you mind sharing that project or the rationale that made it work?
Thank you very much
I believe Rails 3.2.0.beta fixes the bug where it creates both
app/views/layouts/application.html.erb
andapp/views/layouts/uhoh/application.html.erb
(where "uhoh" represents any mountable engine name).I was just playing around with this and noticed that the url helper prefixing seems to have changed. I am on Rails 3.1 rc5, but had to downgrade to Arel 2.1.4 to get things launched (which wouldn't seem to make a difference w/r/t this)
It seems that configuration you place inside
lib/uhoh/engine.rb
will affect your main app's configuration attest/dummy/config/application.rb
I did this in my engine:
And now the main app uses haml for scaffold generation. That seems pretty counterintuitive, since it is within the class of the engine; think that's a bug I should file on Rails?
No it's not a bug.
The only configs that are scoped to the engine are load_paths, eager_load_paths, and load_once_paths
Thank you for the great cast! Is there a way to share the layout between main app and engine app?
pado
I wonder how can I access the assets data in a test?
I tried
Dummy::Application.config.assets['application.js']
but it always returnsnil
for all the assets.Probably I should initialise it somehow...
Found - I needed to add
require 'sprockets/railtie'
to dummyapplication.rb
(when not reqruing the whole rails).Then assets can be accessed via
Rails.application.assets
.Using rails 3.1 for creating and running the engine gives me the following error.
"couldn't find file 'jquery'"
I have jquery-rails as dependency in the gemspec file and the gem is installed.
I get the same error for both the engine and the dummy app.
I've asked in both ruby-forum and on IRC but no one seems to know anything about it.
http://www.ruby-forum.com/topic/2484569
Would be great if someone could shed some light on this matter :)
Did you solved the issue? I've got the same problem (jquery, bootstrap) and the solution or "fix" presented in your linked thread isn't that satisfactory, is it?
And I finally found a solution for it. Not a solution, THE solution :-)
You'll have to require your dependencies in the gem's initialization file and then it will work like a charm. I think Ryan would say "Yay" ;-)
Example as image
I hate to be the negative one here, but this feature seems uncharacteristically over-engineered.
For example, it appears to require much more work to setup a Rails engine than before. You used to be able to simply add a plugin that included a config/routes.rb file, and those routes would automagically be mounted in your main application. Is that still the case? Is there any reason to go through all these steps if you're not making a shared gem?
Last point: the syntax of
mount Uhoh::Engine => "/failures"
seems backwards to me. I would have expected:
mount "/failures" => Uhoh::Engine
instead, which seems a lot more intuitive and consistent.
You're thinking of match, which matches a route to a thing. This is "mount" which mounts a thing at a route. It's reversed because the subject/object of the two verbs are reversed.
Rails 3.1.1, 3.1.0: problem with routing. No route matches:
Tried it with Rails 3.1.1.rc3 but got
uninitialized constant Uhoh (NameError) when trying
to mount the engine from another rails application.
Then I thought perhaps I have to do build/install the plugin
as a gem. After doing this and including the gem uhoh in the
main app's Gemfile the problem went away.
Not sure if this is correct.
Can any one look at the following question related to mountable engines? I urgently need it to solve,
http://stackoverflow.com/questions/9310287/rails-3-1-engines-gemspec-and-config-initializers
in rails 3.2.3 you need to run your dummy server with the
rails s
command from inside the tests/dummy folder, not at the root of the engine
docs:
http://edgeguides.rubyonrails.org/engines.html
Thank you Luke for mentioning that!
Hi,
thanks for this great cast. I wrote a small engine with two controllers and now I want to integrate this controllers into the devise based security infrastructure. In my app controllers I'm using a before_filter :backend_rights_required. How can I add this filter to my engine controllers as well from the main app without changing the engine as self?
Thanks a lot
Dirk
Does anyone know if Mongoid can be used instead of activerecord?
It seems that when using the HttpHelpers in an Engine's routes file, I keep getting "uninitialized constant Controller". But, if I use a URL matcher in the Engine's routes file, it routes correctly.
For instance in the dummy application I have config/routes.rb:
In the engine, if the config/routes.rb look like:
... then,
rake routes
in the dummy application looks like:... and when I try to access
http://<host>/myengine/things/index
, I'll get anuninitialized constant ThingsController
.However, if I change my Engine routes to:
... then,
rake routes
in the dummy application looks like:... and when I try to access
http://<host>/myengine/things/index
, I get the expected view.Btw,
rails g controller things index
will generate a route using the HttpHelper method (not the matcher).I googled for any corroborating accounts, but didn't see any. So, I'm not sure if this is just something I'm doing wrong. Or, if maybe this is because engines are fairly new.
I'll also post this to stack overflow to see if there's any help there.