datatables-net 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +73 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +618 -0
- data/Rakefile +14 -0
- data/lib/ajax-datatables-rails.rb +11 -0
- data/lib/ajax-datatables-rails/base.rb +205 -0
- data/lib/ajax-datatables-rails/config.rb +24 -0
- data/lib/ajax-datatables-rails/datatable/column.rb +106 -0
- data/lib/ajax-datatables-rails/datatable/datatable.rb +69 -0
- data/lib/ajax-datatables-rails/datatable/simple_order.rb +31 -0
- data/lib/ajax-datatables-rails/datatable/simple_search.rb +19 -0
- data/lib/ajax-datatables-rails/orm/active_record.rb +52 -0
- data/lib/ajax-datatables-rails/version.rb +3 -0
- data/lib/generators/datatable/config_generator.rb +17 -0
- data/lib/generators/datatable/templates/ajax_datatables_rails_config.rb +7 -0
- data/lib/generators/rails/datatable_generator.rb +27 -0
- data/lib/generators/rails/templates/datatable.rb +41 -0
- data/spec/ajax-datatables-rails/base_spec.rb +140 -0
- data/spec/ajax-datatables-rails/configuration_spec.rb +43 -0
- data/spec/ajax-datatables-rails/datatable/column_spec.rb +65 -0
- data/spec/ajax-datatables-rails/datatable/datatable_spec.rb +97 -0
- data/spec/ajax-datatables-rails/datatable/simple_order_spec.rb +12 -0
- data/spec/ajax-datatables-rails/datatable/simple_search_spec.rb +16 -0
- data/spec/ajax-datatables-rails/orm/active_record_filter_records_spec.rb +154 -0
- data/spec/ajax-datatables-rails/orm/active_record_paginate_records_spec.rb +51 -0
- data/spec/ajax-datatables-rails/orm/active_record_sort_records_spec.rb +42 -0
- data/spec/ajax-datatables-rails/orm/active_record_spec.rb +34 -0
- data/spec/schema.rb +43 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/test_helpers.rb +66 -0
- data/spec/test_models.rb +20 -0
- metadata +202 -0
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
task :console do
|
9
|
+
require 'pry'
|
10
|
+
require 'rails'
|
11
|
+
require 'ajax-datatables-rails'
|
12
|
+
ARGV.clear
|
13
|
+
Pry.start
|
14
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'ajax-datatables-rails/version'
|
2
|
+
require 'ajax-datatables-rails/config'
|
3
|
+
require 'ajax-datatables-rails/base'
|
4
|
+
require 'ajax-datatables-rails/datatable/datatable'
|
5
|
+
require 'ajax-datatables-rails/datatable/simple_search'
|
6
|
+
require 'ajax-datatables-rails/datatable/simple_order'
|
7
|
+
require 'ajax-datatables-rails/datatable/column'
|
8
|
+
require 'ajax-datatables-rails/orm/active_record'
|
9
|
+
|
10
|
+
module AjaxDatatablesRails
|
11
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
module AjaxDatatablesRails
|
2
|
+
class NotImplemented < StandardError; end
|
3
|
+
|
4
|
+
class Base
|
5
|
+
extend Forwardable
|
6
|
+
<<<<<<< HEAD
|
7
|
+
include ActiveRecord::Sanitization::ClassMethods
|
8
|
+
class MethodNotImplementedError < StandardError; end
|
9
|
+
=======
|
10
|
+
>>>>>>> v-0-4-0
|
11
|
+
|
12
|
+
attr_reader :view, :options
|
13
|
+
def_delegator :@view, :params, :params
|
14
|
+
|
15
|
+
def initialize(view, options = {})
|
16
|
+
@view = view
|
17
|
+
@options = options
|
18
|
+
load_orm_extension
|
19
|
+
end
|
20
|
+
|
21
|
+
def config
|
22
|
+
@config ||= AjaxDatatablesRails.config
|
23
|
+
end
|
24
|
+
|
25
|
+
def datatable
|
26
|
+
@datatable ||= Datatable::Datatable.new self
|
27
|
+
end
|
28
|
+
|
29
|
+
# Must overrited methods
|
30
|
+
def view_columns
|
31
|
+
fail(NotImplemented, view_columns_error_text)
|
32
|
+
end
|
33
|
+
|
34
|
+
def data
|
35
|
+
fail(NotImplemented, data_error_text)
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_raw_records
|
39
|
+
fail(NotImplemented, raw_records_error_text)
|
40
|
+
end
|
41
|
+
|
42
|
+
def as_json
|
43
|
+
{
|
44
|
+
recordsTotal: total_records,
|
45
|
+
recordsFiltered: filter_records,
|
46
|
+
data: data
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def total_records
|
51
|
+
get_raw_records.count(:all)
|
52
|
+
end
|
53
|
+
|
54
|
+
def filtered_records
|
55
|
+
get_raw_records.model.from("(#{filter_records(get_raw_records).except(:limit, :offset, :order).to_sql}) AS foo").count
|
56
|
+
end
|
57
|
+
|
58
|
+
def records
|
59
|
+
@records ||= retrieve_records
|
60
|
+
end
|
61
|
+
|
62
|
+
# helper methods
|
63
|
+
def searchable_columns
|
64
|
+
@searchable_columns ||= begin
|
65
|
+
connected_columns.select &:searchable?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def search_columns
|
70
|
+
@search_columns ||= begin
|
71
|
+
searchable_columns.select { |column| column.search.value.present? }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def connected_columns
|
76
|
+
@connected_columns ||= begin
|
77
|
+
view_columns.keys.map do |field_name|
|
78
|
+
datatable.column_by(:data, field_name.to_s)
|
79
|
+
end.compact
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
# view_columns can be an Array or Hash. we have to support all these formats of defining columns
|
85
|
+
def connect_view_columns
|
86
|
+
# @connect_view_columns ||= begin
|
87
|
+
# adapted_options =
|
88
|
+
# case view_columns
|
89
|
+
# when Hash
|
90
|
+
# when Array
|
91
|
+
# cols = {}
|
92
|
+
# view_columns.each_with_index({}) do |index, source|
|
93
|
+
# cols[index.to_s] = { source: source }
|
94
|
+
# end
|
95
|
+
# cols
|
96
|
+
# else
|
97
|
+
# view_columns
|
98
|
+
# end
|
99
|
+
# ActiveSupport::HashWithIndifferentAccess.new adapted_options
|
100
|
+
# end
|
101
|
+
end
|
102
|
+
|
103
|
+
def retrieve_records
|
104
|
+
records = fetch_records
|
105
|
+
records = filter_records(records)
|
106
|
+
records = sort_records(records) if datatable.orderable?
|
107
|
+
records = paginate_records(records) if datatable.paginate?
|
108
|
+
records
|
109
|
+
end
|
110
|
+
|
111
|
+
# Private helper methods
|
112
|
+
def load_orm_extension
|
113
|
+
case config.orm
|
114
|
+
when :mongoid then nil
|
115
|
+
when :active_record then extend ORM::ActiveRecord
|
116
|
+
else
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
<<<<<<< HEAD
|
122
|
+
def new_search_condition(column, value)
|
123
|
+
model, column = column.split('.')
|
124
|
+
model = model.constantize
|
125
|
+
casted_column = ::Arel::Nodes::NamedFunction.new('CAST', [model.arel_table[column.to_sym].as(typecast)])
|
126
|
+
casted_column.matches("%#{sanitize_sql_like(value)}%")
|
127
|
+
end
|
128
|
+
|
129
|
+
def deprecated_search_condition(column, value)
|
130
|
+
model, column = column.split('.')
|
131
|
+
model = model.singularize.titleize.gsub( / /, '' ).constantize
|
132
|
+
|
133
|
+
casted_column = ::Arel::Nodes::NamedFunction.new('CAST', [model.arel_table[column.to_sym].as(typecast)])
|
134
|
+
casted_column.matches("%#{sanitize_sql_like(value)}%")
|
135
|
+
end
|
136
|
+
|
137
|
+
def aggregate_query
|
138
|
+
conditions = searchable_columns.each_with_index.map do |column, index|
|
139
|
+
value = params[:columns]["#{index}"][:search][:value] if params[:columns]
|
140
|
+
search_condition(column, value) unless value.blank?
|
141
|
+
end
|
142
|
+
conditions.compact.reduce(:and)
|
143
|
+
end
|
144
|
+
|
145
|
+
def typecast
|
146
|
+
case config.db_adapter
|
147
|
+
when :oracle then 'VARCHAR2(4000)'
|
148
|
+
when :pg then 'VARCHAR'
|
149
|
+
when :mysql2 then 'CHAR'
|
150
|
+
when :sqlite3 then 'TEXT'
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def offset
|
155
|
+
(page - 1) * per_page
|
156
|
+
end
|
157
|
+
|
158
|
+
def page
|
159
|
+
(params[:start].to_i / per_page) + 1
|
160
|
+
end
|
161
|
+
|
162
|
+
def per_page
|
163
|
+
params.fetch(:length, 10).to_i
|
164
|
+
end
|
165
|
+
|
166
|
+
def sort_column(item)
|
167
|
+
new_sort_column(item)
|
168
|
+
rescue
|
169
|
+
::AjaxDatatablesRails::Base.deprecated '[DEPRECATED] Using table_name.column_name notation is deprecated. Please refer to: https://github.com/antillas21/ajax-datatables-rails#searchable-and-sortable-columns-syntax'
|
170
|
+
deprecated_sort_column(item)
|
171
|
+
end
|
172
|
+
|
173
|
+
def deprecated_sort_column(item)
|
174
|
+
sortable_columns[sortable_displayed_columns.index(item[:column])]
|
175
|
+
end
|
176
|
+
=======
|
177
|
+
def raw_records_error_text
|
178
|
+
return <<-eos
|
179
|
+
|
180
|
+
You should implement this method in your class and specify
|
181
|
+
how records are going to be retrieved from the database.
|
182
|
+
eos
|
183
|
+
end
|
184
|
+
|
185
|
+
def data_error_text
|
186
|
+
return <<-eos
|
187
|
+
>>>>>>> v-0-4-0
|
188
|
+
|
189
|
+
You should implement this method in your class and return an array
|
190
|
+
of arrays, or an array of hashes, as defined in the jQuery.dataTables
|
191
|
+
plugin documentation.
|
192
|
+
eos
|
193
|
+
end
|
194
|
+
|
195
|
+
def view_columns_error_text
|
196
|
+
return <<-eos
|
197
|
+
|
198
|
+
You should implement this method in your class and return an array
|
199
|
+
of database columns based on the columns displayed in the HTML view.
|
200
|
+
These columns should be represented in the ModelName.column_name,
|
201
|
+
or aliased_join_table.column_name notation.
|
202
|
+
eos
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'active_support/configurable'
|
2
|
+
|
3
|
+
module AjaxDatatablesRails
|
4
|
+
|
5
|
+
# configure AjaxDatatablesRails global settings
|
6
|
+
# AjaxDatatablesRails.configure do |config|
|
7
|
+
# config.db_adapter = :pg
|
8
|
+
# end
|
9
|
+
def self.configure &block
|
10
|
+
yield @config ||= AjaxDatatablesRails::Configuration.new
|
11
|
+
end
|
12
|
+
|
13
|
+
# AjaxDatatablesRails global settings
|
14
|
+
def self.config
|
15
|
+
@config ||= AjaxDatatablesRails::Configuration.new
|
16
|
+
end
|
17
|
+
|
18
|
+
class Configuration
|
19
|
+
include ActiveSupport::Configurable
|
20
|
+
|
21
|
+
config_accessor(:orm) { :active_record }
|
22
|
+
config_accessor(:db_adapter) { :pg }
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module AjaxDatatablesRails
|
2
|
+
module Datatable
|
3
|
+
class Column
|
4
|
+
attr_reader :datatable, :index, :options
|
5
|
+
|
6
|
+
def initialize(datatable, index, options)
|
7
|
+
@datatable, @index, @options = datatable, index, options
|
8
|
+
@view_column = datatable.view_columns[options["data"].to_sym]
|
9
|
+
end
|
10
|
+
|
11
|
+
def data
|
12
|
+
options[:data].presence || options[:name]
|
13
|
+
end
|
14
|
+
|
15
|
+
def searchable?
|
16
|
+
options[:searchable] == TRUE_VALUE
|
17
|
+
end
|
18
|
+
|
19
|
+
def orderable?
|
20
|
+
options[:orderable] == TRUE_VALUE
|
21
|
+
end
|
22
|
+
|
23
|
+
def search
|
24
|
+
@search ||= SimpleSearch.new(options[:search])
|
25
|
+
end
|
26
|
+
|
27
|
+
def search= value
|
28
|
+
@search = value
|
29
|
+
end
|
30
|
+
|
31
|
+
def cond
|
32
|
+
@view_column[:cond] || :like
|
33
|
+
end
|
34
|
+
|
35
|
+
def filter value
|
36
|
+
@view_column[:cond].call self
|
37
|
+
end
|
38
|
+
|
39
|
+
def source
|
40
|
+
@view_column[:source]
|
41
|
+
end
|
42
|
+
|
43
|
+
def table
|
44
|
+
model = source.split('.').first.constantize
|
45
|
+
model.arel_table rescue model
|
46
|
+
end
|
47
|
+
|
48
|
+
def field
|
49
|
+
source.split('.').last.to_sym
|
50
|
+
end
|
51
|
+
|
52
|
+
def search_query
|
53
|
+
search.regexp? ? regex_search : non_regex_search
|
54
|
+
end
|
55
|
+
|
56
|
+
def sort_query
|
57
|
+
if custom_field?
|
58
|
+
source
|
59
|
+
else
|
60
|
+
"#{ table.name }.#{ field }"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def custom_field?
|
66
|
+
!source.include?('.')
|
67
|
+
end
|
68
|
+
|
69
|
+
def config
|
70
|
+
@config ||= AjaxDatatablesRails.config
|
71
|
+
end
|
72
|
+
|
73
|
+
def regex_search
|
74
|
+
::Arel::Nodes::Regexp.new((custom_field? ? field : table[field]), ::Arel::Nodes.build_quoted(search.value))
|
75
|
+
end
|
76
|
+
|
77
|
+
def non_regex_search
|
78
|
+
case cond
|
79
|
+
when Proc
|
80
|
+
filter search.value
|
81
|
+
when :eq, :not_eq, :lt, :gt, :lteq, :gteq, :in
|
82
|
+
if custom_field?
|
83
|
+
::Arel::Nodes::SqlLiteral.new(field).eq(search.value)
|
84
|
+
else
|
85
|
+
table[field].send(cond, search.value)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
casted_column = ::Arel::Nodes::NamedFunction.new(
|
89
|
+
'CAST', [table[field].as(typecast)]
|
90
|
+
)
|
91
|
+
casted_column.matches("%#{ search.value }%")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def typecast
|
96
|
+
case config.db_adapter
|
97
|
+
when :mysql, :mysql2 then 'CHAR'
|
98
|
+
when :sqlite, :sqlite3 then 'TEXT'
|
99
|
+
else
|
100
|
+
'VARCHAR'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module AjaxDatatablesRails
|
2
|
+
module Datatable
|
3
|
+
|
4
|
+
TRUE_VALUE = 'true'
|
5
|
+
|
6
|
+
class Datatable
|
7
|
+
attr_reader :datatable, :options
|
8
|
+
|
9
|
+
def initialize datatable
|
10
|
+
@datatable = datatable
|
11
|
+
@options = datatable.params
|
12
|
+
end
|
13
|
+
|
14
|
+
# ----------------- ORDER METHODS --------------------
|
15
|
+
|
16
|
+
def orderable?
|
17
|
+
options[:order].present?
|
18
|
+
end
|
19
|
+
|
20
|
+
def orders
|
21
|
+
@orders ||= options[:order].map { |index, order_options| SimpleOrder.new(self, order_options) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def order_by(how, what)
|
25
|
+
orders.find { |simple_order| simple_order.send(how) == what }
|
26
|
+
end
|
27
|
+
|
28
|
+
# ----------------- SEARCH METHODS --------------------
|
29
|
+
|
30
|
+
def searchable?
|
31
|
+
options[:search].present? && options[:search][:value].present?
|
32
|
+
end
|
33
|
+
|
34
|
+
def search
|
35
|
+
@search ||= SimpleSearch.new(options[:search])
|
36
|
+
end
|
37
|
+
|
38
|
+
# ----------------- COLUMN METHODS --------------------
|
39
|
+
|
40
|
+
def columns
|
41
|
+
@columns ||= options[:columns].map do |index, column_options|
|
42
|
+
Column.new(datatable, index, column_options)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def column_by how, what
|
47
|
+
columns.find { |simple_column| simple_column.send(how) == what }
|
48
|
+
end
|
49
|
+
|
50
|
+
# ----------------- OPTIONS METHODS --------------------
|
51
|
+
|
52
|
+
def paginate?
|
53
|
+
per_page != -1
|
54
|
+
end
|
55
|
+
|
56
|
+
def offset
|
57
|
+
(page - 1) * per_page
|
58
|
+
end
|
59
|
+
|
60
|
+
def page
|
61
|
+
(options[:start].to_i / per_page) + 1
|
62
|
+
end
|
63
|
+
|
64
|
+
def per_page
|
65
|
+
options.fetch(:length, 10).to_i
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module AjaxDatatablesRails
|
2
|
+
module Datatable
|
3
|
+
class SimpleOrder
|
4
|
+
attr_reader :datatable, :options
|
5
|
+
|
6
|
+
DIRECTIONS = %w(DESC ASC)
|
7
|
+
|
8
|
+
def initialize(datatable, options)
|
9
|
+
@datatable = datatable
|
10
|
+
@options = options || {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def query sort_column
|
14
|
+
"#{ sort_column } #{ dir }"
|
15
|
+
end
|
16
|
+
|
17
|
+
def column
|
18
|
+
datatable.column_by(:index, column_index)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def dir
|
23
|
+
DIRECTIONS.find { |direction| direction == options[:dir].upcase } || 'ASC'
|
24
|
+
end
|
25
|
+
|
26
|
+
def column_index
|
27
|
+
options[:column]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|