trk_datatables 0.2.1 → 0.2.2
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +132 -5
- data/lib/trk_datatables/active_record.rb +5 -1
- data/lib/trk_datatables/column_key_options.rb +31 -10
- data/lib/trk_datatables/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3ccb77ab8bb5b29b22c346044bdb06171bed27a50721873cd1d5ec619c8ef1f1
|
4
|
+
data.tar.gz: 0d747920ac2dd280c0274dd46af82c59afd7c55151910e598d97d64ac2447a6d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2ce8344fdb8912d0116b5c21695b62cc83292f91a2987917ac78b3ff30c45793be8634b46354d9bb50e5181fa30469d4dcfddf4ed8de64deaba938eaaac29d4
|
7
|
+
data.tar.gz: a4e65f172f9916df7b79fcfc9d084975564623f915dacbfef017521dd8c7a66a5b54a885fab14362b049a6f43b9f30caa1f7e2e4d13e4b16aaed0e145a40dc86
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -245,11 +245,13 @@ For specific columns you can use following keys
|
|
245
245
|
* `title: 'My Title'` set up column header
|
246
246
|
* `search: false` disable searching for this column
|
247
247
|
* `order: false` disable ordering for this column
|
248
|
-
* `predefined_ranges: {}` for datetime fiels add ranges to pick up from
|
249
248
|
* `select_options: Post.statuses` generate select box instead of text input
|
249
|
+
* `predefined_ranges: {}` for datetime fiels add ranges to pick up from
|
250
250
|
* `hide: true` hide column with display none
|
251
251
|
* `class_name: 'Admin::User'` use different class name than
|
252
252
|
`table_name.classify` (in this case of `admin_users` will be `AdminUser`)
|
253
|
+
* `column_type_in_db` one of the: `:string`, `:integer`, `:date`, `:datetime`,
|
254
|
+
`:boolean`
|
253
255
|
|
254
256
|
### Column 'BETWEEN' search with js daterangepicker
|
255
257
|
|
@@ -372,9 +374,10 @@ use empty column_key
|
|
372
374
|
|
373
375
|
If you have more columns that are not actually columns in database (for example
|
374
376
|
links or Ruby calculated values) than you can not use empty column_key since
|
375
|
-
there could be only one in hash. When you disable
|
376
|
-
can use any column name since that column will not
|
377
|
-
example
|
377
|
+
there could be only one (keys in the hash should be unique). When you disable
|
378
|
+
`order` and `search` than you can use any column name since that column will not
|
379
|
+
be used in queries. For example column key `posts.body_size` is not in database
|
380
|
+
nor in Ruby code.
|
378
381
|
|
379
382
|
```
|
380
383
|
def columns
|
@@ -396,7 +399,131 @@ example this column key `posts.body_size` is not in database nor in Ruby code.
|
|
396
399
|
|
397
400
|
### Values calculated in database
|
398
401
|
|
399
|
-
|
402
|
+
There are three types of calculated values (new custom fields that are
|
403
|
+
generated based on other columns):
|
404
|
+
* simple calculations like `SELECT *, quantity * price as full_price`
|
405
|
+
* subqueries like `SELECT *, (SELECT COUNT(*) FROM posts WHERE users.id =
|
406
|
+
posts.user_id) AS posts_count`
|
407
|
+
* aggregate functions in joins/group_by `SELECT users.*, COUNT(posts.id) AS
|
408
|
+
posts_count FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
|
409
|
+
"users"."id" GROUP BY users.id`
|
410
|
+
|
411
|
+
Since in SQL you can not use aggregate functions in WHERE (we can repeat
|
412
|
+
calculation and subqueries), currently TrkDatatables does not support using
|
413
|
+
aggregate functions since it requires implementation of `HAVING` (unless you
|
414
|
+
disable search and order `'users.posts_count': { search: false, order: false }`).
|
415
|
+
You can use concatenation aggregate function: in postgres `STRING_AGG`, in mysql
|
416
|
+
`GROUP_CONCAT` since we search in real columns and show concatenation (we do not
|
417
|
+
search concatenation like we need to search new field `post_count`).
|
418
|
+
|
419
|
+
Simple calculations and subqueries works fine, just you have to use public
|
420
|
+
method to define calculation (that method is also used in filtering). Name of
|
421
|
+
method is the same as column name `title_and_body` or `comments_count`. For table
|
422
|
+
name you should use one of:
|
423
|
+
`:string_calculated_in_db`, `:integer_calculated_in_db`,
|
424
|
+
`:date_calculated_in_db`, `:datetime_calculated_in_db` or
|
425
|
+
`:boolean_calculated_in_db`.
|
426
|
+
|
427
|
+
Before we give an example there is an issue in calling `all.count` if you are
|
428
|
+
using subquery so we need to patch ActiveRecord
|
429
|
+
```
|
430
|
+
# config/initializers/active_record_group_count.rb
|
431
|
+
# When you are using subquery or joins/group_by than all.count does not work
|
432
|
+
# so we need to wrap sql in returns_count_sum
|
433
|
+
# all.returns_count_sum.count # => 123
|
434
|
+
# https://stackoverflow.com/a/21031792/287166
|
435
|
+
# https://github.com/mrbrdo/active_record_group_count/blob/master/lib/active_record_group_count/scope.rb
|
436
|
+
module ActiveRecordGroupCount
|
437
|
+
module Scope
|
438
|
+
extend ActiveSupport::Concern
|
439
|
+
|
440
|
+
module ExtensionMethods
|
441
|
+
def count(*_args)
|
442
|
+
scope = except(:select).select('1')
|
443
|
+
scope_sql = if scope.klass.connection.respond_to?(:unprepared_statement)
|
444
|
+
scope.klass.connection.unprepared_statement { scope.to_sql }
|
445
|
+
else
|
446
|
+
scope.to_sql
|
447
|
+
end
|
448
|
+
query = "SELECT count(*) AS count_all FROM (#{scope_sql}) x"
|
449
|
+
first_result = ActiveRecord::Base.connection.execute(query).first
|
450
|
+
if first_result.is_a? Array
|
451
|
+
first_result.first
|
452
|
+
else
|
453
|
+
first_result['count_all']
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
module ClassMethods
|
459
|
+
def returns_count_sum
|
460
|
+
all.extending(ExtensionMethods)
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
# https://github.com/mrbrdo/active_record_group_count/blob/master/lib/active_record_group_count/railtie.rb
|
466
|
+
ActiveSupport.on_load :active_record do
|
467
|
+
include ActiveRecordGroupCount::Scope
|
468
|
+
end
|
469
|
+
```
|
470
|
+
|
471
|
+
So now we can use `all_items.returns_count_sum.count`. Here is example of simple
|
472
|
+
calulation and subquery
|
473
|
+
|
474
|
+
```
|
475
|
+
class MostLikedPostsDatatable < TrkDatatables::ActiveRecord
|
476
|
+
def columns
|
477
|
+
{
|
478
|
+
'posts.id': {},
|
479
|
+
'string_calculated_in_db.title_and_body': {},
|
480
|
+
'integer_calculated_in_db.comments_count': {},
|
481
|
+
}
|
482
|
+
end
|
483
|
+
|
484
|
+
def all_items
|
485
|
+
Post.select(%(
|
486
|
+
posts.*,
|
487
|
+
#{title_and_body} AS title_and_body,
|
488
|
+
(#{comments_count}) AS comments_count
|
489
|
+
))
|
490
|
+
end
|
491
|
+
|
492
|
+
def title_and_body
|
493
|
+
"concat(posts.title, ' ', posts.body)"
|
494
|
+
end
|
495
|
+
|
496
|
+
def comments_count
|
497
|
+
<<~SQL
|
498
|
+
(SELECT COUNT(*) FROM comments
|
499
|
+
WHERE comments.post_id = posts.id)
|
500
|
+
SQL
|
501
|
+
end
|
502
|
+
|
503
|
+
def all_items_count
|
504
|
+
all_items.returns_count_sum.count
|
505
|
+
end
|
506
|
+
|
507
|
+
def filtered_items_count
|
508
|
+
filtered_items.returns_count_sum.count
|
509
|
+
end
|
510
|
+
|
511
|
+
def rows(filtered)
|
512
|
+
# you can use @view.link_to and other helpers
|
513
|
+
filtered.map do |post|
|
514
|
+
[
|
515
|
+
@view.link_to(post.id, post),
|
516
|
+
post.title_and_body,
|
517
|
+
post.comments_count,
|
518
|
+
]
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
def default_order
|
523
|
+
[[2, :desc]]
|
524
|
+
end
|
525
|
+
end
|
526
|
+
```
|
400
527
|
|
401
528
|
### Default order and page length
|
402
529
|
|
@@ -121,7 +121,11 @@ module TrkDatatables
|
|
121
121
|
end
|
122
122
|
|
123
123
|
def _arel_column(column_key_option)
|
124
|
-
column_key_option[:table_class]
|
124
|
+
if column_key_option[:table_class] < TrkDatatables::CalculatedInDb
|
125
|
+
Arel.sql send(column_key_option[:column_key])
|
126
|
+
else
|
127
|
+
column_key_option[:table_class].arel_table[column_key_option[:column_name]]
|
128
|
+
end
|
125
129
|
end
|
126
130
|
end
|
127
131
|
end
|
@@ -1,5 +1,18 @@
|
|
1
1
|
module TrkDatatables
|
2
2
|
# rubocop:disable Metrics/ClassLength
|
3
|
+
class CalculatedInDb
|
4
|
+
def self.human_attribute_name(column_name)
|
5
|
+
column_name
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.determine_db_type_for_column
|
9
|
+
# converts TrkDatatables::IntegerCalculatedInDb to :integer
|
10
|
+
name.sub('TrkDatatables::', '').sub('CalculatedInDb', '').downcase.to_sym
|
11
|
+
end
|
12
|
+
end
|
13
|
+
class StringCalculatedInDb < CalculatedInDb; end
|
14
|
+
class IntegerCalculatedInDb < CalculatedInDb; end
|
15
|
+
|
3
16
|
class ColumnKeyOptions
|
4
17
|
include Enumerable
|
5
18
|
|
@@ -12,11 +25,10 @@ module TrkDatatables
|
|
12
25
|
# {
|
13
26
|
# 'users.name': { search: false }
|
14
27
|
# }
|
28
|
+
TITLE_OPTION = :title
|
15
29
|
SEARCH_OPTION = :search
|
16
30
|
ORDER_OPTION = :order
|
17
|
-
TITLE_OPTION = :title
|
18
31
|
SELECT_OPTIONS = :select_options
|
19
|
-
CHECKBOX_OPTION = :checkbox_option
|
20
32
|
PREDEFINED_RANGES = :predefined_ranges
|
21
33
|
HIDE_OPTION = :hide
|
22
34
|
CLASS_NAME = :class_name
|
@@ -26,15 +38,16 @@ module TrkDatatables
|
|
26
38
|
# SEARCH_OPTION_DATETIME_VALUE = :datetime
|
27
39
|
COLUMN_OPTIONS = [
|
28
40
|
SEARCH_OPTION, ORDER_OPTION, TITLE_OPTION, SELECT_OPTIONS,
|
29
|
-
|
41
|
+
PREDEFINED_RANGES, HIDE_OPTION, CLASS_NAME,
|
30
42
|
COLUMN_TYPE_IN_DB
|
31
43
|
].freeze
|
32
44
|
|
33
45
|
# for columns that as calculated in db query
|
34
|
-
STRING_CALCULATED_IN_DB = :
|
46
|
+
STRING_CALCULATED_IN_DB = :string_calculated_in_db
|
35
47
|
INTEGER_CALCULATED_IN_DB = :integer_calculated_in_db
|
36
48
|
DATE_CALCULATED_IN_DB = :date_calculated_in_db
|
37
|
-
DATETIME_CALCULATED_IN_DB = :
|
49
|
+
DATETIME_CALCULATED_IN_DB = :datetime_calculated_in_db
|
50
|
+
BOOLEAN_CALCULATED_IN_DB = :boolean_calculated_in_db
|
38
51
|
|
39
52
|
# for 'columns' that are calculated in Ruby you need to disable search and
|
40
53
|
# order and than it will not be used in queries
|
@@ -90,7 +103,12 @@ module TrkDatatables
|
|
90
103
|
|
91
104
|
column_options.assert_valid_keys(*COLUMN_OPTIONS)
|
92
105
|
table_name, column_name = column_key.to_s.split '.'
|
93
|
-
|
106
|
+
if table_name.present? && table_name.ends_with?('_calculated_in_db')
|
107
|
+
# in calculated columns table_name is used only to determine type
|
108
|
+
column_key = column_name
|
109
|
+
elsif table_name.present? && column_name.nil?
|
110
|
+
raise Error, 'Unless table name ends with _calculad_in_db, column key needs to have one dot for example: posts.name'
|
111
|
+
end
|
94
112
|
|
95
113
|
if table_name.blank?
|
96
114
|
column_name = column_options[TITLE_OPTION] || 'actions' # some default name for a title
|
@@ -101,7 +119,6 @@ module TrkDatatables
|
|
101
119
|
|
102
120
|
unless column_options[SEARCH_OPTION] == false && column_options[ORDER_OPTION] == false
|
103
121
|
column_type_in_db = column_options[COLUMN_TYPE_IN_DB] || _determine_db_type_for_column(table_class, column_name)
|
104
|
-
column_options[CHECKBOX_OPTION] = true if column_type_in_db == :boolean && column_options[CHECKBOX_OPTION].nil?
|
105
122
|
end
|
106
123
|
end
|
107
124
|
arr << {
|
@@ -120,11 +137,13 @@ module TrkDatatables
|
|
120
137
|
def _determine_table_class(table_name, class_name = nil)
|
121
138
|
# post_users -> PostUser
|
122
139
|
# but also check if admin_posts -> Admin::Post so user can defined
|
123
|
-
# class_name: 'Admin::
|
140
|
+
# class_name: 'Admin::Post'
|
124
141
|
# https://github.com/duleorlovic/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L289
|
125
142
|
# note that when class is not eager loaded than const_defined? returns false
|
126
143
|
if class_name.present?
|
127
144
|
class_name.constantize
|
145
|
+
elsif table_name.ends_with? '_calculated_in_db'
|
146
|
+
"TrkDatatables::#{table_name.classify}".constantize
|
128
147
|
else
|
129
148
|
table_name.classify.constantize
|
130
149
|
end
|
@@ -157,7 +176,9 @@ module TrkDatatables
|
|
157
176
|
# @return
|
158
177
|
# :string, :integer, :date, :datetime
|
159
178
|
def _determine_db_type_for_column(table_class, column_name)
|
160
|
-
if
|
179
|
+
if table_class < TrkDatatables::CalculatedInDb
|
180
|
+
table_class.determine_db_type_for_column
|
181
|
+
elsif defined?(::ActiveRecord::Base)
|
161
182
|
ar_column = table_class.columns_hash[column_name]
|
162
183
|
raise Error, "Can't find column #{column_name} in #{table_class.name}" unless ar_column
|
163
184
|
|
@@ -214,7 +235,7 @@ module TrkDatatables
|
|
214
235
|
res['data-searchable'] = false if column_options[SEARCH_OPTION] == false
|
215
236
|
res['data-orderable'] = false if column_options[ORDER_OPTION] == false
|
216
237
|
res['data-datatable-hidden-column'] = true if column_options[HIDE_OPTION] == true
|
217
|
-
res['data-datatable-checkbox'] = true if
|
238
|
+
res['data-datatable-checkbox'] = true if column_type_in_db == :boolean
|
218
239
|
if %i[date datetime].include? column_type_in_db
|
219
240
|
res['data-datatable-range'] = column_type_in_db == :datetime ? :datetime : true
|
220
241
|
if column_options[PREDEFINED_RANGES].present? ||
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trk_datatables
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dusan Orlovic
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|