datagrid 1.4.4 → 1.5.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.
@@ -90,6 +90,15 @@ module Datagrid
90
90
  end
91
91
  end
92
92
 
93
+ def default_preload(scope, value)
94
+ scope.eager(value)
95
+ end
96
+
97
+ def can_preload?(scope, association)
98
+ !! scope.model.association_reflection(association)
99
+ end
100
+
101
+
93
102
  protected
94
103
 
95
104
  def prefix_table_name(scope, field)
@@ -31,14 +31,17 @@ module Datagrid
31
31
  :dynamic => Filters::DynamicFilter
32
32
  }
33
33
 
34
+ class DefaultFilterScope
35
+ end
36
+
34
37
  def self.included(base) #:nodoc:
35
38
  base.extend ClassMethods
36
39
  base.class_eval do
37
40
 
38
41
  include Datagrid::Core
39
42
  include Datagrid::Filters::CompositeFilters
40
- class_attribute :filters
41
- self.filters = []
43
+ class_attribute :filters_array
44
+ self.filters_array = []
42
45
 
43
46
  end
44
47
  base.send :include, InstanceMethods
@@ -80,6 +83,10 @@ module Datagrid
80
83
  # by adding it after the filter passed here (when using datagrid_form_for helper)
81
84
  # * <tt>:dummy</tt> - if true, this filter will not be applied automatically
82
85
  # and will be just displayed in form. In case you may want to apply it manually.
86
+ # * <tt>:if</tt> - specify the condition when the filter can be dislayed and used.
87
+ # Accepts a block or a symbol with an instance method name
88
+ # * <tt>:unless</tt> - specify the reverse condition when the filter can be dislayed and used.
89
+ # Accepts a block or a symbol with an instance method name
83
90
  #
84
91
  # See: https://github.com/bogdan/datagrid/wiki/Filters for examples
85
92
  def filter(name, type = :default, options = {}, &block)
@@ -91,9 +98,9 @@ module Datagrid
91
98
  klass = type.is_a?(Class) ? type : FILTER_TYPES[type]
92
99
  raise ConfigurationError, "filter class #{type.inspect} not found" unless klass
93
100
 
94
- position = Datagrid::Utils.extract_position_from_options(self.filters, options)
101
+ position = Datagrid::Utils.extract_position_from_options(filters_array, options)
95
102
  filter = klass.new(self, name, options, &block)
96
- self.filters.insert(position, filter)
103
+ filters_array.insert(position, filter)
97
104
 
98
105
  datagrid_attribute(name) do |value|
99
106
  filter.parse_values(value)
@@ -101,20 +108,38 @@ module Datagrid
101
108
 
102
109
  end
103
110
 
111
+ def default_filter
112
+ DefaultFilterScope.new
113
+ end
114
+
115
+ def inspect
116
+ "#{super}(#{filters_inspection})"
117
+ end
118
+
119
+ def filters
120
+ filters_array
121
+ end
122
+
104
123
  protected
105
124
 
106
125
  def inherited(child_class)
107
126
  super(child_class)
108
- child_class.filters = self.filters.clone
127
+ child_class.filters_array = self.filters_array.clone
109
128
  end
110
129
 
130
+ def filters_inspection
131
+ return "no filters" if filters.empty?
132
+ filters.map do |filter|
133
+ "#{filter.name}: #{filter.type}"
134
+ end.join(", ")
135
+ end
111
136
  end # ClassMethods
112
137
 
113
138
  module InstanceMethods
114
139
 
115
140
  def initialize(*args, &block) # :nodoc:
116
- self.filters = self.class.filters.clone
117
- self.filters.each do |filter|
141
+ self.filters_array = self.class.filters_array.clone
142
+ filters.each do |filter|
118
143
  self[filter.name] = filter.default(self)
119
144
  end
120
145
  super(*args, &block)
@@ -133,7 +158,7 @@ module Datagrid
133
158
  def filter_value_as_string(name)
134
159
  filter = filter_by_name(name)
135
160
  value = filter_value(filter)
136
- if value.is_a?(Array)
161
+ if value.is_a?(Array)
137
162
  value.map {|v| filter.format(v) }.join(filter.separator)
138
163
  else
139
164
  filter.format(value)
@@ -161,6 +186,17 @@ module Datagrid
161
186
  filter.select(self)
162
187
  end
163
188
 
189
+ def default_filter
190
+ self.class.default_filter
191
+ end
192
+
193
+ # Returns all currently enabled filters
194
+ def filters
195
+ self.class.filters.select do |filter|
196
+ filter.enabled?(self)
197
+ end
198
+ end
199
+
164
200
  protected
165
201
 
166
202
  def apply_filters(current_scope, filters)
@@ -24,10 +24,15 @@ class Datagrid::Filters::BaseFilter #:nodoc:
24
24
  return scope if unapplicable_value?(value)
25
25
 
26
26
  result = execute(value, scope, grid_object)
27
+
27
28
  return scope unless result
29
+ if result.is_a?(Datagrid::Filters::DefaultFilterScope)
30
+ result = default_filter(value, scope, grid_object)
31
+ end
28
32
  unless grid_object.driver.match?(result)
29
33
  raise Datagrid::FilteringError, "Can not apply #{name.inspect} filter: result #{result.inspect} no longer match #{grid_object.driver.class}."
30
34
  end
35
+
31
36
  result
32
37
  end
33
38
 
@@ -49,7 +54,7 @@ class Datagrid::Filters::BaseFilter #:nodoc:
49
54
  end
50
55
 
51
56
  def header
52
- if header = options[:header]
57
+ if header = options[:header]
53
58
  callable(header)
54
59
  else
55
60
  Datagrid::Utils.translate_from_namespace(:filters, grid_class, name)
@@ -68,7 +73,7 @@ class Datagrid::Filters::BaseFilter #:nodoc:
68
73
  Datagrid::Utils.warn_once(":default as a Symbol is now treated as a method name. Use String instead or -> { default } if you really want default value to be a Symbol but not a String.")
69
74
  default
70
75
  end
71
- elsif default.respond_to?(:call)
76
+ elsif default.respond_to?(:call)
72
77
  Datagrid::Utils.apply_args(object, &default)
73
78
  else
74
79
  default
@@ -128,6 +133,10 @@ class Datagrid::Filters::BaseFilter #:nodoc:
128
133
  raise "wtf is #{inspect}"
129
134
  end
130
135
 
136
+ def enabled?(grid)
137
+ ::Datagrid::Utils.process_availability(grid, options[:if], options[:unless])
138
+ end
139
+
131
140
  protected
132
141
 
133
142
  def default_filter_where(scope, value)
@@ -1,5 +1,5 @@
1
1
  module Datagrid::Filters::RangedFilter
2
-
2
+
3
3
 
4
4
  def initialize(grid, name, options, &block)
5
5
  super(grid, name, options, &block)
@@ -23,6 +23,8 @@ module Datagrid::Filters::RangedFilter
23
23
  if result.first && result.last && result.first > result.last
24
24
  # If wrong range is given - reverse it to be always valid
25
25
  result.reverse
26
+ elsif !result.first && !result.last
27
+ nil
26
28
  else
27
29
  result
28
30
  end
@@ -46,7 +48,7 @@ module Datagrid::Filters::RangedFilter
46
48
  scope = driver.less_equal(scope, name, right)
47
49
  end
48
50
  scope
49
- else
51
+ else
50
52
  super(scope, value)
51
53
  end
52
54
  end
@@ -55,7 +55,8 @@ module Datagrid
55
55
  end
56
56
 
57
57
 
58
- # Renders HTML table rows using given grid definition using columns defined in it
58
+ # Renders HTML table rows using given grid definition using columns defined in it.
59
+ # Allows to provide a custom layout for each for in place with a block
59
60
  #
60
61
  # Supported options:
61
62
  #
@@ -64,8 +65,15 @@ module Datagrid
64
65
  # and needs different columns. Default: all defined columns.
65
66
  # * <tt>:partials</tt> - Path for partials lookup.
66
67
  # Default: 'datagrid'.
67
- def datagrid_rows(grid, assets, options = {})
68
- datagrid_renderer.rows(grid, assets, options)
68
+ #
69
+ # = datagrid_rows(grid) # Generic table rows Layout
70
+ #
71
+ # = datagrid_rows(grid) do |row| # Custom Layout
72
+ # %tr
73
+ # %td= row.project_name
74
+ # %td.project-status{class: row.status}= row.status
75
+ def datagrid_rows(grid, assets = grid.assets, **options, &block)
76
+ datagrid_renderer.rows(grid, assets, options, &block)
69
77
  end
70
78
 
71
79
  # Renders ordering controls for the given column name
@@ -116,6 +124,8 @@ module Datagrid
116
124
  # row.asset # => User object
117
125
  class HtmlRow
118
126
 
127
+ include Enumerable
128
+
119
129
  attr_reader :grid, :asset
120
130
 
121
131
  def initialize(context, grid, asset) # :nodoc:
@@ -124,9 +134,22 @@ module Datagrid
124
134
  @asset = asset
125
135
  end
126
136
 
137
+ # Return a column value for given column name
138
+ def get(column)
139
+ @context.datagrid_value(@grid, column, @asset)
140
+ end
141
+
142
+ # Iterates over all column values that are available in the row
143
+ def each
144
+ @grid.columns.each do |column|
145
+ yield(get(column))
146
+ end
147
+ end
148
+
149
+ protected
127
150
  def method_missing(method, *args, &blk)
128
151
  if column = @grid.column_by_name(method)
129
- @context.datagrid_value(@grid, column, @asset)
152
+ get(column)
130
153
  else
131
154
  super
132
155
  end
@@ -44,7 +44,7 @@ module Datagrid
44
44
  end
45
45
 
46
46
  # Returns a column definition that is currently used to order assets
47
- #
47
+ #
48
48
  # class MyGrid
49
49
  # scope { Model }
50
50
  # column(:id)
@@ -87,11 +87,11 @@ module Datagrid
87
87
  def check_order_valid!
88
88
  return unless order
89
89
  column = column_by_name(order)
90
- unless column
90
+ unless column
91
91
  self.class.order_unsupported(order, "no column #{order} in #{self.class}")
92
92
  end
93
93
  unless column.supports_order?
94
- self.class.order_unsupported(column.name, "column don't support order" )
94
+ self.class.order_unsupported(column.name, "column don't support order" )
95
95
  end
96
96
  end
97
97
 
@@ -99,7 +99,7 @@ module Datagrid
99
99
  if order.respond_to?(:call)
100
100
  apply_block_order(assets, order)
101
101
  else
102
- driver.asc(assets, order)
102
+ driver.asc(assets, order)
103
103
  end
104
104
  end
105
105
 
@@ -118,7 +118,7 @@ module Datagrid
118
118
  end
119
119
 
120
120
  def apply_block_order(assets, order)
121
- case order.arity
121
+ case order.arity
122
122
  when -1, 0
123
123
  assets.instance_eval(&order)
124
124
  when 1
@@ -55,15 +55,19 @@ module Datagrid
55
55
  { :grid => grid, :options => options })
56
56
  end
57
57
 
58
- def rows(grid, assets, options = {})
58
+ def rows(grid, assets = grid.assets, **options, &block)
59
59
  result = assets.map do |asset|
60
- _render_partial(
61
- 'row', options[:partials],
62
- {
60
+ if block_given?
61
+ @template.capture do
62
+ yield(Datagrid::Helper::HtmlRow.new(@template, grid, asset))
63
+ end
64
+ else
65
+ _render_partial( 'row', options[:partials], {
63
66
  :grid => grid,
64
67
  :options => options,
65
68
  :asset => asset
66
69
  })
70
+ end
67
71
  end.to_a.join
68
72
 
69
73
  _safe(result)
@@ -127,6 +127,24 @@ module Datagrid
127
127
  end
128
128
  end
129
129
 
130
+ def process_availability(grid, if_option, unless_option)
131
+ property_availability(grid, if_option, true) &&
132
+ !property_availability(grid, unless_option, false)
133
+ end
134
+
135
+ protected
136
+ def property_availability(grid, option, default)
137
+ case option
138
+ when nil
139
+ default
140
+ when Proc
141
+ option.call(grid)
142
+ when Symbol, String
143
+ grid.send(option.to_sym)
144
+ else
145
+ raise Datagrid::ConfigurationError, "Incorrect column availability option: #{option.insepct}"
146
+ end
147
+ end
130
148
  end
131
149
  end
132
150
  end
@@ -562,4 +562,49 @@ describe Datagrid::Columns do
562
562
  expect(grid.rows).to eq([['Hello']])
563
563
  end
564
564
  end
565
+
566
+ describe "column scope" do
567
+ it "appends preload as non block" do
568
+ grid = test_report do
569
+ scope { Entry }
570
+ column(:id, preload: [:group])
571
+ end
572
+ expect(grid.assets.preload_values).to_not be_blank
573
+ end
574
+
575
+ it "appends preload with no args" do
576
+ grid = test_report do
577
+ scope { Entry }
578
+ column(:id, preload: -> { order(:id) })
579
+ end
580
+ expect(grid.assets.order_values).to_not be_blank
581
+ end
582
+
583
+ it "appends preload with arg" do
584
+ grid = test_report do
585
+ scope { Entry }
586
+ column(:id, preload: ->(a) { a.order(:id) })
587
+ end
588
+ expect(grid.assets.order_values).to_not be_blank
589
+ end
590
+
591
+ it "appends preload as true value" do
592
+ grid = test_report do
593
+ scope { Entry }
594
+ column(:group, preload: true)
595
+ end
596
+ expect(grid.assets.preload_values).to eq([:group])
597
+ end
598
+
599
+ it "doesn't append preload when column is invisible" do
600
+ grid = test_report do
601
+ scope { Entry }
602
+ column(:id1, preload: ->(a) { a.order(:id) })
603
+ column(:id2, preload: ->(a) { a.order(:id) }, if: ->(a) { false })
604
+ column(:name)
605
+ end
606
+ grid.column_names = [:name]
607
+ expect(grid.assets.order_values).to be_blank
608
+ end
609
+ end
565
610
  end
@@ -6,9 +6,11 @@ describe Datagrid::Core do
6
6
  before { 2.times { Entry.create } }
7
7
 
8
8
  let(:report_class) do
9
- test_report_class do
9
+ class ScopeTestReport
10
+ include Datagrid
10
11
  scope { Entry.order("id desc") }
11
12
  end
13
+ ScopeTestReport
12
14
  end
13
15
 
14
16
  describe '#scope' do
@@ -16,6 +18,22 @@ describe Datagrid::Core do
16
18
  let(:report) { report_class.new }
17
19
 
18
20
  it { expect(report.scope.to_a.size).to eq(2) }
21
+ it { expect(report).to_not be_redefined_scope }
22
+
23
+ context "when redefined" do
24
+ it "should accept previous scope" do
25
+ module Ns83827
26
+ class TestGrid < ScopeTestReport
27
+ scope do |previous|
28
+ previous.reorder("id asc")
29
+ end
30
+ end
31
+ end
32
+
33
+ expect(Ns83827::TestGrid.new.assets.order_values).to eq(["id asc"])
34
+ end
35
+ end
36
+
19
37
  end
20
38
 
21
39
  context 'changes scope on the fly' do
@@ -26,11 +44,13 @@ describe Datagrid::Core do
26
44
  end
27
45
 
28
46
  it { expect(report.scope.to_a.size).to eq(1) }
47
+ it { expect(report).to be_redefined_scope }
29
48
  end
30
49
 
31
50
  context 'overriding scope by initializer' do
32
51
  let(:report) { report_class.new { Entry.limit(1) } }
33
52
 
53
+ it { expect(report).to be_redefined_scope }
34
54
  it { expect(report.scope.to_a.size).to eq(1) }
35
55
 
36
56
  context "reset scope to default" do
@@ -38,6 +58,7 @@ describe Datagrid::Core do
38
58
  report.reset_scope
39
59
  end
40
60
  it { expect(report.scope.to_a.size).to eq(2) }
61
+ it { expect(report).to_not be_redefined_scope }
41
62
  end
42
63
  end
43
64
 
@@ -45,6 +66,7 @@ describe Datagrid::Core do
45
66
  let(:report) { report_class.new {|scope| scope.limit(1)} }
46
67
  it { expect(report.scope.to_a.size).to eq(1) }
47
68
  it { expect(report.scope.order_values.size).to eq(1) }
69
+ it { expect(report).to be_redefined_scope }
48
70
  end
49
71
  end
50
72
  end