= Welcome This document gives an overview of how the Engines mechanism works within a Rails environment. In most cases the code below is just an example. For more information or documentation, please go to http://rails-engines.org. == Background Rails Engines are a way of dropping in whole chunks of functionality into your existing application without affecting *any* of your existing code. The could also be described as mini-applications, or vertical application slices - top-to-bottom units which provide full MVC coverage for a certain, specific application function. As an example, the Login Engine provides a full user login subsystem, including: * controllers to manage user accounts; * helpers for you to interact with account information from other parts of your application; * the model objects and schemas to create the required tables; * stylesheets and javascript files to enhance the views; * and any other library files required. Once the Rails Core team decides on a suitable method for packaging plugins, Engines can be distributed using the same mechanisms. If you are developing engines yourself for use across multiple projects, linking them as svn externals allows seamless updating of bugfixes across multiple applications. = Using the Engines plugin itself There are a number of features of the Engines plugin itself which may be useful to know: === Engines.log The Engines plugin comes with its own logger, which is invaluable when debugging. To use it, simply call Engines.create_logger Two optional arguments may be passed to this method: Engines.create_logger(io) Would set the outputter to the logger to the given io object. For example, this could be STDERR or STDOUT (the default). The second argument is the logger level: Engines.create_logger(STDOUT, Logger::INFO) The logger can be accessed using either of the following: Engines.log.[debug|info|whatever] "message" Engines.logger.[debug|info|whatever] "message" ... essentially it's a Logger object. It's worth noting that if you *don't* create a logger, calls to Engines.log will just be swallowed without a sound, making it very very easy to completely silence Engine logging. === Engines.config(:root) By default, the Engines plugin expects to be starting Engines from within RAILS_ROOT/vendor/plugins. However, if you'd like to store your engines in a different directory, add the following line to the *top* of environment.rb module Engines CONFIG[:root] = "/path/to/your/directory" end === Rake Tasks The engines plugin comes with a number of handy rake tasks: rake engine_info # displays version information about the engines subsystem rake engine_migrate # migrates engines' database schemas in a controlled way rake enginedoc # generates full documentation for all engines There are more, but you'll have to discover them yourself... == More information For more information about what you can do with the Engines plugin, you'll need to generate the documentation (rake plugindoc), or go to http://rails-engines.org. Good luck! = Quickstart === Gentlemen, Start your Engines! Here's an *example* of how you might go about using Rails Engines. Please bear in mind that actual Engines may differ from this, but these are the steps you will *typically* have to take. Refer to individual Engine documentation for specific installation instructions. Anyway, on with the show: 1. Install the Rails Engines plugin into your plugins directory. You'll probably need to accept the SSL certificate here for the OpenSVN servers. For example: $ script/plugin install engines or $ svn co https://opensvn.csie.org/rails_engines/plugins/engines /vendor/plugins/engines 2. Install your engine into the plugins directory in a similar way. 3. Create the RDoc for the engine so you know what's going on: $ rake plugindoc 4. Initialize any database schema provided. The Engine may provide Rake tasks to do this for you. Beware that accepting an Engine schema might affect any existing database tables you have installed! You are STRONGLY recommended to inspect the db/schema.rb file to see exactly what running it might change. 5. Add configuration to environment.rb: e.g. # Add your application configuration here module MyEngine config :top_speed, "MegaTurboFast" end Engines.start :my_engine 6. Run your server! $ script/server = Building an Engine Here's a sample rails application with a detailed listing of an example engines as a concrete example: RAILS_ROOT |- app |- lib |- config |- <... other directories ...> |- vendor |-plugins |- engines <-- the engines plugin |- some_other_plugin |- my_engine <-- our example engine |- init_engine.rb |- app | |- controllers | |- model | |- helpers | |- views |- db |- tasks |- lib |- public | |- javascripts | |- stylesheets |- test The internal structure of an engine mirrors the familiar core of a Rails application, with most of the engine within the app subdirectory. Within app, the controllers, views and model objects behave just as you might expect if there in teh top-level app directory. When you call Engines.start :my_engine in environment.rb a few important bits of black magic voodoo happen: * the engine's controllers, views and modesl are mixed in to your running Rails application; * files in the lib directory of your engine (and subdirectories) are made available to the rest of your system * any directory structure in the folder public/ within your engine is made available to the webserver * the file init_engine.rb is loaded from within the engine (just like a plugin - the reason why engines need an init_engine.rb rather than an init.rb is because Rails' default plugin system might try and load an engine before the Engines plugin has been loaded, resulting in all manner of badness. Instead, Rails' skips over any engine plugins, and the Engines plugin handles initializing your Engines plugins when you 'start' each engine). From within init_engine.rb you should load any libraries from your lib directory that your engine might need to function. You can also perform any configuration required. === Loading all Engines Calling either Engines.start (with no arguments) or Engines.start_all will load all engines available. Please note that your plugin can only be detected as an engine by the presence of an 'init_engine.rb' file, or if the engine is in a directory named _engine. If neither of these conditions hold, then your engine will not be loaded by Engines.start() or Engines.start_all(). = Configuring Engines Often your engine will require a number of configuration parameters set, some of which should be alterable by the user to reflect their particular needs. For example, a Login System might need a unique Salt value set to encrypt user passwords. This value should be unique to each application. Engines provides a simple mechanism to handle this, and it's already been hinted at above. Within any module, a new method is now available: config. This method creates a special CONFIG Hash object within the Module it is called, and can be used to store your parameters. For a user to set these parameters, they should reopen the module (before the corresponding Engines.start call), as follows: module MyModule config :some_option, "really_important_value" end Engines.start :my_engine Because this config value has been set before the Engine is started, subsequent attempts to set this config value will be ignored and the user-specified value used instead. Of course, there are situations where you *really* want to set the config value, even if it already exists. In such cases the config call can be changed to: config :some_option, "no_THIS_really_important_value", :force The additional parameter will force the new value to be used. For more information, see Module#config. = Tweaking Engines One of the best things about Engines is that if you don't like the default behaviour of any component, you can override it without needing to overhaul the whole engine. This makes adding your customisations to engines almost painless, and allows for upgrading/updating engine code without affecting the specialisations you need for your particular application. === View Tweaks These are the simplest - just drop your customised view (or partial) into you /app/views directory in the corresponding location for the engine, and your view will be used in preference to the engine view. For example, if we have a ItemController with an action 'show', it will (normally) expect to find its view as report/show.rhtml in the views directory. The view is found in the engine at /vendor/engines/my_engine/app/views/report/show.rhtml. However, if you create the file /app/views/report/show.rhtml, that file will be used instead! The same goes for partials. === Controller Tweaks You can override controller behaviour by replacing individual controller methods with your custom behaviour. Lets say that our ItemController's 'show' method isn't up to scratch, but the rest of it behaves just fine. To override the single method, create /app/controllers/item_controller.rb, just as if it were going to be a new controller in a regular Rails application. then, implement your show method as you would like it to happen. ... and that's it. Your custom code will be mixed in to the engine controller, replacing its old method with your custom code. === Model/Lib Tweaks Alas, tweaking model objects isn't quite so easy (yet). If you need to change the behaviour of model objects, you'll need to copy the model file from the engine into /app/models and edit the methods yourself. Library code can be overridden in a similar way. = Public Files If the Engine includes a public directory, its contents will be mirrored into RAILS_ROOT/public/engine_files/<engine_name>/ so that these files can be served by your webserver to browsers and users over the internet. Engines also provides two convenience methods for loading stylesheets and javascript files in your layouts: engine_stylesheet and engine_javascript. === Engine Stylesheets <%= engine_stylesheet "my_engine" %> will include RAILS_ROOT/public/<engine_files>/my_engine/stylesheets/my_engine.css in your layout. If you have more than one stylesheet, you can include them in the same call: <%= engine_stylesheet "my_engine", "stylesheet_2", "another_one" %> will give you: in your rendered layout. === Engine Javascripts The engine_javascript command works in exactly the same way as above. = Databases and Engines Engine schema information can be handled using similar mechanisms to your normal application schemas. == CAVEAT EMPTOR! I.E. Big Huge Warning In Flashing Lights. PLEASE, PLEASE, PLEASE bear in mind that if you are letting someone ELSE have a say in what tables you are using, you're basically opening your application schema open to potential HAVOC. I cannot stress how serious this is. It is YOUR responsibility to ensure that you trust the schema and migration information, and that it's not going to drop your whole database. You need to inspect these things. YOU do. If you run these voodoo commands and your database essplodes because you didn't double double double check what was going on, your embarassment will be infinite. And your project will be skroo'd if the migration is irreversible. That said, if you are working in a team and you all trust each other, which is hopefully true, this can be quite useful. == Migrating Engines To migrate all engines to the latest version: rake engine_migrate To migrate a single engine, for example the login engine: rake engine_migrate ENGINE=login (or login_engine) To migrate a single engine to a specific revision: rake engine_migrate ENGINE=login VERSION=4 This: rake engine_migrate VERSION=1 ... will not work, because we felt it was too dangerous to allow ALL engines to be migrated to a specific version, since such versions might be incompatible. = Testing Engines The Engines plugin comes with mechanisms to help test Engines within a developer's own application. The testing extensions enable developers to load fixtures into specific tables irrespective of the name of the fixtures file. This work is heavily based on patches made by Duane Johnson (canadaduane), viewable at http://dev.rubyonrails.org/ticket/1911 Engine developers should supply fixture files in the /test/fixtures directory as normal. Within their tests, they should load the fixtures using the 'fixture' command (rather than the normal 'fixtures' command). For example: class UserTest < Test::Unit::TestCase fixture :users, :table_name => LoginEngine.config(:user_table), :class_name => "User" ... This will ensure that the fixtures/users.yml file will get loaded into the correct table, and will use the correct model object class. Your engine should provide a test_helper.rb file in /test, the contents of which should include the following: # Load the default rails test helper - this will load the environment. require File.dirname(__FILE__) + '/../../../../test/test_helper' # ensure that the Engines testing enhancements are loaded and will override Rails own # code where needed. This line is very important! require File.join(Engines.config(:root), "engines", "lib", "testing_extensions") # Load the schema - if migrations have been performed, this will be up to date. load(File.dirname(__FILE__) + "/../db/schema.rb") # set up the fixtures location to use your engine's fixtures Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/" $LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path) == Loading Fixtures An additional helpful task for loading fixture data is also provided (thanks to Joe Van Dyk): rake load_plugin_fixtures rake load_plugin_fixtures PLUGIN=login_engine will load the engine fixture data into your development database. === Important Caveat Unlike the new 'fixture' directive described above, this task currently relies on you ensuring that the table name to load fixtures into is the same as the name of the fixtures file you are trying to load. If you are using defaults, this should be fine. If you have changed table names, you will need to rename your fixtures files (and possibly update your tests to reflect this too). You should also note that fixtures typically tend to depend on test configuration information (such as test salt values), so not all data will be usable in fixture form.