ajax-datatables-rails 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +26 -0
- data/.gitignore +20 -0
- data/.rspec +1 -0
- data/.rubocop.yml +1157 -0
- data/.travis.yml +68 -0
- data/Appraisals +34 -0
- data/Gemfile +5 -1
- data/LICENSE +17 -18
- data/README.md +239 -239
- data/Rakefile +1 -1
- data/ajax-datatables-rails.gemspec +31 -24
- data/gemfiles/rails_4.0.13.gemfile +14 -0
- data/gemfiles/rails_4.1.15.gemfile +14 -0
- data/gemfiles/rails_4.2.8.gemfile +13 -0
- data/gemfiles/rails_5.0.3.gemfile +13 -0
- data/gemfiles/rails_5.1.1.gemfile +13 -0
- data/lib/ajax-datatables-rails.rb +9 -8
- data/lib/ajax-datatables-rails/base.rb +80 -156
- data/lib/ajax-datatables-rails/config.rb +8 -5
- data/lib/ajax-datatables-rails/datatable/column.rb +169 -0
- data/lib/ajax-datatables-rails/datatable/column_date_filter.rb +41 -0
- data/lib/ajax-datatables-rails/datatable/datatable.rb +79 -0
- data/lib/ajax-datatables-rails/datatable/simple_order.rb +31 -0
- data/lib/ajax-datatables-rails/datatable/simple_search.rb +18 -0
- data/lib/ajax-datatables-rails/orm/active_record.rb +52 -0
- data/lib/ajax-datatables-rails/version.rb +1 -1
- data/lib/generators/datatable/templates/ajax_datatables_rails_config.rb +3 -3
- data/lib/generators/rails/datatable_generator.rb +7 -19
- data/lib/generators/rails/templates/datatable.rb +26 -14
- data/spec/ajax-datatables-rails/base_spec.rb +190 -0
- data/spec/ajax-datatables-rails/configuration_spec.rb +43 -0
- data/spec/ajax-datatables-rails/datatable/column_spec.rb +109 -0
- data/spec/ajax-datatables-rails/datatable/datatable_spec.rb +87 -0
- data/spec/ajax-datatables-rails/datatable/simple_order_spec.rb +13 -0
- data/spec/ajax-datatables-rails/datatable/simple_search_spec.rb +17 -0
- data/spec/ajax-datatables-rails/extended_spec.rb +20 -0
- data/spec/ajax-datatables-rails/orm/active_record_filter_records_spec.rb +439 -0
- data/spec/ajax-datatables-rails/orm/active_record_paginate_records_spec.rb +66 -0
- data/spec/ajax-datatables-rails/orm/active_record_sort_records_spec.rb +34 -0
- data/spec/ajax-datatables-rails/orm/active_record_spec.rb +25 -0
- data/spec/factories/user.rb +9 -0
- data/spec/install_oracle.sh +12 -0
- data/spec/spec_helper.rb +75 -3
- data/spec/support/schema.rb +14 -0
- data/spec/support/test_helpers.rb +174 -0
- data/spec/support/test_models.rb +2 -0
- metadata +169 -37
- data/lib/ajax-datatables-rails/extensions/kaminari.rb +0 -12
- data/lib/ajax-datatables-rails/extensions/simple_paginator.rb +0 -12
- data/lib/ajax-datatables-rails/extensions/will_paginate.rb +0 -12
- data/lib/ajax-datatables-rails/models.rb +0 -6
- data/spec/ajax-datatables-rails/ajax_datatables_rails_spec.rb +0 -351
- data/spec/ajax-datatables-rails/kaminari_spec.rb +0 -35
- data/spec/ajax-datatables-rails/models_spec.rb +0 -10
- data/spec/ajax-datatables-rails/simple_paginator_spec.rb +0 -32
- data/spec/ajax-datatables-rails/will_paginate_spec.rb +0 -28
- data/spec/schema.rb +0 -35
- data/spec/test_models.rb +0 -21
@@ -4,9 +4,9 @@ module AjaxDatatablesRails
|
|
4
4
|
|
5
5
|
# configure AjaxDatatablesRails global settings
|
6
6
|
# AjaxDatatablesRails.configure do |config|
|
7
|
-
# config.db_adapter = :
|
7
|
+
# config.db_adapter = :postgresql
|
8
8
|
# end
|
9
|
-
def self.configure
|
9
|
+
def self.configure
|
10
10
|
yield @config ||= AjaxDatatablesRails::Configuration.new
|
11
11
|
end
|
12
12
|
|
@@ -15,11 +15,14 @@ module AjaxDatatablesRails
|
|
15
15
|
@config ||= AjaxDatatablesRails::Configuration.new
|
16
16
|
end
|
17
17
|
|
18
|
+
def self.old_rails?
|
19
|
+
Rails::VERSION::MAJOR == 4 && (Rails::VERSION::MINOR == 1 || Rails::VERSION::MINOR == 0)
|
20
|
+
end
|
21
|
+
|
18
22
|
class Configuration
|
19
23
|
include ActiveSupport::Configurable
|
20
24
|
|
21
|
-
|
22
|
-
config_accessor(:db_adapter) { :
|
23
|
-
config_accessor(:paginator) { :simple_paginator }
|
25
|
+
config_accessor(:orm) { :active_record }
|
26
|
+
config_accessor(:db_adapter) { :postgresql }
|
24
27
|
end
|
25
28
|
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module AjaxDatatablesRails
|
4
|
+
module Datatable
|
5
|
+
class Column
|
6
|
+
attr_reader :datatable, :index, :options
|
7
|
+
|
8
|
+
unless AjaxDatatablesRails.old_rails?
|
9
|
+
prepend ColumnDateFilter
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(datatable, index, options)
|
13
|
+
@datatable, @index, @options = datatable, index, options
|
14
|
+
@view_column = datatable.view_columns[options["data"].to_sym]
|
15
|
+
end
|
16
|
+
|
17
|
+
def data
|
18
|
+
options[:data].presence || options[:name]
|
19
|
+
end
|
20
|
+
|
21
|
+
def searchable?
|
22
|
+
@view_column.fetch(:searchable, true)
|
23
|
+
end
|
24
|
+
|
25
|
+
def orderable?
|
26
|
+
@view_column.fetch(:orderable, true)
|
27
|
+
end
|
28
|
+
|
29
|
+
def search
|
30
|
+
@search ||= SimpleSearch.new(options[:search])
|
31
|
+
end
|
32
|
+
|
33
|
+
def searched?
|
34
|
+
search.value.present?
|
35
|
+
end
|
36
|
+
|
37
|
+
def search=(value)
|
38
|
+
@search = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def cond
|
42
|
+
@view_column[:cond] || :like
|
43
|
+
end
|
44
|
+
|
45
|
+
def filter(value)
|
46
|
+
@view_column[:cond].call(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def source
|
50
|
+
@view_column[:source]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Add sort_field option to allow overriding of sort field
|
54
|
+
def sort_field
|
55
|
+
@view_column[:sort_field] || field
|
56
|
+
end
|
57
|
+
|
58
|
+
# Add formater option to allow modification of the value
|
59
|
+
# before passing it to the database
|
60
|
+
def formater
|
61
|
+
@view_column[:formater]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Add use_regex option to allow bypassing of regex search
|
65
|
+
def use_regex?
|
66
|
+
@view_column.fetch(:use_regex, true)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Add delimiter option to handle range search
|
70
|
+
def delimiter
|
71
|
+
@view_column[:delimiter] || '-'
|
72
|
+
end
|
73
|
+
|
74
|
+
def table
|
75
|
+
model = source.split('.').first.constantize
|
76
|
+
model.arel_table rescue model
|
77
|
+
end
|
78
|
+
|
79
|
+
def field
|
80
|
+
source.split('.').last.to_sym
|
81
|
+
end
|
82
|
+
|
83
|
+
def search_query
|
84
|
+
search.regexp? ? regex_search : non_regex_search
|
85
|
+
end
|
86
|
+
|
87
|
+
def sort_query
|
88
|
+
custom_field? ? source : "#{table.name}.#{sort_field}"
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def custom_field?
|
94
|
+
!source.include?('.')
|
95
|
+
end
|
96
|
+
|
97
|
+
def config
|
98
|
+
@config ||= AjaxDatatablesRails.config
|
99
|
+
end
|
100
|
+
|
101
|
+
def formated_value
|
102
|
+
formater ? formater.call(search.value) : search.value
|
103
|
+
end
|
104
|
+
|
105
|
+
# Using multi-select filters in JQuery Datatable auto-enables regex_search.
|
106
|
+
# Unfortunately regex_search doesn't work when filtering on primary keys with integer.
|
107
|
+
# It generates this kind of query : AND ("regions"."id" ~ '2|3') which throws an error :
|
108
|
+
# operator doesn't exist : integer ~ unknown
|
109
|
+
# The solution is to bypass regex_search and use non_regex_search with :in operator
|
110
|
+
def regex_search
|
111
|
+
if use_regex?
|
112
|
+
::Arel::Nodes::Regexp.new((custom_field? ? field : table[field]), ::Arel::Nodes.build_quoted(formated_value))
|
113
|
+
else
|
114
|
+
non_regex_search
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def non_regex_search
|
119
|
+
case cond
|
120
|
+
when Proc
|
121
|
+
filter(formated_value)
|
122
|
+
when :eq, :not_eq, :lt, :gt, :lteq, :gteq, :in
|
123
|
+
numeric_search
|
124
|
+
when :null_value
|
125
|
+
null_value_search
|
126
|
+
when :start_with
|
127
|
+
casted_column.matches("#{formated_value}%")
|
128
|
+
when :end_with
|
129
|
+
casted_column.matches("%#{formated_value}")
|
130
|
+
when :like
|
131
|
+
casted_column.matches("%#{formated_value}%")
|
132
|
+
else
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def typecast
|
138
|
+
case config.db_adapter
|
139
|
+
when :oracle, :oracleenhanced then 'VARCHAR2(4000)'
|
140
|
+
when :mysql, :mysql2 then 'CHAR'
|
141
|
+
when :sqlite, :sqlite3 then 'TEXT'
|
142
|
+
else
|
143
|
+
'VARCHAR'
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def casted_column
|
148
|
+
::Arel::Nodes::NamedFunction.new('CAST', [table[field].as(typecast)])
|
149
|
+
end
|
150
|
+
|
151
|
+
def null_value_search
|
152
|
+
if formated_value == '!NULL'
|
153
|
+
table[field].not_eq(nil)
|
154
|
+
else
|
155
|
+
table[field].eq(nil)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def numeric_search
|
160
|
+
if custom_field?
|
161
|
+
::Arel::Nodes::SqlLiteral.new(field).eq(formated_value)
|
162
|
+
else
|
163
|
+
table[field].send(cond, formated_value)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module AjaxDatatablesRails
|
2
|
+
module Datatable
|
3
|
+
module ColumnDateFilter
|
4
|
+
|
5
|
+
def empty_range_search?
|
6
|
+
(formated_value == delimiter) || (range_start.blank? && range_end.blank?)
|
7
|
+
end
|
8
|
+
|
9
|
+
# A range value is in form '<range_start><delimiter><range_end>'
|
10
|
+
# This returns <range_start>
|
11
|
+
def range_start
|
12
|
+
@range_start ||= formated_value.split(delimiter)[0]
|
13
|
+
end
|
14
|
+
|
15
|
+
# A range value is in form '<range_start><delimiter><range_end>'
|
16
|
+
# This returns <range_end>
|
17
|
+
def range_end
|
18
|
+
@range_end ||= formated_value.split(delimiter)[1]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Do a range search
|
22
|
+
def date_range_search
|
23
|
+
return nil if empty_range_search?
|
24
|
+
new_start = range_start.blank? ? DateTime.parse('01/01/1970') : DateTime.parse(range_start)
|
25
|
+
new_end = range_end.blank? ? DateTime.current : DateTime.parse("#{range_end} 23:59:59")
|
26
|
+
table[field].between(OpenStruct.new(begin: new_start, end: new_end))
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def non_regex_search
|
32
|
+
if cond == :date_range
|
33
|
+
date_range_search
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module AjaxDatatablesRails
|
2
|
+
module Datatable
|
3
|
+
|
4
|
+
TRUE_VALUE = 'true'.freeze
|
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 ||= get_param(:order).map do |_, order_options|
|
22
|
+
SimpleOrder.new(self, order_options)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def order_by(how, what)
|
27
|
+
orders.find { |simple_order| simple_order.send(how) == what }
|
28
|
+
end
|
29
|
+
|
30
|
+
# ----------------- SEARCH METHODS --------------------
|
31
|
+
|
32
|
+
def searchable?
|
33
|
+
options[:search].present? && options[:search][:value].present?
|
34
|
+
end
|
35
|
+
|
36
|
+
def search
|
37
|
+
@search ||= SimpleSearch.new(options[:search])
|
38
|
+
end
|
39
|
+
|
40
|
+
# ----------------- COLUMN METHODS --------------------
|
41
|
+
|
42
|
+
def columns
|
43
|
+
@columns ||= get_param(:columns).map do |index, column_options|
|
44
|
+
Column.new(datatable, index, column_options)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def column_by(how, what)
|
49
|
+
columns.find { |simple_column| simple_column.send(how) == what }
|
50
|
+
end
|
51
|
+
|
52
|
+
# ----------------- OPTIONS METHODS --------------------
|
53
|
+
|
54
|
+
def paginate?
|
55
|
+
per_page != -1
|
56
|
+
end
|
57
|
+
|
58
|
+
def offset
|
59
|
+
(page - 1) * per_page
|
60
|
+
end
|
61
|
+
|
62
|
+
def page
|
63
|
+
(options[:start].to_i / per_page) + 1
|
64
|
+
end
|
65
|
+
|
66
|
+
def per_page
|
67
|
+
options.fetch(:length, 10).to_i
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_param(param)
|
71
|
+
if AjaxDatatablesRails.old_rails?
|
72
|
+
options[param]
|
73
|
+
else
|
74
|
+
options[param].to_unsafe_h.with_indifferent_access
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module AjaxDatatablesRails
|
2
|
+
module Datatable
|
3
|
+
class SimpleOrder
|
4
|
+
|
5
|
+
DIRECTIONS = %w[DESC ASC].freeze
|
6
|
+
|
7
|
+
def initialize(datatable, options = {})
|
8
|
+
@datatable = datatable
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def query(sort_column)
|
13
|
+
"#{sort_column} #{direction}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def column
|
17
|
+
@datatable.column_by(:index, column_index)
|
18
|
+
end
|
19
|
+
|
20
|
+
def direction
|
21
|
+
DIRECTIONS.find { |dir| dir == @options[:dir].upcase } || 'ASC'
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def column_index
|
27
|
+
@options[:column]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module AjaxDatatablesRails
|
2
|
+
module Datatable
|
3
|
+
class SimpleSearch
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def value
|
10
|
+
@options[:value]
|
11
|
+
end
|
12
|
+
|
13
|
+
def regexp?
|
14
|
+
@options[:regex] == TRUE_VALUE
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module AjaxDatatablesRails
|
2
|
+
module ORM
|
3
|
+
module ActiveRecord
|
4
|
+
|
5
|
+
def fetch_records
|
6
|
+
get_raw_records
|
7
|
+
end
|
8
|
+
|
9
|
+
def filter_records(records)
|
10
|
+
records.where(build_conditions)
|
11
|
+
end
|
12
|
+
|
13
|
+
def sort_records(records)
|
14
|
+
sort_by = datatable.orders.inject([]) do |queries, order|
|
15
|
+
column = order.column
|
16
|
+
queries << order.query(column.sort_query) if column
|
17
|
+
end
|
18
|
+
records.order(sort_by.join(", "))
|
19
|
+
end
|
20
|
+
|
21
|
+
def paginate_records(records)
|
22
|
+
records.offset(datatable.offset).limit(datatable.per_page)
|
23
|
+
end
|
24
|
+
|
25
|
+
# ----------------- SEARCH HELPER METHODS --------------------
|
26
|
+
|
27
|
+
def build_conditions
|
28
|
+
if datatable.searchable?
|
29
|
+
build_conditions_for_datatable
|
30
|
+
else
|
31
|
+
build_conditions_for_selected_columns
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_conditions_for_datatable
|
36
|
+
search_for = datatable.search.value.split(' ')
|
37
|
+
criteria = search_for.inject([]) do |crit, atom|
|
38
|
+
search = Datatable::SimpleSearch.new({ value: atom, regex: datatable.search.regexp? })
|
39
|
+
crit << searchable_columns.map do |simple_column|
|
40
|
+
simple_column.search = search
|
41
|
+
simple_column.search_query
|
42
|
+
end.reduce(:or)
|
43
|
+
end.reduce(:and)
|
44
|
+
criteria
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_conditions_for_selected_columns
|
48
|
+
search_columns.map(&:search_query).compact.reduce(:and)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
AjaxDatatablesRails.configure do |config|
|
2
|
-
# available options for db_adapter are: :
|
2
|
+
# available options for db_adapter are: :pg, :mysql, :mysql2, :sqlite, :sqlite3
|
3
3
|
# config.db_adapter = :pg
|
4
4
|
|
5
|
-
# available options for
|
6
|
-
# config.
|
5
|
+
# available options for orm are: :active_record, :mongoid
|
6
|
+
# config.orm = :active_record
|
7
7
|
end
|
@@ -5,35 +5,23 @@ module Rails
|
|
5
5
|
class DatatableGenerator < ::Rails::Generators::Base
|
6
6
|
desc 'Creates a *_datatable model in the app/datatables directory.'
|
7
7
|
source_root File.expand_path('../templates', __FILE__)
|
8
|
-
argument :name, :
|
8
|
+
argument :name, type: :string
|
9
9
|
|
10
10
|
def generate_datatable
|
11
|
-
file_prefix = set_filename(name)
|
12
|
-
@datatable_name = set_datatable_name(name)
|
13
11
|
template 'datatable.rb', File.join(
|
14
|
-
'app/datatables', "#{
|
12
|
+
'app/datatables', "#{datatable_path}.rb"
|
15
13
|
)
|
16
14
|
end
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
def set_filename(name)
|
21
|
-
name.include?('_') ? name : name.to_s.underscore
|
22
|
-
end
|
23
|
-
|
24
|
-
def set_datatable_name(name)
|
25
|
-
name.include?('_') ? build_name(name) : capitalize(name)
|
16
|
+
def datatable_name
|
17
|
+
datatable_path.classify
|
26
18
|
end
|
27
19
|
|
28
|
-
|
29
|
-
|
30
|
-
|
20
|
+
private
|
21
|
+
def datatable_path
|
22
|
+
"#{name.underscore}_datatable"
|
31
23
|
end
|
32
24
|
|
33
|
-
def capitalize(name)
|
34
|
-
return name if name[0] == name[0].upcase
|
35
|
-
name.capitalize
|
36
|
-
end
|
37
25
|
end
|
38
26
|
end
|
39
27
|
end
|