Skip to content

Commit c9a947c

Browse files
committed
Merge branch 'base'
Conflicts: CHANGELOG.md Rakefile ajax-datatables-rails.gemspec lib/ajax-datatables-rails.rb lib/generators/ajaxdatatable/templates/datatable.rb spec/ajax-datatables-rails/ajax_datatables_rails_spec.rb spec/spec_helper.rb
2 parents 3931500 + 1988e27 commit c9a947c

21 files changed

+606
-60
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,10 @@
22

33
## 0.1.0
44
* A fresh start. Changes base class name to: `DatatablesRails`.
5-
*
5+
* Extracts pagination functions to mixable modules.
6+
* A user would have the option to stick to the base
7+
`DatatablesRails::Extensions::SimplePaginator` or replace it with
8+
`DatatablesRails::Extensions::Kaminari` or
9+
`DatatablesRails::Extensions::WillPaginate`, depending on what he/she is using to handle record pagination.
10+
* Removes dependency to pass in a model name to the generator. This way, the developer has more flexibility to implement whatever datatable feature is required.
11+
* Datatable constructor accepts an optional `options` hash to provide more flexibility. See [README](https://github.com/antillas21/ajax-datatables-rails/blob/master/README.md) for examples.

README.md

Lines changed: 93 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,59 +13,83 @@ Datatables is a nifty jquery plugin that adds the ability to paginate, sort, and
1313

1414
`ajax-datatables-rails` is a wrapper around datatable's ajax methods that allow synchronization with server-side pagination in a rails app. It was inspired by this [Railscast](http://railscasts.com/episodes/340-datatables). I needed to implement a similar solution in a couple projects I was working on so I extracted it out into a gem.
1515

16+
## ORM Support
17+
18+
Currently, it only supports `ActiveRecord` as ORM for performing database queries.
19+
1620
## Installation
1721

1822
Add these lines to your application's Gemfile:
1923

2024
gem 'jquery-datatables-rails'
21-
gem 'ajax-datatables-rails'
25+
gem 'rails-datatables'
2226

2327
And then execute:
2428

2529
$ bundle
2630

2731
## Usage
32+
*The following examples assume that we are setting up rails-datatables for an index of users from a `User` model*
2833

29-
*The following examples assume that we are setting up ajax-datatables-rails for an index of users from a `User` model*
30-
31-
### Model
34+
### Generate
3235
Run the following command:
3336

34-
$ rails generate ajaxdatatable User
37+
$ rails generate datatable User
3538

36-
This will generate a file named `users_datatable.rb` in `app/datatables`. Open the file and customize in the functions as directed by the comments
3739

38-
#### Initializer
40+
This will generate a file named `user_datatable.rb` in `app/datatables`. Open the file and customize in the functions as directed by the comments
41+
42+
### Customize
3943
```ruby
40-
def initialize(view)
41-
@model_name = User
42-
@columns = # insert array of column names here
43-
@searchable_columns = #insert array of columns that will be searched
44-
super(view)
44+
# uncomment the appropriate paginator module,
45+
# depending on gems available in your project.
46+
# include AjaxDatatablesRails::Extensions::Kaminari
47+
# include AjaxDatatablesRails::Extensions::WillPaginate
48+
# include AjaxDatatablesRails::Extensions::SimplePaginator
49+
50+
def sortable_columns
51+
# list columns inside the Array in string dot notation.
52+
# Example: 'users.email'
53+
@sortable_columns ||= []
54+
end
55+
56+
def searchable_columns
57+
# list columns inside the Array in string dot notation.
58+
# Example: 'users.email'
59+
@searchable_columns ||= []
4560
end
4661
```
4762

48-
* For `@columns`, assign an array of the database columns that correspond to the columns in our view table. For example `[users.f_name, users.l_name, users.bio]`. This array is used for sorting by various columns
63+
* For `extensions`, just uncomment the paginator you would like to use, given
64+
the gems bundled in your project. For example, if your models are using `Kaminari`, uncomment `AjaxDatatablesRails::Extensions::Kaminari`. You may remove all commented lines.
65+
* `SimplePaginator` falls back to passing `offset` and `limit` at the database level (through `ActiveRecord` of course).
66+
67+
* For `sortable_columns`, assign an array of the database columns that correspond to the columns in our view table. For example `[users.f_name, users.l_name, users.bio]`. This array is used for sorting by various columns.
4968

50-
* For `@searchable_columns`, assign an array of the database columns that you want searchable by datatables. For example `[users.f_name, users.l_name]`
69+
* For `searchable_columns`, assign an array of the database columns that you want searchable by datatables. For example `[users.f_name, users.l_name]`
5170

5271
This gives us:
5372
```ruby
54-
def initialize(view)
55-
@model_name = User
56-
@columns = [users.f_name, users.l_name, users.bio]
57-
@searchable_columns = [users.f_name, users.l_name]
58-
super(view)
73+
include AjaxDatatablesRails::Extensions::Kaminari
74+
75+
def sortable_columns
76+
@sortable_columns ||= ['users.f_name', 'users.l_name', 'users.bio']
5977
end
78+
79+
def searchable_columns
80+
@searchable_columns ||= ['users.f_name', 'users.l_name']
81+
end
82+
6083
```
6184

62-
#### Data
85+
### Map data
6386
```ruby
6487
def data
65-
users.map do |user|
88+
records.map do |record|
6689
[
67-
# comma separated list of the values for each cell of a table row
68-
]
90+
# comma separated list of the values for each cell of a table row
91+
# example: record.attribute,
92+
]
6993
end
7094
end
7195
```
@@ -74,11 +98,11 @@ This method builds a 2d array that is used by datatables to construct the html t
7498

7599
```ruby
76100
def data
77-
users.map do |user|
101+
records.map do |record|
78102
[
79-
user.f_name,
80-
user.l_name,
81-
user.bio
103+
record.f_name,
104+
record.l_name,
105+
record.bio
82106
]
83107
end
84108
end
@@ -106,7 +130,7 @@ Set up the controller to respond to JSON
106130
def index
107131
respond_to do |format|
108132
format.html
109-
format.json { render json: UsersDatatable.new(view_context) }
133+
format.json { render json: UserDatatable.new(view_context) }
110134
end
111135
end
112136
```
@@ -138,17 +162,55 @@ Finally, the javascript to tie this all together. In the appropriate `js.coffee`
138162

139163
```coffeescript
140164
$ ->
141-
$('#users-table').dataTable
165+
$('#user-table').dataTable
142166
bProcessing: true
143167
bServerSide: true
144-
sAjaxSource: $('#users-table').data('source')
168+
sAjaxSource: $('#user-table').data('source')
145169
```
146170

171+
### Additional Notes
172+
173+
#### Options
174+
175+
An `AjaxDatatablesRails::Base` inherited class can accept an options hash at initialization. This provides room for flexibility when required. Example:
176+
177+
```ruby
178+
class UnrespondedMessagesDatatable < AjaxDatatablesRails::Base
179+
# customized methods here
180+
end
181+
182+
datatable = UnrespondedMessagesDatatable.new(
183+
view_context, { :foo => { :bar => Baz.new }, :from => 1.month.ago }
184+
)
185+
186+
datatable.options
187+
#=> { :foo => { :bar => #<Baz:0x007fe9cb4e0220> }, :from => 2014-04-16 19:55:28 -0700 }
188+
```
189+
190+
#### Generator Syntax
191+
192+
Also, a class that inherits from `AjaxDatatablesRails::Base` is not tied to an existing model, module, constant or any type of class in your Rails app. You can pass a name to your datatable class like this:
193+
194+
195+
```
196+
$ rails generate datatable users
197+
# returns a users_datatable.rb file with a UsersDatatable class
198+
199+
$ rails generate datatable contact_messages
200+
# returns a contact_messages_datatable.rb file with a ContactMessagesDatatable class
201+
202+
$ rails generate datatable UnrespondedMessages
203+
# returns an unresponded_messages_datatable.rb file with an UnrespondedMessagesDatatable class
204+
```
205+
206+
207+
In the end, it's up to the developer which model(s), scope(s), relationship(s) (or else) to employ inside the datatable class to retrieve records from the database.
208+
209+
147210
## Contributing
148211

149212
1. Fork it
150213
2. Create your feature branch (`git checkout -b my-new-feature`)
151214
3. Commit your changes (`git commit -am 'Added some feature'`)
152215
4. Push to the branch (`git push origin my-new-feature`)
153216
5. Create new Pull Request
154-

Rakefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ task :default => :spec
88
task :console do
99
require 'pry'
1010
require 'rails'
11-
require 'ajax_datatables_rails'
11+
require 'ajax-datatables-rails'
1212
ARGV.clear
1313
Pry.start
14-
end
14+
end

ajax-datatables-rails.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# -*- encoding: utf-8 -*-
22
lib = File.expand_path('../lib', __FILE__)
33
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4-
require 'ajax_datatables_rails/version'
4+
require 'ajax-datatables-rails/version'
55

66
Gem::Specification.new do |gem|
77
gem.name = "ajax-datatables-rails"
8-
gem.version = DatatablesRails::VERSION
8+
gem.version = AjaxDatatablesRails::VERSION
99
gem.authors = ["Joel Quenneville"]
1010
gem.email = ["[email protected]"]
1111
gem.description = %q{A gem that simplifies using datatables and hundreds of records via ajax}

lib/ajax-datatables-rails.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require 'ajax-datatables-rails/version'
2+
require 'ajax-datatables-rails/base'
3+
require 'ajax-datatables-rails/extensions/simple_paginator'
4+
require 'ajax-datatables-rails/extensions/kaminari'
5+
require 'ajax-datatables-rails/extensions/will_paginate'
6+
7+
module AjaxDatatablesRails
8+
end

lib/ajax-datatables-rails/base.rb

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
module AjaxDatatablesRails
2+
class Base
3+
extend Forwardable
4+
class MethodNotImplementedError < StandardError; end
5+
6+
attr_reader :view, :options, :sortable_columns, :searchable_columns
7+
def_delegator :@view, :params, :params
8+
9+
def initialize(view, options = {})
10+
@view = view
11+
@options = options
12+
end
13+
14+
def sortable_columns
15+
@sortable_columns ||= []
16+
end
17+
18+
def searchable_columns
19+
@searchable_columns ||= []
20+
end
21+
22+
def data
23+
fail(
24+
MethodNotImplementedError,
25+
'Please implement this method in your class.'
26+
)
27+
end
28+
29+
def get_raw_records
30+
fail(
31+
MethodNotImplementedError,
32+
'Please implement this method in your class.'
33+
)
34+
end
35+
36+
def as_json(options = {})
37+
{
38+
:sEcho => params[:sEcho].to_i,
39+
:iTotalRecords => get_raw_records.count,
40+
:iTotalDisplayRecords => filter_records(get_raw_records).count,
41+
:aaData => data
42+
}
43+
end
44+
45+
private
46+
47+
def records
48+
@records ||= fetch_records
49+
end
50+
51+
def fetch_records
52+
records = get_raw_records
53+
records = sort_records(records)
54+
records = filter_records(records)
55+
records = paginate_records(records)
56+
records
57+
end
58+
59+
def sort_records(records)
60+
records.order("#{sort_column} #{sort_direction}")
61+
end
62+
63+
def paginate_records(records)
64+
fail(
65+
MethodNotImplementedError,
66+
'Please mixin a pagination extension.'
67+
)
68+
end
69+
70+
def filter_records(records)
71+
records = simple_search(records)
72+
records = composite_search(records)
73+
records
74+
end
75+
76+
def simple_search(records)
77+
return records unless params[:sSearch]
78+
conditions = build_conditions_for(params[:sSearch])
79+
records = records.where(conditions) if conditions
80+
records
81+
end
82+
83+
def composite_search(records)
84+
conditions = aggregate_query
85+
records = records.where(conditions) if conditions
86+
records
87+
end
88+
89+
def build_conditions_for(query)
90+
searchable_columns.map { |col| search_condition(col, query) }.reduce(:or)
91+
end
92+
93+
def search_condition(column, value)
94+
model, column = column.split('.')
95+
model = model.singularize.titleize.constantize
96+
model.arel_table[column.to_sym].matches("%#{value}%")
97+
end
98+
99+
def aggregate_query
100+
conditions = searchable_columns.each_with_index.map do |column, index|
101+
value = params["sSearch_#{index}".to_sym]
102+
search_condition(column, value) if value
103+
end
104+
conditions.compact.reduce(:and)
105+
end
106+
107+
def offset
108+
(page - 1) * per_page
109+
end
110+
111+
def page
112+
(params[:iDisplayStart].to_i / per_page) + 1
113+
end
114+
115+
def per_page
116+
params.fetch(:iDisplayLength, 10).to_i
117+
end
118+
119+
def sort_column
120+
sortable_columns[params[:iSortCol_0].to_i]
121+
end
122+
123+
def sort_direction
124+
options = %w(desc asc)
125+
options.include?(params[:sSortDir_0]) ? params[:sSortDir_0].upcase : 'ASC'
126+
end
127+
end
128+
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module AjaxDatatablesRails
2+
module Extensions
3+
module Kaminari
4+
5+
private
6+
7+
def paginate_records(records)
8+
records.page(page).per(per_page)
9+
end
10+
end
11+
end
12+
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module AjaxDatatablesRails
2+
module Extensions
3+
module SimplePaginator
4+
5+
private
6+
7+
def paginate_records(records)
8+
records.offset(offset).limit(per_page)
9+
end
10+
end
11+
end
12+
end

0 commit comments

Comments
 (0)