trk_datatables 0.2.12 → 0.2.14

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: 07f18203116549f3dedce931bea8ea8c01d61dd550396899b4cfcb50fc52f779
4
- data.tar.gz: 4f85a893e6b8f281caf64472c61ae7bc28635a45c73dfb6fb9b5fa4f44310b2d
3
+ metadata.gz: 0f8339f6cc77f6372dff2350851b738f1c02bc157496a23fe371795df5c7e379
4
+ data.tar.gz: a6f7b37a8fb9c7b5a21f3fda2edd5a3a01f4f34a3c209ceb324ea7cdd8c15be2
5
5
  SHA512:
6
- metadata.gz: 3e1cce7b7e3f561ba68bba76b7a078f9d7d4980a2805976cff1100d4863f20d25d667c4469d08904cf70ee64ef9a49e199385564c94a7a2fd9df44d443014c82
7
- data.tar.gz: d1c6ddeb4bc762b7f4af30848466b9478386bfcf9c45cede0f3008cba5772c9ca04d3cd7e2cd2c19fb006e9903078575d51897a174b9cba852045153277ebfc6
6
+ metadata.gz: a6c9d856519bf89090cdb4fb5b6856b3eb59568f816ffeed838425e23f9bb75a57c94be9afb0155b0064a2c4fa69568d426c949cf5847fb5bdc35577c148e824
7
+ data.tar.gz: 88432304b3a19c6ac6cb7a683d6ed01919494f61f9b45050b6c3ee7102ef3f610a15852a47f8ce1282fca2d1c24d4192b895e6c66f2b1c92ceb9b4abd706c03f
data/Gemfile.lock CHANGED
@@ -1,39 +1,45 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- trk_datatables (0.2.12)
4
+ trk_datatables (0.2.14)
5
5
  activesupport
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activemodel (6.0.0)
11
- activesupport (= 6.0.0)
12
- activerecord (6.0.0)
13
- activemodel (= 6.0.0)
14
- activesupport (= 6.0.0)
15
- activesupport (6.0.0)
10
+ activemodel (6.1.7.2)
11
+ activesupport (= 6.1.7.2)
12
+ activerecord (6.1.7.2)
13
+ activemodel (= 6.1.7.2)
14
+ activesupport (= 6.1.7.2)
15
+ activesupport (6.1.7.2)
16
16
  concurrent-ruby (~> 1.0, >= 1.0.2)
17
- i18n (>= 0.7, < 2)
18
- minitest (~> 5.1)
19
- tzinfo (~> 1.1)
20
- zeitwerk (~> 2.1, >= 2.1.8)
21
- byebug (11.0.1)
22
- concurrent-ruby (1.1.5)
23
- database_cleaner (1.7.0)
24
- i18n (1.6.0)
17
+ i18n (>= 1.6, < 2)
18
+ minitest (>= 5.1)
19
+ tzinfo (~> 2.0)
20
+ zeitwerk (~> 2.3)
21
+ byebug (11.1.3)
22
+ concurrent-ruby (1.2.0)
23
+ database_cleaner (2.0.1)
24
+ database_cleaner-active_record (~> 2.0.0)
25
+ database_cleaner-active_record (2.0.1)
26
+ activerecord (>= 5.a)
27
+ database_cleaner-core (~> 2.0.0)
28
+ database_cleaner-core (2.0.1)
29
+ i18n (1.12.0)
25
30
  concurrent-ruby (~> 1.0)
26
- minitest (5.12.0)
31
+ mini_portile2 (2.8.1)
32
+ minitest (5.17.0)
27
33
  minitest-color (0.0.2)
28
34
  minitest (~> 5)
29
- pg (1.1.4)
35
+ pg (1.4.5)
30
36
  rake (10.5.0)
31
- sqlite3 (1.4.1)
32
- thread_safe (0.3.6)
33
- timecop (0.9.4)
34
- tzinfo (1.2.5)
35
- thread_safe (~> 0.1)
36
- zeitwerk (2.1.10)
37
+ sqlite3 (1.6.0)
38
+ mini_portile2 (~> 2.8.0)
39
+ timecop (0.9.6)
40
+ tzinfo (2.0.6)
41
+ concurrent-ruby (~> 1.0)
42
+ zeitwerk (2.6.6)
37
43
 
38
44
  PLATFORMS
39
45
  ruby
@@ -52,4 +58,4 @@ DEPENDENCIES
52
58
  trk_datatables!
53
59
 
54
60
  BUNDLED WITH
55
- 2.1.4
61
+ 2.2.15
data/README.md CHANGED
@@ -96,7 +96,7 @@ module.exports = environment
96
96
  <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
97
97
  <%# we need stylesheet for production server, locally it could work without stylesheet_pack_tag even in production mode %>
98
98
  <%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
99
- <%# we use jQuery from wepbacker so asset pipeline should be included later %>
99
+ <%# we use jQuery from wepbacker so asset pipeline should be included later if you use asset pipeline %>
100
100
  <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
101
101
  ```
102
102
 
@@ -106,7 +106,7 @@ Than add a gem and sample PostsDatatable
106
106
  # Gemfile
107
107
  gem 'trk_datatables'
108
108
 
109
- # in console
109
+ # in console you can use rails generator to generate app/datatables/xxx_datatable.rb
110
110
  bundle
111
111
  rails g trk_datatables post
112
112
  vi app/datatables/posts_datatable.rb
@@ -153,7 +153,7 @@ class PostsDatatable < TrkDatatables::ActiveRecord
153
153
  Post.left_joins(:user)
154
154
  end
155
155
 
156
- def rows(filtered)
156
+ def rows(filtered) # rubocop:disable Metrics/MethodLength
157
157
  filtered.map do |post|
158
158
  [
159
159
  @view.link_to(post.title, post),
@@ -217,6 +217,9 @@ method.
217
217
  class PostsDatatable < TrkDatatables::ActiveRecord
218
218
  def global_search_columns
219
219
  # in addition to columns those fields will be used to match global search
220
+ # instead Post.all you should use Post.joins(:user) or
221
+ # Post.left_joins(:user) if user is optional. For has_many relations you
222
+ # need to join them since you will get multiple table rows
220
223
  %w[posts.body users.name]
221
224
  end
222
225
  end
@@ -256,7 +259,9 @@ For specific columns you can use following keys
256
259
  * `order: false` disable ordering for this column
257
260
  * `select_options: Post.statuses` generate select box instead of text input
258
261
  * `predefined_ranges: {}` for datetime fiels add ranges to pick up from
259
- * `hide: true` hide column with display none, for example `{ hide: @view.params[:user_id].present? }`
262
+ * `hide: true` hide column with display none, for example `{ hide:
263
+ @view.params[:user_id].present? }`, note that you should send that column data
264
+ anyway, just it will not be visible
260
265
  * `class_name: 'Admin::User'` use different class name than
261
266
  `table_name.classify` (in this case of `admin_users` will be `AdminUser`)
262
267
  * `column_type_in_db` one of the: `:string`, `:integer`, `:date`, `:datetime`,
@@ -391,6 +396,15 @@ nor in Ruby code.
391
396
  end
392
397
  ```
393
398
 
399
+ You can access filtered items for example
400
+ ```
401
+ def totals
402
+ filter_by_columns all_items
403
+ filter_by_search_all filter_by_columns all_items # this is actually filtered_items
404
+ order_and_paginate_items filter_by_search_all filter_by_columns all_items # this is ordered_paginated_filtered_items
405
+ end
406
+ ```
407
+
394
408
  ### Values calculated in database
395
409
 
396
410
  There are three types of calculated values (new custom fields that are
@@ -402,22 +416,64 @@ generated based on other columns):
402
416
  posts_count FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
403
417
  "users"."id" GROUP BY users.id`
404
418
 
419
+ Simple calculations and subqueries works fine, you can search and order by them.
420
+ Note that when you use join with other table, than you should group by all
421
+ columns that you have used as columns, for example
422
+ ```
423
+ # app/datatables/member_profiles_datatable.rb
424
+ def columns
425
+ {
426
+ 'member_profiles.full_name': {},
427
+ 'users.email': {},
428
+ 'users.current_sign_in_at': { },
429
+ }
430
+ end
431
+ def all_items
432
+ MemberProfile
433
+ .joins(:user)
434
+ .group("member_profiles.id")
435
+ .group("users.email")
436
+ .group("users.current_sign_in_at")
437
+ end
438
+ ```
439
+ otherwise the error like
440
+ `PG::GroupingError: ERROR: column "users.current_sign_in_at" must appear in the
441
+ GROUP BY clause or be used in an aggregate function`
442
+ You should test by searching and selecting each column as sortable column.
443
+ ```
444
+ # test/controllers/admin/member_profiles_controller_test.rb
445
+ test "#search when order by is by each defined column" do
446
+ columns = MemberProfilesDatatable.new(OpenStruct.new(params: {})).columns
447
+
448
+ columns.each_with_index do |_column_key_option, index|
449
+ post search_superadmin_member_profiles_path(search: { value: "foo" }, order: { "0": { column: index, dir: "asc" } })
450
+ assert_response :success
451
+ end
452
+ end
453
+ ```
454
+
405
455
  Since in SQL you can not use aggregate functions in WHERE (we should repeat
406
456
  calculation and subqueries), currently TrkDatatables does not support using
407
- aggregate functions since it requires implementation of `HAVING` (unless you
408
- disable search and order for those fields with aggregate functions
409
- `'users.posts_count': { search: false, order: false }`).
410
- You can use concatenation aggregate function: in postgres `STRING_AGG`, in mysql
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`.
457
+ aggregate functions since it requires implementation of `HAVING` so when you use
458
+ aggregate functions you need to disable search and order for those fields with
459
+ aggregate functions `'users.posts_count': { search: false, order: false }`).
460
+
461
+ You can use concatenation aggregate function: in postgres
462
+ `STRING_AGG(comments.body, ' ') comments_body`, in mysql `GROUP_CONCAT` so in
463
+ this case we search on real columns. For example let's we have
464
+ ```
465
+ Post
466
+ .select(%(posts.*, GROUP_CONCAT(comments.body) AS comments_body))
467
+ .left_outer_joins(:comments)
468
+ .group('posts.id')
469
+ ```
470
+ and that we have a row `postName, comment1, comment2` than when we searh for
471
+ `comment2` we will get a row `postName, comment2`.
416
472
 
417
473
  Simple calculations and subqueries works fine, just you have to use public
418
474
  method to define calculation (that method is also used in filtering). Name of
419
- method is the same as column name `title_and_body` or `comments_count`. For table
420
- name you should use one of:
475
+ method is the same as column name `title_and_body` or `comments_count`. For
476
+ table name you should use one of:
421
477
  `:string_calculated_in_db`, `:integer_calculated_in_db`,
422
478
  `:date_calculated_in_db`, `:datetime_calculated_in_db` or
423
479
  `:boolean_calculated_in_db`.
@@ -501,8 +557,8 @@ class MostLikedPostsDatatable < TrkDatatables::ActiveRecord
501
557
  # you have { search: false }
502
558
  def comments_count
503
559
  <<~SQL
504
- (SELECT COUNT(*) FROM comments
505
- WHERE comments.post_id = posts.id)
560
+ SELECT COUNT(*) FROM comments
561
+ WHERE comments.post_id = posts.id
506
562
  SQL
507
563
  end
508
564
 
@@ -525,10 +581,138 @@ class MostLikedPostsDatatable < TrkDatatables::ActiveRecord
525
581
  end
526
582
  end
527
583
 
584
+ # You can use this in config/initializers/trk_datatables.rb
585
+ # class TrkDatatables::ActiveRecord; def default_order;
586
+ # or put in specific datatable class like here
528
587
  def default_order
529
- [[2, :desc]]
588
+ [[columns.size - 1, :desc]]
589
+ end
590
+ end
591
+ ```
592
+
593
+ ### Table less models
594
+
595
+ You can use raw sql to fetch the data and use it as a model.
596
+ Here is an example when there is no relations to other models
597
+ ```
598
+ # app/models/table_less.rb
599
+ class TableLess < ApplicationRecord
600
+ self.abstract_class = true
601
+
602
+ def self.load_schema!
603
+ @columns_hash ||= {}
604
+
605
+ # From active_record/attributes.rb
606
+ attributes_to_define_after_schema_loads.each do |name, (type, options)|
607
+ type = ActiveRecord::Type.lookup(type, **options.except(:default)) if type.is_a?(Symbol)
608
+
609
+ define_attribute(name, type, **options.slice(:default))
610
+
611
+ # Improve Model#inspect output
612
+ @columns_hash[name.to_s] = ActiveRecord::ConnectionAdapters::Column.new(name.to_s, options[:default])
613
+ end
614
+ end
615
+ end
616
+ ```
617
+
618
+ ```
619
+ # app/models/translation.rb
620
+ class Translation < TableLess
621
+ self.table_name = :translations
622
+
623
+ attribute :translateable_type, :string, default: nil
624
+ attribute :translateable_id, :string, default: nil
625
+ attribute :column_name, :string, default: nil
626
+ attribute :column_value, :string, default: nil
627
+
628
+ belongs_to :translateable, polymorphic: true
629
+ end
630
+ ```
631
+
632
+ ```
633
+ # app/datatables/translations_datatable.rb
634
+ # rubocop:disable Layout/LineLength
635
+ class TranslationsDatatable < BaseDatatable
636
+ def columns
637
+ {
638
+ 'translations.translateable_id': {},
639
+ 'translations.translateable_type': {hide: true},
640
+ 'translations.column_name': {},
641
+ 'translations.column_value': {},
642
+ '': {},
643
+ }
644
+ end
645
+
646
+ def all_items
647
+ sql = <<~SQL.squish
648
+ (
649
+ (SELECT 'Activity' AS translateable_type, id AS translateable_id, 'name' AS column_name, name AS column_value FROM activities)
650
+ UNION
651
+ (SELECT 'Activity' AS translateable_type, id AS translateable_id, 'description' AS column_name, description AS column_value FROM activities)
652
+ ) as translations
653
+ SQL
654
+ Translation.from([Arel.sql(sql)])
655
+ end
656
+
657
+ def rows(filtered)
658
+ filtered.map do |translation|
659
+ edit_link = @view.button_tag_open_modal(
660
+ @view.edit_translation_path(translation.translateable_id, translateable_type: translation.translateable_type, column_name: translation.column_name), title: @view.t_crud('edit', Translation)
661
+ )
662
+ [
663
+ @view.link_to(translation.translateable, translation.translateable),
664
+ translation.translateable_type,
665
+ translation.column_name,
666
+ translation.column_value,
667
+ edit_link,
668
+ ]
669
+ end
530
670
  end
531
671
  end
672
+ # rubocop:enable Layout/LineLength
673
+ ```
674
+
675
+ For column title we use `table_class.human_attribute_name column_name`. When
676
+ calculated_ columns is used than it can not find translation so better is to:
677
+ ```
678
+ 'string_calculated_in_db.column_value_translated': {search: false, title: @view.t('activerecord.attributes.translation.column_value_translated')},
679
+ ```
680
+
681
+ Note that when you have two associations to the same class, for example
682
+ `Interest` has `:from_member_profile` and `:to_member_profile` you can use
683
+ search for the first column, but for the second you should use
684
+ `string_calculated_in_db` and inside method use table name that is generated in
685
+ joins like `to_member_profiles_interests` (trk_datatable can not determine which
686
+ class is this, in order to find its column)
687
+ ```
688
+ # app/models/interest.rb
689
+ class Interest < ApplicationRecord
690
+ belongs_to :from_member_profile, class_name: 'MemberProfile'
691
+ belongs_to :to_member_profile, class_name: 'MemberProfile'
692
+ end
693
+
694
+ # app/datatables/interests_datatable.rb
695
+ class InterestsDatatable < TrkDatatables::ActiveRecord
696
+ def columns
697
+ {
698
+ 'member_profiles.full_name': {},
699
+ 'string_calculated_in_db.to_member_profiles_interests_full_name': {},
700
+ # if we want to search by this column than we can not use
701
+ # "to_member_profiles_interests.full_name": {},
702
+ # since we can not find that class and its arel_table
703
+ }
704
+ end
705
+
706
+ def to_member_profiles_interests_full_name
707
+ "to_member_profiles_interests.full_name"
708
+ end
709
+
710
+ def all_items
711
+ Interest
712
+ .joins(:from_member_profile) # this will be "member_profiles" table in sql
713
+ .joins(:to_member_profile) # this will be "to_member_profiles_interests"
714
+ end
715
+ end
532
716
  ```
533
717
 
534
718
  ### Default order and page length
@@ -540,10 +724,11 @@ save preferences) and this default values will be used
540
724
  ```
541
725
  # app/datatables/posts_datatable.rb
542
726
  class PostsDatatable
543
- # when we show some invoice_no on first column, and that is reset every year
544
- # on first april, thatn it is better is to use date column ordering
727
+ # when we show invoice_no on first column, and that is reset every year
728
+ # on first april, than it is better is to use date column ordering
729
+ # column starts from zero 0, 1, 2, 3
545
730
  def default_order
546
- [[3, :desc]]
731
+ [[columns.size - 1, :desc]]
547
732
  end
548
733
 
549
734
  def default_page_length
@@ -575,7 +760,18 @@ Default [DOM](https://datatables.net/reference/option/dom) is
575
760
  To set parameters that you can use for links to set column search value, use
576
761
  this `PostsDatatable.param_set 'users.email', 'my@email.com'`. For between
577
762
  search you can use range `Time.zone.today..(Time.zone.today + 1.year)` and for
578
- in multiple values use array `[Post.statuses[:draft]]`:
763
+ in multiple values use array `[Post.statuses[:draft]]`. Note that in Rails
764
+ `Time.zone.now.to_s` usually returns seconds ('23:59:59') but if you change
765
+ default format like in `config/initializers/time_formats.rb` with
766
+ `Time::DATE_FORMATS[:default] = '%d-%b-%Y %I:%M %p'` than range
767
+ `Time.zone.now.at_beginning_of_day..Time.zone.now.at_end_of_day` will not
768
+ include those at end of day since param will not include seconds ('23:59') so in
769
+ this case you can use range for strings
770
+ `Time.zone.now.at_beginning_of_day.to_s..Time.zone.now.at_end_of_day.to_s(:with_seconds)`
771
+ and config/initializers/time_formats.rb `Time::DATE_FORMATS[:with_seconds] = '%d-%b-%Y %I:%M:%S %p'`
772
+
773
+ (or use date `Time.zone.today..Time.zone.today` but that will not populate
774
+ datetime fields in dateRangePicker correctly)
579
775
 
580
776
  ```
581
777
  <%= link_to 'Active posts for my@email.com', \
@@ -697,7 +893,7 @@ class PostsDatatable
697
893
  end
698
894
  ```
699
895
 
700
- It will store order and page lenght inside `dt_preferences` on
896
+ It will store order and page length inside `dt_preferences` on
701
897
  `user.preferences`.
702
898
 
703
899
  ### Additional data to json response
@@ -793,7 +989,7 @@ link_to 'Active', search_posts_path(PostsDatatable.param_set('posts.status',
793
989
  Here is example how you can test
794
990
 
795
991
  ```
796
- # test/datatables/happ
992
+ # test/datatables/posts_datatable_test.rb
797
993
  require 'test_helper'
798
994
 
799
995
  class PostsDatatableTest < ActiveSupport::TestCase
@@ -812,6 +1008,27 @@ class PostsDatatableTest < ActiveSupport::TestCase
812
1008
  end
813
1009
  ```
814
1010
 
1011
+ You can also write controller test
1012
+ ```
1013
+ # test/controllers/posts_controller_test.rb
1014
+ require 'test_helper'
1015
+
1016
+ class PostsControllerTest < ActiveSupport::TestCase
1017
+ test "#index" do
1018
+ get posts_path
1019
+ assert_response :success
1020
+ end
1021
+
1022
+ test "#search" do
1023
+ post = posts(:post)
1024
+ post search_posts_path(search: { value: post.title })
1025
+ assert_response :success
1026
+ response_json = JSON.parse response.body
1027
+ assert_equal 1, response_json["data"].size
1028
+ end
1029
+ end
1030
+ ```
1031
+
815
1032
  ## Exceptions
816
1033
 
817
1034
  To catch errors from TrkDatables you can
@@ -927,6 +1144,13 @@ rm -rf .yardoc/
927
1144
 
928
1145
  Bug reports and pull requests are welcome on GitHub at https://github.com/trkin/trk_datatables. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
929
1146
 
1147
+ ## TODOs
1148
+
1149
+ Column filtering with dropdowns https://datatables.net/extensions/searchpanes/examples/advanced/columnFilter.html
1150
+ Adding graphs https://datatables.net/forums/discussion/comment/123621/#Comment_123621
1151
+ https://datatables.net/examples/api/highcharts.html
1152
+
1153
+
930
1154
  ## License
931
1155
 
932
1156
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -121,7 +121,11 @@ module TrkDatatables
121
121
  column_key_option = @column_key_options[index]
122
122
  next if column_key_option[:column_options][ColumnKeyOptions::ORDER_OPTION] == false
123
123
 
124
- queries << "#{column_key_option[:column_key]} #{direction}"
124
+ queries << if column_key_option[:table_class] < TrkDatatables::CalculatedInDb
125
+ "#{send(column_key_option[:column_key])} #{direction}"
126
+ else
127
+ "#{column_key_option[:column_key]} #{direction}"
128
+ end
125
129
  end
126
130
  filtered.order(Arel.sql(order_by.join(', ')))
127
131
  end
@@ -241,10 +241,7 @@ module TrkDatatables
241
241
  'Last Month':
242
242
  Time.zone.today.prev_month.beginning_of_month.beginning_of_day...Time.zone.today.prev_month.end_of_month.end_of_day,
243
243
  'This Year': Time.zone.today.beginning_of_year.beginning_of_day...Time.zone.today.end_of_day,
244
- }.transform_values do |range|
245
- # datepicker expects format 2020-11-29 11:59:59
246
- range.first.strftime('%F %T')..range.last.strftime('%F %T')
247
- end
244
+ }
248
245
  end
249
246
  end
250
247
  end
@@ -1,3 +1,3 @@
1
1
  module TrkDatatables
2
- VERSION = '0.2.12'.freeze
2
+ VERSION = '0.2.14'.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.12
4
+ version: 0.2.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dusan Orlovic
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-04-10 00:00:00.000000000 Z
11
+ date: 2023-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -205,7 +205,7 @@ metadata:
205
205
  source_code_uri: https://github.com/trkin/trk_datatables
206
206
  changelog_uri: https://github.com/trkin/trk_datatables/blob/master/CHANGELOG.md
207
207
  yard.run: yri
208
- post_install_message:
208
+ post_install_message:
209
209
  rdoc_options: []
210
210
  require_paths:
211
211
  - lib
@@ -220,8 +220,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
220
220
  - !ruby/object:Gem::Version
221
221
  version: '0'
222
222
  requirements: []
223
- rubygems_version: 3.0.3
224
- signing_key:
223
+ rubygems_version: 3.2.15
224
+ signing_key:
225
225
  specification_version: 4
226
226
  summary: Gem that simplify using datatables with Ruby on Rails and Sinatra.
227
227
  test_files: []