datatables-net 0.4.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 (34) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +73 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +22 -0
  5. data/README.md +618 -0
  6. data/Rakefile +14 -0
  7. data/lib/ajax-datatables-rails.rb +11 -0
  8. data/lib/ajax-datatables-rails/base.rb +205 -0
  9. data/lib/ajax-datatables-rails/config.rb +24 -0
  10. data/lib/ajax-datatables-rails/datatable/column.rb +106 -0
  11. data/lib/ajax-datatables-rails/datatable/datatable.rb +69 -0
  12. data/lib/ajax-datatables-rails/datatable/simple_order.rb +31 -0
  13. data/lib/ajax-datatables-rails/datatable/simple_search.rb +19 -0
  14. data/lib/ajax-datatables-rails/orm/active_record.rb +52 -0
  15. data/lib/ajax-datatables-rails/version.rb +3 -0
  16. data/lib/generators/datatable/config_generator.rb +17 -0
  17. data/lib/generators/datatable/templates/ajax_datatables_rails_config.rb +7 -0
  18. data/lib/generators/rails/datatable_generator.rb +27 -0
  19. data/lib/generators/rails/templates/datatable.rb +41 -0
  20. data/spec/ajax-datatables-rails/base_spec.rb +140 -0
  21. data/spec/ajax-datatables-rails/configuration_spec.rb +43 -0
  22. data/spec/ajax-datatables-rails/datatable/column_spec.rb +65 -0
  23. data/spec/ajax-datatables-rails/datatable/datatable_spec.rb +97 -0
  24. data/spec/ajax-datatables-rails/datatable/simple_order_spec.rb +12 -0
  25. data/spec/ajax-datatables-rails/datatable/simple_search_spec.rb +16 -0
  26. data/spec/ajax-datatables-rails/orm/active_record_filter_records_spec.rb +154 -0
  27. data/spec/ajax-datatables-rails/orm/active_record_paginate_records_spec.rb +51 -0
  28. data/spec/ajax-datatables-rails/orm/active_record_sort_records_spec.rb +42 -0
  29. data/spec/ajax-datatables-rails/orm/active_record_spec.rb +34 -0
  30. data/spec/schema.rb +43 -0
  31. data/spec/spec_helper.rb +10 -0
  32. data/spec/test_helpers.rb +66 -0
  33. data/spec/test_models.rb +20 -0
  34. metadata +202 -0
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ task :default => :spec
7
+
8
+ task :console do
9
+ require 'pry'
10
+ require 'rails'
11
+ require 'ajax-datatables-rails'
12
+ ARGV.clear
13
+ Pry.start
14
+ end
@@ -0,0 +1,11 @@
1
+ require 'ajax-datatables-rails/version'
2
+ require 'ajax-datatables-rails/config'
3
+ require 'ajax-datatables-rails/base'
4
+ require 'ajax-datatables-rails/datatable/datatable'
5
+ require 'ajax-datatables-rails/datatable/simple_search'
6
+ require 'ajax-datatables-rails/datatable/simple_order'
7
+ require 'ajax-datatables-rails/datatable/column'
8
+ require 'ajax-datatables-rails/orm/active_record'
9
+
10
+ module AjaxDatatablesRails
11
+ end
@@ -0,0 +1,205 @@
1
+ module AjaxDatatablesRails
2
+ class NotImplemented < StandardError; end
3
+
4
+ class Base
5
+ extend Forwardable
6
+ <<<<<<< HEAD
7
+ include ActiveRecord::Sanitization::ClassMethods
8
+ class MethodNotImplementedError < StandardError; end
9
+ =======
10
+ >>>>>>> v-0-4-0
11
+
12
+ attr_reader :view, :options
13
+ def_delegator :@view, :params, :params
14
+
15
+ def initialize(view, options = {})
16
+ @view = view
17
+ @options = options
18
+ load_orm_extension
19
+ end
20
+
21
+ def config
22
+ @config ||= AjaxDatatablesRails.config
23
+ end
24
+
25
+ def datatable
26
+ @datatable ||= Datatable::Datatable.new self
27
+ end
28
+
29
+ # Must overrited methods
30
+ def view_columns
31
+ fail(NotImplemented, view_columns_error_text)
32
+ end
33
+
34
+ def data
35
+ fail(NotImplemented, data_error_text)
36
+ end
37
+
38
+ def get_raw_records
39
+ fail(NotImplemented, raw_records_error_text)
40
+ end
41
+
42
+ def as_json
43
+ {
44
+ recordsTotal: total_records,
45
+ recordsFiltered: filter_records,
46
+ data: data
47
+ }
48
+ end
49
+
50
+ def total_records
51
+ get_raw_records.count(:all)
52
+ end
53
+
54
+ def filtered_records
55
+ get_raw_records.model.from("(#{filter_records(get_raw_records).except(:limit, :offset, :order).to_sql}) AS foo").count
56
+ end
57
+
58
+ def records
59
+ @records ||= retrieve_records
60
+ end
61
+
62
+ # helper methods
63
+ def searchable_columns
64
+ @searchable_columns ||= begin
65
+ connected_columns.select &:searchable?
66
+ end
67
+ end
68
+
69
+ def search_columns
70
+ @search_columns ||= begin
71
+ searchable_columns.select { |column| column.search.value.present? }
72
+ end
73
+ end
74
+
75
+ def connected_columns
76
+ @connected_columns ||= begin
77
+ view_columns.keys.map do |field_name|
78
+ datatable.column_by(:data, field_name.to_s)
79
+ end.compact
80
+ end
81
+ end
82
+
83
+ private
84
+ # view_columns can be an Array or Hash. we have to support all these formats of defining columns
85
+ def connect_view_columns
86
+ # @connect_view_columns ||= begin
87
+ # adapted_options =
88
+ # case view_columns
89
+ # when Hash
90
+ # when Array
91
+ # cols = {}
92
+ # view_columns.each_with_index({}) do |index, source|
93
+ # cols[index.to_s] = { source: source }
94
+ # end
95
+ # cols
96
+ # else
97
+ # view_columns
98
+ # end
99
+ # ActiveSupport::HashWithIndifferentAccess.new adapted_options
100
+ # end
101
+ end
102
+
103
+ def retrieve_records
104
+ records = fetch_records
105
+ records = filter_records(records)
106
+ records = sort_records(records) if datatable.orderable?
107
+ records = paginate_records(records) if datatable.paginate?
108
+ records
109
+ end
110
+
111
+ # Private helper methods
112
+ def load_orm_extension
113
+ case config.orm
114
+ when :mongoid then nil
115
+ when :active_record then extend ORM::ActiveRecord
116
+ else
117
+ nil
118
+ end
119
+ end
120
+
121
+ <<<<<<< HEAD
122
+ def new_search_condition(column, value)
123
+ model, column = column.split('.')
124
+ model = model.constantize
125
+ casted_column = ::Arel::Nodes::NamedFunction.new('CAST', [model.arel_table[column.to_sym].as(typecast)])
126
+ casted_column.matches("%#{sanitize_sql_like(value)}%")
127
+ end
128
+
129
+ def deprecated_search_condition(column, value)
130
+ model, column = column.split('.')
131
+ model = model.singularize.titleize.gsub( / /, '' ).constantize
132
+
133
+ casted_column = ::Arel::Nodes::NamedFunction.new('CAST', [model.arel_table[column.to_sym].as(typecast)])
134
+ casted_column.matches("%#{sanitize_sql_like(value)}%")
135
+ end
136
+
137
+ def aggregate_query
138
+ conditions = searchable_columns.each_with_index.map do |column, index|
139
+ value = params[:columns]["#{index}"][:search][:value] if params[:columns]
140
+ search_condition(column, value) unless value.blank?
141
+ end
142
+ conditions.compact.reduce(:and)
143
+ end
144
+
145
+ def typecast
146
+ case config.db_adapter
147
+ when :oracle then 'VARCHAR2(4000)'
148
+ when :pg then 'VARCHAR'
149
+ when :mysql2 then 'CHAR'
150
+ when :sqlite3 then 'TEXT'
151
+ end
152
+ end
153
+
154
+ def offset
155
+ (page - 1) * per_page
156
+ end
157
+
158
+ def page
159
+ (params[:start].to_i / per_page) + 1
160
+ end
161
+
162
+ def per_page
163
+ params.fetch(:length, 10).to_i
164
+ end
165
+
166
+ def sort_column(item)
167
+ new_sort_column(item)
168
+ rescue
169
+ ::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'
170
+ deprecated_sort_column(item)
171
+ end
172
+
173
+ def deprecated_sort_column(item)
174
+ sortable_columns[sortable_displayed_columns.index(item[:column])]
175
+ end
176
+ =======
177
+ def raw_records_error_text
178
+ return <<-eos
179
+
180
+ You should implement this method in your class and specify
181
+ how records are going to be retrieved from the database.
182
+ eos
183
+ end
184
+
185
+ def data_error_text
186
+ return <<-eos
187
+ >>>>>>> v-0-4-0
188
+
189
+ You should implement this method in your class and return an array
190
+ of arrays, or an array of hashes, as defined in the jQuery.dataTables
191
+ plugin documentation.
192
+ eos
193
+ end
194
+
195
+ def view_columns_error_text
196
+ return <<-eos
197
+
198
+ You should implement this method in your class and return an array
199
+ of database columns based on the columns displayed in the HTML view.
200
+ These columns should be represented in the ModelName.column_name,
201
+ or aliased_join_table.column_name notation.
202
+ eos
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,24 @@
1
+ require 'active_support/configurable'
2
+
3
+ module AjaxDatatablesRails
4
+
5
+ # configure AjaxDatatablesRails global settings
6
+ # AjaxDatatablesRails.configure do |config|
7
+ # config.db_adapter = :pg
8
+ # end
9
+ def self.configure &block
10
+ yield @config ||= AjaxDatatablesRails::Configuration.new
11
+ end
12
+
13
+ # AjaxDatatablesRails global settings
14
+ def self.config
15
+ @config ||= AjaxDatatablesRails::Configuration.new
16
+ end
17
+
18
+ class Configuration
19
+ include ActiveSupport::Configurable
20
+
21
+ config_accessor(:orm) { :active_record }
22
+ config_accessor(:db_adapter) { :pg }
23
+ end
24
+ end
@@ -0,0 +1,106 @@
1
+ module AjaxDatatablesRails
2
+ module Datatable
3
+ class Column
4
+ attr_reader :datatable, :index, :options
5
+
6
+ def initialize(datatable, index, options)
7
+ @datatable, @index, @options = datatable, index, options
8
+ @view_column = datatable.view_columns[options["data"].to_sym]
9
+ end
10
+
11
+ def data
12
+ options[:data].presence || options[:name]
13
+ end
14
+
15
+ def searchable?
16
+ options[:searchable] == TRUE_VALUE
17
+ end
18
+
19
+ def orderable?
20
+ options[:orderable] == TRUE_VALUE
21
+ end
22
+
23
+ def search
24
+ @search ||= SimpleSearch.new(options[:search])
25
+ end
26
+
27
+ def search= value
28
+ @search = value
29
+ end
30
+
31
+ def cond
32
+ @view_column[:cond] || :like
33
+ end
34
+
35
+ def filter value
36
+ @view_column[:cond].call self
37
+ end
38
+
39
+ def source
40
+ @view_column[:source]
41
+ end
42
+
43
+ def table
44
+ model = source.split('.').first.constantize
45
+ model.arel_table rescue model
46
+ end
47
+
48
+ def field
49
+ source.split('.').last.to_sym
50
+ end
51
+
52
+ def search_query
53
+ search.regexp? ? regex_search : non_regex_search
54
+ end
55
+
56
+ def sort_query
57
+ if custom_field?
58
+ source
59
+ else
60
+ "#{ table.name }.#{ field }"
61
+ end
62
+ end
63
+
64
+ private
65
+ def custom_field?
66
+ !source.include?('.')
67
+ end
68
+
69
+ def config
70
+ @config ||= AjaxDatatablesRails.config
71
+ end
72
+
73
+ def regex_search
74
+ ::Arel::Nodes::Regexp.new((custom_field? ? field : table[field]), ::Arel::Nodes.build_quoted(search.value))
75
+ end
76
+
77
+ def non_regex_search
78
+ case cond
79
+ when Proc
80
+ filter search.value
81
+ when :eq, :not_eq, :lt, :gt, :lteq, :gteq, :in
82
+ if custom_field?
83
+ ::Arel::Nodes::SqlLiteral.new(field).eq(search.value)
84
+ else
85
+ table[field].send(cond, search.value)
86
+ end
87
+ else
88
+ casted_column = ::Arel::Nodes::NamedFunction.new(
89
+ 'CAST', [table[field].as(typecast)]
90
+ )
91
+ casted_column.matches("%#{ search.value }%")
92
+ end
93
+ end
94
+
95
+ def typecast
96
+ case config.db_adapter
97
+ when :mysql, :mysql2 then 'CHAR'
98
+ when :sqlite, :sqlite3 then 'TEXT'
99
+ else
100
+ 'VARCHAR'
101
+ end
102
+ end
103
+
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,69 @@
1
+ module AjaxDatatablesRails
2
+ module Datatable
3
+
4
+ TRUE_VALUE = 'true'
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 ||= options[:order].map { |index, order_options| SimpleOrder.new(self, order_options) }
22
+ end
23
+
24
+ def order_by(how, what)
25
+ orders.find { |simple_order| simple_order.send(how) == what }
26
+ end
27
+
28
+ # ----------------- SEARCH METHODS --------------------
29
+
30
+ def searchable?
31
+ options[:search].present? && options[:search][:value].present?
32
+ end
33
+
34
+ def search
35
+ @search ||= SimpleSearch.new(options[:search])
36
+ end
37
+
38
+ # ----------------- COLUMN METHODS --------------------
39
+
40
+ def columns
41
+ @columns ||= options[:columns].map do |index, column_options|
42
+ Column.new(datatable, index, column_options)
43
+ end
44
+ end
45
+
46
+ def column_by how, what
47
+ columns.find { |simple_column| simple_column.send(how) == what }
48
+ end
49
+
50
+ # ----------------- OPTIONS METHODS --------------------
51
+
52
+ def paginate?
53
+ per_page != -1
54
+ end
55
+
56
+ def offset
57
+ (page - 1) * per_page
58
+ end
59
+
60
+ def page
61
+ (options[:start].to_i / per_page) + 1
62
+ end
63
+
64
+ def per_page
65
+ options.fetch(:length, 10).to_i
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,31 @@
1
+ module AjaxDatatablesRails
2
+ module Datatable
3
+ class SimpleOrder
4
+ attr_reader :datatable, :options
5
+
6
+ DIRECTIONS = %w(DESC ASC)
7
+
8
+ def initialize(datatable, options)
9
+ @datatable = datatable
10
+ @options = options || {}
11
+ end
12
+
13
+ def query sort_column
14
+ "#{ sort_column } #{ dir }"
15
+ end
16
+
17
+ def column
18
+ datatable.column_by(:index, column_index)
19
+ end
20
+
21
+ private
22
+ def dir
23
+ DIRECTIONS.find { |direction| direction == options[:dir].upcase } || 'ASC'
24
+ end
25
+
26
+ def column_index
27
+ options[:column]
28
+ end
29
+ end
30
+ end
31
+ end