ajax-datatables-rails 0.4.3 → 1.3.1

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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +120 -0
  3. data/.rubocop.yml +17 -7
  4. data/Appraisals +15 -20
  5. data/CHANGELOG.md +54 -1
  6. data/Gemfile +0 -5
  7. data/Guardfile +16 -0
  8. data/README.md +238 -112
  9. data/Rakefile +1 -0
  10. data/ajax-datatables-rails.gemspec +24 -20
  11. data/bin/_guard-core +29 -0
  12. data/bin/appraisal +29 -0
  13. data/bin/bundle +114 -0
  14. data/bin/guard +29 -0
  15. data/bin/rake +29 -0
  16. data/bin/rspec +29 -0
  17. data/bin/rubocop +29 -0
  18. data/doc/migrate.md +97 -0
  19. data/doc/webpack.md +7 -2
  20. data/gemfiles/{rails_5.2.0.gemfile → rails_5.2.4.gemfile} +3 -5
  21. data/gemfiles/{rails_5.0.7.gemfile → rails_6.0.3.gemfile} +4 -6
  22. data/gemfiles/{rails_5.1.6.gemfile → rails_6.1.0.gemfile} +4 -6
  23. data/lib/ajax-datatables-rails.rb +12 -1
  24. data/lib/ajax-datatables-rails/active_record.rb +7 -0
  25. data/lib/ajax-datatables-rails/base.rb +47 -46
  26. data/lib/ajax-datatables-rails/datatable.rb +6 -0
  27. data/lib/ajax-datatables-rails/datatable/column.rb +65 -20
  28. data/lib/ajax-datatables-rails/datatable/column/date_filter.rb +12 -21
  29. data/lib/ajax-datatables-rails/datatable/column/order.rb +1 -1
  30. data/lib/ajax-datatables-rails/datatable/column/search.rb +37 -22
  31. data/lib/ajax-datatables-rails/datatable/datatable.rb +16 -7
  32. data/lib/ajax-datatables-rails/datatable/simple_order.rb +23 -10
  33. data/lib/ajax-datatables-rails/datatable/simple_search.rb +2 -0
  34. data/lib/ajax-datatables-rails/error.rb +9 -0
  35. data/lib/ajax-datatables-rails/orm.rb +6 -0
  36. data/lib/ajax-datatables-rails/orm/active_record.rb +11 -12
  37. data/lib/ajax-datatables-rails/version.rb +13 -1
  38. data/lib/generators/rails/templates/datatable.rb +1 -1
  39. data/spec/ajax-datatables-rails/base_spec.rb +129 -93
  40. data/spec/ajax-datatables-rails/datatable/column_spec.rb +105 -37
  41. data/spec/ajax-datatables-rails/datatable/datatable_spec.rb +71 -31
  42. data/spec/ajax-datatables-rails/datatable/simple_order_spec.rb +36 -14
  43. data/spec/ajax-datatables-rails/datatable/simple_search_spec.rb +4 -2
  44. data/spec/ajax-datatables-rails/orm/active_record_filter_records_spec.rb +315 -272
  45. data/spec/ajax-datatables-rails/orm/active_record_paginate_records_spec.rb +9 -8
  46. data/spec/ajax-datatables-rails/orm/active_record_sort_records_spec.rb +17 -14
  47. data/spec/factories/user.rb +3 -1
  48. data/spec/install_oracle.sh +9 -3
  49. data/spec/spec_helper.rb +33 -28
  50. data/spec/support/datatables/complex_datatable.rb +31 -0
  51. data/spec/support/datatables/complex_datatable_array.rb +16 -0
  52. data/spec/support/{datatable_cond_date.rb → datatables/datatable_cond_date.rb} +2 -0
  53. data/spec/support/{datatable_cond_numeric.rb → datatables/datatable_cond_numeric.rb} +3 -1
  54. data/spec/support/{datatable_cond_proc.rb → datatables/datatable_cond_proc.rb} +2 -0
  55. data/spec/support/{datatable_cond_string.rb → datatables/datatable_cond_string.rb} +9 -1
  56. data/spec/support/datatables/datatable_cond_unknown.rb +7 -0
  57. data/spec/support/{datatable_order_nulls_last.rb → datatables/datatable_order_nulls_last.rb} +2 -0
  58. data/spec/support/{test_helpers.rb → helpers/params.rb} +17 -42
  59. data/spec/support/{test_models.rb → models/user.rb} +2 -0
  60. data/spec/support/schema.rb +3 -1
  61. metadata +76 -75
  62. data/.travis.yml +0 -80
  63. data/gemfiles/rails_4.0.13.gemfile +0 -14
  64. data/gemfiles/rails_4.1.16.gemfile +0 -14
  65. data/gemfiles/rails_4.2.10.gemfile +0 -14
  66. data/lib/ajax-datatables-rails/config.rb +0 -31
  67. data/lib/ajax_datatables_rails.rb +0 -15
  68. data/lib/generators/datatable/config_generator.rb +0 -19
  69. data/lib/generators/datatable/templates/ajax_datatables_rails_config.rb +0 -12
  70. data/spec/ajax-datatables-rails/configuration_spec.rb +0 -43
  71. data/spec/ajax-datatables-rails/extended_spec.rb +0 -20
  72. data/spec/ajax-datatables-rails/orm/active_record_spec.rb +0 -25
data/doc/webpack.md CHANGED
@@ -5,7 +5,9 @@ We assume here that Bootstrap and FontAwesome are already installed with Webpack
5
5
  Inspired by https://datatables.net/download and completed :
6
6
 
7
7
  Add npm packages :
8
-
8
+ ```sh
9
+ $ yarn add imports-loader
10
+ ```
9
11
  ```sh
10
12
  $ yarn add datatables.net
11
13
  $ yarn add datatables.net-bs
@@ -22,7 +24,10 @@ In `config/webpack/loaders/datatables.js` :
22
24
  ```js
23
25
  module.exports = {
24
26
  test: /datatables\.net.*/,
25
- loader: 'imports-loader?define=>false'
27
+ loader: 'imports-loader',
28
+ options: {
29
+ additionalCode: 'var define = false;'
30
+ }
26
31
  }
27
32
  ```
28
33
 
@@ -2,12 +2,10 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "5.2.0"
5
+ gem "rails", "5.2.4"
6
6
  gem "activerecord-oracle_enhanced-adapter", "~> 5.2.0"
7
+ gem "sqlite3", "~> 1.3.0"
8
+ gem "mysql2"
7
9
  gem "ruby-oci8" if ENV["DB_ADAPTER"] == "oracle_enhanced"
8
10
 
9
- group :test do
10
- gem "codeclimate-test-reporter", "~> 1.0.0"
11
- end
12
-
13
11
  gemspec path: "../"
@@ -2,12 +2,10 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "5.0.7"
6
- gem "activerecord-oracle_enhanced-adapter", "~> 1.7.0"
5
+ gem "rails", "6.0.3"
6
+ gem "activerecord-oracle_enhanced-adapter", "~> 6.0.0"
7
+ gem "sqlite3", "~> 1.4.0"
8
+ gem "mysql2"
7
9
  gem "ruby-oci8" if ENV["DB_ADAPTER"] == "oracle_enhanced"
8
10
 
9
- group :test do
10
- gem "codeclimate-test-reporter", "~> 1.0.0"
11
- end
12
-
13
11
  gemspec path: "../"
@@ -2,12 +2,10 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "5.1.6"
6
- gem "activerecord-oracle_enhanced-adapter", "~> 1.8.0"
5
+ gem "rails", "6.1.0"
6
+ gem "activerecord-oracle_enhanced-adapter", "~> 6.1.0"
7
+ gem "sqlite3", "~> 1.4.0"
8
+ gem "mysql2"
7
9
  gem "ruby-oci8" if ENV["DB_ADAPTER"] == "oracle_enhanced"
8
10
 
9
- group :test do
10
- gem "codeclimate-test-reporter", "~> 1.0.0"
11
- end
12
-
13
11
  gemspec path: "../"
@@ -1,3 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ajax_datatables_rails'
3
+ require 'zeitwerk'
4
+ loader = Zeitwerk::Loader.for_gem
5
+ generators = "#{__dir__}/generators"
6
+ loader.ignore(generators)
7
+ loader.inflector.inflect(
8
+ 'orm' => 'ORM',
9
+ 'ajax-datatables-rails' => 'AjaxDatatablesRails'
10
+ )
11
+ loader.setup
12
+
13
+ module AjaxDatatablesRails
14
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AjaxDatatablesRails
4
+ class ActiveRecord < AjaxDatatablesRails::Base
5
+ include AjaxDatatablesRails::ORM::ActiveRecord
6
+ end
7
+ end
@@ -2,23 +2,21 @@
2
2
 
3
3
  module AjaxDatatablesRails
4
4
  class Base
5
- extend Forwardable
6
5
 
7
- attr_reader :view, :options
8
- def_delegator :@view, :params
6
+ class_attribute :db_adapter, default: ::ActiveRecord::Base.connection.adapter_name.downcase.to_sym
7
+ class_attribute :nulls_last, default: false
9
8
 
10
- GLOBAL_SEARCH_DELIMITER = ' '
9
+ attr_reader :params, :options, :datatable
11
10
 
12
- def initialize(view, options = {})
13
- @view = view
14
- @options = options
15
- load_orm_extension
16
- end
11
+ GLOBAL_SEARCH_DELIMITER = ' '
17
12
 
18
- def datatable
19
- @datatable ||= Datatable::Datatable.new(self)
13
+ def initialize(params, options = {})
14
+ @params = params
15
+ @options = options
16
+ @datatable = Datatable::Datatable.new(self)
20
17
  end
21
18
 
19
+ # User defined methods
22
20
  def view_columns
23
21
  raise(NotImplementedError, view_columns_error_text)
24
22
  end
@@ -31,22 +29,49 @@ module AjaxDatatablesRails
31
29
  raise(NotImplementedError, data_error_text)
32
30
  end
33
31
 
32
+ # ORM defined methods
33
+ def fetch_records
34
+ get_raw_records
35
+ end
36
+
37
+ def filter_records(records)
38
+ raise(NotImplementedError)
39
+ end
40
+
41
+ def sort_records(records)
42
+ raise(NotImplementedError)
43
+ end
44
+
45
+ def paginate_records(records)
46
+ raise(NotImplementedError)
47
+ end
48
+
49
+ # User overides
34
50
  def additional_data
35
51
  {}
36
52
  end
37
53
 
54
+ # JSON structure sent to jQuery DataTables
38
55
  def as_json(*)
39
56
  {
40
- recordsTotal: records_total_count,
57
+ recordsTotal: records_total_count,
41
58
  recordsFiltered: records_filtered_count,
42
- data: sanitize(data)
43
- }.merge(get_additional_data)
59
+ data: sanitize_data(data),
60
+ }.merge(additional_data)
44
61
  end
45
62
 
46
- def records
47
- @records ||= retrieve_records
63
+ # User helper methods
64
+ def column_id(name)
65
+ view_columns.keys.index(name.to_sym)
66
+ end
67
+
68
+ def column_data(column)
69
+ id = column_id(column)
70
+ params.dig('columns', id.to_s, 'search', 'value')
48
71
  end
49
72
 
73
+ private
74
+
50
75
  # helper methods
51
76
  def connected_columns
52
77
  @connected_columns ||= begin
@@ -68,26 +93,7 @@ module AjaxDatatablesRails
68
93
  end
69
94
  end
70
95
 
71
- private
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
-
90
- def sanitize(data)
96
+ def sanitize_data(data)
91
97
  data.map do |record|
92
98
  if record.is_a?(Array)
93
99
  record.map { |td| ERB::Util.html_escape(td) }
@@ -97,6 +103,11 @@ module AjaxDatatablesRails
97
103
  end
98
104
  end
99
105
 
106
+ # called from within #data
107
+ def records
108
+ @records ||= retrieve_records
109
+ end
110
+
100
111
  def retrieve_records
101
112
  records = fetch_records
102
113
  records = filter_records(records)
@@ -113,16 +124,6 @@ module AjaxDatatablesRails
113
124
  filter_records(fetch_records).count(:all)
114
125
  end
115
126
 
116
- # Private helper methods
117
- def load_orm_extension
118
- case AjaxDatatablesRails.config.orm
119
- when :active_record
120
- extend ORM::ActiveRecord
121
- when :mongoid
122
- nil
123
- end
124
- end
125
-
126
127
  def global_search_delimiter
127
128
  GLOBAL_SEARCH_DELIMITER
128
129
  end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AjaxDatatablesRails
4
+ module Datatable
5
+ end
6
+ end
@@ -4,28 +4,23 @@ module AjaxDatatablesRails
4
4
  module Datatable
5
5
  class Column
6
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
-
16
- attr_reader :datatable, :index, :options
17
- attr_writer :search
18
-
19
7
  include Search
20
8
  include Order
21
- prepend DateFilter unless AjaxDatatablesRails.old_rails?
9
+ include DateFilter
22
10
 
11
+ attr_reader :datatable, :index, :options
12
+ attr_writer :search
23
13
 
24
14
  def initialize(datatable, index, options)
25
15
  @datatable = datatable
26
16
  @index = index
27
17
  @options = options
28
- @view_column = datatable.view_columns[options[:data].to_sym]
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
29
24
  end
30
25
 
31
26
  def data
@@ -52,26 +47,76 @@ module AjaxDatatablesRails
52
47
  !source.include?('.')
53
48
  end
54
49
 
55
- # Add formater option to allow modification of the value
50
+ # Add formatter option to allow modification of the value
56
51
  # before passing it to the database
57
- def formater
58
- @view_column[:formater]
52
+ def formatter
53
+ @view_column[:formatter]
59
54
  end
60
55
 
61
- def formated_value
62
- formater ? formater.call(search.value) : search.value
56
+ def formatted_value
57
+ formatter ? formatter.call(search.value) : search.value
63
58
  end
64
59
 
65
60
  private
66
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
+
67
85
  def type_cast
68
- @type_cast ||= (DB_ADAPTER_TYPE_CAST[AjaxDatatablesRails.config.db_adapter] || 'VARCHAR')
86
+ @type_cast ||= DB_ADAPTER_TYPE_CAST.fetch(datatable.db_adapter, TYPE_CAST_DEFAULT)
69
87
  end
70
88
 
71
89
  def casted_column
72
90
  @casted_column ||= ::Arel::Nodes::NamedFunction.new('CAST', [table[field].as(type_cast)])
73
91
  end
74
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
+
75
120
  end
76
121
  end
77
122
  end
@@ -5,6 +5,8 @@ module AjaxDatatablesRails
5
5
  class Column
6
6
  module DateFilter
7
7
 
8
+ RANGE_DELIMITER = '-'
9
+
8
10
  class DateRange
9
11
  attr_reader :begin, :end
10
12
 
@@ -20,55 +22,44 @@ module AjaxDatatablesRails
20
22
 
21
23
  # Add delimiter option to handle range search
22
24
  def delimiter
23
- @view_column[:delimiter] || '-'
24
- end
25
-
26
- def empty_range_search?
27
- (formated_value == delimiter) || (range_start.blank? && range_end.blank?)
25
+ @delimiter ||= @view_column.fetch(:delimiter, RANGE_DELIMITER)
28
26
  end
29
27
 
30
28
  # A range value is in form '<range_start><delimiter><range_end>'
31
29
  # This returns <range_start>
32
30
  def range_start
33
- @range_start ||= formated_value.split(delimiter)[0]
31
+ @range_start ||= formatted_value.split(delimiter)[0]
34
32
  end
35
33
 
36
34
  # A range value is in form '<range_start><delimiter><range_end>'
37
35
  # This returns <range_end>
38
36
  def range_end
39
- @range_end ||= formated_value.split(delimiter)[1]
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?)
40
42
  end
41
43
 
42
44
  # Do a range search
43
45
  def date_range_search
44
46
  return nil if empty_range_search?
47
+
45
48
  table[field].between(DateRange.new(range_start_casted, range_end_casted))
46
49
  end
47
50
 
48
51
  private
49
52
 
50
- def non_regex_search
51
- if cond == :date_range
52
- date_range_search
53
- else
54
- super
55
- end
56
- end
57
-
58
53
  def range_start_casted
59
54
  range_start.blank? ? parse_date('01/01/1970') : parse_date(range_start)
60
55
  end
61
56
 
62
57
  def range_end_casted
63
- range_end.blank? ? Time.current : parse_date("#{range_end} 23:59:59")
58
+ range_end.blank? ? parse_date('9999-12-31 23:59:59') : parse_date("#{range_end} 23:59:59")
64
59
  end
65
60
 
66
61
  def parse_date(date)
67
- if Time.zone
68
- Time.zone.parse(date)
69
- else
70
- Time.parse(date)
71
- end
62
+ Time.zone ? Time.zone.parse(date) : Time.parse(date)
72
63
  end
73
64
 
74
65
  end