datagrid 1.8.1 → 1.8.2

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.
@@ -44,7 +44,6 @@ module Datagrid
44
44
  class_attribute :cached, default: false
45
45
  class_attribute :decorator, instance_writer: false
46
46
  end
47
- base.include InstanceMethods
48
47
  end
49
48
 
50
49
  module ClassMethods
@@ -161,6 +160,9 @@ module Datagrid
161
160
  # @!visibility private
162
161
  def filter_columns(columns_array, *names, data: false, html: false)
163
162
  names.compact!
163
+ if names.size >= 1 && names.all? {|n| n.is_a?(Datagrid::Columns::Column) && n.grid_class == self.class}
164
+ return names
165
+ end
164
166
  names.map!(&:to_sym)
165
167
  columns_array.select do |column|
166
168
  (!data || column.data?) &&
@@ -173,7 +175,7 @@ module Datagrid
173
175
  def define_column(columns, name, query = nil, **options, &block)
174
176
  check_scope_defined!("Scope should be defined before columns")
175
177
  block ||= lambda do |model|
176
- model.send(name)
178
+ model.public_send(name)
177
179
  end
178
180
  position = Datagrid::Utils.extract_position_from_options(columns, options)
179
181
  column = Datagrid::Columns::Column.new(
@@ -193,326 +195,328 @@ module Datagrid
193
195
 
194
196
  end
195
197
 
196
- module InstanceMethods
197
-
198
- # @!visibility private
199
- def assets
200
- append_column_preload(
201
- driver.append_column_queries(
202
- super, columns.select(&:query)
203
- )
198
+ # @!visibility private
199
+ def assets
200
+ append_column_preload(
201
+ driver.append_column_queries(
202
+ super, columns.select(&:query)
204
203
  )
205
- end
204
+ )
205
+ end
206
206
 
207
- # @param column_names [Array<String>] list of column names if you want to limit data only to specified columns
208
- # @return [Array<String>] human readable column names. See also "Localization" section
209
- def header(*column_names)
210
- data_columns(*column_names).map(&:header)
211
- end
207
+ # @param column_names [Array<String>] list of column names if you want to limit data only to specified columns
208
+ # @return [Array<String>] human readable column names. See also "Localization" section
209
+ def header(*column_names)
210
+ data_columns(*column_names).map(&:header)
211
+ end
212
212
 
213
- # @param asset [Object] asset from datagrid scope
214
- # @param column_names [Array<String>] list of column names if you want to limit data only to specified columns
215
- # @return [Array<Object>] column values for given asset
216
- def row_for(asset, *column_names)
217
- data_columns(*column_names).map do |column|
218
- data_value(column, asset)
219
- end
213
+ # @param asset [Object] asset from datagrid scope
214
+ # @param column_names [Array<String>] list of column names if you want to limit data only to specified columns
215
+ # @return [Array<Object>] column values for given asset
216
+ def row_for(asset, *column_names)
217
+ data_columns(*column_names).map do |column|
218
+ data_value(column, asset)
220
219
  end
220
+ end
221
221
 
222
- # @param asset [Object] asset from datagrid scope
223
- # @return [Hash] A mapping where keys are column names and values are column values for the given asset
224
- def hash_for(asset)
225
- result = {}
226
- self.data_columns.each do |column|
227
- result[column.name] = data_value(column, asset)
228
- end
229
- result
222
+ # @param asset [Object] asset from datagrid scope
223
+ # @return [Hash] A mapping where keys are column names and values are column values for the given asset
224
+ def hash_for(asset)
225
+ result = {}
226
+ self.data_columns.each do |column|
227
+ result[column.name] = data_value(column, asset)
230
228
  end
229
+ result
230
+ end
231
231
 
232
- # @param column_names [Array<String>] list of column names if you want to limit data only to specified columns
233
- # @return [Array<Array<Object>>] with data for each row in datagrid assets without header
234
- def rows(*column_names)
235
- map_with_batches do |asset|
236
- self.row_for(asset, *column_names)
237
- end
232
+ # @param column_names [Array<String>] list of column names if you want to limit data only to specified columns
233
+ # @return [Array<Array<Object>>] with data for each row in datagrid assets without header
234
+ def rows(*column_names)
235
+ map_with_batches do |asset|
236
+ self.row_for(asset, *column_names)
238
237
  end
238
+ end
239
239
 
240
- # @param column_names [Array<String>] list of column names if you want to limit data only to specified columns
241
- # @return [Array<Array<Object>>] data for each row in datagrid assets with header.
242
- def data(*column_names)
243
- self.rows(*column_names).unshift(self.header(*column_names))
244
- end
240
+ # @param column_names [Array<String>] list of column names if you want to limit data only to specified columns
241
+ # @return [Array<Array<Object>>] data for each row in datagrid assets with header.
242
+ def data(*column_names)
243
+ self.rows(*column_names).unshift(self.header(*column_names))
244
+ end
245
245
 
246
- # Return Array of Hashes where keys are column names and values are column values
247
- # for each row in filtered datagrid relation.
248
- #
249
- # @example
250
- # class MyGrid
251
- # scope { Model }
252
- # column(:id)
253
- # column(:name)
254
- # end
255
- #
256
- # Model.create!(name: "One")
257
- # Model.create!(name: "Two")
258
- #
259
- # MyGrid.new.data_hash # => [{name: "One"}, {name: "Two"}]
260
- def data_hash
261
- map_with_batches do |asset|
262
- hash_for(asset)
263
- end
246
+ # Return Array of Hashes where keys are column names and values are column values
247
+ # for each row in filtered datagrid relation.
248
+ #
249
+ # @example
250
+ # class MyGrid
251
+ # scope { Model }
252
+ # column(:id)
253
+ # column(:name)
254
+ # end
255
+ #
256
+ # Model.create!(name: "One")
257
+ # Model.create!(name: "Two")
258
+ #
259
+ # MyGrid.new.data_hash # => [{name: "One"}, {name: "Two"}]
260
+ def data_hash
261
+ map_with_batches do |asset|
262
+ hash_for(asset)
264
263
  end
264
+ end
265
265
 
266
- # @param column_names [Array<String>]
267
- # @param options [Hash] CSV generation options
268
- # @return [String] a CSV representation of the data in the grid
269
- #
270
- # @example
271
- # grid.to_csv
272
- # grid.to_csv(:id, :name)
273
- # grid.to_csv(col_sep: ';')
274
- def to_csv(*column_names, **options)
275
- require "csv"
276
- CSV.generate(
277
- headers: self.header(*column_names),
278
- write_headers: true,
279
- **options
280
- ) do |csv|
281
- each_with_batches do |asset|
282
- csv << row_for(asset, *column_names)
283
- end
266
+ # @param column_names [Array<String>]
267
+ # @param options [Hash] CSV generation options
268
+ # @return [String] a CSV representation of the data in the grid
269
+ #
270
+ # @example
271
+ # grid.to_csv
272
+ # grid.to_csv(:id, :name)
273
+ # grid.to_csv(col_sep: ';')
274
+ def to_csv(*column_names, **options)
275
+ require "csv"
276
+ CSV.generate(
277
+ headers: self.header(*column_names),
278
+ write_headers: true,
279
+ **options
280
+ ) do |csv|
281
+ each_with_batches do |asset|
282
+ csv << row_for(asset, *column_names)
284
283
  end
285
284
  end
285
+ end
286
286
 
287
287
 
288
- # @param column_names [Array<Symbol, String>]
289
- # @return [Array<Datagrid::Columns::Column>] all columns selected in grid instance
290
- # @example
291
- # MyGrid.new.columns # => all defined columns
292
- # grid = MyGrid.new(column_names: [:id, :name])
293
- # grid.columns # => id and name columns
294
- # grid.columns(:id, :category) # => id and category column
295
- def columns(*column_names, data: false, html: false)
296
- self.class.filter_columns(
297
- columns_array, *column_names, data: data, html: html
298
- ).select do |column|
299
- column.enabled?(self)
300
- end
288
+ # @param column_names [Array<Symbol, String>]
289
+ # @return [Array<Datagrid::Columns::Column>] all columns selected in grid instance
290
+ # @example
291
+ # MyGrid.new.columns # => all defined columns
292
+ # grid = MyGrid.new(column_names: [:id, :name])
293
+ # grid.columns # => id and name columns
294
+ # grid.columns(:id, :category) # => id and category column
295
+ def columns(*column_names, data: false, html: false)
296
+ self.class.filter_columns(
297
+ columns_array, *column_names, data: data, html: html
298
+ ).select do |column|
299
+ column.enabled?(self)
301
300
  end
301
+ end
302
302
 
303
- # @param column_names [Array<String, Symbol>] list of column names if you want to limit data only to specified columns
304
- # @return columns that can be represented in plain data(non-html) way
305
- def data_columns(*column_names, **options)
306
- self.columns(*column_names, **options, data: true)
307
- end
303
+ # @param column_names [Array<String, Symbol>] list of column names if you want to limit data only to specified columns
304
+ # @return columns that can be represented in plain data(non-html) way
305
+ def data_columns(*column_names, **options)
306
+ self.columns(*column_names, **options, data: true)
307
+ end
308
308
 
309
- # @param column_names [Array<String>] list of column names if you want to limit data only to specified columns
310
- # @return all columns that can be represented in HTML table
311
- def html_columns(*column_names, **options)
312
- self.columns(*column_names, **options, html: true)
313
- end
309
+ # @param column_names [Array<String>] list of column names if you want to limit data only to specified columns
310
+ # @return all columns that can be represented in HTML table
311
+ def html_columns(*column_names, **options)
312
+ self.columns(*column_names, **options, html: true)
313
+ end
314
314
 
315
- # Finds a column definition by name
316
- # @param name [String, Symbol] column name to be found
317
- # @return [Datagrid::Columns::Column, nil]
318
- def column_by_name(name)
319
- self.class.find_column_by_name(columns_array, name)
320
- end
315
+ # Finds a column definition by name
316
+ # @param name [String, Symbol] column name to be found
317
+ # @return [Datagrid::Columns::Column, nil]
318
+ def column_by_name(name)
319
+ self.class.find_column_by_name(columns_array, name)
320
+ end
321
321
 
322
- # Gives ability to have a different formatting for CSV and HTML column value.
323
- #
324
- # @example
325
- # column(:name) do |model|
326
- # format(model.name) do |value|
327
- # content_tag(:strong, value)
328
- # end
329
- # end
330
- #
331
- # column(:company) do |model|
332
- # format(model.company.name) do
333
- # render partial: "company_with_logo", locals: {company: model.company }
334
- # end
335
- # end
336
- # @return [Datagrid::Columns::Column::ResponseFormat] Format object
337
- def format(value, &block)
338
- if block_given?
339
- self.class.format(value, &block)
340
- else
341
- # don't override Object#format method
342
- super
343
- end
322
+ # Gives ability to have a different formatting for CSV and HTML column value.
323
+ #
324
+ # @example
325
+ # column(:name) do |model|
326
+ # format(model.name) do |value|
327
+ # content_tag(:strong, value)
328
+ # end
329
+ # end
330
+ #
331
+ # column(:company) do |model|
332
+ # format(model.company.name) do
333
+ # render partial: "company_with_logo", locals: {company: model.company }
334
+ # end
335
+ # end
336
+ # @return [Datagrid::Columns::Column::ResponseFormat] Format object
337
+ def format(value, &block)
338
+ if block_given?
339
+ self.class.format(value, &block)
340
+ else
341
+ # don't override Object#format method
342
+ super
344
343
  end
344
+ end
345
345
 
346
- # @return [Datagrid::Columns::DataRow] an object representing a grid row.
347
- # @example
348
- # class MyGrid
349
- # scope { User }
350
- # column(:id)
351
- # column(:name)
352
- # column(:number_of_purchases) do |user|
353
- # user.purchases.count
354
- # end
355
- # end
356
- #
357
- # row = MyGrid.new.data_row(User.last)
358
- # row.id # => user.id
359
- # row.number_of_purchases # => user.purchases.count
360
- def data_row(asset)
361
- ::Datagrid::Columns::DataRow.new(self, asset)
362
- end
346
+ # @return [Datagrid::Columns::DataRow] an object representing a grid row.
347
+ # @example
348
+ # class MyGrid
349
+ # scope { User }
350
+ # column(:id)
351
+ # column(:name)
352
+ # column(:number_of_purchases) do |user|
353
+ # user.purchases.count
354
+ # end
355
+ # end
356
+ #
357
+ # row = MyGrid.new.data_row(User.last)
358
+ # row.id # => user.id
359
+ # row.number_of_purchases # => user.purchases.count
360
+ def data_row(asset)
361
+ ::Datagrid::Columns::DataRow.new(self, asset)
362
+ end
363
363
 
364
- # Defines a column at instance level
365
- #
366
- # @see Datagrid::Columns::ClassMethods#column
367
- def column(name, query = nil, **options, &block)
368
- self.class.define_column(columns_array, name, query, **options, &block)
369
- end
364
+ # Defines a column at instance level
365
+ #
366
+ # @see Datagrid::Columns::ClassMethods#column
367
+ def column(name, query = nil, **options, &block)
368
+ self.class.define_column(columns_array, name, query, **options, &block)
369
+ end
370
370
 
371
- # @!visibility private
372
- def initialize(*)
373
- self.columns_array = self.class.columns_array.clone
374
- super
371
+ # @!visibility private
372
+ def initialize(*)
373
+ self.columns_array = self.class.columns_array.clone
374
+ super
375
+ end
376
+
377
+ # @return [Array<Datagrid::Columns::Column>] all columns that are possible to be displayed for the current grid object
378
+ #
379
+ # @example
380
+ # class MyGrid
381
+ # filter(:search) {|scope, value| scope.full_text_search(value)}
382
+ # column(:id)
383
+ # column(:name, mandatory: true)
384
+ # column(:search_match, if: proc {|grid| grid.search.present? }) do |model, grid|
385
+ # search_match_line(model.searchable_content, grid.search)
386
+ # end
387
+ # end
388
+ #
389
+ # grid = MyGrid.new
390
+ # grid.columns # => [ #<Column:name> ]
391
+ # grid.available_columns # => [ #<Column:id>, #<Column:name> ]
392
+ # grid.search = "keyword"
393
+ # grid.available_columns # => [ #<Column:id>, #<Column:name>, #<Column:search_match> ]
394
+ def available_columns
395
+ columns_array.select do |column|
396
+ column.enabled?(self)
375
397
  end
398
+ end
376
399
 
377
- # @return [Array<Datagrid::Columns::Column>] all columns that are possible to be displayed for the current grid object
378
- #
379
- # @example
380
- # class MyGrid
381
- # filter(:search) {|scope, value| scope.full_text_search(value)}
382
- # column(:id)
383
- # column(:name, mandatory: true)
384
- # column(:search_match, if: proc {|grid| grid.search.present? }) do |model, grid|
385
- # search_match_line(model.searchable_content, grid.search)
386
- # end
387
- # end
388
- #
389
- # grid = MyGrid.new
390
- # grid.columns # => [ #<Column:name> ]
391
- # grid.available_columns # => [ #<Column:id>, #<Column:name> ]
392
- # grid.search = "keyword"
393
- # grid.available_columns # => [ #<Column:id>, #<Column:name>, #<Column:search_match> ]
394
- def available_columns
395
- columns_array.select do |column|
396
- column.enabled?(self)
397
- end
400
+ # @return [Object] a cell data value for given column name and asset
401
+ def data_value(column_name, asset)
402
+ column = column_by_name(column_name)
403
+ cache(column, asset, :data_value) do
404
+ raise "no data value for #{column.name} column" unless column.data?
405
+ result = generic_value(column, asset)
406
+ result.is_a?(Datagrid::Columns::Column::ResponseFormat) ? result.call_data : result
398
407
  end
408
+ end
399
409
 
400
- # @return [Object] a cell data value for given column name and asset
401
- def data_value(column_name, asset)
402
- column = column_by_name(column_name)
403
- cache(column, asset, :data_value) do
404
- raise "no data value for #{column.name} column" unless column.data?
410
+ # @return [Object] a cell HTML value for given column name and asset and view context
411
+ def html_value(column_name, context, asset)
412
+ column = column_by_name(column_name)
413
+ cache(column, asset, :html_value) do
414
+ if column.html? && column.html_block
415
+ value_from_html_block(context, asset, column)
416
+ else
405
417
  result = generic_value(column, asset)
406
- result.is_a?(Datagrid::Columns::Column::ResponseFormat) ? result.call_data : result
418
+ result.is_a?(Datagrid::Columns::Column::ResponseFormat) ? result.call_html(context) : result
407
419
  end
408
420
  end
421
+ end
409
422
 
410
- # @return [Object] a cell HTML value for given column name and asset and view context
411
- def html_value(column_name, context, asset)
412
- column = column_by_name(column_name)
413
- cache(column, asset, :html_value) do
414
- if column.html? && column.html_block
415
- value_from_html_block(context, asset, column)
416
- else
417
- result = generic_value(column, asset)
418
- result.is_a?(Datagrid::Columns::Column::ResponseFormat) ? result.call_html(context) : result
419
- end
423
+ # @return [Object] a decorated version of given model if decorator is specified or the model otherwise.
424
+ def decorate(model)
425
+ self.class.decorate(model)
426
+ end
427
+
428
+ # @!visibility private
429
+ def generic_value(column, model)
430
+ cache(column, model, :generic_value) do
431
+ presenter = decorate(model)
432
+ unless column.enabled?(self)
433
+ raise Datagrid::ColumnUnavailableError, "Column #{column.name} disabled for #{inspect}"
420
434
  end
421
- end
422
435
 
423
- # @return [Object] a decorated version of given model if decorator is specified or the model otherwise.
424
- def decorate(model)
425
- self.class.decorate(model)
436
+ if column.data_block.arity >= 1
437
+ Datagrid::Utils.apply_args(presenter, self, data_row(model), &column.data_block)
438
+ else
439
+ presenter.instance_eval(&column.data_block)
440
+ end
426
441
  end
442
+ end
427
443
 
428
- # @!visibility private
429
- def generic_value(column, model)
430
- cache(column, model, :generic_value) do
431
- presenter = decorate(model)
432
- unless column.enabled?(self)
433
- raise Datagrid::ColumnUnavailableError, "Column #{column.name} disabled for #{inspect}"
434
- end
444
+ # @!visibility private
445
+ def reset
446
+ super
447
+ @cache = {}
448
+ end
435
449
 
436
- if column.data_block.arity >= 1
437
- Datagrid::Utils.apply_args(presenter, self, data_row(model), &column.data_block)
438
- else
439
- presenter.instance_eval(&column.data_block)
440
- end
441
- end
450
+ protected
442
451
 
452
+ def append_column_preload(relation)
453
+ columns.inject(relation) do |current, column|
454
+ column.append_preload(current)
443
455
  end
456
+ end
444
457
 
445
- protected
446
-
447
- def append_column_preload(relation)
448
- columns.inject(relation) do |current, column|
449
- column.append_preload(current)
450
- end
458
+ def cache(column, asset, type)
459
+ @cache ||= {}
460
+ unless cached?
461
+ @cache.clear
462
+ return yield
451
463
  end
452
-
453
- def cache(column, asset, type)
454
- @cache ||= {}
455
- unless cached?
456
- @cache.clear
457
- return yield
458
- end
459
- key = cache_key(asset)
460
- unless key
461
- raise(Datagrid::CacheKeyError, "Datagrid Cache key is #{key.inspect} for #{asset.inspect}.")
462
- end
463
- @cache[column.name] ||= {}
464
- @cache[column.name][key] ||= {}
465
- @cache[column.name][key][type] ||= yield
464
+ key = cache_key(asset)
465
+ unless key
466
+ raise(Datagrid::CacheKeyError, "Datagrid Cache key is #{key.inspect} for #{asset.inspect}.")
466
467
  end
468
+ @cache[column.name] ||= {}
469
+ @cache[column.name][key] ||= {}
470
+ @cache[column.name][key][type] ||= yield
471
+ end
467
472
 
468
- def cache_key(asset)
469
- if cached.respond_to?(:call)
470
- cached.call(asset)
471
- else
472
- driver.default_cache_key(asset)
473
- end
474
- rescue NotImplementedError
475
- raise Datagrid::ConfigurationError, "#{self} is setup to use cache. But there was appropriate cache key found for #{asset.inspect}. Please set cached option to block with asset as argument and cache key as returning value to resolve the issue."
473
+ def cache_key(asset)
474
+ if cached.respond_to?(:call)
475
+ cached.call(asset)
476
+ else
477
+ driver.default_cache_key(asset)
476
478
  end
479
+ rescue NotImplementedError
480
+ raise Datagrid::ConfigurationError, "#{self} is setup to use cache. But there was appropriate cache key found for #{asset.inspect}. Please set cached option to block with asset as argument and cache key as returning value to resolve the issue."
481
+ end
477
482
 
478
483
 
479
- def map_with_batches(&block)
480
- result = []
481
- each_with_batches do |asset|
482
- result << block.call(asset)
483
- end
484
- result
484
+ def map_with_batches(&block)
485
+ result = []
486
+ each_with_batches do |asset|
487
+ result << block.call(asset)
485
488
  end
489
+ result
490
+ end
486
491
 
487
- def each_with_batches(&block)
488
- if batch_size && batch_size > 0
489
- driver.batch_each(assets, batch_size, &block)
490
- else
491
- assets.each(&block)
492
- end
492
+ def each_with_batches(&block)
493
+ if batch_size && batch_size > 0
494
+ driver.batch_each(assets, batch_size, &block)
495
+ else
496
+ assets.each(&block)
493
497
  end
498
+ end
494
499
 
495
- def value_from_html_block(context, asset, column)
496
- args = []
497
- remaining_arity = column.html_block.arity
498
- remaining_arity = 1 if remaining_arity < 0
500
+ def value_from_html_block(context, asset, column)
501
+ args = []
502
+ remaining_arity = column.html_block.arity
503
+ remaining_arity = 1 if remaining_arity < 0
499
504
 
500
- asset = decorate(asset)
505
+ asset = decorate(asset)
501
506
 
502
- if column.data?
503
- args << data_value(column, asset)
504
- remaining_arity -= 1
505
- end
507
+ if column.data?
508
+ args << data_value(column, asset)
509
+ remaining_arity -= 1
510
+ end
506
511
 
507
- args << asset if remaining_arity > 0
508
- args << self if remaining_arity > 1
512
+ args << asset if remaining_arity > 0
513
+ args << self if remaining_arity > 1
509
514
 
510
- context.instance_exec(*args, &column.html_block)
511
- end
515
+ context.instance_exec(*args, &column.html_block)
512
516
  end
513
517
 
514
518
  # Object representing a single row of data when building a datagrid table
515
- # @see Datagrid::Columns::InstanceMethods#data_row
519
+ # @see Datagrid::Columns#data_row
516
520
  class DataRow < BasicObject
517
521
  def initialize(grid, model)
518
522
  @grid = grid