ajax-datatables-rails 0.4.2 → 1.3.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.
Files changed (69) 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 +56 -4
  6. data/Gemfile +0 -5
  7. data/Guardfile +16 -0
  8. data/README.md +223 -97
  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/guard +29 -0
  14. data/bin/rake +29 -0
  15. data/bin/rspec +29 -0
  16. data/bin/rubocop +29 -0
  17. data/doc/migrate.md +97 -0
  18. data/doc/webpack.md +7 -2
  19. data/gemfiles/{rails_5.2.0.gemfile → rails_5.2.4.gemfile} +3 -5
  20. data/gemfiles/{rails_5.0.7.gemfile → rails_6.0.3.gemfile} +4 -6
  21. data/gemfiles/{rails_5.1.6.gemfile → rails_6.1.0.gemfile} +4 -6
  22. data/lib/ajax-datatables-rails.rb +12 -1
  23. data/lib/ajax-datatables-rails/active_record.rb +7 -0
  24. data/lib/ajax-datatables-rails/base.rb +47 -46
  25. data/lib/ajax-datatables-rails/datatable.rb +6 -0
  26. data/lib/ajax-datatables-rails/datatable/column.rb +65 -20
  27. data/lib/ajax-datatables-rails/datatable/column/date_filter.rb +12 -21
  28. data/lib/ajax-datatables-rails/datatable/column/order.rb +1 -1
  29. data/lib/ajax-datatables-rails/datatable/column/search.rb +36 -23
  30. data/lib/ajax-datatables-rails/datatable/datatable.rb +16 -6
  31. data/lib/ajax-datatables-rails/datatable/simple_order.rb +23 -10
  32. data/lib/ajax-datatables-rails/datatable/simple_search.rb +2 -0
  33. data/lib/ajax-datatables-rails/error.rb +9 -0
  34. data/lib/ajax-datatables-rails/orm.rb +6 -0
  35. data/lib/ajax-datatables-rails/orm/active_record.rb +11 -12
  36. data/lib/ajax-datatables-rails/version.rb +13 -1
  37. data/lib/generators/rails/templates/datatable.rb +1 -1
  38. data/spec/ajax-datatables-rails/base_spec.rb +95 -79
  39. data/spec/ajax-datatables-rails/datatable/column_spec.rb +83 -12
  40. data/spec/ajax-datatables-rails/datatable/datatable_spec.rb +62 -24
  41. data/spec/ajax-datatables-rails/datatable/simple_order_spec.rb +32 -12
  42. data/spec/ajax-datatables-rails/orm/active_record_filter_records_spec.rb +329 -221
  43. data/spec/ajax-datatables-rails/orm/active_record_paginate_records_spec.rb +5 -6
  44. data/spec/ajax-datatables-rails/orm/active_record_sort_records_spec.rb +12 -11
  45. data/spec/ajax-datatables-rails/orm/active_record_spec.rb +3 -4
  46. data/spec/install_oracle.sh +9 -3
  47. data/spec/spec_helper.rb +10 -21
  48. data/spec/support/datatables/complex_datatable.rb +29 -0
  49. data/spec/support/datatables/complex_datatable_array.rb +14 -0
  50. data/spec/support/{datatable_cond_date.rb → datatables/datatable_cond_date.rb} +0 -0
  51. data/spec/support/{datatable_cond_numeric.rb → datatables/datatable_cond_numeric.rb} +1 -1
  52. data/spec/support/{datatable_cond_proc.rb → datatables/datatable_cond_proc.rb} +0 -0
  53. data/spec/support/datatables/datatable_cond_string.rb +41 -0
  54. data/spec/support/datatables/datatable_cond_unknown.rb +5 -0
  55. data/spec/support/{datatable_order_nulls_last.rb → datatables/datatable_order_nulls_last.rb} +0 -0
  56. data/spec/support/{test_helpers.rb → helpers/params.rb} +14 -41
  57. data/spec/support/{test_models.rb → models/user.rb} +0 -0
  58. metadata +75 -74
  59. data/.travis.yml +0 -80
  60. data/gemfiles/rails_4.0.13.gemfile +0 -14
  61. data/gemfiles/rails_4.1.16.gemfile +0 -14
  62. data/gemfiles/rails_4.2.10.gemfile +0 -14
  63. data/lib/ajax-datatables-rails/config.rb +0 -31
  64. data/lib/ajax_datatables_rails.rb +0 -15
  65. data/lib/generators/datatable/config_generator.rb +0 -19
  66. data/lib/generators/datatable/templates/ajax_datatables_rails_config.rb +0 -12
  67. data/spec/ajax-datatables-rails/configuration_spec.rb +0 -43
  68. data/spec/ajax-datatables-rails/extended_spec.rb +0 -20
  69. data/spec/support/datatable_cond_string.rb +0 -23
@@ -3,10 +3,8 @@
3
3
  module AjaxDatatablesRails
4
4
  module Datatable
5
5
 
6
- TRUE_VALUE = 'true'
7
-
8
6
  class Datatable
9
- attr_reader :datatable, :options
7
+ attr_reader :options
10
8
 
11
9
  def initialize(datatable)
12
10
  @datatable = datatable
@@ -43,7 +41,7 @@ module AjaxDatatablesRails
43
41
 
44
42
  def columns
45
43
  @columns ||= get_param(:columns).map do |index, column_options|
46
- Column.new(datatable, index, column_options)
44
+ Column.new(@datatable, index, column_options)
47
45
  end
48
46
  end
49
47
 
@@ -70,13 +68,25 @@ module AjaxDatatablesRails
70
68
  end
71
69
 
72
70
  def get_param(param)
73
- if AjaxDatatablesRails.old_rails?
74
- options[param]
71
+ return {} if options[param].nil?
72
+
73
+ if options[param].is_a? Array
74
+ hash = {}
75
+ options[param].each_with_index { |value, index| hash[index] = value }
76
+ hash
75
77
  else
76
78
  options[param].to_unsafe_h.with_indifferent_access
77
79
  end
78
80
  end
79
81
 
82
+ def db_adapter
83
+ @datatable.db_adapter
84
+ end
85
+
86
+ def nulls_last
87
+ @datatable.nulls_last
88
+ end
89
+
80
90
  end
81
91
  end
82
92
  end
@@ -4,19 +4,19 @@ module AjaxDatatablesRails
4
4
  module Datatable
5
5
  class SimpleOrder
6
6
 
7
- DIRECTIONS = %w[DESC ASC].freeze
7
+ DIRECTION_ASC = 'ASC'
8
+ DIRECTION_DESC = 'DESC'
9
+ DIRECTIONS = [DIRECTION_ASC, DIRECTION_DESC].freeze
8
10
 
9
11
  def initialize(datatable, options = {})
10
- @datatable = datatable
11
- @options = options
12
+ @datatable = datatable
13
+ @options = options
14
+ @adapter = datatable.db_adapter
15
+ @nulls_last = datatable.nulls_last
12
16
  end
13
17
 
14
18
  def query(sort_column)
15
- if sort_nulls_last?
16
- "CASE WHEN #{sort_column} IS NULL THEN 1 ELSE 0 END, #{sort_column} #{direction}"
17
- else
18
- "#{sort_column} #{direction}"
19
- end
19
+ [sort_column, direction, nulls_last_sql].compact.join(' ')
20
20
  end
21
21
 
22
22
  def column
@@ -24,7 +24,7 @@ module AjaxDatatablesRails
24
24
  end
25
25
 
26
26
  def direction
27
- DIRECTIONS.find { |dir| dir == column_direction } || 'ASC'
27
+ DIRECTIONS.find { |dir| dir == column_direction } || DIRECTION_ASC
28
28
  end
29
29
 
30
30
  private
@@ -38,7 +38,20 @@ module AjaxDatatablesRails
38
38
  end
39
39
 
40
40
  def sort_nulls_last?
41
- column.nulls_last? || AjaxDatatablesRails.config.nulls_last == true
41
+ column.nulls_last? || @nulls_last == true
42
+ end
43
+
44
+ def nulls_last_sql
45
+ return unless sort_nulls_last?
46
+
47
+ case @adapter
48
+ when :pg, :postgresql, :postgres, :oracle
49
+ 'NULLS LAST'
50
+ when :mysql, :mysql2, :sqlite, :sqlite3
51
+ 'IS NULL'
52
+ else
53
+ raise "unsupported database adapter: #{@adapter}"
54
+ end
42
55
  end
43
56
 
44
57
  end
@@ -4,6 +4,8 @@ module AjaxDatatablesRails
4
4
  module Datatable
5
5
  class SimpleSearch
6
6
 
7
+ TRUE_VALUE = 'true'
8
+
7
9
  def initialize(options = {})
8
10
  @options = options
9
11
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AjaxDatatablesRails
4
+ module Error
5
+ class BaseError < StandardError; end
6
+ class InvalidSearchColumn < BaseError; end
7
+ class InvalidSearchCondition < BaseError; end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AjaxDatatablesRails
4
+ module ORM
5
+ end
6
+ end
@@ -4,15 +4,11 @@ module AjaxDatatablesRails
4
4
  module ORM
5
5
  module ActiveRecord
6
6
 
7
- def fetch_records
8
- get_raw_records
9
- end
10
-
11
7
  def filter_records(records)
12
8
  records.where(build_conditions)
13
9
  end
14
10
 
15
- # rubocop:disable Style/EachWithObject
11
+ # rubocop:disable Style/EachWithObject, Style/SafeNavigation
16
12
  def sort_records(records)
17
13
  sort_by = datatable.orders.inject([]) do |queries, order|
18
14
  column = order.column
@@ -21,7 +17,7 @@ module AjaxDatatablesRails
21
17
  end
22
18
  records.order(Arel.sql(sort_by.join(', ')))
23
19
  end
24
- # rubocop:enable Style/EachWithObject
20
+ # rubocop:enable Style/EachWithObject, Style/SafeNavigation
25
21
 
26
22
  def paginate_records(records)
27
23
  records.offset(datatable.offset).limit(datatable.per_page)
@@ -30,23 +26,26 @@ module AjaxDatatablesRails
30
26
  # ----------------- SEARCH HELPER METHODS --------------------
31
27
 
32
28
  def build_conditions
33
- if datatable.searchable?
34
- build_conditions_for_datatable
35
- else
36
- build_conditions_for_selected_columns
29
+ @build_conditions ||= begin
30
+ criteria = [build_conditions_for_selected_columns]
31
+ criteria << build_conditions_for_datatable if datatable.searchable?
32
+ criteria.compact.reduce(:and)
37
33
  end
38
34
  end
39
35
 
36
+ # rubocop:disable Metrics/AbcSize
40
37
  def build_conditions_for_datatable
38
+ columns = searchable_columns.reject(&:searched?)
41
39
  criteria = search_for.inject([]) do |crit, atom|
42
40
  search = Datatable::SimpleSearch.new(value: atom, regex: datatable.search.regexp?)
43
- crit << searchable_columns.map do |simple_column|
41
+ crit << columns.map do |simple_column|
44
42
  simple_column.search = search
45
43
  simple_column.search_query
46
- end.reduce(:or)
44
+ end.compact.reduce(:or)
47
45
  end.compact.reduce(:and)
48
46
  criteria
49
47
  end
48
+ # rubocop:enable Metrics/AbcSize
50
49
 
51
50
  def build_conditions_for_selected_columns
52
51
  search_columns.map(&:search_query).compact.reduce(:and)
@@ -1,5 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AjaxDatatablesRails
4
- VERSION = '0.4.2'
4
+
5
+ def self.gem_version
6
+ Gem::Version.new VERSION::STRING
7
+ end
8
+
9
+ module VERSION
10
+ MAJOR = 1
11
+ MINOR = 3
12
+ TINY = 0
13
+ PRE = nil
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
16
+ end
5
17
  end
@@ -1,4 +1,4 @@
1
- class <%= datatable_name %> < AjaxDatatablesRails::Base
1
+ class <%= datatable_name %> < AjaxDatatablesRails::ActiveRecord
2
2
 
3
3
  def view_columns
4
4
  # Declare strings in this format: ModelName.column_name
@@ -3,30 +3,26 @@ require 'spec_helper'
3
3
  describe AjaxDatatablesRails::Base do
4
4
 
5
5
  describe 'an instance' do
6
- let(:view) { double('view', params: sample_params) }
7
-
8
- it 'requires a view_context' do
6
+ it 'requires a hash of params' do
9
7
  expect { described_class.new }.to raise_error ArgumentError
10
8
  end
11
9
 
12
10
  it 'accepts an options hash' do
13
- datatable = described_class.new(view, foo: 'bar')
11
+ datatable = described_class.new(sample_params, foo: 'bar')
14
12
  expect(datatable.options).to eq(foo: 'bar')
15
13
  end
16
14
  end
17
15
 
18
- context 'Public API' do
19
- let(:view) { double('view', params: sample_params) }
20
-
16
+ describe 'User API' do
21
17
  describe '#view_columns' do
22
18
  it 'raises an error if not defined by the user' do
23
- datatable = described_class.new(view)
19
+ datatable = described_class.new(sample_params)
24
20
  expect { datatable.view_columns }.to raise_error NotImplementedError
25
21
  end
26
22
 
27
23
  context 'child class implements view_columns' do
28
24
  it 'expects a hash based defining columns' do
29
- datatable = ComplexDatatable.new(view)
25
+ datatable = ComplexDatatable.new(sample_params)
30
26
  expect(datatable.view_columns).to be_a(Hash)
31
27
  end
32
28
  end
@@ -34,19 +30,19 @@ describe AjaxDatatablesRails::Base do
34
30
 
35
31
  describe '#get_raw_records' do
36
32
  it 'raises an error if not defined by the user' do
37
- datatable = described_class.new(view)
33
+ datatable = described_class.new(sample_params)
38
34
  expect { datatable.get_raw_records }.to raise_error NotImplementedError
39
35
  end
40
36
  end
41
37
 
42
38
  describe '#data' do
43
39
  it 'raises an error if not defined by the user' do
44
- datatable = described_class.new(view)
40
+ datatable = described_class.new(sample_params)
45
41
  expect { datatable.data }.to raise_error NotImplementedError
46
42
  end
47
43
 
48
44
  context 'when data is defined as a hash' do
49
- let(:datatable) { ComplexDatatable.new(view) }
45
+ let(:datatable) { ComplexDatatable.new(sample_params) }
50
46
 
51
47
  it 'should return an array of hashes' do
52
48
  create_list(:user, 5)
@@ -58,7 +54,7 @@ describe AjaxDatatablesRails::Base do
58
54
 
59
55
  it 'should html escape data' do
60
56
  create(:user, first_name: 'Name "><img src=x onerror=alert("first_name")>', last_name: 'Name "><img src=x onerror=alert("last_name")>')
61
- data = datatable.send(:sanitize, datatable.data)
57
+ data = datatable.send(:sanitize_data, datatable.data)
62
58
  item = data.first
63
59
  expect(item[:first_name]).to eq 'Name &quot;&gt;&lt;img src=x onerror=alert(&quot;first_name&quot;)&gt;'
64
60
  expect(item[:last_name]).to eq 'Name &quot;&gt;&lt;img src=x onerror=alert(&quot;last_name&quot;)&gt;'
@@ -66,7 +62,7 @@ describe AjaxDatatablesRails::Base do
66
62
  end
67
63
 
68
64
  context 'when data is defined as a array' do
69
- let(:datatable) { ComplexDatatableArray.new(view) }
65
+ let(:datatable) { ComplexDatatableArray.new(sample_params) }
70
66
 
71
67
  it 'should return an array of arrays' do
72
68
  create_list(:user, 5)
@@ -78,16 +74,87 @@ describe AjaxDatatablesRails::Base do
78
74
 
79
75
  it 'should html escape data' do
80
76
  create(:user, first_name: 'Name "><img src=x onerror=alert("first_name")>', last_name: 'Name "><img src=x onerror=alert("last_name")>')
81
- data = datatable.send(:sanitize, datatable.data)
77
+ data = datatable.send(:sanitize_data, datatable.data)
82
78
  item = data.first
83
79
  expect(item[2]).to eq 'Name &quot;&gt;&lt;img src=x onerror=alert(&quot;first_name&quot;)&gt;'
84
80
  expect(item[3]).to eq 'Name &quot;&gt;&lt;img src=x onerror=alert(&quot;last_name&quot;)&gt;'
85
81
  end
86
82
  end
87
83
  end
84
+ end
85
+
86
+ describe 'ORM API' do
87
+ context 'when ORM is not implemented' do
88
+ let(:datatable) { AjaxDatatablesRails::Base.new(sample_params) }
89
+
90
+ describe '#fetch_records' do
91
+ it 'raises an error if it does not include an ORM module' do
92
+ expect { datatable.fetch_records }.to raise_error NotImplementedError
93
+ end
94
+ end
95
+
96
+ describe '#filter_records' do
97
+ it 'raises an error if it does not include an ORM module' do
98
+ expect { datatable.filter_records([]) }.to raise_error NotImplementedError
99
+ end
100
+ end
101
+
102
+ describe '#sort_records' do
103
+ it 'raises an error if it does not include an ORM module' do
104
+ expect { datatable.sort_records([]) }.to raise_error NotImplementedError
105
+ end
106
+ end
107
+
108
+ describe '#paginate_records' do
109
+ it 'raises an error if it does not include an ORM module' do
110
+ expect { datatable.paginate_records([]) }.to raise_error NotImplementedError
111
+ end
112
+ end
113
+ end
88
114
 
115
+ context 'when ORM is implemented' do
116
+ describe 'it allows method override' do
117
+ let(:datatable) do
118
+ datatable = Class.new(ComplexDatatable) do
119
+ def filter_records(records)
120
+ raise NotImplementedError.new('FOO')
121
+ end
122
+
123
+ def sort_records(records)
124
+ raise NotImplementedError.new('FOO')
125
+ end
126
+
127
+ def paginate_records(records)
128
+ raise NotImplementedError.new('FOO')
129
+ end
130
+ end
131
+ datatable.new(sample_params)
132
+ end
133
+
134
+ describe '#filter_records' do
135
+ it {
136
+ expect { datatable.filter_records([]) }.to raise_error(NotImplementedError).with_message('FOO')
137
+ }
138
+ end
139
+
140
+ describe '#sort_records' do
141
+ it {
142
+ expect { datatable.sort_records([]) }.to raise_error(NotImplementedError).with_message('FOO')
143
+ }
144
+ end
145
+
146
+ describe '#paginate_records' do
147
+ it {
148
+ expect { datatable.paginate_records([]) }.to raise_error(NotImplementedError).with_message('FOO')
149
+ }
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ describe 'JSON format' do
89
156
  describe '#as_json' do
90
- let(:datatable) { ComplexDatatable.new(view) }
157
+ let(:datatable) { ComplexDatatable.new(sample_params) }
91
158
 
92
159
  it 'should return a hash' do
93
160
  create_list(:user, 5)
@@ -113,74 +180,23 @@ describe AjaxDatatablesRails::Base do
113
180
  end
114
181
  end
115
182
 
183
+ describe 'User helper methods' do
184
+ describe '#column_id' do
185
+ let(:datatable) { ComplexDatatable.new(sample_params) }
116
186
 
117
- context 'Private API' do
118
-
119
- let(:view) { double('view', params: sample_params) }
120
- let(:datatable) { ComplexDatatable.new(view) }
121
-
122
- before(:each) do
123
- allow_any_instance_of(AjaxDatatablesRails::Configuration).to receive(:orm) { nil }
124
- end
125
-
126
- describe '#fetch_records' do
127
- it 'raises an error if it does not include an ORM module' do
128
- expect { datatable.send(:fetch_records) }.to raise_error NoMethodError
187
+ it 'should return column id from view_columns hash' do
188
+ expect(datatable.column_id(:username)).to eq(0)
189
+ expect(datatable.column_id('username')).to eq(0)
129
190
  end
130
191
  end
131
192
 
132
- describe '#filter_records' do
133
- it 'raises an error if it does not include an ORM module' do
134
- expect { datatable.send(:filter_records) }.to raise_error NoMethodError
135
- end
136
- end
137
-
138
- describe '#sort_records' do
139
- it 'raises an error if it does not include an ORM module' do
140
- expect { datatable.send(:sort_records) }.to raise_error NoMethodError
141
- end
142
- end
193
+ describe '#column_data' do
194
+ let(:datatable) { ComplexDatatable.new(sample_params) }
195
+ before { datatable.params[:columns]['0'][:search][:value] = 'doe' }
143
196
 
144
- describe '#paginate_records' do
145
- it 'raises an error if it does not include an ORM module' do
146
- expect { datatable.send(:paginate_records) }.to raise_error NoMethodError
147
- end
148
- end
149
-
150
- describe 'helper methods' do
151
- describe '#offset' do
152
- it 'defaults to 0' do
153
- default_view = double('view', params: {})
154
- datatable = described_class.new(default_view)
155
- expect(datatable.datatable.send(:offset)).to eq(0)
156
- end
157
-
158
- it 'matches the value on view params[:start]' do
159
- paginated_view = double('view', params: { start: '11' })
160
- datatable = described_class.new(paginated_view)
161
- expect(datatable.datatable.send(:offset)).to eq(11)
162
- end
163
- end
164
-
165
- describe '#page' do
166
- it 'calculates page number from params[:start] and #per_page' do
167
- paginated_view = double('view', params: { start: '11' })
168
- datatable = described_class.new(paginated_view)
169
- expect(datatable.datatable.send(:page)).to eq(2)
170
- end
171
- end
172
-
173
- describe '#per_page' do
174
- it 'defaults to 10' do
175
- datatable = described_class.new(view)
176
- expect(datatable.datatable.send(:per_page)).to eq(10)
177
- end
178
-
179
- it 'matches the value on view params[:length]' do
180
- other_view = double('view', params: { length: 20 })
181
- datatable = described_class.new(other_view)
182
- expect(datatable.datatable.send(:per_page)).to eq(20)
183
- end
197
+ it 'should return column data from params' do
198
+ expect(datatable.column_data(:username)).to eq('doe')
199
+ expect(datatable.column_data('username')).to eq('doe')
184
200
  end
185
201
  end
186
202
  end