This repository has been archived by the owner on Nov 17, 2017. It is now read-only.
/
README
426 lines (323 loc) · 18.5 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
= 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[http://www.djangoproject.com/] 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[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.auto_field :active
b.select :store
end
admin_child_table 'Payments', :payments do |b|
b.static_text :payment_date
b.static_text :amount
end
end
== 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?
The public git repository is hosted by Github[http://github.com] and can be
reached starting from http://github.com/baldowl/auto_admin
I cannot stress enough that this is just a fork of the original code written
by Matthew Draper; if you prefer the original version, then get it from the
Subversion repository at http://svn.trebex.net/auto-admin/trunk/auto-admin
== 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 specify it with the <tt>admin_model=</tt> and
<tt>admin_model_id=</tt> methods in the configuration block of AutoAdmin; the
class must respond to +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 *must* return
the authenticated user's id (or, in a less ideal turn, the user object
itself: we will extract the id by ourself) -- the id will be stored in the
session using the key provided by +admin_model_id+ and the currently
logged-in user will be looked for; if the returned value responds to one or
more of <tt>active?</tt>, <tt>enabled?</tt>, <tt>disabled?</tt> or
<tt>admin?</tt>, they will be treated appropriately. So if other parts of
your site do the same, things will Just Work.
=== History
Admins' actions are automatically recorded and shown if the optional
<tt>admin_histories</tt> table is available.
See AutoAdminController#history for details.
== What do you need?
To use AutoAdmin with Rails 2.* you need an extra plugin,
will_paginate[git://github.com/mislav/will_paginate.git], because the
pagination mechanism has been removed from the Rails core base.
To use the +text_field_with_auto_complete+ helper you need an extra plugin,
auto_complete[git://github.com/rails/auto_complete.git], because the
autocompletion mechanism has been removed from the Rails core base.
For the optional export mechanism you also need:
* the *faster_csv* gem for the CSV export module;
* the <b>pdf-writer</b> gem for the PDF export module.
== 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 or in an initializer file:
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.com/', 'example.com',
'Administration area for example.com'
# "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.
# The configurable, optional access control system.
admin.admin_model = Account
admin.admin_model_id = :account_id
# The optional export mechanism.
admin.save_as = %w(pdf csv)
end
Having done that, you can now (re-)start <tt>script/server</tt>, and navigate
to http://localhost:3000/admin/. Yes, it installs its own routes, but they are
partially configurable; for now, just don't try to use <tt>/admin/</tt> 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 <tt>MyModel.search(many, query, options={})</tt> wrapper around
<tt>MyModel.find(many, options)</tt>.
[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 <tt>(field_name, options={}, *other_stuff)</tt>. 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+, +secure_password+.
* +select+ and +radio_group+ operate in basically the same way; they both
provide a method of selecting one out of several choices (ignoring
<tt>select :multiple</tt>, that is). Note that select's list of choices,
normally the second parameter to the select helper, has been relegated to a
<tt>:choices</tt> 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).
* +static_image+ sports a number of options used to build an hash suitable for
the +tag+ helper responsible for the creation of final <tt><img></tt> tag:
* <tt>:controller</tt> (default "auto_admin") and :action (default "edit")
allows to select a controller which should return the image based upon
this object's id;
* :src override the previous two options and can be used with any URL,
static or dynamic;
* with :size one can write the width and height used to show the image
(format: "XxY");
* :alt, which defaults to this object's to_label().
Everything else is passed as is to the +tag+ helper (so one could use
<tt>:style</tt> or <tt>:class</tt> to alter the looks of <tt><img></tt>).
This helper accepts a block, with a single parameter (this object), which
can be used to return an hash to be merged into the options that are about
to be passed to the tag helper.
* +text_field_with_auto_complete+ provides a simple text field with
autocompletion delegating all the work to the homonymous ActionView's
helper; there's a complication, however: there must be a separate controller
which provides the completion data:
b.text_field_with_auto_complete :name,
:completion => { :url => { :controller => 'items',
:action => 'auto_complete_for_item_name' }}
For information on this controller, see Rails documentation. Note that if
you designed your application in a REST-like way, you should tweak your
resources' definitios or add a specific route for the new
<tt>auto_complete_for_<em>model</em>_<em>field</em></tt>.
* 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_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.
== 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.