datagrid 1.4.4 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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