datagrid 1.4.4 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -1
- data/Gemfile +2 -2
- data/Rakefile +1 -0
- data/Readme.markdown +6 -0
- data/VERSION +1 -1
- data/datagrid.gemspec +92 -52
- data/lib/datagrid/columns.rb +18 -3
- data/lib/datagrid/columns/column.rb +27 -13
- data/lib/datagrid/core.rb +11 -2
- data/lib/datagrid/drivers/abstract_driver.rb +8 -0
- data/lib/datagrid/drivers/active_record.rb +8 -0
- data/lib/datagrid/drivers/array.rb +5 -1
- data/lib/datagrid/drivers/mongo_mapper.rb +8 -0
- data/lib/datagrid/drivers/mongoid.rb +9 -0
- data/lib/datagrid/drivers/sequel.rb +9 -0
- data/lib/datagrid/filters.rb +44 -8
- data/lib/datagrid/filters/base_filter.rb +11 -2
- data/lib/datagrid/filters/ranged_filter.rb +4 -2
- data/lib/datagrid/helper.rb +27 -4
- data/lib/datagrid/ordering.rb +5 -5
- data/lib/datagrid/renderer.rb +8 -4
- data/lib/datagrid/utils.rb +18 -0
- data/spec/datagrid/columns_spec.rb +45 -0
- data/spec/datagrid/core_spec.rb +23 -1
- data/spec/datagrid/filters/date_filter_spec.rb +9 -0
- data/spec/datagrid/filters/dynamic_filter_spec.rb +27 -1
- data/spec/datagrid/filters_spec.rb +49 -19
- data/spec/datagrid/helper_spec.rb +19 -0
- metadata +187 -5
@@ -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)
|
data/lib/datagrid/filters.rb
CHANGED
@@ -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 :
|
41
|
-
self.
|
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(
|
101
|
+
position = Datagrid::Utils.extract_position_from_options(filters_array, options)
|
95
102
|
filter = klass.new(self, name, options, &block)
|
96
|
-
|
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.
|
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.
|
117
|
-
|
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
|
data/lib/datagrid/helper.rb
CHANGED
@@ -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
|
-
|
68
|
-
|
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
|
-
|
152
|
+
get(column)
|
130
153
|
else
|
131
154
|
super
|
132
155
|
end
|
data/lib/datagrid/ordering.rb
CHANGED
@@ -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
|
data/lib/datagrid/renderer.rb
CHANGED
@@ -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
|
-
|
61
|
-
|
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)
|
data/lib/datagrid/utils.rb
CHANGED
@@ -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
|
data/spec/datagrid/core_spec.rb
CHANGED
@@ -6,9 +6,11 @@ describe Datagrid::Core do
|
|
6
6
|
before { 2.times { Entry.create } }
|
7
7
|
|
8
8
|
let(:report_class) do
|
9
|
-
|
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
|