ajax-datatables-rails 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +23 -1132
  3. data/.travis.yml +36 -24
  4. data/Appraisals +14 -8
  5. data/CHANGELOG.md +32 -7
  6. data/Gemfile +2 -0
  7. data/README.md +400 -335
  8. data/Rakefile +2 -1
  9. data/ajax-datatables-rails.gemspec +8 -6
  10. data/doc/webpack.md +50 -0
  11. data/gemfiles/{rails_4.1.15.gemfile → rails_4.1.16.gemfile} +1 -1
  12. data/gemfiles/{rails_4.2.8.gemfile → rails_4.2.10.gemfile} +2 -1
  13. data/gemfiles/{rails_5.0.3.gemfile → rails_5.0.7.gemfile} +1 -1
  14. data/gemfiles/{rails_5.1.1.gemfile → rails_5.1.6.gemfile} +1 -1
  15. data/gemfiles/rails_5.2.0.gemfile +13 -0
  16. data/lib/ajax-datatables-rails.rb +3 -11
  17. data/lib/ajax-datatables-rails/base.rb +44 -24
  18. data/lib/ajax-datatables-rails/config.rb +3 -0
  19. data/lib/ajax-datatables-rails/datatable/column.rb +33 -125
  20. data/lib/ajax-datatables-rails/datatable/column/date_filter.rb +77 -0
  21. data/lib/ajax-datatables-rails/datatable/column/order.rb +29 -0
  22. data/lib/ajax-datatables-rails/datatable/column/search.rb +88 -0
  23. data/lib/ajax-datatables-rails/datatable/datatable.rb +10 -7
  24. data/lib/ajax-datatables-rails/datatable/simple_order.rb +17 -2
  25. data/lib/ajax-datatables-rails/datatable/simple_search.rb +3 -0
  26. data/lib/ajax-datatables-rails/orm/active_record.rb +14 -5
  27. data/lib/ajax-datatables-rails/version.rb +3 -1
  28. data/lib/ajax_datatables_rails.rb +15 -0
  29. data/lib/generators/datatable/config_generator.rb +2 -0
  30. data/lib/generators/datatable/templates/ajax_datatables_rails_config.rb +5 -0
  31. data/lib/generators/rails/datatable_generator.rb +6 -5
  32. data/lib/generators/rails/templates/datatable.rb +1 -15
  33. data/spec/ajax-datatables-rails/base_spec.rb +23 -26
  34. data/spec/ajax-datatables-rails/datatable/column_spec.rb +68 -23
  35. data/spec/ajax-datatables-rails/datatable/datatable_spec.rb +1 -1
  36. data/spec/ajax-datatables-rails/datatable/simple_order_spec.rb +29 -2
  37. data/spec/ajax-datatables-rails/datatable/simple_search_spec.rb +1 -1
  38. data/spec/ajax-datatables-rails/extended_spec.rb +3 -3
  39. data/spec/ajax-datatables-rails/orm/active_record_filter_records_spec.rb +94 -35
  40. data/spec/ajax-datatables-rails/orm/active_record_paginate_records_spec.rb +6 -6
  41. data/spec/ajax-datatables-rails/orm/active_record_sort_records_spec.rb +43 -0
  42. data/spec/ajax-datatables-rails/orm/active_record_spec.rb +1 -1
  43. data/spec/factories/user.rb +1 -1
  44. data/spec/install_oracle.sh +2 -2
  45. data/spec/spec_helper.rb +8 -3
  46. data/spec/support/datatable_cond_date.rb +5 -0
  47. data/spec/support/datatable_cond_numeric.rb +41 -0
  48. data/spec/support/datatable_cond_proc.rb +11 -0
  49. data/spec/support/datatable_cond_string.rb +23 -0
  50. data/spec/support/datatable_order_nulls_last.rb +5 -0
  51. data/spec/support/test_helpers.rb +13 -88
  52. metadata +28 -13
  53. data/lib/ajax-datatables-rails/datatable/column_date_filter.rb +0 -41
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env rake
1
+ # frozen_string_literal: true
2
+
2
3
  require 'bundler/gem_tasks'
3
4
  require 'rspec/core/rake_task'
4
5
 
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  # coding: utf-8
2
- $:.push File.expand_path('../lib', __FILE__)
3
+
4
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
3
5
  require 'ajax-datatables-rails/version'
4
6
 
5
7
  Gem::Specification.new do |s|
@@ -9,15 +11,15 @@ Gem::Specification.new do |s|
9
11
  s.authors = ['Joel Quenneville', 'Antonio Antillon']
10
12
  s.email = ['joel.quenneville@collegeplus.org', 'antillas21@gmail.com']
11
13
  s.homepage = 'https://github.com/jbox-web/ajax-datatables-rails'
12
- s.summary = %q{A gem that simplifies using datatables and hundreds of records via ajax}
13
- s.description = %q{A wrapper around datatable's ajax methods that allow synchronization with server-side pagination in a rails app}
14
+ s.summary = 'A gem that simplifies using datatables and hundreds of records via ajax'
15
+ s.description = "A wrapper around datatable's ajax methods that allow synchronization with server-side pagination in a rails app"
14
16
  s.license = 'MIT'
15
17
 
16
18
  s.add_dependency 'railties', '>= 4.0'
17
19
 
18
20
  s.add_development_dependency 'rails', '>= 4.0'
19
21
  s.add_development_dependency 'rake'
20
- s.add_development_dependency 'pg'
22
+ s.add_development_dependency 'pg', '< 1.0'
21
23
  s.add_development_dependency 'mysql2'
22
24
  s.add_development_dependency 'sqlite3'
23
25
  s.add_development_dependency 'activerecord-oracle_enhanced-adapter'
@@ -26,12 +28,12 @@ Gem::Specification.new do |s|
26
28
  s.add_development_dependency 'pry'
27
29
  s.add_development_dependency 'simplecov'
28
30
  s.add_development_dependency 'database_cleaner'
29
- s.add_development_dependency 'factory_girl'
31
+ s.add_development_dependency 'factory_bot'
30
32
  s.add_development_dependency 'faker'
31
33
  s.add_development_dependency 'appraisal'
32
34
 
33
35
  s.files = `git ls-files`.split("\n")
34
36
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
35
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
37
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
36
38
  s.require_paths = ['lib']
37
39
  end
@@ -0,0 +1,50 @@
1
+ ### Webpack
2
+
3
+ We assume here that Bootstrap and FontAwesome are already installed with Webpack.
4
+
5
+ Inspired by https://datatables.net/download and completed :
6
+
7
+ Add npm packages :
8
+
9
+ ```sh
10
+ $ yarn add datatables.net
11
+ $ yarn add datatables.net-bs
12
+ $ yarn add datatables.net-buttons
13
+ $ yarn add datatables.net-buttons-bs
14
+ $ yarn add datatables.net-responsive
15
+ $ yarn add datatables.net-responsive-bs
16
+ $ yarn add datatables.net-select
17
+ $ yarn add datatables.net-select-bs
18
+ ```
19
+
20
+ In `config/webpack/loaders/datatables.js` :
21
+
22
+ ```js
23
+ module.exports = {
24
+ test: /datatables\.net.*/,
25
+ loader: 'imports-loader?define=>false'
26
+ }
27
+ ```
28
+
29
+ In `config/webpack/environment.js` :
30
+
31
+ ```js
32
+ const { environment } = require('@rails/webpacker')
33
+ const datatables = require('./loaders/datatables')
34
+ environment.loaders.append('datatables', datatables)
35
+ module.exports = environment
36
+ ```
37
+
38
+ in `app/javascript/pack/application.js` :
39
+
40
+ ```js
41
+ // Load Datatables
42
+ require('datatables.net-bs')(window, $)
43
+ require('datatables.net-buttons-bs')(window, $)
44
+ require('datatables.net-buttons/js/buttons.colVis.js')(window, $)
45
+ require('datatables.net-buttons/js/buttons.html5.js')(window, $)
46
+ require('datatables.net-buttons/js/buttons.print.js')(window, $)
47
+ require('datatables.net-responsive-bs')(window, $)
48
+ require('datatables.net-select')(window, $)
49
+ // require('yadcf')(window, $) // Uncomment if you use yadcf (need a recent version of yadcf)
50
+ ```
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "4.1.15"
5
+ gem "rails", "4.1.16"
6
6
  gem "mysql2", "~> 0.3.18"
7
7
  gem "activerecord-oracle_enhanced-adapter", "~> 1.5.0"
8
8
  gem "ruby-oci8" if ENV["DB_ADAPTER"] == "oracle_enhanced"
@@ -2,7 +2,8 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "4.2.8"
5
+ gem "rails", "4.2.10"
6
+ gem "mysql2", "0.4.10"
6
7
  gem "activerecord-oracle_enhanced-adapter", "~> 1.6.0"
7
8
  gem "ruby-oci8" if ENV["DB_ADAPTER"] == "oracle_enhanced"
8
9
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "5.0.3"
5
+ gem "rails", "5.0.7"
6
6
  gem "activerecord-oracle_enhanced-adapter", "~> 1.7.0"
7
7
  gem "ruby-oci8" if ENV["DB_ADAPTER"] == "oracle_enhanced"
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "5.1.1"
5
+ gem "rails", "5.1.6"
6
6
  gem "activerecord-oracle_enhanced-adapter", "~> 1.8.0"
7
7
  gem "ruby-oci8" if ENV["DB_ADAPTER"] == "oracle_enhanced"
8
8
 
@@ -0,0 +1,13 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "5.2.0"
6
+ gem "activerecord-oracle_enhanced-adapter", "~> 5.2.0"
7
+ gem "ruby-oci8" if ENV["DB_ADAPTER"] == "oracle_enhanced"
8
+
9
+ group :test do
10
+ gem "codeclimate-test-reporter", "~> 1.0.0"
11
+ end
12
+
13
+ gemspec path: "../"
@@ -1,11 +1,3 @@
1
- module AjaxDatatablesRails
2
- require 'ajax-datatables-rails/version'
3
- require 'ajax-datatables-rails/config'
4
- require 'ajax-datatables-rails/base'
5
- require 'ajax-datatables-rails/datatable/datatable'
6
- require 'ajax-datatables-rails/datatable/simple_search'
7
- require 'ajax-datatables-rails/datatable/simple_order'
8
- require 'ajax-datatables-rails/datatable/column_date_filter' unless AjaxDatatablesRails.old_rails?
9
- require 'ajax-datatables-rails/datatable/column'
10
- require 'ajax-datatables-rails/orm/active_record'
11
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'ajax_datatables_rails'
@@ -1,39 +1,37 @@
1
- module AjaxDatatablesRails
2
- class NotImplemented < StandardError; end
1
+ # frozen_string_literal: true
3
2
 
3
+ module AjaxDatatablesRails
4
4
  class Base
5
5
  extend Forwardable
6
6
 
7
7
  attr_reader :view, :options
8
8
  def_delegator :@view, :params
9
9
 
10
+ GLOBAL_SEARCH_DELIMITER = ' '
11
+
10
12
  def initialize(view, options = {})
11
13
  @view = view
12
14
  @options = options
13
15
  load_orm_extension
14
16
  end
15
17
 
16
- def config
17
- @config ||= AjaxDatatablesRails.config
18
- end
19
-
20
18
  def datatable
21
19
  @datatable ||= Datatable::Datatable.new(self)
22
20
  end
23
21
 
24
22
  def view_columns
25
- fail(NotImplemented, view_columns_error_text)
23
+ raise(NotImplementedError, view_columns_error_text)
26
24
  end
27
25
 
28
26
  def get_raw_records
29
- fail(NotImplemented, raw_records_error_text)
27
+ raise(NotImplementedError, raw_records_error_text)
30
28
  end
31
29
 
32
30
  def data
33
- fail(NotImplemented, data_error_text)
31
+ raise(NotImplementedError, data_error_text)
34
32
  end
35
33
 
36
- def additional_datas
34
+ def additional_data
37
35
  {}
38
36
  end
39
37
 
@@ -42,7 +40,7 @@ module AjaxDatatablesRails
42
40
  recordsTotal: records_total_count,
43
41
  recordsFiltered: records_filtered_count,
44
42
  data: sanitize(data)
45
- }.merge(additional_datas)
43
+ }.merge(get_additional_data)
46
44
  end
47
45
 
48
46
  def records
@@ -72,12 +70,29 @@ module AjaxDatatablesRails
72
70
 
73
71
  private
74
72
 
73
+ # This method is necessary for smooth transition from
74
+ # `additinonal_datas` method to `additional_data`
75
+ # without breaking change.
76
+ def get_additional_data
77
+ if respond_to?(:additional_datas)
78
+ puts <<-WARNING
79
+ `additional_datas` has been deprecated and
80
+ will be removed in next major version update!
81
+ Please use `additional_data` instead.
82
+ WARNING
83
+
84
+ additional_datas
85
+ else
86
+ additional_data
87
+ end
88
+ end
89
+
75
90
  def sanitize(data)
76
91
  data.map do |record|
77
92
  if record.is_a?(Array)
78
93
  record.map { |td| ERB::Util.html_escape(td) }
79
94
  else
80
- record.update(record){ |_, v| ERB::Util.html_escape(v) }
95
+ record.update(record) { |_, v| ERB::Util.html_escape(v) }
81
96
  end
82
97
  end
83
98
  end
@@ -91,48 +106,53 @@ module AjaxDatatablesRails
91
106
  end
92
107
 
93
108
  def records_total_count
94
- get_raw_records.count(:all)
109
+ fetch_records.count(:all)
95
110
  end
96
111
 
97
112
  def records_filtered_count
98
- filter_records(get_raw_records).count(:all)
113
+ filter_records(fetch_records).count(:all)
99
114
  end
100
115
 
101
116
  # Private helper methods
102
117
  def load_orm_extension
103
- case config.orm
104
- when :mongoid then nil
105
- when :active_record then extend ORM::ActiveRecord
106
- else
118
+ case AjaxDatatablesRails.config.orm
119
+ when :active_record
120
+ extend ORM::ActiveRecord
121
+ when :mongoid
107
122
  nil
108
123
  end
109
124
  end
110
125
 
126
+ def global_search_delimiter
127
+ GLOBAL_SEARCH_DELIMITER
128
+ end
129
+
111
130
  def raw_records_error_text
112
- return <<-eos
131
+ <<-ERROR
113
132
 
114
133
  You should implement this method in your class and specify
115
134
  how records are going to be retrieved from the database.
116
- eos
135
+ ERROR
117
136
  end
118
137
 
119
138
  def data_error_text
120
- return <<-eos
139
+ <<-ERROR
121
140
 
122
141
  You should implement this method in your class and return an array
123
142
  of arrays, or an array of hashes, as defined in the jQuery.dataTables
124
143
  plugin documentation.
125
- eos
144
+ ERROR
126
145
  end
127
146
 
128
147
  def view_columns_error_text
129
- return <<-eos
148
+ <<-ERROR
130
149
 
131
150
  You should implement this method in your class and return an array
132
151
  of database columns based on the columns displayed in the HTML view.
133
152
  These columns should be represented in the ModelName.column_name,
134
153
  or aliased_join_table.column_name notation.
135
- eos
154
+ ERROR
136
155
  end
156
+
137
157
  end
138
158
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/configurable'
2
4
 
3
5
  module AjaxDatatablesRails
@@ -24,5 +26,6 @@ module AjaxDatatablesRails
24
26
 
25
27
  config_accessor(:orm) { :active_record }
26
28
  config_accessor(:db_adapter) { :postgresql }
29
+ config_accessor(:nulls_last) { false }
27
30
  end
28
31
  end
@@ -1,167 +1,75 @@
1
- require 'ostruct'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module AjaxDatatablesRails
4
4
  module Datatable
5
5
  class Column
6
+
7
+ DB_ADAPTER_TYPE_CAST = {
8
+ mysql: 'CHAR',
9
+ mysql2: 'CHAR',
10
+ sqlite: 'TEXT',
11
+ sqlite3: 'TEXT',
12
+ oracle: 'VARCHAR2(4000)',
13
+ oracleenhanced: 'VARCHAR2(4000)'
14
+ }.freeze
15
+
6
16
  attr_reader :datatable, :index, :options
17
+ attr_writer :search
18
+
19
+ include Search
20
+ include Order
21
+ prepend DateFilter unless AjaxDatatablesRails.old_rails?
7
22
 
8
- unless AjaxDatatablesRails.old_rails?
9
- prepend ColumnDateFilter
10
- end
11
23
 
12
24
  def initialize(datatable, index, options)
13
- @datatable, @index, @options = datatable, index, options
14
- @view_column = datatable.view_columns[options["data"].to_sym]
25
+ @datatable = datatable
26
+ @index = index
27
+ @options = options
28
+ @view_column = datatable.view_columns[options[:data].to_sym]
15
29
  end
16
30
 
17
31
  def data
18
32
  options[:data].presence || options[:name]
19
33
  end
20
34
 
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
35
  def source
50
36
  @view_column[:source]
51
37
  end
52
38
 
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
39
  def table
75
- model = source.split('.').first.constantize
76
- model.arel_table rescue model
40
+ model.respond_to?(:arel_table) ? model.arel_table : model
77
41
  end
78
42
 
79
- def field
80
- source.split('.').last.to_sym
81
- end
82
-
83
- def search_query
84
- search.regexp? ? regex_search : non_regex_search
43
+ def model
44
+ @model ||= source.split('.').first.constantize
85
45
  end
86
46
 
87
- def sort_query
88
- custom_field? ? source : "#{table.name}.#{sort_field}"
47
+ def field
48
+ @field ||= source.split('.').last.to_sym
89
49
  end
90
50
 
91
- private
92
-
93
51
  def custom_field?
94
52
  !source.include?('.')
95
53
  end
96
54
 
97
- def config
98
- @config ||= AjaxDatatablesRails.config
55
+ # Add formater option to allow modification of the value
56
+ # before passing it to the database
57
+ def formater
58
+ @view_column[:formater]
99
59
  end
100
60
 
101
61
  def formated_value
102
62
  formater ? formater.call(search.value) : search.value
103
63
  end
104
64
 
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
65
+ private
136
66
 
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
67
+ def type_cast
68
+ @type_cast ||= (DB_ADAPTER_TYPE_CAST[AjaxDatatablesRails.config.db_adapter] || 'VARCHAR')
145
69
  end
146
70
 
147
71
  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
72
+ @casted_column ||= ::Arel::Nodes::NamedFunction.new('CAST', [table[field].as(type_cast)])
165
73
  end
166
74
 
167
75
  end