trk_datatables 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d75c4de5838a5899996deab1a0737969b6a04294f0de0e7a0e2af1c82713f01
4
- data.tar.gz: a307532660a052632e782a2a47d837b6b22c6071c30ee18027b0020084cc275b
3
+ metadata.gz: 3ccb77ab8bb5b29b22c346044bdb06171bed27a50721873cd1d5ec619c8ef1f1
4
+ data.tar.gz: 0d747920ac2dd280c0274dd46af82c59afd7c55151910e598d97d64ac2447a6d
5
5
  SHA512:
6
- metadata.gz: 11532df36b2a5be0a3b942112943bbb1d6f4aeb229c986435e11316a826aab8c82117b9e4550334988ef0b06ab5ce19368db7dbc691aff49802a6cedf02b2d67
7
- data.tar.gz: a59f1cd547aad4b95be0859aade92c9d1fc49e6d031bc09fdfc790f0742ee17b28644121dab6d5cbdc6e1dcaa5883557c088ea4f1345ca0f3f0fae4a4d395d63
6
+ metadata.gz: a2ce8344fdb8912d0116b5c21695b62cc83292f91a2987917ac78b3ff30c45793be8634b46354d9bb50e5181fa30469d4dcfddf4ed8de64deaba938eaaac29d4
7
+ data.tar.gz: a4e65f172f9916df7b79fcfc9d084975564623f915dacbfef017521dd8c7a66a5b54a885fab14362b049a6f43b9f30caa1f7e2e4d13e4b16aaed0e145a40dc86
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- trk_datatables (0.2.1)
4
+ trk_datatables (0.2.2)
5
5
  activesupport
6
6
 
7
7
  GEM
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 `order` and `search` than you
376
- can use any column name since that column will not be used in queries. For
377
- example this column key `posts.body_size` is not in database nor in Ruby code.
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
- For values that
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].arel_table[column_key_option[:column_name]]
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
- CHECKBOX_OPTION, PREDEFINED_RANGES, HIDE_OPTION, CLASS_NAME,
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 = :string_calculcated_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 = :datetime_calculcated_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
- raise Error, 'Column key needs to have one dot for example: posts.name' if table_name.present? && column_name.nil?
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::Ppost'
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 defined?(::ActiveRecord::Base)
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 column_options[CHECKBOX_OPTION] == true
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? ||
@@ -1,3 +1,3 @@
1
1
  module TrkDatatables
2
- VERSION = '0.2.1'.freeze
2
+ VERSION = '0.2.2'.freeze
3
3
  end
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.1
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-08-31 00:00:00.000000000 Z
11
+ date: 2020-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport