ajax-datatables-rails 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +26 -0
  3. data/.gitignore +20 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +1157 -0
  6. data/.travis.yml +68 -0
  7. data/Appraisals +34 -0
  8. data/Gemfile +5 -1
  9. data/LICENSE +17 -18
  10. data/README.md +239 -239
  11. data/Rakefile +1 -1
  12. data/ajax-datatables-rails.gemspec +31 -24
  13. data/gemfiles/rails_4.0.13.gemfile +14 -0
  14. data/gemfiles/rails_4.1.15.gemfile +14 -0
  15. data/gemfiles/rails_4.2.8.gemfile +13 -0
  16. data/gemfiles/rails_5.0.3.gemfile +13 -0
  17. data/gemfiles/rails_5.1.1.gemfile +13 -0
  18. data/lib/ajax-datatables-rails.rb +9 -8
  19. data/lib/ajax-datatables-rails/base.rb +80 -156
  20. data/lib/ajax-datatables-rails/config.rb +8 -5
  21. data/lib/ajax-datatables-rails/datatable/column.rb +169 -0
  22. data/lib/ajax-datatables-rails/datatable/column_date_filter.rb +41 -0
  23. data/lib/ajax-datatables-rails/datatable/datatable.rb +79 -0
  24. data/lib/ajax-datatables-rails/datatable/simple_order.rb +31 -0
  25. data/lib/ajax-datatables-rails/datatable/simple_search.rb +18 -0
  26. data/lib/ajax-datatables-rails/orm/active_record.rb +52 -0
  27. data/lib/ajax-datatables-rails/version.rb +1 -1
  28. data/lib/generators/datatable/templates/ajax_datatables_rails_config.rb +3 -3
  29. data/lib/generators/rails/datatable_generator.rb +7 -19
  30. data/lib/generators/rails/templates/datatable.rb +26 -14
  31. data/spec/ajax-datatables-rails/base_spec.rb +190 -0
  32. data/spec/ajax-datatables-rails/configuration_spec.rb +43 -0
  33. data/spec/ajax-datatables-rails/datatable/column_spec.rb +109 -0
  34. data/spec/ajax-datatables-rails/datatable/datatable_spec.rb +87 -0
  35. data/spec/ajax-datatables-rails/datatable/simple_order_spec.rb +13 -0
  36. data/spec/ajax-datatables-rails/datatable/simple_search_spec.rb +17 -0
  37. data/spec/ajax-datatables-rails/extended_spec.rb +20 -0
  38. data/spec/ajax-datatables-rails/orm/active_record_filter_records_spec.rb +439 -0
  39. data/spec/ajax-datatables-rails/orm/active_record_paginate_records_spec.rb +66 -0
  40. data/spec/ajax-datatables-rails/orm/active_record_sort_records_spec.rb +34 -0
  41. data/spec/ajax-datatables-rails/orm/active_record_spec.rb +25 -0
  42. data/spec/factories/user.rb +9 -0
  43. data/spec/install_oracle.sh +12 -0
  44. data/spec/spec_helper.rb +75 -3
  45. data/spec/support/schema.rb +14 -0
  46. data/spec/support/test_helpers.rb +174 -0
  47. data/spec/support/test_models.rb +2 -0
  48. metadata +169 -37
  49. data/lib/ajax-datatables-rails/extensions/kaminari.rb +0 -12
  50. data/lib/ajax-datatables-rails/extensions/simple_paginator.rb +0 -12
  51. data/lib/ajax-datatables-rails/extensions/will_paginate.rb +0 -12
  52. data/lib/ajax-datatables-rails/models.rb +0 -6
  53. data/spec/ajax-datatables-rails/ajax_datatables_rails_spec.rb +0 -351
  54. data/spec/ajax-datatables-rails/kaminari_spec.rb +0 -35
  55. data/spec/ajax-datatables-rails/models_spec.rb +0 -10
  56. data/spec/ajax-datatables-rails/simple_paginator_spec.rb +0 -32
  57. data/spec/ajax-datatables-rails/will_paginate_spec.rb +0 -28
  58. data/spec/schema.rb +0 -35
  59. 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 = :pg
7
+ # config.db_adapter = :postgresql
8
8
  # end
9
- def self.configure &block
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
- # default db_adapter is pg (postgresql)
22
- config_accessor(:db_adapter) { :pg }
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,3 +1,3 @@
1
1
  module AjaxDatatablesRails
2
- VERSION = '0.3.1'
2
+ VERSION = '0.4.0'.freeze
3
3
  end
@@ -1,7 +1,7 @@
1
1
  AjaxDatatablesRails.configure do |config|
2
- # available options for db_adapter are: :oracle, :pg, :mysql2, :sqlite3
2
+ # available options for db_adapter are: :pg, :mysql, :mysql2, :sqlite, :sqlite3
3
3
  # config.db_adapter = :pg
4
4
 
5
- # available options for paginator are: :simple_paginator, :kaminari, :will_paginate
6
- # config.paginator = :simple_paginator
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, :type => :string
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', "#{file_prefix}_datatable.rb"
12
+ 'app/datatables', "#{datatable_path}.rb"
15
13
  )
16
14
  end
17
15
 
18
- private
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
- def build_name(name)
29
- pieces = name.split('_')
30
- pieces.map(&:titleize).join
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