Plugins Framework
To highlight the functionality and power available through the Plugins framework, it is best to start with a concrete example of a simple, yet complete one. What follows is an example plugin for user authentication, SimpleAuthenticationPlugin
, broken into sections for explanation.
Anatomy of a Plugin
The first thing to do to create a Plugin is to define the plugin’s module, and include Praxis::PluginConcern
:
Note: each class in our examples below must live inside the SimpleAuthenticationPlugin
module. We are merely omitting it here for conciseness.
Then we’ll need an inner Plugin
class inside our defined SimpleAuthenticationPlugin
module. This class is responsible for setting up the plugin’s configuration details. We can also define any other utility methods that might seem appropriate for the Plugin. For example, we’ve thrown in an authenticate
instance method, because it seemed as good of a place as any.
If we define a Request
module inside the plugin, it will automatically be included in Praxis::Request
. So to add a simple current_user
method to a Praxis Request
it is enough to define it like this.
Extensions to Praxis::Controller
are done by defining a Controller
module within the plugin. As in the case above, any code inside the module will be automatically included in Praxis::Controller
. Controller extensions typically want to register before
, after
, or around
callbacks. The code below shows an example of that, registering a before :action
block for all controllers:
Our example authentication Plugin needs to allow designers to tag certain actions as requiring an authenticated user. A clean way to achieve that is to provide a new DSL method available within an ActionDefinition
. We can do that by adding a handy requires_authentication
method within the ActionDefinition
module of our plugin. Similarly, we can include a decorate_docs
hook to the ActionDefinition
module so that when documents are generated, this plugin has the chance to decorate the output for actions that :authentication_required
is true.
Enabling a Plugin
In order to use a plugin named MyPlugin
, you need to invoke the use
directive of the Praxis bootloader. To do so, you would typically have the following in your config/environment.rb
:
As outlined above, MyPlugin
may be either a subclass of Plugin
or a module that includes PluginConcern
. In the latter case, Praxis will expect the module that contains a class named Plugin
, e.g. MyPlugin::Plugin
.
Plugin Components
The two main components of a plugin in Praxis are:
Plugin
: The class that is instantiated for each use of the plugin (unless it includesSingleton
, in which case itsinstance
is used).PluginConcern
: AnActiveSupport::Concern
module that encloses extensions to core Praxis classes.
In addition, plugins that use the PluginConcern
module may provide modules that should be included in the core Praxis classes. The modules must be named after the class they are to be included in to, at present the following classes are supported:
Request
Controller
ResourceDefinition
ActionDefinition
Response
A plugin in Praxis must consist of a subclass of Plugin
, and that subclass may be enclosed in a module that has included PluginConcern
Plugin Bootstrapping and Configuration
Praxis processes your Bootloader#use
invocations while loading environment.rb and performs a few actions to load your plugin:
- The plugin module’s
setup!
method is called. By default, that method will inject any relevant modules into the core Praxis classes, provided that is the first time the method has been called (in the event the plugin is used multiple times in one application). - An instance of that plugin’s
Plugin
class is created and saved in the application’s set of plugins. Or, if that subclass is aSingleton
, then itsinstance
is retrieved and used instead.
The configuration of the Plugin instance, however, is deferred until a separate :plugin
stage during bootstrapping. The :plugin
stage, contains three sub-stages: :prepare
, :load
and :setup
. Praxis will call your Plugins in this particular order by using the following callback methods:
prepare
: The prepare phase will invoke thePlugin#prepare_config!(node)
method. This phase adds the plugin’s configuration definition to the providednode
from the application’s own configuration. Thenode
parameter is anAttributor::Struct
which can be enhanced with whatever attribute structure the plugin requires. Typically the code inprepare_config!
will pass a block to thenode.attributes
method containing a structure of typed attributes. (see authentication example above)load
: The load phase is in charge of loading and returning the relevant configuration data (based on the structure defined in the previous phase). This is implemented in thePlugin#load_config!
method. This callback method is only responsible for returning the loaded configuration data. Praxis will internally take care of validating and saving such data onto the appropriate place in the application. There is a default implementation of this method in the base Plugin class. Such default method will automatically return the contents of the:config_file
attribute of the plugin, assuming that isYAML
-parseable. So, for plugins that simply get their configuration through a singleYAML
file, they can setup their:config_file
variable appropriately and skip implementing theload_config!
method in the Plugin class.setup
: The last phase issetup
, and it allows the Plugin to perform any final initialization before the application’s code is loaded. This is implemented in thePlugin#setup!
method. The default implementation of this method is empty.
Note that these phases are invoked for all registered Plugins as a block, one phase after the other. In other words, all registered Plugins will go through the prepare
phase first, before they all move to the load
phase, and finally move onto the setup
phase.
Doc Browser Customization
It is possible to modify and enhance the Doc browser from plugins. To begin, you must register your plugin as extending the doc browser by calling Praxis::Plugin#register_doc_browser_plugin(path)
, where path
will be the path to the directory where you store the assets for your plugin. The best place to call this is in the Plugin#setup!
method mentioned above. Once this is done, Praxis will automatically pick up your plugin’s components into its build system. This allows you to do the following:
Add Dependencies
If you place a bower.json
into your path
, the dependencies
field will be merged into a master bower.json
and then the dependencies will be automatically installed and linked into the doc browser.
Override or Add Templates
You may choose to override any of the Praxis builtin templates, simply place them at a matching path within a views
subdirectory and they will be automatically picked up.
Add Scripts
Any code you provide will be loaded after the core Praxis doc browser, but before any of the user’s code. This will allow you to override any components from core you need as well as take advantage of any APIs exposed. You can also expose APIs to the user from your plugin. See the doc browser customization wiki for more details.
Provide SCSS Styles
path
will be made available to the user’s docs/styles.scss
file, but will not be included automatically. Therefore it is up to you to instruct users to add any necessary imports to use styles you provide.
Add Other Assets
Any assets not mentioned above will be copied to the user’s docs/output
directory on build. This way you can provide images, fonts or other assets in a plugin.