I've now set up a Trac instance at http://code.trebex.net/auto-admin/. Go there instead.
A plugin for Ruby on Rails that automagically creates an administration interface, based on your models. It is heavily inspired by the Django administration system, and the only theme currently available is based directly on Django's administration system. From the screenshots posted so far, it appears to share goals with Streamlined.
class Customer < ActiveRecord::Base
belongs_to :store
has_many :payments, :order => 'payment_date DESC'
def name; first_name + ' ' + last_name; end
sort_by :last_name
search_by :first_name, :last_name
filter_by :active, :store
default_filter :active => true
list_columns :store, :first_name, :last_name
admin_fieldset do |b|
b.text_field :first_name
b.text_field :last_name
b.select :store
end
admin_child_table 'Payments', :payments do |b|
b.static_text :payment_date
b.static_text :amount
end
end
Scaffolding. This is not a view generator for you to then customise. Either it provides the interface you want, or it doesn't. (With a limited, but hopefully expanding, set of exceptions.)
For everyone. This is for applications that have a public interface and a restricted-access administrative interface. Its goal is not to generate views you would otherwise have to craft manually, so much as generating views you otherwise wouldn't bother to create. Of course, a neat side-effect of using this is that your boss (or your client's IT manager) can make simple database-level changes that would otherwise require a developer to use either the console or direct SQL. If you're trying to create an interface for all your users, this probably isn't for you.
Right now, there's just a tarball available at http://trebex.net/~matthew/auto-admin-0.0/auto-admin-0.0.tar.gz.
I need to get a public SVN repository set up for it, and populate a useful Web page. Writing this has at least given me some material to that end.
Perhaps, but probably not quite yet. It currently doesn't like editable sublists, for one, and it lacks a reliable set of tests... I've TDDed a few features, but the tests covering the rest of the functionality are rather sparse.
I'm releasing mostly for selfish reasons: I'm hoping that publishing the code will shame me into fixing the nasty bits. :)
Other, more pressing, time constraints are forcing this release before I get it cleaned up as much as I'd like (hence the lack of public SVN or a website). An unfortunate side-effect of this is the timing with respect to Streamlined's release. On that note, I'll be looking closely at Streamlined upon its release, probably with a view to moving any useful functionality I've built here into it; Justin and Stuart have far more Rails experience than I do, and I expect that fact will be heavily reflected in any comparison between this plugin and Streamlined.
All objects it encounters can be usefully represented to a human as a string. It achieves this by adding a to_label method to Object, which will return the first available of (label, name, to_s, or inspect).
Your access control requirements for the administration section are relatively "all or nothing". I intend to add simple class- and fieldset- level declarative permission checking soonish (whenever I start to need it). Access control based on querying individual objects should come at some point, but I don't anticipate needing that level of control any time soon. You can currently customise which fields are displayed (the field list is a block of code, after all), but will end up with empty fieldsets if you don't include any.
If you have any access control (which I expect will pretty much always be the case), you must have a User constant, it must respond to one of (authenticate, login, or find_by_username_and_password), and that method must take two strings, and return nil for failure or a non-false value for success. It *should* return the authenticated user's model — if the returned value responds to one or more of (active?, enabled?, disabled?, and admin?), they will be treated appropriately. The currently logged-in user (as returned by the authentication function) will be looked for and stored in session[:user], so if other parts of your site do the same, things will Just Work. I'm concious of the fact that storing ActiveRecord instances in the session is inadvisable, and will probably change this sometime soon.
Initially (after installing the plugin, obviously), you need to add a few lines to the bottom of your environment.rb:
AutoAdmin.config do |admin|
# This information is used by the theme to construct a useful
# header; the first parameter is the full URL of the main site, the
# second is the displayed name of the site, and the third (optional)
# parameter is the title for the administration site.
admin.set_site_info 'http://www.example.tld/', 'example.tld'
# "Primary Objects" are those for which lists should be directly
# accessible from the home page.
admin.primary_objects = %w(actor film user)
admin.theme = :django # Optional; this is the default.
end
Having done that, you can now (re-)start script/server, and navigate to http://localhost:3000/admin/. Yes, it installs its own routes. Yes, they're hard-coded. Yes, that needs to change... for now, just don't try to use /admin/ for anything else. :)
To customise which fields appear in the edit and list screens, you go on to...
The plugin adds a number of singleton methods to ActiveRecord::Base, which permit you to declare how the administration interface should behave.
This set of methods, which are quite central to the utility of the plugin, have grown rather organically, over a period of time (as has my Ruby-fu). I've attempted to clear out the most glaring API inconsitencies, but it's still a bit of a mess. Some of the implementations definitely leave a bit to be desired. Cleaning this up is near the top of my TODO list. That said, it should all work. :)
I really need to go through and write decent documentation for all the published methods, but for now, the following summary should at least act as a guide. Essentially, inside the model, you can use the following methods:
MyModel.search(many, query, options={})
wrapper around MyModel.find(many, options).WARNING: This has no tests, and I'm almost certain it will break horribly if you try to use anything other than static_text.
WARNING: This also has no tests, and I believe it will break horribly if you try to use it at all.
A number of the above methods provide for a block to declare what
fields are to be shown. This is achieved by yielding a builder to the
block. Depending on context, the mood of a theme author, and the phase
of the moon, a given block will see several builders in its lifetime.
Not all builders will have an active object; all will respond to the
object method, though. A basic field definition block will
just call a field helper on the builder for each field that it wishes to
display. The auto_field helper (which automatically
determines an appropriate field type based on column and association
metadata) is available if you only want to specify the field type for
some of the fields. All field helpers take (field_name,
options={}, *other_stuff). Most just take the two parameters, and
I'm considering deprecating the extra parameters on those that currently
support them. Note that unlike a standard builder, you don't have to do
anything with the return value; the theme's actual
FormBuilder is wrapped by a
DeclarativeFormBuilder, which takes care of that for you.
In theory, there's no compelling reason you can't add complex logic to a field definition block, such as examining the current user, or even the builder's active object (though I strongly encourage you to handle nil permissively, at this stage). It would be unwise to vary the fields returned based on the object for a list view, for fairly obvious reasons.
Simple helpers that just delegate to ActionView's FormBuilder:
hidden_field, date_select, datetime_select, text_field, text_area, check_box
select and radio_group operate in basically the
same way; they both provide a method of selecting one out of several
choices (ignoring select :multiple, that is). Note that
select's list of choices, normally the second parameter to
the select helper, has been relegated to a
:choices entry in the options, for API
consistency.
static_text just outputs an HTML-escaped string representation of the field's value. It is useful both for read-only fields in forms, and as the primary helper in lists.
auto_field, as discussed above, will automatically select a suitable field helper, based on the column and association metadata. Where there are multiple suitable candidates, it tries to go for the more generally-applicable choice (for example, it favours a select over a radio_group for a belongs_to association).
None of the following actually work, but they're defined, waiting for me to come back and write them. html_area will eventually use FCKeditor by default, and presumably the file/image fields will delegate to file_column.
html_area, hyperlink, file_field, image_field, static_image, static_file, static_html
The theme bundled with the plugin is named 'django'; all credit for its excellent appearance goes to the Django project. I hope we can get a couple of standard themes, but they won't be coming from me... experience shows that I shouldn't try to make things look good. I believe I've successfully drawn lines in all the right places for what is in the plugin's core, and what's in a theme. I've already developed most of a second theme (which will not be released) for my employer, so the infrastructure is mostly proven. A more coherent HOWTO on creating themes (which can just be installed as seperate Rails plugins, then selected in environment.rb) will be forthcoming Real Soon Now, though this section has ended up covering most of the basics.
The 30 second summary — a theme comprises:
Extending your theme module with AutoAdmin::ThemeHelpers
will help to keep the module fairly DRY; it provides a helper
method, which can be given a list of modules and/or a block (much like
ActionController's), and directs the
view_directory and asset_root methods to a
directory(*subdirs) singleton method, which you must define
— presumably using __FILE__.
NB: For good reasons that I can't remember right now, a couple of
helper methods have APIs that don't match the standard Rails
FormBuilder, despite matching names. The one that comes to
mind is select — the choices have been moved into the
options hash, to keep all method signatures of the form
(field_name, options, *other_stuff).
Decent documentation. :)
The ability for the application to inject custom field types into the base FormBuilder and FormProcessor. The theme-specific versions of these classes are available so that, for example, a theme can decide how a date_field should be presented, and can correspondingly recover the values from multiple inputs... they don't map as well to an application's requirement for a 'currency' field. Of course, there's nothing stopping an application re-opening the classes and adding an appropriate helper method to each... there's just a bit of undesirable complexity involved if you want auto_field to detect and use it (which suggests to me that auto_field needs a bit of a rethink).
A way for the application to reliably extend the AutoAdminController, and add appropriate views somewhere, for those occasions when you have a couple of screens that need to be hand-crafted, such as a statistics display, or a particular edit screen that needs a specialised workflow. Note that if you feel this constraint too much, you're probably pushing the plugin into a role it doesn't fit.
Simple methods allowing an application to add navigation options, and perhaps the ability to insert Components into the "dashboard" on the index page?
A top-level "menu", containing links to the primary object lists by default, that a theme can permanently display.
It's probably a better idea to store the logged-in user's id, instead of the user object, in the session.
After starting off defining the administration interfaces directly in the models (as Django does), I was strongly considering moving them all into an application-specific controller, that would subclass AutoAdminController. I haven't gotten around to doing that, and am now quite intruiged by the approach taken by Streamlined — adding a new type of class. Any such move is primarily aimed at solving a problem I'm not yet sufferring, though, so for now it's just a topic to ponder.
Matthew Draper - matthew@trebex.net