trk_datatables 0.2.2 → 0.2.7

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: 3ccb77ab8bb5b29b22c346044bdb06171bed27a50721873cd1d5ec619c8ef1f1
4
- data.tar.gz: 0d747920ac2dd280c0274dd46af82c59afd7c55151910e598d97d64ac2447a6d
3
+ metadata.gz: 25c48f4088f3f708eaa1dc7d464e0daaad342e57cdeca53c2d1a6b53cf317e6e
4
+ data.tar.gz: 3ab420703241cd84845e88890027840a48c70f203363e2799322837963d47e73
5
5
  SHA512:
6
- metadata.gz: a2ce8344fdb8912d0116b5c21695b62cc83292f91a2987917ac78b3ff30c45793be8634b46354d9bb50e5181fa30469d4dcfddf4ed8de64deaba938eaaac29d4
7
- data.tar.gz: a4e65f172f9916df7b79fcfc9d084975564623f915dacbfef017521dd8c7a66a5b54a885fab14362b049a6f43b9f30caa1f7e2e4d13e4b16aaed0e145a40dc86
6
+ metadata.gz: 7244afc3f897a92c6ea63b5b21721398a70ba5882a19a5296e79396c87df287fc9d257aa5a6a1efe99162ae31f673c5bb1400d4aa457236e8068ad6aac2b6aa1
7
+ data.tar.gz: 4a9dd2fc25a1ad1be8f2ccab02409a7f0c00f38ba4dffe8d25eb0ebce308c29190e6043e955e352f17d12f4b432061a8baa763b53b1c8e6b4f8b9082d1c613d8
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- trk_datatables (0.2.2)
4
+ trk_datatables (0.2.7)
5
5
  activesupport
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -18,6 +18,10 @@ you can get something like
18
18
 
19
19
  ![trk-datatables](test/trk_datatables_with_daterangepicker.png "TRK Datatables")
20
20
 
21
+ Currently supports:
22
+ * ActiveRecord
23
+ * Neo4j
24
+
21
25
  ## Table of Contents
22
26
  <!--ts-->
23
27
  * [Trk Datatables](#trk-datatables)
@@ -62,9 +66,10 @@ import 'bootstrap'
62
66
 
63
67
  // our stuff
64
68
  import 'stylesheet/application'
65
- import 'turbolinks.load'
66
69
 
67
- # app/javascript/turbolinks.load.js
70
+ // attach jQuery so it is available for javascript included with asset pipeline
71
+ window.$ = window.jQuery = jQuery;
72
+
68
73
  const trkDatatables = require('trk_datatables')
69
74
 
70
75
  document.addEventListener('turbolinks:load', () => {
@@ -88,7 +93,11 @@ environment.plugins.append('Provide', new webpack.ProvidePlugin({
88
93
  module.exports = environment
89
94
 
90
95
  # app/views/layouts/application.html.erb
96
+ <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
97
+ <%# we need stylesheet for production server, locally it could work without stylesheet_pack_tag even in production mode %>
91
98
  <%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
99
+ <%# we use jQuery from wepbacker so asset pipeline should be included later %>
100
+ <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
92
101
  ```
93
102
 
94
103
  Than add a gem and sample PostsDatatable
@@ -275,47 +284,32 @@ class PostsDatatable < TrkDatatables::ActiveRecord
275
284
  end
276
285
  ```
277
286
 
278
- To enable shortcuts for selecting ranges, you can override predefined ranges and
279
- enable for all `date` and `datetime` column
280
-
281
- ```
282
- # app/datatables/base_trk_datatable.rb
283
- class BaseTrkDatable < TrkDatatables::ActiveRecord
284
- def predefined_ranges
285
- # defaults are defined in https://github.com/trkin/trk_datatables/blob/master/lib/trk_datatables/base.rb
286
- default_predefined_ranges
287
- end
288
- end
289
- ```
290
- or you can enable for all `date` and `datetime columns` for specific datatable
291
- by defining `predefined_ranges` on that datatable. You can disable for specific columns also
287
+ By default, predefined ranges are enabled. You can find source for
288
+ `predefined_date_ranges` and `predefined_datetime_ranges` in
289
+ [base.rb](https://github.com/trkin/trk_datatables/blob/master/lib/trk_datatables/base.rb)
290
+ You can overwrite them in BaseTrkDatable or your trk datatable class.
291
+ You can disable or enable for specific columns like in this example:
292
292
  ```
293
293
  class PostsDatatable < TrkDatatables::ActiveRecord
294
- def predefined_ranges
294
+ def predefined_datetime_ranges
295
295
  {
296
296
  'Today': Time.zone.now.beginning_of_day..Time.zone.now.end_of_day,
297
297
  'Yesterday': [Time.zone.now.beginning_of_day - 1.day, Time.zone.now.end_of_day - 1.day],
298
298
  'This Month': Time.zone.today.beginning_of_month...Time.zone.now.end_of_day,
299
299
  'Last Month': Time.zone.today.prev_month.beginning_of_month...Time.zone.today.prev_month.end_of_month.end_of_day,
300
300
  'This Year': Time.zone.today.beginning_of_year...Time.zone.today.end_of_day,
301
- }
301
+ }.transform_values do |range|
302
+ # datepicker expects format 2020-11-29 11:59:59
303
+ range.first.strftime('%F %T')..range.last.strftime('%F %T')
304
+ end
302
305
  end
303
306
 
304
307
  def columns
305
308
  {
306
- 'posts.created_at': {}, # this column will have predefined_ranges
307
- 'posts.published_on': { predefined_ranges: false }
308
- end
309
- end
310
- ```
311
- or you can define for specific column
312
- ```
313
- # app/datatables/posts_datatable.rb
314
- class PostsDatatable < TrkDatatables::ActiveRecord
315
- def columns
316
- {
317
- 'posts.created_at': { predefined_ranges: { 'Today': Time.zone.now.beginning_of_day...Time.zone.now.end_of_day } },
318
- }
309
+ 'posts.created_at': {}, # this column will have predefined_datetime_ranges
310
+ 'posts.published_on': { predefined_ranges: false },
311
+ 'posts.updated_at': { predefined_ranges: { 'Today': Time.zone.now.beginning_of_day...Time.zone.now.end_of_day } },
312
+ }
319
313
  end
320
314
  end
321
315
  ```
@@ -408,13 +402,17 @@ generated based on other columns):
408
402
  posts_count FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
409
403
  "users"."id" GROUP BY users.id`
410
404
 
411
- Since in SQL you can not use aggregate functions in WHERE (we can repeat
405
+ Since in SQL you can not use aggregate functions in WHERE (we should repeat
412
406
  calculation and subqueries), currently TrkDatatables does not support using
413
407
  aggregate functions since it requires implementation of `HAVING` (unless you
414
- disable search and order `'users.posts_count': { search: false, order: false }`).
408
+ disable search and order for those fields with aggregate functions
409
+ `'users.posts_count': { search: false, order: false }`).
415
410
  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`).
411
+ `GROUP_CONCAT` so in this case we search on real columns. For example let's we
412
+ have `Post.select(%(posts.*, GROUP_CONCAT(comments.body) AS
413
+ comments_body)).left_outer_joins(:comments) .group('posts.id')` and that we have
414
+ a row `postName, comment1, comment2` than when we searh for `comment2` we will
415
+ get a row `postName, comment2`.
418
416
 
419
417
  Simple calculations and subqueries works fine, just you have to use public
420
418
  method to define calculation (that method is also used in filtering). Name of
@@ -424,8 +422,12 @@ name you should use one of:
424
422
  `:date_calculated_in_db`, `:datetime_calculated_in_db` or
425
423
  `:boolean_calculated_in_db`.
426
424
 
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
425
+ There is an issue in calling `all.count` when you are using subquery, or
426
+ selecting two columns `User.select(:id, :email).count`, or using star in string
427
+ `User.select('users.*').count` (although `User.select(Arel.star).count` works),
428
+ using AS `User.select('users.id AS i').count` (here arel does not help, still
429
+ raise exception `User.select(User.arel_table[:email].as('imejl')).count`).
430
+ We need to patch ActiveRecord to define `returns_count_sum`:
429
431
  ```
430
432
  # config/initializers/active_record_group_count.rb
431
433
  # When you are using subquery or joins/group_by than all.count does not work
@@ -489,10 +491,14 @@ class MostLikedPostsDatatable < TrkDatatables::ActiveRecord
489
491
  ))
490
492
  end
491
493
 
494
+ # This is used for filtering so you can move this to main query if
495
+ # you have { search: false }
492
496
  def title_and_body
493
497
  "concat(posts.title, ' ', posts.body)"
494
498
  end
495
499
 
500
+ # This is used for filtering so you can move this to main query if
501
+ # you have { search: false }
496
502
  def comments_count
497
503
  <<~SQL
498
504
  (SELECT COUNT(*) FROM comments
@@ -568,8 +574,12 @@ This will fill proper column search values so you do not need to do it manually
568
574
  (`post_path(:columns=>{"3"=>{:search=>{:value=>"my@email.com"}},
569
575
  "2"=>{:search=>{:value=>"1|2"}}}, :user_id=>1)`)
570
576
 
571
- For form fields you can use similar helper `PostsDatatable.form_field_name
572
- 'users.email'`
577
+ For form fields you can use similar helper that will return name which points to
578
+ specific column, for example:
579
+ ```
580
+ PostsDatatable.form_field_name('users.email'`) # => 'columns[3][search][value]'
581
+ ```
582
+ Usefull when you want to provide a form for a user to search on specific column
573
583
  ```
574
584
  <%= form_tag url: posts_path, method: :get do |f| %>
575
585
  <%= f.text_field PostsDatatable.form_field_name('users.email'), 'my@email.com' %>
@@ -577,6 +587,15 @@ For form fields you can use similar helper `PostsDatatable.form_field_name
577
587
  <% end %>
578
588
  ```
579
589
 
590
+ For global search you can use `[search][value]` for example
591
+
592
+ ```
593
+ <%= form_tag url: posts_path, method: :get do |f| %>
594
+ <%= f.text_field '[search][value]', 'my@email.com' %>
595
+ <%= f.submit 'Search' %>
596
+ <% end %>
597
+ ```
598
+
580
599
  If you need, you can fetch params with this helper
581
600
 
582
601
  ```
@@ -747,6 +766,36 @@ end
747
766
 
748
767
  ```
749
768
 
769
+ ## Neo4j
770
+
771
+ User `.as(:users)` so we know which node us used
772
+
773
+ ```
774
+ class UsersDatatable < TrkDatatables::Neo4j
775
+ def columns
776
+ {
777
+ 'users.email': {},
778
+ 'users.created_at': {},
779
+ }
780
+ end
781
+
782
+ def all_items
783
+ User
784
+ .as(:users)
785
+ .with_associations(moves: { from_group: [:location], to_groups: [:location] })
786
+ end
787
+
788
+ def rows(filtered)
789
+ filtered.map do |user|
790
+ [
791
+ @view.link_to(user.email, @view.admin_user_path(user)),
792
+ user.created_at.to_s(:long),
793
+ ]
794
+ end
795
+ end
796
+ end
797
+ ```
798
+
750
799
  ## Alternatives
751
800
 
752
801
  There are alternatives, for example:
@@ -774,8 +823,8 @@ release a new version, update the version number and then publish with
774
823
 
775
824
  ```
776
825
  vi lib/trk_datatables/version.rb
777
- git commit -am'...'
778
826
  bundle
827
+ git commit -am'...'
779
828
  bundle exec rake release
780
829
  ```
781
830
 
@@ -1,11 +1,14 @@
1
1
  module TrkDatatables
2
2
  class ActiveRecord < Base
3
+ # there is a stack level too deep exception for more than 190 strings
4
+ MAX_NUMBER_OF_STRINGS = 190
5
+
3
6
  # Global search. All columns are typecasted to string. Search string is
4
7
  # splited by space and "and"-ed.
5
8
  def filter_by_search_all(filtered)
6
- conditions = @dt_params.search_all.split(' ').map do |search_string|
9
+ conditions = @dt_params.search_all.split(' ').first(MAX_NUMBER_OF_STRINGS).map do |search_string|
7
10
  @column_key_options.searchable_and_global_search.map do |column_key_option|
8
- filter_column_as_string column_key_option, search_string
11
+ _filter_column_as_string column_key_option, search_string
9
12
  end.reduce(:or) # any searchable column is 'or'-ed
10
13
  end.reduce(:and) # 'and' for each search_string
11
14
 
@@ -40,12 +43,12 @@ module TrkDatatables
40
43
  from, to = search_value.split BETWEEN_SEPARATOR
41
44
  filter_column_as_between(column_key_option, from, to)
42
45
  else
43
- filter_column_as_string(column_key_option, search_value)
46
+ _filter_column_as_string(column_key_option, search_value)
44
47
  end
45
48
  end
46
49
 
47
- def filter_column_as_string(column_key_option, search_value)
48
- search_value.split(' ').map do |search_string|
50
+ def _filter_column_as_string(column_key_option, search_value)
51
+ search_value.split(' ').first(MAX_NUMBER_OF_STRINGS).map do |search_string|
49
52
  casted_column = ::Arel::Nodes::NamedFunction.new(
50
53
  'CAST',
51
54
  [_arel_column(column_key_option).as(@column_key_options.string_cast)]
@@ -219,18 +219,34 @@ module TrkDatatables
219
219
  end
220
220
 
221
221
  def predefined_ranges
222
- {}
222
+ Time.zone ||= 'UTC'
223
+ {
224
+ date: predefined_date_ranges,
225
+ datetime: predefined_datetime_ranges,
226
+ }
223
227
  end
224
228
 
225
- def default_predefined_ranges
226
- Time.zone ||= 'UTC'
229
+ def predefined_date_ranges
230
+ {
231
+ 'Today': Time.zone.today..Time.zone.today,
232
+ 'Yesterday': [Time.zone.today - 1.day, Time.zone.today - 1.day],
233
+ 'This Month': Time.zone.today.beginning_of_month...Time.zone.today,
234
+ 'Last Month': Time.zone.today.prev_month.beginning_of_month...Time.zone.today.prev_month.end_of_month,
235
+ 'This Year': Time.zone.today.beginning_of_year...Time.zone.today,
236
+ }
237
+ end
238
+
239
+ def predefined_datetime_ranges
227
240
  {
228
241
  'Today': Time.zone.now.beginning_of_day..Time.zone.now.end_of_day,
229
242
  'Yesterday': [Time.zone.now.beginning_of_day - 1.day, Time.zone.now.end_of_day - 1.day],
230
- 'This Month': Time.zone.today.beginning_of_month...Time.zone.now.end_of_day,
231
- 'Last Month': Time.zone.today.prev_month.beginning_of_month...Time.zone.today.prev_month.end_of_month.end_of_day,
232
- 'This Year': Time.zone.today.beginning_of_year...Time.zone.today.end_of_day,
233
- }
243
+ 'This Month': Time.zone.today.beginning_of_month.beginning_of_day...Time.zone.now.end_of_day,
244
+ 'Last Month': Time.zone.today.prev_month.beginning_of_month.beginning_of_day...Time.zone.today.prev_month.end_of_month.end_of_day,
245
+ 'This Year': Time.zone.today.beginning_of_year.beginning_of_day...Time.zone.today.end_of_day,
246
+ }.transform_values do |range|
247
+ # datepicker expects format 2020-11-29 11:59:59
248
+ range.first.strftime('%F %T')..range.last.strftime('%F %T')
249
+ end
234
250
  end
235
251
  end
236
252
  end
@@ -22,11 +22,15 @@ module TrkDatatables
22
22
  end
23
23
 
24
24
  # Get the form field name for column. This is class method so you do not
25
- # need datatable instance.
25
+ # need datatable instance. It returns something like
26
+ # 'column[3][search][value]`. For global search you can use
27
+ # '[search][value]`
26
28
  #
27
29
  # @example
28
30
  # form_tag url: posts_path, method: :get do |f|
29
31
  # f.text_field PostsDatatable.form_field_name('users.email'), 'my@email.com'
32
+ # # it is the same as
33
+ # f.text_field 'columns[3][search][value]', 'my@email.com'
30
34
  def form_field_name(column_key)
31
35
  datatable = new OpenStruct.new(params: {})
32
36
  column_index = datatable.index_by_column_key column_key
@@ -12,6 +12,9 @@ module TrkDatatables
12
12
  end
13
13
  class StringCalculatedInDb < CalculatedInDb; end
14
14
  class IntegerCalculatedInDb < CalculatedInDb; end
15
+ class DateCalculatedInDb < CalculatedInDb; end
16
+ class DatetimeCalculatedInDb < CalculatedInDb; end
17
+ class BooleanCalculatedInDb < CalculatedInDb; end
15
18
 
16
19
  class ColumnKeyOptions
17
20
  include Enumerable
@@ -107,7 +110,7 @@ module TrkDatatables
107
110
  # in calculated columns table_name is used only to determine type
108
111
  column_key = column_name
109
112
  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'
113
+ raise Error, 'Unless table name ends with _calculated_in_db, column key needs to have one dot for example: posts.name'
111
114
  end
112
115
 
113
116
  if table_name.blank?
@@ -150,6 +153,8 @@ module TrkDatatables
150
153
  end
151
154
 
152
155
  def _set_global_search_cols(global_search_cols)
156
+ raise Error, 'global_search_cols should be array, for example %w[users.name]' unless global_search_cols.is_a? Array
157
+
153
158
  @global_search_cols = global_search_cols.each_with_object([]) do |column_key, arr|
154
159
  table_name, column_name = column_key.to_s.split '.'
155
160
  table_class = _determine_table_class table_name
@@ -191,7 +196,7 @@ module TrkDatatables
191
196
  end
192
197
 
193
198
  def _determine_column_name(table_class, column_name)
194
- return column_name.humanize if table_class.blank?
199
+ return column_name.titleize if table_class.blank?
195
200
 
196
201
  # maybe we should check if human_attribute_name exists
197
202
  table_class.human_attribute_name column_name
@@ -239,11 +244,11 @@ module TrkDatatables
239
244
  if %i[date datetime].include? column_type_in_db
240
245
  res['data-datatable-range'] = column_type_in_db == :datetime ? :datetime : true
241
246
  if column_options[PREDEFINED_RANGES].present? ||
242
- (@predefined_ranges.present? && column_options[PREDEFINED_RANGES] != false)
247
+ (@predefined_ranges.try(:[], column_type_in_db).present? && column_options[PREDEFINED_RANGES] != false)
243
248
  res['data-datatable-predefined-ranges'] = if column_options[PREDEFINED_RANGES].is_a? Hash
244
249
  column_options[PREDEFINED_RANGES]
245
250
  else
246
- @predefined_ranges
251
+ @predefined_ranges[column_type_in_db]
247
252
  end
248
253
  res['data-datatable-predefined-ranges'].transform_values! do |range|
249
254
  [range.first.to_s, range.last.to_s]
@@ -1,7 +1,14 @@
1
1
  module TrkDatatables
2
2
  class Neo4j < Base
3
3
  def filter_by_search_all(filtered)
4
- filtered
4
+ return filtered unless @dt_params.search_all.present?
5
+
6
+ # https://neo4jrb.readthedocs.io/en/stable/QueryClauseMethods.html?highlight=where#where
7
+ sql = @column_key_options.searchable_and_global_search.map do |column_key_option|
8
+ "#{column_key_option[:column_key]} =~ ?"
9
+ end.join(' or ')
10
+
11
+ filtered.where sql, ".*#{@dt_params.search_all}.*"
5
12
  end
6
13
 
7
14
  def filter_by_columns(all)
@@ -15,7 +22,13 @@ module TrkDatatables
15
22
  end
16
23
 
17
24
  def order_items(filtered)
18
- filtered
25
+ order_by = dt_orders_or_default_index_and_direction.each_with_object([]) do |(index, direction), queries|
26
+ column_key_option = @column_key_options[index]
27
+ next if column_key_option[:column_options][ColumnKeyOptions::ORDER_OPTION] == false
28
+
29
+ queries << "#{column_key_option[:column_key]} #{direction}"
30
+ end
31
+ filtered.order(order_by.join(', '))
19
32
  end
20
33
  end
21
34
  end
@@ -5,7 +5,7 @@ module TrkDatatables
5
5
  @search_link = search_link
6
6
  @datatable = datatable
7
7
  @html_options = html_options
8
- self.class.indent = -1
8
+ self.class.indent = 0
9
9
  end
10
10
 
11
11
  def result
@@ -53,7 +53,7 @@ module TrkDatatables
53
53
  else
54
54
  ">\n".html_safe << yield << "\n#{' ' * self.class.indent}</#{tag}>".html_safe
55
55
  end
56
- self.class.indent -= 1
56
+ self.class.indent -= 1 if self.class.indent > 1
57
57
  html
58
58
  end
59
59
  # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
@@ -128,6 +128,25 @@ module TrkDatatables
128
128
  end
129
129
 
130
130
  def table_tag_client
131
+ # Should we allow generating datatable only in view
132
+ # <%= ClientDatatable.new(self).render_html do %>
133
+ # <thead>
134
+ # <tr>
135
+ # so we do not need datatable and search route
136
+ # than we just need <table> tag, but it uses datatable to determine page
137
+ # length and order (which in turn it determines from params or
138
+ # preferences)
139
+ # _content_tag(
140
+ # :table,
141
+ # class: "table table-bordered table-striped #{@html_options[:class]}",
142
+ # 'data-datatable': true,
143
+ # 'data-datatable-ajax-url': @search_link,
144
+ # 'data-datatable-page-length': @datatable.dt_per_page_or_default,
145
+ # 'data-datatable-order': @datatable.dt_orders_or_default_index_and_direction.to_json,
146
+ # # for initial page load we do not have ability to show recordsTotal
147
+ # # https://github.com/trkin/trk_datatables_js/issues/1
148
+ # 'data-datatable-total-length': @datatable.filtered_items_count,
149
+ # ) do
131
150
  ''
132
151
  end
133
152
  end
@@ -1,3 +1,3 @@
1
1
  module TrkDatatables
2
- VERSION = '0.2.2'.freeze
2
+ VERSION = '0.2.7'.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.2
4
+ version: 0.2.7
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-09-03 00:00:00.000000000 Z
11
+ date: 2021-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -206,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
206
  - !ruby/object:Gem::Version
207
207
  version: '0'
208
208
  requirements: []
209
- rubygems_version: 3.0.3
209
+ rubygems_version: 3.0.8
210
210
  signing_key:
211
211
  specification_version: 4
212
212
  summary: Gem that simplify using datatables with Ruby on Rails and Sinatra.