AjaxScaffold has been deprecated in favour of ActiveScaffold
The aim of this post is to show how to extend or replace the default functionality of the Ajax Scaffold plugin (ASp). I will go over two areas, firstly how you filter the dataset the table displays, and secondly the methods you can override to really gain control.
Simple Filtering of the Dataset
In order to filter the data that the table displays you need to simply override a single method (for each model) in your controller.
conditions_for_{table_name}_collection
This method is expected to return the same array that the Rails find() method accepts as its :conditions parameter.
Lets take a simple user model as an example. The db table has the following columns: id, name, password, employer_id where the employer_id represents a has-one relationship to the user’s employer (represented by another table). Now lets say we only want to see users from the current employer in our table, then all we need to do is define the following method in our controller:
def conditions_for_users_collection [ 'employer_id = ?', params[:current_employer] ] end
This of course assumes that the current request defines :current_employer as the employer’s id.
You can use any conditional statements you like, if they would work in the Rails find() method, they will work here.
More Complex Filtering
At a lower level the collection used by the table is provided by a combination of three methods:
- count_{table_name}_collection(model, options)
- page_and_sort_{table_name}_collection(model, options, paginator)
- page_and_sort_{table_name}_collection_with_method(model, options, paginator)
These methods represent a slightly extended version of the methods found in the Rails paginator. The first two are called for any normal page or sort (i.e not using a ScaffoldColumn defined using the :sort parameter) and the last is called only for ScaffoldColumns defined with, wait for it, the :sort parameter.
For example lets say we have a set of Report objects that are displayed using the following column definitions:
@@scaffold_columns = [
AjaxScaffold::ScaffoldColumn.new(Report, {
:name => "title",
:eval => "row.current_definition.title",
:sort => 'current_definition.title'}),
AjaxScaffold::ScaffoldColumn.new(Report, {
:name => "viewed",
:eval => "row.views",
:sort => 'views' }),
]
We also have defined a method that returns the reports for a particular project, or the reports for a project in a particular category (all of which would be difficult to do with a simple :conditions statement):
def get_reports
project = get_project
reports = project.reports
if request.post?
selected_category = params[:category]
reports = project.reports_for_category(selected_category) if selected_category
end
reports
end
We could then define the three methods above as follows (in ReportsController.rb):
def count_reports_collection(model, options)
get_reports.size
end
def page_and_sort_reports_collection_with_method(model, options, paginator)
collection = get_reports
order_parts = options[:order].split(' ')
sort_collection_by_method(collection, order_parts[0], order_parts[1]).slice(paginator.current.offset,options[:per_page])
end
def page_and_sort_reports_collection(model, options, paginator)
page_and_sort_reports_collection_with_method(model, options, paginator)
end
This would cause every sort / page to use the with_method methods, as we need due to all columns using the :sort parameter. Our count would come from our get_reports method and everything should work as expected.
Other Extension Points
Here are some of the other methods that you can override to change the behaviour of the scaffold, its probably a good idea to actually look at the plugin code to see how each method is used, although some are pretty self explanatory.
In all of these {prefix} is defined as {table_name}_.That is the table name with an underscore appended. {suffix} is defined as _{model_name_in_lower_case}. That is the model name in lower case with an underscore prepended. These are only required in multi table scenarios (I have written it like this here to match the actual code). The reason for doing this is to give CRUD like methods when only using a single table.
Default Sort Column
By default the rows are sorted using the primary key, id. To change this you can override: default_sort / default_{prefix}sort
def default_{prefix}sort
"users.name"
end
Default Sort Direction
To change this from ‘asc’ you can override: default_sort_direction / default_{prefix}sort_direction. The only other possible return value is “desc”.
def default_{prefix}sort_direction
"desc"
end
Create
If you want to change the default behaviour when a model is created you can override the following method which is called internally:
def do_create#{suffix}
@user = User.new(params[:user])
@successful = @user.save
end
Update
If you want to change the default behaviour when a model is updated you can override the following method which is called internally:
def do_update#{suffix}
@user = User.find(params[:id])
@successful = @user.update_attributes(params[:user])
end
Table
To add custom behvaiour to the table rendering you can use this method. This is really to add functionality, you still need to call the methods as shown or things may go a little wrong:
def #{prefix}table
self.#{prefix}table_setup
OTHER CODE GOES IN HERE
render#{suffix}_template(:action => 'table')
end
Added in 3.2.2
New
Called before the new form appears
def do_new#{suffix}
@user = User.new
@successful = true
end
Edit
Called to find the object to edit
def do_edit#{suffix}
@user = User.find(params[:id])
@successful = !@user.nil?
end
Destroy
Called to find and destroy an object
def do_destroy#{suffix}
@successful = User.find(params[:id]).destroy
end










55 Comments
think theres a bug in your conditions example.
def conditions_for_collection
[ 'employer_id' => params[:current_employer] ]
end
makes nothing happen.
def conditions_for_users_collection
[ 'employer_id' => params[:current_employer] ]
end
produces an not method error.
but
def conditions_for_users_collection
[ 'employer_id=?',params[:current_employer] ]
end
works.
Makes sense when you look at :conditions in the rails api
gabs
Hi Gabson,
Well spotted. I have updated the post.
Thanks
Scott.
Happy to help
By the way, I was in such a rush before, I forgot to mention how much I appreciate your work.
Thank you very very much for all your time and efford! You guys have done a great job.
Now, back to finding out how I can maintain parent – child relationships between scaffolds, when editing.
Cheers,
Gabs.
Hi,
Great work. I needed to allow users to edit only the records belonging to them, so I changed the ajax_scaffold.rb to implement a do_edit method. Then I overrode it in my controller.I am not sure this is the best way to do this but it worked. Here is my modified code:
def edit#{suffix}
begin
do_edit#{suffix}
rescue
flash[:error], @successful = $!.to_s, false
end
return render#{suffix}_template(:action => ‘edit.rjs’) if request.xhr?
if @successful
@options = { :scaffold_id => params[:scaffold_id], :action => "update", :id => params[:id] }
render#{suffix}_template(:action => "_new_edit.rhtml", :layout => true)
else
#{prefix}return_to_main
end
end
def do_edit#{suffix}
@#{singular_name} = #{class_name}.find(params[:id])
@successful = !@#{singular_name}.nil?
end
@gabson: No problem. The relationship thing is a bit of a tricky one isn’t it. We haven’t really looked at how to implement it in the plugin yet. There is some info on then "wiki":http://ajaxscaffold.stikipad.com about doing it with the generator. Let me know how you get on.
@Haim: Your approach seems reasonable (EDIT: although see my later comment on why changing the plugin isn’t a good idea), we probably should of included an extension point for the edit, and maybe for the new. I think I probably would call your do_edit method something different as it isn’t actually doing the edit. Maybe find#{suffix} or something that actually implies the action (see below). I take it you actually meant you updated the code in the ajax_scaffold_plugin.rb file.
<pre>
def edit#{suffix}
begin
find#{suffix}
rescue
flash[:error], @successful = $!.to_s, false
end
[REST OF METHOD STAYS THE SAME]
end
def find#{suffix}
@#{singular_name} = #{class_name}.find(params[:id])
@successful = !@#{singular_name}.nil?
end
</pre>
Cheers guys,
Scott.
Yes, I did change it in the ajax_scaffold_plugin.rb, and yes your naming is probably better. I also added it to the destroy method, so I placed the find method in the private section, and created the following two methods, each right after the relevant main method. Here is edit:
<pre>
def edit#{suffix}
begin
prepare_edit#{suffix}
rescue
flash[:error], @successful = $!.to_s, false
end
[REST OF METHOD STAYS THE SAME]
end
def prepare_edit#{suffix}
find#{suffix}
end
</pre>
Thanks.
Haim
Btw, how can you format the code here like you did?
Is there a way to override this code so it will redirect to list instead of table?:
<pre>
nly => [ :destroy#{suffix}, :create#{suffix}, :update#{suffix} ],
verify :method => :post,
:redirect_to => { :action => :#{prefix}table }
</pre>
Hi Haim,
For the formatting you just wrap the code in ‘pre’ tags (I added them to your comment).
As for overriding the verify method, I think if you just redefine it in your controller then that will be picked up. All of those post actions are from ajax calls though so you need to be sure thats what you want / need to do.
Scott.
@Haim: Just a point, but if you can (and I see no reason why you can’t) you should really implement these changes in your controller and not change the plugin code. That way you can upgrade without fear of breaking anything.
With this particular change I may well include something like it, but it would be better to override the plugin functionality on a controller by controller basis.
It doesn’t look like your conditions_for_users_collection example has been updated to change the ‘=>’ to ‘,’
thanks,
-Chad
I tried putting the following in my controller, but it did not work (it stil redirects to the table action:
nly => [ :destroy, :create, :update ], :redirect_to => { :action => :list }
<pre>
verify :method => :post,
</pre>
I want to change this because if someone is trying to access the destroy method by typing the url directly they will get the table with no layout or css formatting (ugly).
@Chad: Thanks, updated. The other syntax (but using a hash not an array):
<pre>
:conditions => { :employer_id => params[:current_employer] }
</pre>
is available in edge Rails, see "this Ryan Daigle post":http://www.ryandaigle.com/articles/2006/06/06/whats-new-in-edge-rails-convenient-finder-parameter-hashes. So if you are using edge the conditions method can return a hash like this too.
<br/>
@Haim: Fair enough. I guess thats actually more likely than someone submitting an ajax request using a GET anyway. Just place your declaration before the ajax_scaffold call and that should work.
It worked, thanks. I guess I can also add
hr => true,
nly => [ :destroy, :create, :update ], :redirect_to => { :action => :table}
<pre>
verify :method => :post,
</pre>
and solve the xhr get problem.
Thanks again
Haim
Yep, sounds good. This seems like a good thing to have in the plugin as default really, as do the other extension points.
Thanks for the feedback Haim.
Scott.
Hi Scott,
first of all: great work, nice plugin, just what many of us generator-users wanted…
Elsewhere you mentioned either to use the generator OR the plugin, not both at the same time. So (wanting to migrate) I got another question concerning the <a href="http://ajaxscaffold.stikipad.com/doc/show/Integrating+Associations">wiki-article</a> about integrating associations. With the generator it works well, but I’m just too stupid to do the same with the plugin. Is this possible at all? If yes, a short description would be more than perfect.
thx
Hi B,
Thanks. You should be able to use the latest version of the generator and the plugin together fine now, Richard fixed the outstanding issues a couple of weeks ago.
As for the associations, unfortunately I haven’t had time yet to sit down and figure it out. If you have any success please let me know.
Thanks
Scott.
Update on my problem with the top and bottom table borders:
I had been using IE (which works fine with the generator code, but not the plugin-based code on my PC). When I load http://localhost:3000/users in Firefox, it renders perfectly. Can someone try a plugin-based scaffold in IE6 and let me know if the problem is mine only?
Hi Terry (there is also some more on this on the other post..),
You can see the issue in IE in my demo at http://fckeditor.caronsoftware.com. So it has happening for me too. Its now on the todo list.
Cheers Scott.
I implemented a live search using a combo of a text_field_tag and observe_field. I pass the value of the text field to my custom conditions_for_{pluralname}_collection where I filter through [ 'somefield LIKE ?', @phrase ].
This works very well as long as I keep to fields in the same table. But I can’t get joining with other tables to work as I would want to search on results in the scaffold from a joined table. I see that the page_and_sort_#{plural_name}_collection_with_method does accept a join parameter, but I can’t seem to pass the magically correct parameters.
Could someone help me out with an example?
Thanks for the good work! /Michael
Hi Michael,
If you look at the example above in the "More Complex Filtering" section you can see the basis for solving your problem. All you need to do is define a method like the get_reports which returns the filtered collection. Within that method you can then use any of the scoped ActiveRecord methods or find_by_sql to do your joins.
Cheers
Scott.
I’m a RoR newbie, so take this with usual caveats, etc.
I’m setting up dynamic forms for topics based on a topic_type and the topic_type_fields associated with them. I’ve enjoyed the plugin thus far and have used the overriding of supporting view files via app/views/my_model_plural/ successfully. Namely I’ve added two additional forms to the topic_type edit. Still some kinks with the ajax, but basically working.
However, for new topics, before a dynamic form can be created, a user has to pick a topic_type and then proceed to the generated form. I’ve tried to place an intermediate "pick_topic_type_id" by adding it to my controller, copying and modifying to suit various bits and pieces of _form.rhtml, _new_edit.rhtml, and new.rjs, etc., but I keep getting caught up on loss of the ajaxscaffold instance variables in the pick_topic_type_id stage.
Whew. So I guess my questions are two fold:
* If you were me, what would you do to achieve the same outcome (it needs to be able to fall back to non-js working version)?
* Am I missing a simple way of preserving these instance variables while going through the intermediate form?
Cheers,
Walter
I ended up switching to the generator which worked for what I needed to do.
Cheers,
Walter
Hi Walter,
Glad to hear you got one of them to do your bidding…
Cheers
Scott.
Scott,
Thanks for the plugin!
Trying it with JRuby, I noticed that pagination, and footer page number links, are fine, but footer Previous and Next are unresponsive. (These are fine with Ruby of course.) Any thoughts on what the cause might be?
Regards,
Raphael
Is it possible to easily override the format that the plugin uses to display dates?
I’m a bit of a RoR newb, and I found in the plugin code where it’s set – but I’m not sure if it’s overridable, or whether I’d have to change the plugin itself (obviously not good for upgrade purposes).
Thanks,
Bryan
@Raphael – I would guess that it may be to do with the javascript calls the previous and next links make to the dhtml_history code. They are in the :before section of the helper. You can take them out without affecting the main functionality of the code. Just the back button on the browser will stop working.
@ Bryan – Probably the easiest thing to do is copy the formating code from the plugin to the helper file for your controller and call it something different. Then simply copy the _row.rhtml template over to the app/views/controller dir and change the call in it to format_column to match your renamed method.
Cheers
Scott.
How can we filter the columns output into the table? Say based on authentication roles…
ie. I want an admin to see columns a,b,c,d,e
but I want a regular user to see only b,d,e
help!
Hi Jonathon,
I think I will put up a post describing how to do this tomorrow (bit long winded for a comment) as its come up before and is I think a pretty standard thing to do.
Scott.
Ok, again I’m a newb… thanks for the tutorial and tips… I’m trying to create an admin portal and when an admin creates a user the ruby on rails code needs to make a couple of xmlrpc calls to existing systems that have xmlrpc APIs for configuring users and such…
I tried creating a do_create#{suffix} method in my controller but that doesn’t seem to do anything, I tried putting it in the model also didn’t do anything… and I also don’t know where to "require xmlrpc/client" should I put that in my controller or in my environment.rb?
my controller is called Admin and my user model is User. I have the plugin installed and working (I can create new users in the database). I’ve tried calling the function do_create_user, do_create_users, do_create#{suffix} in both the model and the controller… it never seems to do anything, with those functions in there it still creates users just fine, its not making any errors happen, but I’ve tried to making the do_create render some text to see if it is calling my code at all and it doesn’t seem to be.
Ok, I figured it out.. I don’t know why do_create_user wasn’t working… do_create is what does work for me anyway….
Hi Tom, you only need to add the class name if you have passed the :suffix option to the ajax_scaffold call. This was to allow multiple tables on a page. However, that behaviour is deprecated in the next version so this potential complication will vanish.
Cheers
Scott
Hi Scott,
I’m trying to display a custom column that’s, for example, a concatenation of data from two different columns;
ScaffoldColumn.new(Obj, {:name => ‘custom’, :eval => "row.data1 + ‘:’ + row.data2" })
DOESN’T work. What should the syntax of the eval be, please?
Thanks.
Nevermind. I needed to use
<pre>
:eval => "row.data1.to_s + ‘:’ + row.data2.to_s"
</pre>
Is there a way to add custom actions to each record other than create, edit, delete? For example, in our application we have a ‘manage’ action for each account which lists all the associated account records (users, bills, orders, etc.)
Thanks for all your work and this wonderful plugin!
Hi Shimon, simply copy the _row.rhtml template to your controllers template directory and then add the custom actions there.
Scott.
Hi Scott,
Thanks for all the great work you have put in the plugin and the documentation.
One small thing. I think you made a typo in the ‘More Complex Filtering’ paragraph. You showed the three methods to override:
* def count_reports_collection
* def page_and_sort_reports_collection_with_method
* def page_and_sorts_reports_collection
The last method should be ‘page_and_sort_reports_collection’ (‘_sorts_’ -> ‘_sort_’).
One other thing. On the wiki (http://ajaxscaffold.stikipad.com/doc/) in the ‘Differences between the generator and the plugin’ section it is written that the plugin doesn’t have any unit/functional tests. Does that mean that the testing is not automated? If so, do you have plans to create unit/functional tests in the future?
Thanks again!
hi Scott,
Is it possible to use :include in the queries to have lazy population; I have the impression that it is foreseen but it doesn’t show up in the upper API’s.
Thanks for the plugin.
hi Scott,
I have made the rowactions a partial; makes it easy to override them in a view.
made a include_for_#{plural}_collection and pass that to options[;include]; easy
@Sjors, thanks for pointing that out, I’ll update the post. As for the tests right now, well there aren’t any. But they are in the pipeline……
@Bart, cool. Thanks for posting that back. Glad you got it working. We have done something similar with the actions in the next version.
Cheers
Scott.
Scott, hi
Any news on how to make it work [the plugin] in a parent-child relation ? Saw gabson’s post, but nothing more.
I really, really don’t want to go back to the generator, but I might have no choice ? Any advice appreciated.
Thanks
BTW, amaising great stuff you did here !!!
Hi Valin, thanks. Unfortunately as this stuff is much better supported in v4.0 I haven’t really spent any time trying to get the associations to work in v3.*. There is no fundamental reason why its shouldn’t though.
Cheers
Scott.
Hi, I’m trying to use the plugin and the single table inheritance, but I don’t know how. Are there any guide like the ajax scaffold generator to build scaffolds with single table inheritance?? There’s an example with person and pets.
Thanks.
Ariel
I’m just starting with R/R and this plugin is saving me a lot of hassle. Only one problem so far. I’m working on a little sandbox app – a quotes app – and I have authors has_many quotes. The author_id field and column won’t show up in the table or forms. I don’t care if it’s a number, I just want to be able to enter a value.
I was able to get the table for show the author_id column through the instructions on the other page on this site. But how can I get the author_id field to show up in the edit form?
@Ariel, I’m not sure why you would have difficulties with STI, just pass the appropriate model name to the ajax_scaffold call and all should work fine.
@Mark, to change the edit form you need to override the _form.rhtml template by copying it your controllers view dir and altering it to suit your needs.
Cheers
Scott.
Great work Scott, I use ajax scaffold too a great extent and hence I could spot a bug (not sure) in the conditions_for_methodName_collection.
As you had said, you can put any query which works in conditions of find in Rails, but I could not use nested SQL which could be used in the find but not the ajax scaffold method.
waiting for response
the ajax_scaffold plugin needs to
<blockquote>require ‘csv.rb’</blockquote>
Otherwise I get an "uninitialized constant MyController::CSV" error when using "totals".
That said, the plugin seems to rock. Thanks! Still need to check, whether extensions (group by aggregations etc.) can be implemented in a clean and easy way, though.
I modified the _form field to fit my needs. I have a helper method that adds a text_field_with_auto_complete field to the form. My form is failing to show up and I see in the console that the action is returning an http error 500 23217 but that is all it shows. Is there any way I can make it show the standard rails error page that shows me the exact error (not just a number)?
Why isn’t sorting and pagination done by the database system, using something like
rder => options[:order],
ffset => paginator.current.offset,
<pre><code>
model.find
:limit => options[:per_page],
…
</pre></code>
I actually implemented a <code>page_and_sort_my_model_collection_with_method</code> that overrides the default, because I need a special column and I implemented sorting/pagination similar to the code shown. This should be faster and more efficient than doing it in Ruby, shouldn’t it?
Are there any restrictions to the SQL sorting/pagination or what is the reason to do that in Ruby?
@Horst, the sorting is only done in ruby for methods that don’t exist in the database. i.e for virtual columns. If it can be done in sql it is. As for the csv.rb, that should already be required if needed. I’ll have to take a look.
@Nayak, I haven’t tried nested SQL, perhaps you could email me an example.
@juantar, try using firefox and firebug that should give you all the info you need to debug ajax issues.
Hi!
I’ve extended ajax_scaffold a bit with an extra action: details
it’s pretty much like edit except you don’t get to edit anything
It allows you to create a custom detailed view of an entry containing data you don’t want or can’t show in the table.
It’s still pretty crude and probably has some load from copying it from edit, but if anybody wants it, let me know.
If there is some real intrest i’ll clean it up and test it.
An example of why i made it:
I have a People in my model with about 10 attributes and any amount of custom attributes which i wouldn’t want to show in the table ofcourse, but i don’t want to leave the table to take a quick peek at them. So i click ‘details’ and just like edit a view pops up but the fields are uneditable and there only is a close action.
It even works with :except
Thank you for your helpful tutorial. I’m new to RoR and have found your scaffold plugin very helpful.
Is there a way to only filter the dataset for a table under certain conditions? I would like to display the table with all data on one page and a subset of the data on another page. I’m using render_component :action => ‘table’. With the filter example, I can get the correct subset but I don’t know how to have the full set sometimes and the subset sometimes.
Thanks.
Hi Jo, you can just pass parameters in the component call to use in the decision making process:
render_component :action => ‘table’, :params => params.merge(:filter => ‘all’)
or something.
Cheers
Scott
H,I override the method do_update#{suffix},but there is a exception when I do "edit" option,why?
thanks.
Hi, I suspect because you are now using "ActiveScaffold":http://www.activescaffold.com. You will need to refer to those docs for customization. The original AjaxScaffold plugin is now deprecated.
Cheers
Scott
Does anyone knows how to get FCKEDITOR to be saved to the database while editing in the activescaffold ?
I’ve used a column overide to load the Fckeditor but it doesn’t allow me to save it to the database.