effective_datatables 2.3.8 → 2.4.0

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
  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