effective_datatables 2.3.8 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8497b5523a489dc7a7b27a161203782080c6c95a
4
- data.tar.gz: edfd807e2a1032d6bf3724200ba37fdc37626937
3
+ metadata.gz: 5356cff7e6d831538a222820ad35adde6b9c28a8
4
+ data.tar.gz: 273bea0ced295a05ef825e2cfa009670c1a0f682
5
5
  SHA512:
6
- metadata.gz: 9a9f16f1d2248b4f8b80003427a6a6a44bd4c48e5fa2084ae664158d60dd9df55cda4a6642bff2ee33c04a4f54f6dde092987a2ea164eee0b9d1a75c225e6399
7
- data.tar.gz: c000ddce0633521ea5cb132af6a8a8e6a15a098bf7f5948d65f7bcc782fbe7ab18251889c5da1e17d7e7d411a006316bc722c35e5c70f81a29a7b48874750c59
6
+ metadata.gz: 8c0af0da24feda9e04408d4150fd874818d7bf08ecb29f445797e4a06362cb4d9ec454f65163d77a551bfc5af2efb96e23ed0eebb99125a5224fc6883aaafd5d
7
+ data.tar.gz: 3f6e5cd755c9d9c773ed9c32e9096fc2c5b496a7dfc5ff0c8aec14519e5b2719db57f09c332617566bc2b2f3f8a8968f6854326e073d75d5635a9e8fbea8e36a
data/README.md CHANGED
@@ -136,7 +136,7 @@ module Effective
136
136
 
137
137
  table_column :user
138
138
 
139
- table_column :post_category_id, :filter => {:type => :select, :values => Proc.new { PostCategory.all } } do |post|
139
+ table_column :post_category_id, :filter => {:as => :select, :collection => Proc.new { PostCategory.all } } do |post|
140
140
  post.post_category.name.titleize
141
141
  end
142
142
 
@@ -267,7 +267,7 @@ table_column :user
267
267
  # Will have the same behaviour as declaring
268
268
  datatable do
269
269
  if attributes[:user_id].blank?
270
- table_column :user_id, :filter => {:type => :select, :values => Proc.new { User.all.map { |user| [user.id, user.to_s] }.sort { |x, y| x[1] <=> y[1] } } } do |post|
270
+ table_column :user_id, :filter => {:as => :select, :collection => Proc.new { User.all.map { |user| [user.id, user.to_s] }.sort { |x, y| x[1] <=> y[1] } } } do |post|
271
271
  post.user.to_s
272
272
  end
273
273
  end
@@ -284,7 +284,7 @@ The difference occurs with sorting and filtering:
284
284
 
285
285
  array_columns perform searching and sorting on the computed results after all columns have been rendered.
286
286
 
287
- With a `table_column`, the frontend sends some search terms to the server, the raw database table is searched & sorted using standard ActiveRecord .where(), the appropriate rows returned, and then each row is rendered as per the rendering options.
287
+ With a `table_column`, the frontend sends some search terms to the server, the raw database table is searched & sorted using standard ActiveRecord .where() and .order(), the appropriate rows returned, and then each row is rendered as per the rendering options.
288
288
 
289
289
  With an `array_column`, the front end sends some search terms to the server, all rows are returned and rendered, and then the rendered output is searched & sorted.
290
290
 
@@ -292,6 +292,18 @@ This allows the output of an `array_column` to be anything complex that cannot b
292
292
 
293
293
  When searching & sorting with a mix of table_columns and array_columns, all the table_columns are processed first so the most work is put on the database, the least on rails.
294
294
 
295
+ If you're overriding the `search_column` or `order_column` behaviour of an `array_column`, keep in mind that all values will be strings.
296
+
297
+ This has the side effect of ordering an `array_column` of numbers, as if they were strings. To keep them ordered as numbers, call:
298
+
299
+ ```ruby
300
+ array_column :price, type: :number do |product|
301
+ number_to_currency(product.price)
302
+ end
303
+ ```
304
+
305
+ The above code will output the price as a currency, but still sort the values as numbers rather than as strings.
306
+
295
307
 
296
308
  ### General Options
297
309
 
@@ -299,7 +311,7 @@ The following options control the general behaviour of the column:
299
311
 
300
312
  ```ruby
301
313
  :column => 'users.id' # Set this if you're doing something tricky with the database. Used internally for .order() and .where() clauses
302
- :type => :string # Derived from the ActiveRecord attribute default datatype. Controls searching behaviour. Valid options include :string, :text, :datetime, :integer, :boolean, :year
314
+ :type => :string # Derived from the ActiveRecord attribute default datatype. Controls searching behaviour. Valid options include :string, :text, :datetime, :date, :integer, :boolean, :year
303
315
  ```
304
316
 
305
317
  ### Display Options
@@ -325,17 +337,16 @@ The following options control the filtering behaviour of the column:
325
337
  table_column :created_at, :filter => false # Disable filtering on this column entirely
326
338
  table_column :created_at, :filter => {...} # Enable filtering with these options
327
339
 
328
- :filter => {:type => :number}
329
- :filter => {:type => :text}
330
-
331
- :filter => {:type => :select, :values => ['One', 'Two'], :selected => 'Two'}
332
- :filter => {:type => :select, :values => [*2010..(Time.zone.now.year+6)]}
333
- :filter => {:type => :select, :values => Proc.new { PostCategory.all } }
334
- :filter => {:type => :select, :values => Proc.new { User.all.order(:email).map { |obj| [obj.id, obj.email] } } }
340
+ :filter => {:as => :number}
341
+ :filter => {:as => :text}
335
342
 
336
- :filter => {:type => :grouped_select, :values => {'Active' => Events.active, 'Past' => Events.past }}
337
- :filter => {:type => :grouped_select, :values => {'Active' => [['Event A', 1], ['Event B', 2]], 'Past' => [['Event C', 3], ['Event D', 4]]} }
343
+ :filter => {:as => :select, :collection => ['One', 'Two'], :selected => 'Two'}
344
+ :filter => {:as => :select, :collection => [*2010..(Time.zone.now.year+6)]}
345
+ :filter => {:as => :select, :collection => Proc.new { PostCategory.all } }
346
+ :filter => {:as => :select, :collection => Proc.new { User.all.order(:email).map { |obj| [obj.id, obj.email] } } }
338
347
 
348
+ :filter => {:as => :grouped_select, :collection => {'Active' => Events.active, 'Past' => Events.past }}
349
+ :filter => {:as => :grouped_select, :collection => {'Active' => [['Event A', 1], ['Event B', 2]], 'Past' => [['Event C', 3], ['Event D', 4]]} }
339
350
  ```
340
351
 
341
352
  Some additional, lesser used options include:
@@ -537,22 +548,63 @@ This gem does its best to provide "just works" filtering of both raw SQL (table_
537
548
 
538
549
  It's also very easy to override the filter behaviour on a per-column basis.
539
550
 
540
- Keep in mind, the filter terms on hidden columns will still be considered in filter results.
551
+ Keep in mind, that filter terms applied to hidden columns will still be considered in filter results.
541
552
 
542
- For custom filter behaviour, specify a `def search_column` method in the datatables model file:
553
+ To customize filter behaviour, specify a `def search_column` method in the datatables model file.
554
+
555
+ If the table column being customized is a table_column:
543
556
 
544
557
  ```ruby
545
- def collection
546
- User.unscoped.uniq
547
- .joins('LEFT JOIN customers ON customers.user_id = users.id')
548
- .select('users.*')
549
- .select('customers.stripe_customer_id AS stripe_customer_id')
550
- .includes(:addresses)
558
+ def search_column(collection, table_column, search_term, sql_column)
559
+ if table_column[:name] == 'subscription_types'
560
+ collection.where('subscriptions.stripe_plan_id ILIKE ?', "%#{search_term}%")
561
+ else
562
+ super
563
+ end
551
564
  end
565
+ ```
566
+
567
+ And if the table column being customized is an array_column:
552
568
 
553
- def search_column(collection, table_column, search_term)
569
+ ```ruby
570
+ def search_column(collection, table_column, search_term, index)
571
+ if table_column[:name] == 'price'
572
+ collection.select! { |row| row[index].include?(search_term) }
573
+ else
574
+ super
575
+ end
576
+ end
577
+ ```
578
+
579
+ ### Customize Order Behaviour
580
+
581
+ The order behaviour can be overridden on a per-column basis.
582
+
583
+ To custom order behaviour, specify a `def order_column` method in the datatables model file.
584
+
585
+ If the table column being customized is a table_column:
586
+
587
+ ```ruby
588
+ def order_column(collection, table_column, direction, sql_column)
554
589
  if table_column[:name] == 'subscription_types'
555
- collection.where('subscriptions.stripe_plan_id ILIKE ?', "%#{search_term}%")
590
+ sql_direction = (direction == :desc ? 'DESC' : 'ASC')
591
+ collection.joins(:subscriptions).order("subscriptions.stripe_plan_id #{sql_direction}")
592
+ else
593
+ super
594
+ end
595
+ end
596
+ ```
597
+
598
+ And if the table column being customized is an array_column:
599
+
600
+ ```ruby
601
+ def order_column(collection, table_column, direction, index)
602
+ if table_column[:name] == 'price'
603
+ if direction == :asc
604
+ collection.sort! { |a, b| a[index].gsub(/\D/, '').to_i <=> b[index].gsub(/\D/, '').to_i }
605
+ else
606
+ collection.sort! { |a, b| b[index].gsub(/\D/, '').to_i <=> a[index].gsub(/\D/, '').to_i }
607
+ end
556
608
  else
557
609
  super
558
610
  end
@@ -679,7 +731,7 @@ the filters and sorting will be automatically configured.
679
731
 
680
732
  Just define `table_column :roles`
681
733
 
682
- The `EffectiveRoles.roles` collection will be used for the filter values, and sorting will be done by roles_mask.
734
+ The `EffectiveRoles.roles` collection will be used for the filter collection, and sorting will be done by roles_mask.
683
735
 
684
736
 
685
737
  ## Get access to the raw results
@@ -54,7 +54,7 @@ module EffectiveDatatablesHelper
54
54
  def datatable_header_filter(form, name, value, opts)
55
55
  return render(partial: opts[:header_partial], locals: {form: form, name: (opts[:label] || name), column: opts}) if opts[:header_partial].present?
56
56
 
57
- case opts[:filter][:type]
57
+ case opts[:filter][:as]
58
58
  when :string, :text, :number
59
59
  form.input name, label: false, required: false, value: value,
60
60
  as: :string,
@@ -77,7 +77,7 @@ module EffectiveDatatablesHelper
77
77
  when :select, :boolean
78
78
  form.input name, label: false, required: false, value: value,
79
79
  as: (ActionView::Helpers::FormBuilder.instance_methods.include?(:effective_select) ? :effective_select : :select),
80
- collection: opts[:filter][:values],
80
+ collection: opts[:filter][:collection],
81
81
  selected: opts[:filter][:selected],
82
82
  multiple: opts[:filter][:multiple] == true,
83
83
  include_blank: (opts[:label] || name.titleize),
@@ -86,7 +86,7 @@ module EffectiveDatatablesHelper
86
86
  when :grouped_select
87
87
  form.input name, label: false, required: false, value: value,
88
88
  as: (ActionView::Helpers::FormBuilder.instance_methods.include?(:effective_select) ? :effective_select : :grouped_select),
89
- collection: opts[:filter][:values],
89
+ collection: opts[:filter][:collection],
90
90
  selected: opts[:filter][:selected],
91
91
  multiple: opts[:filter][:multiple] == true,
92
92
  include_blank: (opts[:label] || name.titleize),
@@ -2,7 +2,7 @@ module Effective
2
2
  class ActiveRecordDatatableTool
3
3
  attr_accessor :table_columns
4
4
 
5
- delegate :order_name, :order_direction, :page, :per_page, :search_column, :collection_class, :quote_sql, :to => :@datatable
5
+ delegate :page, :per_page, :search_column, :order_column, :collection_class, :quote_sql, :to => :@datatable
6
6
 
7
7
  def initialize(datatable, table_columns)
8
8
  @datatable = datatable
@@ -13,67 +13,72 @@ module Effective
13
13
  @search_terms ||= @datatable.search_terms.select { |name, search_term| table_columns.key?(name) }
14
14
  end
15
15
 
16
- def order_column
17
- @order_column ||= table_columns[order_name]
16
+ def order_by_column
17
+ @order_by_column ||= table_columns[@datatable.order_name]
18
18
  end
19
19
 
20
20
  def order(collection)
21
- return collection if order_column.blank?
21
+ return collection unless order_by_column.present?
22
22
 
23
- column = order_column[:column]
23
+ column_order = order_column(collection, order_by_column, @datatable.order_direction, order_by_column[:column])
24
+ raise 'order_column must return an ActiveRecord::Relation object' unless column_order.kind_of?(ActiveRecord::Relation)
25
+ column_order
26
+ end
27
+
28
+ def order_column_with_defaults(collection, table_column, direction, sql_column)
24
29
  before = ''; after = ''
30
+ sql_direction = (direction == :desc ? 'DESC' : 'ASC')
25
31
 
26
32
  if postgres?
27
- after = if order_column[:nulls] == :first
33
+ after = if table_column[:nulls] == :first
28
34
  ' NULLS FIRST'
29
- elsif order_column[:nulls] == :last
35
+ elsif table_column[:nulls] == :last
30
36
  ' NULLS LAST'
31
37
  else
32
- " NULLS #{order_direction == 'DESC' ? 'FIRST' : 'LAST' }"
38
+ " NULLS #{direction == :desc ? 'FIRST' : 'LAST' }"
33
39
  end
34
40
  elsif mysql?
35
- before = "ISNULL(#{column}), "
41
+ before = "ISNULL(#{sql_column}), "
36
42
  end
37
43
 
38
- if order_column[:type] == :belongs_to_polymorphic
39
- collection.order("#{before}#{column.sub('_id', '_type')} #{order_direction}, #{column} #{order_direction}#{after}")
40
- elsif order_column[:sql_as_column] == true
41
- collection.order("#{column} #{order_direction}")
44
+ if table_column[:type] == :belongs_to_polymorphic
45
+ collection.order("#{before}#{sql_column.sub('_id', '_type')} #{sql_direction}, #{sql_column} #{sql_direction}#{after}")
46
+ elsif table_column[:sql_as_column] == true
47
+ collection.order("#{sql_column} #{sql_direction}")
42
48
  else
43
- collection.order("#{before}#{column} #{order_direction}#{after}")
49
+ collection.order("#{before}#{sql_column} #{sql_direction}#{after}")
44
50
  end
45
51
  end
46
52
 
47
53
  def search(collection)
48
54
  search_terms.each do |name, search_term|
49
- column_search = search_column(collection, table_columns[name], search_term)
55
+ column_search = search_column(collection, table_columns[name], search_term, table_columns[name][:column])
50
56
  raise 'search_column must return an ActiveRecord::Relation object' unless column_search.kind_of?(ActiveRecord::Relation)
51
57
  collection = column_search
52
58
  end
53
59
  collection
54
60
  end
55
61
 
56
- def search_column_with_defaults(collection, table_column, term)
57
- column = table_column[:column]
62
+ def search_column_with_defaults(collection, table_column, term, sql_column)
58
63
  sql_op = table_column[:filter][:sql_operation] || :where # only other option is :having
59
64
 
60
65
  case table_column[:type]
61
66
  when :string, :text
62
- if (table_column[:filter][:type] == :select && table_column[:filter][:fuzzy] != true) || sql_op != :where
63
- if ['null', 'nil', nil].include?(term)
64
- collection.public_send(sql_op, "#{column} = :term OR #{column} IS NULL", term: term)
65
- else
66
- collection.public_send(sql_op, "#{column} = :term", term: term)
67
- end
67
+ if sql_op != :where
68
+ collection.public_send(sql_op, "#{sql_column} = :term", term: term)
69
+ elsif ['null', 'nil', nil].include?(term)
70
+ collection.public_send(sql_op, "#{sql_column} = :term OR #{sql_column} IS NULL", term: '')
71
+ elsif table_column[:filter][:fuzzy] != true
72
+ collection.public_send(sql_op, "#{sql_column} = :term", term: term)
68
73
  else
69
- collection.public_send(sql_op, "#{column} #{ilike} :term", term: "%#{term}%")
74
+ collection.public_send(sql_op, "#{sql_column} #{ilike} :term", term: "%#{term}%")
70
75
  end
71
76
  when :belongs_to_polymorphic
72
77
  # our key will be something like Post_15, or Event_1
73
78
  (type, id) = term.split('_')
74
79
 
75
80
  if type.present? && id.present?
76
- collection.public_send(sql_op, "#{column} = :id AND #{column.sub('_id', '_type')} = :type", id: id, type: type)
81
+ collection.public_send(sql_op, "#{sql_column} = :id AND #{sql_column.sub('_id', '_type')} = :type", id: id, type: type)
77
82
  else
78
83
  collection
79
84
  end
@@ -87,7 +92,7 @@ module Effective
87
92
  inverse = reflection.inverse_of || klass.reflect_on_association(collection.table_name) || obj.class.reflect_on_association(collection.table_name.singularize)
88
93
  raise "unable to find #{klass.name} has_many :#{collection.table_name} or belongs_to :#{collection.table_name.singularize} associations" unless inverse
89
94
 
90
- ids = if [:select, :grouped_select].include?(table_column[:filter][:type])
95
+ ids = if [:select, :grouped_select].include?(table_column[:filter][:as])
91
96
  # Treat the search term as one or more IDs
92
97
  inverse_ids = term.split(',').map { |term| (term = term.to_i) == 0 ? nil : term }.compact
93
98
  return collection unless inverse_ids.present?
@@ -95,10 +100,10 @@ module Effective
95
100
  klass.where(id: inverse_ids).joins(inverse.name).pluck(inverse.foreign_key)
96
101
  else
97
102
  # Treat the search term as a string.
98
- klass_columns = if (table_column[:column] == klass.table_name) # No custom column has been defined
103
+ klass_columns = if (sql_column == klass.table_name) # No custom column has been defined
99
104
  klass.columns.map { |col| col.name if col.text? }.compact # Search all database text? columns
100
105
  else
101
- [table_column[:column].gsub("#{klass.table_name}.", '')] # table_column :order_items, column: 'order_items.title'
106
+ [sql_column.gsub("#{klass.table_name}.", '')] # table_column :order_items, column: 'order_items.title'
102
107
  end
103
108
 
104
109
  conditions = klass_columns.map { |col_name| "#{klass.table_name}.#{col_name} #{ilike} :term" }
@@ -118,7 +123,7 @@ module Effective
118
123
  inverse = reflection.inverse_of || klass.reflect_on_association(collection.table_name) || obj.class.reflect_on_association(collection.table_name.singularize)
119
124
  raise "unable to find #{klass.name} has_and_belongs_to_many :#{collection.table_name} or belongs_to :#{collection.table_name.singularize} associations" unless inverse
120
125
 
121
- ids = if [:select, :grouped_select].include?(table_column[:filter][:type])
126
+ ids = if [:select, :grouped_select].include?(table_column[:filter][:as])
122
127
  # Treat the search term as one or more IDs
123
128
  inverse_ids = term.split(',').map { |term| (term = term.to_i) == 0 ? nil : term }.compact
124
129
  return collection unless inverse_ids.present?
@@ -127,10 +132,10 @@ module Effective
127
132
  else
128
133
  # Treat the search term as a string.
129
134
 
130
- klass_columns = if (table_column[:column] == klass.table_name) # No custom column has been defined
135
+ klass_columns = if (sql_column == klass.table_name) # No custom column has been defined
131
136
  klass.columns.map { |col| col.name if col.text? }.compact # Search all database text? columns
132
137
  else
133
- [table_column[:column].gsub("#{klass.table_name}.", '')] # table_column :order_items, column: 'order_items.title'
138
+ [sql_column.gsub("#{klass.table_name}.", '')] # table_column :order_items, column: 'order_items.title'
134
139
  end
135
140
 
136
141
  conditions = klass_columns.map { |col_name| "#{klass.table_name}.#{col_name} #{ilike} :term" }
@@ -141,9 +146,9 @@ module Effective
141
146
  collection.public_send(sql_op, id: ids)
142
147
  when :obfuscated_id
143
148
  if (deobfuscated_id = collection.deobfuscate(term)) == term # We weren't able to deobfuscate it, so this is an Invalid ID
144
- collection.public_send(sql_op, "#{column} = :term", term: 0)
149
+ collection.public_send(sql_op, "#{sql_column} = :term", term: 0)
145
150
  else
146
- collection.public_send(sql_op, "#{column} = :term", term: deobfuscated_id)
151
+ collection.public_send(sql_op, "#{sql_column} = :term", term: deobfuscated_id)
147
152
  end
148
153
  when :effective_address
149
154
  ids = Effective::Address
@@ -156,7 +161,7 @@ module Effective
156
161
  collection.with_role(term)
157
162
  when :datetime, :date
158
163
  begin
159
- digits = term.scan(/(\d+)/).flatten.map(&:to_i)
164
+ digits = term.scan(/(\d+)/).flatten.map { |digit| digit.to_i }
160
165
  start_at = Time.zone.local(*digits)
161
166
 
162
167
  case digits.length
@@ -176,23 +181,23 @@ module Effective
176
181
  end_at = start_at
177
182
  end
178
183
 
179
- collection.public_send(sql_op, "#{column} >= :start_at AND #{column} <= :end_at", start_at: start_at, end_at: end_at)
184
+ collection.public_send(sql_op, "#{sql_column} >= :start_at AND #{sql_column} <= :end_at", start_at: start_at, end_at: end_at)
180
185
  rescue => e
181
186
  collection
182
187
  end
183
188
  when :boolean
184
- collection.public_send(sql_op, "#{column} = :term", term: [1, 'true', 'yes'].include?(term.to_s.downcase))
189
+ collection.public_send(sql_op, "#{sql_column} = :term", term: [1, 'true', 'yes'].include?(term.to_s.downcase))
185
190
  when :integer
186
- collection.public_send(sql_op, "#{column} = :term", term: term.gsub(/\D/, '').to_i)
191
+ collection.public_send(sql_op, "#{sql_column} = :term", term: term.gsub(/\D/, '').to_i)
187
192
  when :year
188
- collection.public_send(sql_op, "EXTRACT(YEAR FROM #{column}) = :term", term: term.to_i)
193
+ collection.public_send(sql_op, "EXTRACT(YEAR FROM #{sql_column}) = :term", term: term.to_i)
189
194
  when :price
190
195
  price_in_cents = (term.gsub(/[^0-9|\.]/, '').to_f * 100.0).to_i
191
- collection.public_send(sql_op, "#{column} = :term", term: price_in_cents)
192
- when :currency, :decimal
193
- collection.public_send(sql_op, "#{column} = :term", term: term.gsub(/[^0-9|\.]/, '').to_f)
196
+ collection.public_send(sql_op, "#{sql_column} = :term", term: price_in_cents)
197
+ when :currency, :decimal, :number
198
+ collection.public_send(sql_op, "#{sql_column} = :term", term: term.gsub(/[^0-9|\.]/, '').to_f)
194
199
  else
195
- collection.public_send(sql_op, "#{column} = :term", term: term)
200
+ collection.public_send(sql_op, "#{sql_column} = :term", term: term)
196
201
  end
197
202
  end
198
203
 
@@ -3,7 +3,7 @@ module Effective
3
3
  class ArrayDatatableTool
4
4
  attr_accessor :table_columns
5
5
 
6
- delegate :order_name, :order_direction, :page, :per_page, :search_column, :display_table_columns, :to => :@datatable
6
+ delegate :page, :per_page, :search_column, :order_column, :display_table_columns, :to => :@datatable
7
7
 
8
8
  def initialize(datatable, table_columns)
9
9
  @datatable = datatable
@@ -14,37 +14,41 @@ module Effective
14
14
  @search_terms ||= @datatable.search_terms.select { |name, search_term| table_columns.key?(name) }
15
15
  end
16
16
 
17
- def order_column
18
- @order_column ||= table_columns[order_name]
17
+ def order_by_column
18
+ @order_by_column ||= table_columns[@datatable.order_name]
19
19
  end
20
20
 
21
21
  def order(collection)
22
- if order_column.present?
23
- index = display_index(order_column)
24
-
25
- if order_direction == 'ASC'
26
- collection.sort! do |x, y|
27
- if (x[index] && y[index])
28
- x[index] <=> y[index]
29
- elsif x[index]
30
- -1
31
- elsif y[index]
32
- 1
33
- else
34
- 0
35
- end
22
+ return collection unless order_by_column.present?
23
+
24
+ column_order = order_column(collection, order_by_column, @datatable.order_direction, display_index(order_by_column))
25
+ raise 'order_column must return an Array' unless column_order.kind_of?(Array)
26
+ column_order
27
+ end
28
+
29
+ def order_column_with_defaults(collection, table_column, direction, index)
30
+ if direction == :asc
31
+ collection.sort! do |x, y|
32
+ if (x[index] && y[index])
33
+ cast_array_column_value(table_column, x[index]) <=> cast_array_column_value(table_column, y[index])
34
+ elsif x[index]
35
+ -1
36
+ elsif y[index]
37
+ 1
38
+ else
39
+ 0
36
40
  end
37
- else
38
- collection.sort! do |x, y|
39
- if (x[index] && y[index])
40
- y[index] <=> x[index]
41
- elsif x[index]
42
- 1
43
- elsif y[index]
44
- -1
45
- else
46
- 0
47
- end
41
+ end
42
+ else
43
+ collection.sort! do |x, y|
44
+ if (x[index] && y[index])
45
+ cast_array_column_value(table_column, y[index]) <=> cast_array_column_value(table_column, x[index])
46
+ elsif x[index]
47
+ 1
48
+ elsif y[index]
49
+ -1
50
+ else
51
+ 0
48
52
  end
49
53
  end
50
54
  end
@@ -54,21 +58,20 @@ module Effective
54
58
 
55
59
  def search(collection)
56
60
  search_terms.each do |name, search_term|
57
- column_search = search_column(collection, table_columns[name], search_term)
61
+ column_search = search_column(collection, table_columns[name], search_term, display_index(table_columns[name]))
58
62
  raise 'search_column must return an Array object' unless column_search.kind_of?(Array)
59
63
  collection = column_search
60
64
  end
61
65
  collection
62
66
  end
63
67
 
64
- def search_column_with_defaults(collection, table_column, search_term)
68
+ def search_column_with_defaults(collection, table_column, search_term, index)
65
69
  search_term = search_term.downcase
66
- index = display_index(table_column)
67
70
 
68
71
  collection.select! do |row|
69
72
  value = row[index].to_s.downcase
70
73
 
71
- if table_column[:filter][:type] == :select && table_column[:filter][:fuzzy] != true
74
+ if table_column[:filter][:fuzzy] != true
72
75
  value == search_term
73
76
  else
74
77
  value.include?(search_term)
@@ -86,6 +89,19 @@ module Effective
86
89
  display_table_columns.present? ? display_table_columns.keys.index(column[:name]) : column[:array_index]
87
90
  end
88
91
 
92
+ # When we order by Array, it's already a string.
93
+ # This gives us a mechanism to sort numbers as numbers
94
+ def cast_array_column_value(table_column, value)
95
+ case table_column[:type]
96
+ when :number, :price, :decimal, :float
97
+ (value.to_s.gsub(/[^0-9|\.]/, '').to_f rescue 0.00)
98
+ when :integer
99
+ (value.to_s.gsub(/\D/, '').to_i rescue 0)
100
+ else
101
+ value
102
+ end
103
+ end
104
+
89
105
  end
90
106
  end
91
107
 
@@ -11,6 +11,7 @@ module Effective
11
11
  extend Effective::EffectiveDatatable::Dsl::ClassMethods
12
12
 
13
13
  include Effective::EffectiveDatatable::Ajax
14
+ include Effective::EffectiveDatatable::Hooks
14
15
  include Effective::EffectiveDatatable::Options
15
16
  include Effective::EffectiveDatatable::Rendering
16
17
 
@@ -111,7 +112,7 @@ module Effective
111
112
  @view.class.send(:attr_accessor, :effective_datatable)
112
113
  @view.effective_datatable = self
113
114
 
114
- (self.class.instance_methods(false) - [:collection, :search_column]).each do |view_method|
115
+ (self.class.instance_methods(false) - [:collection, :search_column, :order_column]).each do |view_method|
115
116
  @view.class_eval { delegate view_method, :to => :@effective_datatable }
116
117
  end
117
118
 
@@ -19,8 +19,8 @@ module Effective
19
19
  def order_name
20
20
  @order_name ||= begin
21
21
  if params[:order] && params[:columns]
22
- order_column_index = (params[:order].first[1][:column] rescue '0')
23
- (params[:columns][order_column_index] || {})[:name]
22
+ order_by_column_index = (params[:order].first[1][:column] rescue '0')
23
+ (params[:columns][order_by_column_index] || {})[:name]
24
24
  elsif @default_order.present?
25
25
  @default_order.keys.first
26
26
  end || table_columns.find { |col, opts| opts[:type] != :bulk_actions_column }.first
@@ -33,11 +33,11 @@ module Effective
33
33
 
34
34
  def order_direction
35
35
  @order_direction ||= if params[:order].present?
36
- params[:order].first[1][:dir] == 'desc' ? 'DESC' : 'ASC'
36
+ params[:order].first[1][:dir] == 'desc' ? :desc : :asc
37
37
  elsif @default_order.present?
38
- @default_order.values.first.to_s.downcase == 'desc' ? 'DESC' : 'ASC'
38
+ @default_order.values.first.to_s.downcase == 'desc' ? :desc : :asc
39
39
  else
40
- 'ASC'
40
+ :asc
41
41
  end
42
42
  end
43
43
 
@@ -65,15 +65,6 @@ module Effective
65
65
  end
66
66
  end
67
67
 
68
- # This is here so classes that inherit from Datatables can can override the specific where clauses on a search column
69
- def search_column(collection, table_column, search_term)
70
- if table_column[:array_column]
71
- array_tool.search_column_with_defaults(collection, table_column, search_term)
72
- else
73
- table_tool.search_column_with_defaults(collection, table_column, search_term)
74
- end
75
- end
76
-
77
68
  def per_page
78
69
  return 9999999 if simple?
79
70
 
@@ -0,0 +1,30 @@
1
+ module Effective
2
+ module EffectiveDatatable
3
+ module Hooks
4
+
5
+ # Called on the final collection after searching, ordering, arrayizing and formatting have been completed
6
+ def finalize(collection) # Override me if you like
7
+ collection
8
+ end
9
+
10
+ # Override this function to perform custom searching on a column
11
+ def search_column(collection, table_column, search_term, sql_column_or_array_index)
12
+ if table_column[:array_column]
13
+ array_tool.search_column_with_defaults(collection, table_column, search_term, sql_column_or_array_index)
14
+ else
15
+ table_tool.search_column_with_defaults(collection, table_column, search_term, sql_column_or_array_index)
16
+ end
17
+ end
18
+
19
+ # Override this function to perform custom ordering on a column
20
+ # direction will be :asc or :desc
21
+ def order_column(collection, table_column, direction, sql_column_or_array_index)
22
+ if table_column[:array_column]
23
+ array_tool.order_column_with_defaults(collection, table_column, direction, sql_column_or_array_index)
24
+ else
25
+ table_tool.order_column_with_defaults(collection, table_column, direction, sql_column_or_array_index)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -141,22 +141,27 @@ module Effective
141
141
  col_type = column[:type]
142
142
  sql_column = column[:column].to_s.upcase
143
143
 
144
- return {type: :null} if filter == false
144
+ return {as: :null} if filter == false
145
145
 
146
- filter = {type: filter.to_sym} if filter.kind_of?(String)
146
+ filter = {as: filter.to_sym} if filter.kind_of?(String)
147
147
  filter = {} unless filter.kind_of?(Hash)
148
148
 
149
149
  # This is a fix for passing filter[:selected] == false, it needs to be 'false'
150
150
  filter[:selected] = filter[:selected].to_s unless filter[:selected].nil?
151
151
 
152
- # Allow values or collection to be used interchangeably
153
- if filter.key?(:collection)
154
- filter[:values] ||= filter[:collection]
152
+ # Allow :values or :collection to be used interchangeably
153
+ if filter.key?(:values)
154
+ filter[:collection] ||= filter[:values]
155
155
  end
156
156
 
157
- # If you pass values, just assume it's a select
158
- if filter.key?(:values) && col_type != :belongs_to_polymorphic
159
- filter[:type] ||= :select
157
+ # Allow :as or :type to be used interchangeably
158
+ if filter.key?(:type)
159
+ filter[:as] ||= filter[:type]
160
+ end
161
+
162
+ # If you pass a collection, just assume it's a select
163
+ if filter.key?(:collection) && col_type != :belongs_to_polymorphic
164
+ filter[:as] ||= :select
160
165
  end
161
166
 
162
167
  # Check if this is an aggregate column
@@ -167,8 +172,8 @@ module Effective
167
172
  case col_type
168
173
  when :belongs_to
169
174
  {
170
- type: :select,
171
- values: (
175
+ as: :select,
176
+ collection: (
172
177
  if belongs_to[:klass].respond_to?(:datatables_filter)
173
178
  Proc.new { belongs_to[:klass].datatables_filter }
174
179
  else
@@ -177,12 +182,12 @@ module Effective
177
182
  )
178
183
  }
179
184
  when :belongs_to_polymorphic
180
- {type: :grouped_select, polymorphic: true, values: {}}
185
+ {as: :grouped_select, polymorphic: true, collection: {}}
181
186
  when :has_many
182
187
  {
183
- type: :select,
188
+ as: :select,
184
189
  multiple: true,
185
- values: (
190
+ collection: (
186
191
  if has_many[:klass].respond_to?(:datatables_filter)
187
192
  Proc.new { has_many[:klass].datatables_filter }
188
193
  else
@@ -192,9 +197,9 @@ module Effective
192
197
  }
193
198
  when :has_and_belongs_to_many
194
199
  {
195
- type: :select,
200
+ as: :select,
196
201
  multiple: true,
197
- values: (
202
+ collection: (
198
203
  if has_and_belongs_to_manys[:klass].respond_to?(:datatables_filter)
199
204
  Proc.new { has_and_belongs_to_manys[:klass].datatables_filter }
200
205
  else
@@ -203,25 +208,25 @@ module Effective
203
208
  )
204
209
  }
205
210
  when :effective_address
206
- {type: :string}
211
+ {as: :string}
207
212
  when :effective_roles
208
- {type: :select, values: EffectiveRoles.roles}
213
+ {as: :select, collection: EffectiveRoles.roles}
209
214
  when :integer
210
- {type: :number}
215
+ {as: :number}
211
216
  when :boolean
212
217
  if EffectiveDatatables.boolean_format == :yes_no
213
- {type: :boolean, values: [['Yes', true], ['No', false]] }
218
+ {as: :boolean, collection: [['Yes', true], ['No', false]] }
214
219
  else
215
- {type: :boolean, values: [['true', true], ['false', false]] }
220
+ {as: :boolean, collection: [['true', true], ['false', false]] }
216
221
  end
217
222
  when :datetime
218
- {type: :datetime}
223
+ {as: :datetime}
219
224
  when :date
220
- {type: :date}
225
+ {as: :date}
221
226
  when :bulk_actions_column
222
- {type: :bulk_actions_column}
227
+ {as: :bulk_actions_column}
223
228
  else
224
- {type: :string}
229
+ {as: :string}
225
230
  end.merge(filter.symbolize_keys)
226
231
  end
227
232
 
@@ -5,10 +5,6 @@ module Effective
5
5
  module Rendering
6
6
  BLANK = ''.freeze
7
7
 
8
- def finalize(collection) # Override me if you like
9
- collection
10
- end
11
-
12
8
  protected
13
9
 
14
10
  # So the idea here is that we want to do as much as possible on the database in ActiveRecord
@@ -37,7 +33,7 @@ module Effective
37
33
  self.display_records = col.size
38
34
  end
39
35
 
40
- if array_tool.order_column.present?
36
+ if array_tool.order_by_column.present?
41
37
  col = self.arrayize(col)
42
38
  col = array_tool.order(col)
43
39
  end
@@ -1,3 +1,3 @@
1
1
  module EffectiveDatatables
2
- VERSION = '2.3.8'.freeze
2
+ VERSION = '2.4.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effective_datatables
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.8
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Code and Effect
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-29 00:00:00.000000000 Z
11
+ date: 2016-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -142,6 +142,7 @@ files:
142
142
  - app/models/effective/datatable.rb
143
143
  - app/models/effective/effective_datatable/ajax.rb
144
144
  - app/models/effective/effective_datatable/dsl.rb
145
+ - app/models/effective/effective_datatable/hooks.rb
145
146
  - app/models/effective/effective_datatable/options.rb
146
147
  - app/models/effective/effective_datatable/rendering.rb
147
148
  - app/views/effective/datatables/_actions_column.html.haml