ajax-datatables-rails 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|