ajax-datatables-rails 0.3.1 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +26 -0
  3. data/.github/workflows/ci.yml +120 -0
  4. data/.gitignore +20 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +58 -0
  7. data/Appraisals +35 -0
  8. data/CHANGELOG.md +92 -7
  9. data/Gemfile +2 -1
  10. data/Guardfile +16 -0
  11. data/LICENSE +17 -18
  12. data/README.md +595 -404
  13. data/Rakefile +4 -2
  14. data/ajax-datatables-rails.gemspec +38 -25
  15. data/bin/_guard-core +29 -0
  16. data/bin/appraisal +29 -0
  17. data/bin/bundle +114 -0
  18. data/bin/guard +29 -0
  19. data/bin/rake +29 -0
  20. data/bin/rspec +29 -0
  21. data/bin/rubocop +29 -0
  22. data/doc/migrate.md +97 -0
  23. data/doc/webpack.md +55 -0
  24. data/gemfiles/rails_5.2.4.gemfile +11 -0
  25. data/gemfiles/rails_6.0.3.gemfile +11 -0
  26. data/gemfiles/rails_6.1.0.gemfile +11 -0
  27. data/lib/ajax-datatables-rails/active_record.rb +7 -0
  28. data/lib/ajax-datatables-rails/base.rb +96 -151
  29. data/lib/ajax-datatables-rails/datatable/column/date_filter.rb +68 -0
  30. data/lib/ajax-datatables-rails/datatable/column/order.rb +29 -0
  31. data/lib/ajax-datatables-rails/datatable/column/search.rb +129 -0
  32. data/lib/ajax-datatables-rails/datatable/column.rb +122 -0
  33. data/lib/ajax-datatables-rails/datatable/datatable.rb +91 -0
  34. data/lib/ajax-datatables-rails/datatable/simple_order.rb +59 -0
  35. data/lib/ajax-datatables-rails/datatable/simple_search.rb +23 -0
  36. data/lib/ajax-datatables-rails/datatable.rb +6 -0
  37. data/lib/ajax-datatables-rails/error.rb +9 -0
  38. data/lib/ajax-datatables-rails/orm/active_record.rb +60 -0
  39. data/lib/ajax-datatables-rails/orm.rb +6 -0
  40. data/lib/ajax-datatables-rails/version.rb +15 -1
  41. data/lib/ajax-datatables-rails.rb +11 -7
  42. data/lib/generators/rails/datatable_generator.rb +11 -22
  43. data/lib/generators/rails/templates/datatable.rb +13 -15
  44. data/spec/ajax-datatables-rails/base_spec.rb +223 -0
  45. data/spec/ajax-datatables-rails/datatable/column_spec.rb +222 -0
  46. data/spec/ajax-datatables-rails/datatable/datatable_spec.rb +127 -0
  47. data/spec/ajax-datatables-rails/datatable/simple_order_spec.rb +62 -0
  48. data/spec/ajax-datatables-rails/datatable/simple_search_spec.rb +19 -0
  49. data/spec/ajax-datatables-rails/orm/active_record_filter_records_spec.rb +617 -0
  50. data/spec/ajax-datatables-rails/orm/active_record_paginate_records_spec.rb +67 -0
  51. data/spec/ajax-datatables-rails/orm/active_record_sort_records_spec.rb +80 -0
  52. data/spec/factories/user.rb +11 -0
  53. data/spec/install_oracle.sh +18 -0
  54. data/spec/spec_helper.rb +86 -4
  55. data/spec/support/datatables/complex_datatable.rb +31 -0
  56. data/spec/support/datatables/complex_datatable_array.rb +16 -0
  57. data/spec/support/datatables/datatable_cond_date.rb +7 -0
  58. data/spec/support/datatables/datatable_cond_numeric.rb +53 -0
  59. data/spec/support/datatables/datatable_cond_proc.rb +13 -0
  60. data/spec/support/datatables/datatable_cond_string.rb +43 -0
  61. data/spec/support/datatables/datatable_cond_unknown.rb +7 -0
  62. data/spec/support/datatables/datatable_order_nulls_last.rb +7 -0
  63. data/spec/support/helpers/params.rb +74 -0
  64. data/spec/support/models/user.rb +4 -0
  65. data/spec/support/schema.rb +16 -0
  66. metadata +196 -48
  67. data/lib/ajax-datatables-rails/config.rb +0 -25
  68. data/lib/ajax-datatables-rails/extensions/kaminari.rb +0 -12
  69. data/lib/ajax-datatables-rails/extensions/simple_paginator.rb +0 -12
  70. data/lib/ajax-datatables-rails/extensions/will_paginate.rb +0 -12
  71. data/lib/ajax-datatables-rails/models.rb +0 -6
  72. data/lib/generators/datatable/config_generator.rb +0 -17
  73. data/lib/generators/datatable/templates/ajax_datatables_rails_config.rb +0 -7
  74. data/spec/ajax-datatables-rails/ajax_datatables_rails_spec.rb +0 -351
  75. data/spec/ajax-datatables-rails/kaminari_spec.rb +0 -35
  76. data/spec/ajax-datatables-rails/models_spec.rb +0 -10
  77. data/spec/ajax-datatables-rails/simple_paginator_spec.rb +0 -32
  78. data/spec/ajax-datatables-rails/will_paginate_spec.rb +0 -28
  79. data/spec/schema.rb +0 -35
  80. data/spec/test_models.rb +0 -21
@@ -1,214 +1,159 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AjaxDatatablesRails
2
4
  class Base
3
- extend Forwardable
4
- class MethodNotImplementedError < StandardError; end
5
5
 
6
- attr_reader :view, :options, :sortable_columns, :searchable_columns
7
- def_delegator :@view, :params, :params
6
+ class_attribute :db_adapter, default: ::ActiveRecord::Base.connection.adapter_name.downcase.to_sym
7
+ class_attribute :nulls_last, default: false
8
8
 
9
- def initialize(view, options = {})
10
- @view = view
11
- @options = options
12
- load_paginator
13
- end
9
+ attr_reader :params, :options, :datatable
14
10
 
15
- def config
16
- @config ||= AjaxDatatablesRails.config
17
- end
11
+ GLOBAL_SEARCH_DELIMITER = ' '
18
12
 
19
- def sortable_columns
20
- @sortable_columns ||= []
13
+ def initialize(params, options = {})
14
+ @params = params
15
+ @options = options
16
+ @datatable = Datatable::Datatable.new(self)
21
17
  end
22
18
 
23
- def searchable_columns
24
- @searchable_columns ||= []
25
- end
26
-
27
- def data
28
- fail(
29
- MethodNotImplementedError,
30
- 'Please implement this method in your class.'
31
- )
19
+ # User defined methods
20
+ def view_columns
21
+ raise(NotImplementedError, view_columns_error_text)
32
22
  end
33
23
 
34
24
  def get_raw_records
35
- fail(
36
- MethodNotImplementedError,
37
- 'Please implement this method in your class.'
38
- )
25
+ raise(NotImplementedError, raw_records_error_text)
39
26
  end
40
27
 
41
- def as_json(options = {})
42
- {
43
- :draw => params[:draw].to_i,
44
- :recordsTotal => get_raw_records.count(:all),
45
- :recordsFiltered => filter_records(get_raw_records).count(:all),
46
- :data => data
47
- }
48
- end
49
-
50
- def self.deprecated(message, caller = Kernel.caller[1])
51
- warning = caller + ": " + message
52
-
53
- if(respond_to?(:logger) && logger.present?)
54
- logger.warn(warning)
55
- else
56
- warn(warning)
57
- end
28
+ def data
29
+ raise(NotImplementedError, data_error_text)
58
30
  end
59
31
 
60
- private
61
-
62
- def records
63
- @records ||= fetch_records
32
+ # ORM defined methods
33
+ def fetch_records
34
+ get_raw_records
64
35
  end
65
36
 
66
- def fetch_records
67
- records = get_raw_records
68
- records = sort_records(records) if params[:order].present?
69
- records = filter_records(records) if params[:search].present?
70
- records = paginate_records(records) unless params[:length].present? && params[:length] == '-1'
71
- records
37
+ def filter_records(records)
38
+ raise(NotImplementedError)
72
39
  end
73
40
 
74
41
  def sort_records(records)
75
- sort_by = []
76
- params[:order].each_value do |item|
77
- sort_by << "#{sort_column(item)} #{sort_direction(item)}"
78
- end
79
- records.order(sort_by.join(", "))
42
+ raise(NotImplementedError)
80
43
  end
81
44
 
82
45
  def paginate_records(records)
83
- fail(
84
- MethodNotImplementedError,
85
- 'Please mixin a pagination extension.'
86
- )
46
+ raise(NotImplementedError)
87
47
  end
88
48
 
89
- def filter_records(records)
90
- records = simple_search(records)
91
- records = composite_search(records)
92
- records
49
+ # User overides
50
+ def additional_data
51
+ {}
93
52
  end
94
53
 
95
- def simple_search(records)
96
- return records unless (params[:search].present? && params[:search][:value].present?)
97
- conditions = build_conditions_for(params[:search][:value])
98
- records = records.where(conditions) if conditions
99
- records
54
+ # JSON structure sent to jQuery DataTables
55
+ def as_json(*)
56
+ {
57
+ recordsTotal: records_total_count,
58
+ recordsFiltered: records_filtered_count,
59
+ data: sanitize_data(data),
60
+ }.merge(additional_data)
100
61
  end
101
62
 
102
- def composite_search(records)
103
- conditions = aggregate_query
104
- records = records.where(conditions) if conditions
105
- records
63
+ # User helper methods
64
+ def column_id(name)
65
+ view_columns.keys.index(name.to_sym)
106
66
  end
107
67
 
108
- def build_conditions_for(query)
109
- search_for = query.split(' ')
110
- criteria = search_for.inject([]) do |criteria, atom|
111
- criteria << searchable_columns.map { |col| search_condition(col, atom) }.reduce(:or)
112
- end.reduce(:and)
113
- criteria
68
+ def column_data(column)
69
+ id = column_id(column)
70
+ params.dig('columns', id.to_s, 'search', 'value')
114
71
  end
115
72
 
116
- def search_condition(column, value)
117
- if column[0] == column.downcase[0]
118
- ::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'
119
- return deprecated_search_condition(column, value)
120
- else
121
- return new_search_condition(column, value)
122
- end
123
- end
73
+ private
124
74
 
125
- def new_search_condition(column, value)
126
- model, column = column.split('.')
127
- model = model.constantize
128
- casted_column = ::Arel::Nodes::NamedFunction.new('CAST', [model.arel_table[column.to_sym].as(typecast)])
129
- casted_column.matches("%#{value}%")
75
+ # helper methods
76
+ def connected_columns
77
+ @connected_columns ||= begin
78
+ view_columns.keys.map do |field_name|
79
+ datatable.column_by(:data, field_name.to_s)
80
+ end.compact
81
+ end
130
82
  end
131
83
 
132
- def deprecated_search_condition(column, value)
133
- model, column = column.split('.')
134
- model = model.singularize.titleize.gsub( / /, '' ).constantize
135
-
136
- casted_column = ::Arel::Nodes::NamedFunction.new('CAST', [model.arel_table[column.to_sym].as(typecast)])
137
- casted_column.matches("%#{value}%")
84
+ def searchable_columns
85
+ @searchable_columns ||= begin
86
+ connected_columns.select(&:searchable?)
87
+ end
138
88
  end
139
89
 
140
- def aggregate_query
141
- conditions = searchable_columns.each_with_index.map do |column, index|
142
- value = params[:columns]["#{index}"][:search][:value] if params[:columns]
143
- search_condition(column, value) unless value.blank?
90
+ def search_columns
91
+ @search_columns ||= begin
92
+ searchable_columns.select(&:searched?)
144
93
  end
145
- conditions.compact.reduce(:and)
146
94
  end
147
95
 
148
- def typecast
149
- case config.db_adapter
150
- when :oracle then 'VARCHAR2(4000)'
151
- when :pg then 'VARCHAR'
152
- when :mysql2 then 'CHAR'
153
- when :sqlite3 then 'TEXT'
96
+ def sanitize_data(data)
97
+ data.map do |record|
98
+ if record.is_a?(Array)
99
+ record.map { |td| ERB::Util.html_escape(td) }
100
+ else
101
+ record.update(record) { |_, v| ERB::Util.html_escape(v) }
102
+ end
154
103
  end
155
104
  end
156
105
 
157
- def offset
158
- (page - 1) * per_page
106
+ # called from within #data
107
+ def records
108
+ @records ||= retrieve_records
159
109
  end
160
110
 
161
- def page
162
- (params[:start].to_i / per_page) + 1
111
+ def retrieve_records
112
+ records = fetch_records
113
+ records = filter_records(records)
114
+ records = sort_records(records) if datatable.orderable?
115
+ records = paginate_records(records) if datatable.paginate?
116
+ records
163
117
  end
164
118
 
165
- def per_page
166
- params.fetch(:length, 10).to_i
119
+ def records_total_count
120
+ fetch_records.count(:all)
167
121
  end
168
122
 
169
- def sort_column(item)
170
- new_sort_column(item)
171
- rescue
172
- ::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'
173
- deprecated_sort_column(item)
123
+ def records_filtered_count
124
+ filter_records(fetch_records).count(:all)
174
125
  end
175
126
 
176
- def deprecated_sort_column(item)
177
- sortable_columns[sortable_displayed_columns.index(item[:column])]
127
+ def global_search_delimiter
128
+ GLOBAL_SEARCH_DELIMITER
178
129
  end
179
130
 
180
- def new_sort_column(item)
181
- model, column = sortable_columns[sortable_displayed_columns.index(item[:column])].split('.')
182
- col = [model.constantize.table_name, column].join('.')
183
- end
131
+ def raw_records_error_text
132
+ <<-ERROR
184
133
 
185
- def sort_direction(item)
186
- options = %w(desc asc)
187
- options.include?(item[:dir]) ? item[:dir].upcase : 'ASC'
134
+ You should implement this method in your class and specify
135
+ how records are going to be retrieved from the database.
136
+ ERROR
188
137
  end
189
138
 
190
- def sortable_displayed_columns
191
- @sortable_displayed_columns ||= generate_sortable_displayed_columns
192
- end
139
+ def data_error_text
140
+ <<-ERROR
193
141
 
194
- def generate_sortable_displayed_columns
195
- @sortable_displayed_columns = []
196
- params[:columns].each_value do |column|
197
- @sortable_displayed_columns << column[:data] if column[:orderable] == 'true'
198
- end
199
- @sortable_displayed_columns
142
+ You should implement this method in your class and return an array
143
+ of arrays, or an array of hashes, as defined in the jQuery.dataTables
144
+ plugin documentation.
145
+ ERROR
200
146
  end
201
147
 
202
- def load_paginator
203
- case config.paginator
204
- when :kaminari
205
- extend Extensions::Kaminari
206
- when :will_paginate
207
- extend Extensions::WillPaginate
208
- else
209
- extend Extensions::SimplePaginator
210
- end
211
- self
148
+ def view_columns_error_text
149
+ <<-ERROR
150
+
151
+ You should implement this method in your class and return an array
152
+ of database columns based on the columns displayed in the HTML view.
153
+ These columns should be represented in the ModelName.column_name,
154
+ or aliased_join_table.column_name notation.
155
+ ERROR
212
156
  end
157
+
213
158
  end
214
159
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AjaxDatatablesRails
4
+ module Datatable
5
+ class Column
6
+ module DateFilter
7
+
8
+ RANGE_DELIMITER = '-'
9
+
10
+ class DateRange
11
+ attr_reader :begin, :end
12
+
13
+ def initialize(date_start, date_end)
14
+ @begin = date_start
15
+ @end = date_end
16
+ end
17
+
18
+ def exclude_end?
19
+ false
20
+ end
21
+ end
22
+
23
+ # Add delimiter option to handle range search
24
+ def delimiter
25
+ @delimiter ||= @view_column.fetch(:delimiter, RANGE_DELIMITER)
26
+ end
27
+
28
+ # A range value is in form '<range_start><delimiter><range_end>'
29
+ # This returns <range_start>
30
+ def range_start
31
+ @range_start ||= formatted_value.split(delimiter)[0]
32
+ end
33
+
34
+ # A range value is in form '<range_start><delimiter><range_end>'
35
+ # This returns <range_end>
36
+ def range_end
37
+ @range_end ||= formatted_value.split(delimiter)[1]
38
+ end
39
+
40
+ def empty_range_search?
41
+ (formatted_value == delimiter) || (range_start.blank? && range_end.blank?)
42
+ end
43
+
44
+ # Do a range search
45
+ def date_range_search
46
+ return nil if empty_range_search?
47
+
48
+ table[field].between(DateRange.new(range_start_casted, range_end_casted))
49
+ end
50
+
51
+ private
52
+
53
+ def range_start_casted
54
+ range_start.blank? ? parse_date('01/01/1970') : parse_date(range_start)
55
+ end
56
+
57
+ def range_end_casted
58
+ range_end.blank? ? parse_date('9999-12-31 23:59:59') : parse_date("#{range_end} 23:59:59")
59
+ end
60
+
61
+ def parse_date(date)
62
+ Time.zone ? Time.zone.parse(date) : Time.parse(date)
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AjaxDatatablesRails
4
+ module Datatable
5
+ class Column
6
+ module Order
7
+
8
+ def orderable?
9
+ @view_column.fetch(:orderable, true)
10
+ end
11
+
12
+ # Add sort_field option to allow overriding of sort field
13
+ def sort_field
14
+ @view_column.fetch(:sort_field, field)
15
+ end
16
+
17
+ def sort_query
18
+ custom_field? ? source : "#{table.name}.#{sort_field}"
19
+ end
20
+
21
+ # Add option to sort null values last
22
+ def nulls_last?
23
+ @view_column.fetch(:nulls_last, false)
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AjaxDatatablesRails
4
+ module Datatable
5
+ class Column
6
+ module Search
7
+
8
+ SMALLEST_PQ_INTEGER = -2_147_483_648
9
+ LARGEST_PQ_INTEGER = 2_147_483_647
10
+ NOT_NULL_VALUE = '!NULL'
11
+ EMPTY_VALUE = ''
12
+
13
+ def searchable?
14
+ @view_column.fetch(:searchable, true)
15
+ end
16
+
17
+ def cond
18
+ @view_column.fetch(:cond, :like)
19
+ end
20
+
21
+ def filter
22
+ @view_column[:cond].call(self, formatted_value)
23
+ end
24
+
25
+ def search
26
+ @search ||= SimpleSearch.new(options[:search])
27
+ end
28
+
29
+ def searched?
30
+ search.value.present?
31
+ end
32
+
33
+ def search_query
34
+ search.regexp? ? regex_search : non_regex_search
35
+ end
36
+
37
+ # Add use_regex option to allow bypassing of regex search
38
+ def use_regex?
39
+ @view_column.fetch(:use_regex, true)
40
+ end
41
+
42
+ private
43
+
44
+ # Using multi-select filters in JQuery Datatable auto-enables regex_search.
45
+ # Unfortunately regex_search doesn't work when filtering on primary keys with integer.
46
+ # It generates this kind of query : AND ("regions"."id" ~ '2|3') which throws an error :
47
+ # operator doesn't exist : integer ~ unknown
48
+ # The solution is to bypass regex_search and use non_regex_search with :in operator
49
+ def regex_search
50
+ if use_regex?
51
+ ::Arel::Nodes::Regexp.new((custom_field? ? field : table[field]), ::Arel::Nodes.build_quoted(formatted_value))
52
+ else
53
+ non_regex_search
54
+ end
55
+ end
56
+
57
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
58
+ def non_regex_search
59
+ case cond
60
+ when Proc
61
+ filter
62
+ when :eq, :not_eq, :lt, :gt, :lteq, :gteq, :in
63
+ searchable_integer? ? raw_search(cond) : empty_search
64
+ when :start_with
65
+ text_search("#{formatted_value}%")
66
+ when :end_with
67
+ text_search("%#{formatted_value}")
68
+ when :like
69
+ text_search("%#{formatted_value}%")
70
+ when :string_eq
71
+ raw_search(:eq)
72
+ when :string_in
73
+ raw_search(:in)
74
+ when :null_value
75
+ null_value_search
76
+ when :date_range
77
+ date_range_search
78
+ end
79
+ end
80
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
81
+
82
+ def null_value_search
83
+ if formatted_value == NOT_NULL_VALUE
84
+ table[field].not_eq(nil)
85
+ else
86
+ table[field].eq(nil)
87
+ end
88
+ end
89
+
90
+ def raw_search(cond)
91
+ if custom_field?
92
+ ::Arel::Nodes::SqlLiteral.new(field).eq(formatted_value)
93
+ else
94
+ table[field].send(cond, formatted_value)
95
+ end
96
+ end
97
+
98
+ def text_search(value)
99
+ casted_column.matches(value)
100
+ end
101
+
102
+ def empty_search
103
+ casted_column.matches(EMPTY_VALUE)
104
+ end
105
+
106
+ def searchable_integer?
107
+ if formatted_value.is_a?(Array)
108
+ valids = formatted_value.map { |v| integer?(v) && !out_of_range?(v) }
109
+ !valids.include?(false)
110
+ else
111
+ integer?(formatted_value) && !out_of_range?(formatted_value)
112
+ end
113
+ end
114
+
115
+ def out_of_range?(search_value)
116
+ Integer(search_value) > LARGEST_PQ_INTEGER || Integer(search_value) < SMALLEST_PQ_INTEGER
117
+ end
118
+
119
+ def integer?(string)
120
+ Integer(string)
121
+ true
122
+ rescue ArgumentError
123
+ false
124
+ end
125
+
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AjaxDatatablesRails
4
+ module Datatable
5
+ class Column
6
+
7
+ include Search
8
+ include Order
9
+ include DateFilter
10
+
11
+ attr_reader :datatable, :index, :options
12
+ attr_writer :search
13
+
14
+ def initialize(datatable, index, options)
15
+ @datatable = datatable
16
+ @index = index
17
+ @options = options
18
+ @view_column = datatable.view_columns[column_name]
19
+ validate_settings!
20
+ end
21
+
22
+ def column_name
23
+ @column_name ||= options[:data]&.to_sym
24
+ end
25
+
26
+ def data
27
+ options[:data].presence || options[:name]
28
+ end
29
+
30
+ def source
31
+ @view_column[:source]
32
+ end
33
+
34
+ def table
35
+ model.respond_to?(:arel_table) ? model.arel_table : model
36
+ end
37
+
38
+ def model
39
+ @model ||= source.split('.').first.constantize
40
+ end
41
+
42
+ def field
43
+ @field ||= source.split('.').last.to_sym
44
+ end
45
+
46
+ def custom_field?
47
+ !source.include?('.')
48
+ end
49
+
50
+ # Add formatter option to allow modification of the value
51
+ # before passing it to the database
52
+ def formatter
53
+ @view_column[:formatter]
54
+ end
55
+
56
+ def formatted_value
57
+ formatter ? formatter.call(search.value) : search.value
58
+ end
59
+
60
+ private
61
+
62
+ TYPE_CAST_DEFAULT = 'VARCHAR'
63
+ TYPE_CAST_MYSQL = 'CHAR'
64
+ TYPE_CAST_SQLITE = 'TEXT'
65
+ TYPE_CAST_ORACLE = 'VARCHAR2(4000)'
66
+ TYPE_CAST_SQLSERVER = 'VARCHAR(4000)'
67
+
68
+ DB_ADAPTER_TYPE_CAST = {
69
+ mysql: TYPE_CAST_MYSQL,
70
+ mysql2: TYPE_CAST_MYSQL,
71
+ sqlite: TYPE_CAST_SQLITE,
72
+ sqlite3: TYPE_CAST_SQLITE,
73
+ oracle: TYPE_CAST_ORACLE,
74
+ oracleenhanced: TYPE_CAST_ORACLE,
75
+ sqlserver: TYPE_CAST_SQLSERVER,
76
+ }.freeze
77
+
78
+ private_constant :TYPE_CAST_DEFAULT
79
+ private_constant :TYPE_CAST_MYSQL
80
+ private_constant :TYPE_CAST_SQLITE
81
+ private_constant :TYPE_CAST_ORACLE
82
+ private_constant :TYPE_CAST_SQLSERVER
83
+ private_constant :DB_ADAPTER_TYPE_CAST
84
+
85
+ def type_cast
86
+ @type_cast ||= DB_ADAPTER_TYPE_CAST.fetch(datatable.db_adapter, TYPE_CAST_DEFAULT)
87
+ end
88
+
89
+ def casted_column
90
+ @casted_column ||= ::Arel::Nodes::NamedFunction.new('CAST', [table[field].as(type_cast)])
91
+ end
92
+
93
+ def validate_settings!
94
+ raise AjaxDatatablesRails::Error::InvalidSearchColumn, "Unknown column. Check that `data` field is filled on JS side with the column name" if column_name.empty?
95
+ raise AjaxDatatablesRails::Error::InvalidSearchColumn, "Check that column '#{column_name}' exists in view_columns" unless valid_search_column?(column_name)
96
+ raise AjaxDatatablesRails::Error::InvalidSearchCondition, cond unless valid_search_condition?(cond)
97
+ end
98
+
99
+ def valid_search_column?(column_name)
100
+ !datatable.view_columns[column_name].nil?
101
+ end
102
+
103
+ VALID_SEARCH_CONDITIONS = [
104
+ # String condition
105
+ :start_with, :end_with, :like, :string_eq, :string_in, :null_value,
106
+ # Numeric condition
107
+ :eq, :not_eq, :lt, :gt, :lteq, :gteq, :in,
108
+ # Date condition
109
+ :date_range
110
+ ].freeze
111
+
112
+ private_constant :VALID_SEARCH_CONDITIONS
113
+
114
+ def valid_search_condition?(cond)
115
+ return true if cond.is_a?(Proc)
116
+
117
+ VALID_SEARCH_CONDITIONS.include?(cond)
118
+ end
119
+
120
+ end
121
+ end
122
+ end