Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
A Git fork of the original auto_admin repository, with just a bunch of changes to scratch my own itch
Ruby
tag: matthew

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
lib
test
themes/django
LICENSE
README
init.rb

README

Rails AutoAdmin Plugin

What is it?

A plugin for Ruby on Rails that automagically creates an administration
interface, based on your models. It is heavily inspired by the Django
[1] 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 [2].

  [1] Django:      http://www.djangoproject.com/
  [2] Streamlined: http://streamlined.relevancellc.com/


Example?

  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

  Results in:
    http://trebex.net/~matthew/auto-admin-0.0/list.png
    http://trebex.net/~matthew/auto-admin-0.0/edit.png


What isn't it?

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.


Where is it?

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.


Is it usable?

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.


What does it assume?

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.


How do I use it?

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...


How does it work? - Part I, Declarative UI definition

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:
  object_group(group_name)
    # Declares which 'object group' this object belongs to, for use in
    # the interface. Currently, this is used to group together related
    # objects on the index page.
  sort_by(column, reverse=false)
    # Instructs the list view to sort on the specified column by
    # default.
  search_by(*columns)
    # Add rudimentary text searching across the named columns. Note that
    # this defines a MyModel.search(many, query, options={}) wrapper
    # around MyModel.find(many, options).
  filter_by(*columns)
    # Allow filtering of the list screen by the named columns (filtering
    # currently works for: custom, boolean, date, belongs_to, has_one,
    # and string). Note that the last three will do rather nasty and
    # sub-optimal queries to determine the filter options.
  default_filter(filters)
    # Takes a hash of (column, value) pairs, to default a filter to
    # something other than 'All'.
  filter_options_for(column, choices, &block)
    # Specifies a fixed set of choices to be offered as filter options
    # instead of automatically working it out. Choices should be a
    # (value, label) hash. The optional block will be given each value
    # in turn, and should return an SQL condition fragment.
  column_labels(labels)
    # Takes a hash of (column, label) pairs, to change the default label
    # for a field to explicitly define the human label for a column.
    # This label will be the default used in both list and edit views.
  list_columns(*columns, &proc)
    # Takes either a simple-list of column names, or a Field Definition
    # Block (see below)
  admin_fieldset(label='', *columns, &proc)
    # Defines a fieldset for edit views. For simple use, you can just
    # give it a list of columns. Once you get started, you'll want to
    # pass a Field Definition Block, though.
  admin_child_table(label, collection, options={}, &proc)
    # Defines a fieldset for edit views, to show a table of items from a
    # child collection. It uses a Field Definition Block to declare what
    # columns should be shown. Generally, you'd want to use the
    # static_text helper, I suspect.
    # WARNING: This has no tests, and I'm almost certain it will break
    # horribly if you try to use anything other than static_text.
  admin_child_form(collection, options={}, &proc)
    # Defines a "fieldset" for edit views, to show *several* fieldsets,
    # each containing one object from a child collection. It uses a
    # Field Definition Block to declare what columns should be shown.
    # I don't think it'd be wise to use this on a large collection, but
    # it's your application. :)
    # WARNING: This also has no tests, and I believe it will break
    # horribly if you try to use it at all.

Field Definition Block?!?

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.

Available Form Helpers

Simple helpers that just delegate to the 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


How does it work? - Part II, Themes

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:
  FormBuilder (subclass of AutoAdminSimpleTheme::FormBuilder), to create
  an Edit screen (a real form)
  
  TableBuilder (subclass of AutoAdmin::TableBuilder(FormBuilder)), to
  create a List screen (a creative interpretation of "form", which seems
  to map surprisingly well, for now). 
  
  FormProcessor (subclass of AutoAdminSimpleTheme::FormProcessor), which
  implements the same set of helper methods as the FormBuilders, but
  instead of returning HTML, its job is to perform any transformations
  on the params hash to correspond with unusual form field
  representations -- the base FormProcessor transforms keys referencing
  associations to reference the underlying columns (actor -> actor_id),
  for example. This class will often be empty, especially once I provide
  a facility with which to inject custom field helpers (for composed_of
  and maybe some belongs_to, mostly) into the base builder and
  processor.

  A complete set of views, including a layout, which delegate the hard
  work to the FormBuilders.

  A 'public' directory, containing any required image, javascript, and
  stylesheet assets.

  A wrapper module, AutoAdmin#{name}Theme, which is responsible for:
    * Containing the FormBuilders and FormProcessor
    * Returning the full filesystem path to the 'views' and 'public'
      directories
    * Returing any theme-specific helpers, for injection into the
      controller
    * Injecting any theme-specific includes for ActiveRecord::Base
      (I've proven this to be possible, though can't think of a sane
      reason a theme would want to do so)

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, 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).


What's planned, but missing?

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.


Longer-term architectural considerations?

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.




Something went wrong with that request. Please try again.