www.flickr.com
|

Tuesday, June 30, 2009
Mo simple sortable tables in rails
A long time ago I wrote a blog posting on a plugin I created that would allow you to create a sortable table for your rails models with minimal work. Well, 2.5 years later I'm taking the time to write yet another blog post on a revamped version of that plugin that does more with less and is about 10x easier to set up and use. If you refer back to my old posting you'll see that I set out to create a plugin that would allow you to create a non-ajax sortable and paginated table. It certainly worked but involved too much configuration and understanding by you the developer in order to use it. The goal here is the same and the motivation is largely to make it braindead easy to get a table up and running without having to know much of anything. I use this plugin in my apps in user facing pages as well as the starting point for all of my admin pages, completely replacing the ever fragile ActiveScaffold. (Sorry you guys, it totally rocks when it works but it just kept breaking too much for me and I didn't need all the bells and whistles) Over the past couple of years I've completely revamped how this plugin works so let's a have a look at "sortable reloaded" OK let's get started:
- First You either create or use an existing a rails app (2.2.2 is the version I'm using still, not positive but this should work with 2.3.x or any rails 2.x apps without a problem).
- Next we need to install the will_paginate gem. Assuming you're familiar with specifying gems for your app in your environment.rb file here's my entry for will_paginate:
config.gem 'mislav-will_paginate',
:lib => 'will_paginate',
:source => 'http://gems.github.com',
:version => '~> 2.3.6' - Now that we've got the pagination gem in place it's time to install the plugin itself. We can do that with:
ruby script/plugin install git://github.com/kovacs/sortable.git
- Next up I'm going to assume that you have a model for whose objects you'd like to create a paginated, sortable table. You're going to need a model as well as a controller with an entry in your routes.rb file. For example:
in user.rb
class User < ActiveRecord::Base
end
in users_controller.rb
class UsersController < ApplicationController
end
in routes.rb
map.resources :users - Assuming you have that set up configured the simplest possible way to use sortable to create a sortable, paginated, searchable table of your objects is like this:
After you add that snippet to your controller go and hit: http://localhost:3000/users and watch the magic happen. Pretty cool for a one line declaration. The magic happening behind the scenes is that the declaration "sortable_table User" is generating an 'index' action in your UsersController and is using a default index.erb template file for the view of that action. Here's a look at that code: In sortable.rb (vendor/plugins/sortable/lib/sortable.rb)
class UsersController < ApplicationController
sortable_table User
end
in the view for index (vendor/plugins/sortable/views/sortable/index.erb)
def index
get_sorted_objects(params)
render :template => 'sortable/index'
end
Everything is customizable but this is just demonstrating the simplest table you can create with the plugin.
<%= sortable_table %>
- By default you'll notice that the plugin uses all the attributes on your model when it builds the table. I can hear it already: "Dude, I don't want it to show XYZ attribute on my model" to which I'd say "Pfft... way ahead of you dawg". Specify what columns you want displayed in the table by default like this:
class UsersController < ApplicationController
sortable_table User, :display_columns => ['id', 'name', 'email', 'created_at', 'timezone', 'state']
end
Of course this example assumes that you have the attributes on your User model. Replace with your attributes as needed. What you'll end up seeing is this:
- Of course there's a ton of flexibility in the plugin that allows you to place a sortable table on any page you'd like as opposed to having it only show up on an index page that lists all your objects. You can also show a table that sorts, searches, and paginates over more than one object type. So if you wanted to show all your users and one or more attributes from a related object or objects you can easily do that with a few attributes in your sortable_table declaration. You can also easily customize the L&F of the table and turn off searching if you don't want/need it. See more details in sortable.rb and sortable_helper.rb where it outlines all of the options. If there's something that it doesn't do feel free to fork the code and add it yourself and send me a pull request.
- The basic mechanics on how to make use of the plugin in your app is that you want to call:
This call will populate a few variables for you to use:
get_sorted_objects(params, sortable_options)
Then in your view you'll just call:
@objects # The collection of objects that you can display in a table
@headings # The collection of table header objects that are used to build the header and links.
Wherever you'd like that sortable table to appear. It's really that simple. You can easily override the template that's being used to build the table with a parameter to the helper call. For example:
<%= sortable_table %>
Have a look at vendor/plugins/sortable/views/sortable/_table.html.erb for an idea of how to build a table for your objects if you want to have more flexibility than the standard template gives you.
<%= sortable_table :partial => 'loans/loan_table' %>
If you find this plugin useful and especially if you use it on a site that's a project that you're intending to make money with please donate something as it did require time and effort to clean up, document, and package this plugin for general consumption. Your donation will go towards improving this plugin and inspire me to release other plugins that I know would be generally useful to other rails developers. You might want to note that unlike most open sores projects this plugin comes with example code, documented code, tests, and has 100% test coverage as well. It's also being used in production on a real site that charges real money for its products so it's not just someone's hobby project that's going to be abandoned. Think about that while you're busting out your credit card and making a suggested donation of $20 (or whatever you'd like) here:
If you aren't going to donate or are using it for a non-profit project I'd really appreciate a link to my company site: http://www.cablecarsoftware.com. Thanks everyone and hope you enjoy it and find it useful! Oh and finally, get the code here.
I need to implement pagination (and possibly sorting but not a key requirement) for a user-facing page that displays a list of search results. The search results we are getting from Xoopit's CloudQuery service (a hosted search-as-a-service tool--very cool). The search API has pagination and sorting directives but we're not going to the database or ActiveRecord at, for the obvious scalability reasons.
Can I hook this into another backend?
Thanks for the kind words and glad you're finding the plugin useful. You could easily hook into another backend and return a set of ruby objects. Just be aware that the plugin expects the given class is an AR class so you may have to ensure that the class implements methods like 'column_names'. You'd also have to override the sorting logic as it's tied to an RDBMS at the moment. It really would be pretty easy and everything that you would need to change is in sortable.rb.
Feel free to drop me a line directly if you wanted to chat further.
-Michael
I am just trying out the plugin and I am having a bit of a problem. If I insert sortable_table User in the controller it works and shows the whole table. If I add , :display_columns => ['email', 'city'] then it blows up with the following error:
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]
The columns are there. Any ideas?
please ignore my first question regarding the display_columns. I wasn't setting the :default_sort.
Do you have a stack trace for that first error? I'm thinking that perhaps the plugin should change the default sort automatically based on your display_column settings and pick the first column specified or something. I'm also curious because I think the plugin should still work even if you don't specify a :default_sort and you specify the display_columns though I've not looked at the code yet to verify.
Oh and to make your rows editable simply override the table partial template and pass your partial as the param in the view like this:
< %= sortable_table :partial => '/path/to/your/_table_partial.erb' % >
You can find the default one used by sortable in sortable/views/sortable and just copy that to your app's view directory somewhere and modify it to your liking and add your edit links to each row.
Hope that helps,
-Michael
Mario
NoMethodError in Manage clientsController#index
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]
RAILS_ROOT: /Users/mario/Documents/Aptana Studio Workspace/oserver
Application Trace | Framework Trace | Full Trace
/Users/mario/Documents/Aptana Studio Workspace/oserver/vendor/plugins/sortable/lib/sortable.rb:234:in `process_sort'
/Users/mario/Documents/Aptana Studio Workspace/oserver/vendor/plugins/sortable/lib/sortable.rb:193:in `get_sorted_objects'
/Users/mario/Documents/Aptana Studio Workspace/oserver/vendor/plugins/sortable/lib/sortable.rb:172:in `index'
/Library/Ruby/Gems/1.8/gems/actionpack-2.3.3/lib/action_controller/base.rb:1327:in `send'
/Library/Ruby/Gems/1.8/gems/actionpack-2.3.3/lib/action_controller/base.rb:1327:in `perform_action_without_filters'
/Library/Ruby/Gems/1.8/gems/actionpack-2.3.3/lib/action_controller/filters.rb:617:in `call_filters'
/Library/Ruby/Gems/1.8/gems/actionpack-2.3.3/lib/action_controller/filters.rb:610:in `perform_action_without_benchmark'
/Library/Ruby/Gems/1.8/gems/actionpack-2.3.3/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue'
/Library/Ruby/Gems/1.8/gems/activesupport-2.3.3/lib/active_support/core_ext/benchmark.rb:17:in `ms'
/Library/Ruby/Gems/1.8/gems/activesupport-2.3.3/lib/active_support/core_ext/benchmark.rb:10:in `realtime'
/Library/Ruby/Gems/1.8/gems/activesupport-2.3.3/lib/active_support/core_ext/benchmark.rb:17:in `ms'
/Library/Ruby/Gems/1.8/gems/actionpack-2.3.3/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue'
/Library/Ruby/Gems/1.8/gems/actionpack-2.3.3/lib/action_controller/rescue.rb:160:in `perform_action_without_flash'
/Library/Ruby/Gems/1.8/gems/actionpack-2.3.3/lib/action_controller/flash.rb:146:in `perform_action'
/Library/Ruby/Gems/1.8/gems/actionpack-2.3.3/lib/action_controller/base.rb:527:in `send'
/Library/Ruby/Gems/1.8/gems/actionpack-2.3.3/lib/action_controller/base.rb:527:in `process_without_filters'
/Library/Ruby/Gems/1.8/gems/actionpack-2.3.3/lib/action_controller/filters.rb:606:in `sass_old_process'
/Library/Ruby/Gems/1.8/gems/haml-2.2.2/lib/sass/plugin/rails.rb:19:in `process'
/Library/Ruby/Gems/1.8/gems/actionpack-2.3.3/lib/action_controller/base.rb:391:in `process'
/Library/Ruby/Gems/1.8/gems/actionpack-2.3.3/lib/action_controller/base.rb:386:in `call'
/Library/Ruby/Gems/1.8/gems/actionpack-2.3.3/lib/action_controller/routing/route_set.rb:434:in `call'
Request
Parameters:
None
Show session dump
Response
Headers:
{"Content-Type"=>"",
"Cache-Control"=>"no-cache"}
class Order
belongs_to :orderable, :polymorphic => true
class Part
has_many :orders, :as => :orderable
class Layout
has_many :orders, :as => :orderable
I have :include_relations => [:orderable] and I get the table to render correctly, it is just the sort and seach function give an error about eager loading polymorphic associations. I was wondering if you could point me in the right direction as I am quite stuck. And here comes the famous last line "sorry I am still a fair bit of a noob"
Dave
So on your template you'd have:
< %= sortable_table % >
< % @objects = second_set_of_objects_to_sort
@headings = headings_for_objects
% >
< %= sortable_table % >
That'll get you started but I'm guessing that you'll have more things to change.
Thanks for the kind words and I'm glad to hear it's helping you out. Could you post your stack trace? Or what would be even better is if you could create a test case to reproduce the problem and check it into your forked version of the plugin on github but I understand if you're such a noob that you might not feel comfortable modifying and running plugin tests just yet. In case you are up for it though here's how you run the tests:
rake test:plugins PLUGIN=sortable
I'd suggest maybe adding test classes that extand User or ContactInfo to model your scenario there and create a test either in users_controller_test.rb or copy that and create new tests.
ActiveRecord::EagerLoadPolymorphicError in OrdersController#index
Can not eagerly load the polymorphic association :orderable
And the trace.
/host/InstantRails/rails_apps/TT_WithProjects/vendor/rails/activerecord/lib/active_record/associations.rb:1960:in `initialize'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/rails/activerecord/lib/active_record/associations.rb:1853:in `new'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/rails/activerecord/lib/active_record/associations.rb:1853:in `build_join_association'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/rails/activerecord/lib/active_record/associations.rb:1836:in `build'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/rails/activerecord/lib/active_record/associations.rb:1839:in `build'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/rails/activerecord/lib/active_record/associations.rb:1838:in `each'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/rails/activerecord/lib/active_record/associations.rb:1838:in `build'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/rails/activerecord/lib/active_record/associations.rb:1758:in `initialize'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/rails/activerecord/lib/active_record/associations.rb:1334:in `new'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/rails/activerecord/lib/active_record/associations.rb:1334:in `find_with_associations'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/rails/activerecord/lib/active_record/associations.rb:1333:in `catch'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/rails/activerecord/lib/active_record/associations.rb:1333:in `find_with_associations'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/rails/activerecord/lib/active_record/base.rb:1551:in `find_every'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/rails/activerecord/lib/active_record/base.rb:615:in `find'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/plugins/will_paginate/lib/will_paginate/finder.rb:82:in `send'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/plugins/will_paginate/lib/will_paginate/finder.rb:82:in `paginate'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/plugins/will_paginate/lib/will_paginate/collection.rb:87:in `create'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/plugins/will_paginate/lib/will_paginate/finder.rb:76:in `paginate'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/plugins/sortable/lib/sortable.rb:202:in `get_paginated_objects'
/host/InstantRails/rails_apps/TT_WithProjects/vendor/plugins/sortable/lib/sortable.rb:197:in `get_sorted_objects'
/host/InstantRails/rails_apps/TT_WithProjects/app/controllers/orders_controller.rb:202:in `sorted_all'
/host/InstantRails/rails_apps/TT_WithProjects/app/controllers/orders_controller.rb:40:in `index'
I have a lot more reading to do on testing and maybe this is the motivation I need.
Thanks for all the help
Sorry I'm not able to glean what could be wrong from your snippet there. There's no difference between the calls for search, sort, and non-sorting so if it works for just rendering the table it should work for the other scenarios. The eager loading of the association will happen on all of those calls because it's the same exact code path using the same exact params you're passing. Have you figured this out yet? Or is this still broken? Perhaps a pastie of your controller code would help. http://pastie.org/
What I want is a paginated tables of models, and Since each has_many of another model, I'd like to place the submodels, indented, beneath the main model listing.
I understand I can probably do this by hacking up a new sortable table template, and I will start looking into that. However, let's say, for example
- Users have MANY roles
How would I get your sortable table to add a column just indicating the NUMBER of roles (user.roles.length)?
My first attempt looks something like this:
sortable_table User, :table_heading => [['Name', 'name'],['Mail','mail'],['Roles','role_count']],
:sort_map => {'name' => ['user.name'], 'mail' => ['user.email'],
'roles' => ['user.roles.length']},
:include_relations => 'roles'
This (of course) doesn't work -- but perhaps you could suggest an approach which would?
Thanks in advance,
Joe
/Library/Ruby/Gems/1.8/gems/activesupport-2.3.2/lib/active_support/dependencies.rb:585:in `to_constant_name': Anonymous modules have no name to be referenced by
/Library/Ruby/Gems/1.8/gems/activesupport-2.3.2/lib/active_support/dependencies.rb:585:in `to_constant_name': Anonymous modules have no name to be referenced by
Any ideas would be GREATLY appreciated.
object.instance_eval(heading[1])
to:
object.send(heading[1])
on _table.html.erb line 67
That's what it used to use but I accepted a patch that changed it to instance_eval which could be what's wrong here.
Got it nicely working with associated fields and all.
Similar to Stephen on Nov 12, I like to have a way to "link_to" (via one of the fields shown) to the detail view of the record (action=> "show", "edit", etc).
I am a noob (started using rails in Oct 2009). Any advice on where I should start exploring?
Jester
thanks for the great plugin it rocks.
I have only one problem with relations, I tried this simple code, Expense belongs to Creator but it comes out an # istead the login name
sortable_table Expense ,
:table_headings => [['Reference date', 'reference_date'],['Description', 'description'], ['Amount', 'amount'], ['Creator', 'creator']] ,
:sort_map => {'reference_date' => ['expenses.reference_date'], 'description' => ['expenses.description'],'amount' => ['expenses.amount'],'creator.login' => ['creator.name']},
:include_relations => [:creator] ,:default_sort => ['reference_date', 'DESC']
I'm running rails 2.3.5
Its been fun to use your great plugin.
I have a minor problem:
conditions = ['shares.private = ?', false] #after this and
:conditions => conditions # search doesn't work. I think its tries something like ['shares.private = ?, false] + 'searchcriterias'
conditions[0] += ' and' if !conditions.blank?
conditions = [conditions[0] + ' (' + columns_to_search + ')'] + ( [conditions[1]] + values)
now conditions have to be array of size equals 2 ...
<< Home




Post a Comment