datagrid 1.6.3 → 1.8.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -1
- data/Readme.markdown +11 -11
- data/app/views/datagrid/_enum_checkboxes.html.erb +2 -2
- data/app/views/datagrid/_form.html.erb +2 -3
- data/app/views/datagrid/_order_for.html.erb +2 -2
- data/app/views/datagrid/_range_filter.html.erb +3 -3
- data/datagrid.gemspec +10 -14
- data/lib/datagrid/active_model.rb +25 -29
- data/lib/datagrid/column_names_attribute.rb +11 -9
- data/lib/datagrid/columns/column.rb +10 -8
- data/lib/datagrid/columns.rb +142 -176
- data/lib/datagrid/configuration.rb +6 -1
- data/lib/datagrid/core.rb +63 -30
- data/lib/datagrid/drivers/abstract_driver.rb +2 -1
- data/lib/datagrid/drivers/active_record.rb +4 -3
- data/lib/datagrid/drivers/array.rb +2 -1
- data/lib/datagrid/drivers/mongo_mapper.rb +2 -1
- data/lib/datagrid/drivers/mongoid.rb +3 -2
- data/lib/datagrid/drivers/sequel.rb +8 -3
- data/lib/datagrid/drivers.rb +2 -1
- data/lib/datagrid/engine.rb +3 -2
- data/lib/datagrid/filters/base_filter.rb +8 -4
- data/lib/datagrid/filters/boolean_filter.rb +2 -1
- data/lib/datagrid/filters/composite_filters.rb +8 -12
- data/lib/datagrid/filters/dynamic_filter.rb +1 -1
- data/lib/datagrid/filters/extended_boolean_filter.rb +2 -1
- data/lib/datagrid/filters/select_options.rb +34 -1
- data/lib/datagrid/filters.rb +52 -30
- data/lib/datagrid/form_builder.rb +53 -35
- data/lib/datagrid/helper.rb +43 -61
- data/lib/datagrid/locale/en.yml +5 -1
- data/lib/datagrid/ordering.rb +15 -15
- data/lib/datagrid/renderer.rb +71 -15
- data/lib/datagrid/rspec.rb +4 -4
- data/lib/datagrid/scaffold.rb +1 -1
- data/lib/datagrid/utils.rb +1 -0
- data/lib/datagrid/version.rb +1 -1
- data/lib/datagrid.rb +2 -1
- data/templates/grid.rb.erb +1 -1
- data/templates/index.html.erb +1 -1
- metadata +8 -9
- data/lib/datagrid/filters/boolean_enum_filter.rb +0 -17
data/lib/datagrid/core.rb
CHANGED
@@ -4,27 +4,29 @@ require "active_support/core_ext/class/attribute"
|
|
4
4
|
module Datagrid
|
5
5
|
module Core
|
6
6
|
|
7
|
+
# @!visibility private
|
7
8
|
def self.included(base)
|
8
|
-
base.extend
|
9
|
+
base.extend ClassMethods
|
9
10
|
base.class_eval do
|
10
11
|
class_attribute :scope_value
|
11
12
|
|
12
13
|
class_attribute :datagrid_attributes, instance_writer: false
|
13
14
|
self.datagrid_attributes = []
|
14
15
|
|
15
|
-
class_attribute :dynamic_block, :
|
16
|
+
class_attribute :dynamic_block, instance_writer: false
|
16
17
|
class_attribute :forbidden_attributes_protection, instance_writer: false
|
17
18
|
self.forbidden_attributes_protection = false
|
18
19
|
if defined?(::ActiveModel::AttributeAssignment)
|
19
20
|
include ::ActiveModel::AttributeAssignment
|
20
21
|
end
|
21
22
|
end
|
22
|
-
base.
|
23
|
-
end
|
23
|
+
base.include InstanceMethods
|
24
|
+
end
|
24
25
|
|
25
26
|
module ClassMethods
|
26
27
|
|
27
|
-
|
28
|
+
# @!visibility private
|
29
|
+
def datagrid_attribute(name, &block)
|
28
30
|
unless datagrid_attributes.include?(name)
|
29
31
|
block ||= lambda do |value|
|
30
32
|
value
|
@@ -41,6 +43,11 @@ module Datagrid
|
|
41
43
|
end
|
42
44
|
|
43
45
|
# Defines a scope at class level
|
46
|
+
# @return [void]
|
47
|
+
# @example
|
48
|
+
# scope { User }
|
49
|
+
# scope { Project.where(deleted: false) }
|
50
|
+
# scope { Project.preload(:stages) }
|
44
51
|
def scope(&block)
|
45
52
|
if block
|
46
53
|
current_scope = scope_value
|
@@ -49,17 +56,28 @@ module Datagrid
|
|
49
56
|
}
|
50
57
|
self
|
51
58
|
else
|
52
|
-
|
53
|
-
|
59
|
+
scope = original_scope
|
60
|
+
driver.to_scope(scope)
|
54
61
|
end
|
55
62
|
end
|
56
63
|
|
57
|
-
|
58
|
-
|
64
|
+
# @!visibility private
|
65
|
+
def original_scope
|
66
|
+
check_scope_defined!
|
67
|
+
scope_value.call
|
68
|
+
end
|
69
|
+
|
70
|
+
# @!visibility private
|
71
|
+
def driver
|
72
|
+
@driver ||= Drivers::AbstractDriver.guess_driver(scope_value.call).new
|
59
73
|
end
|
60
74
|
|
61
75
|
# Allows dynamic columns definition, that could not be defined at class level
|
62
|
-
#
|
76
|
+
# Columns that depend on the database state or third party service
|
77
|
+
# can be defined this way.
|
78
|
+
# @param block [Proc] block that defines dynamic columns
|
79
|
+
# @return [void]
|
80
|
+
# @example
|
63
81
|
# class MerchantsGrid
|
64
82
|
#
|
65
83
|
# scope { Merchant }
|
@@ -69,12 +87,15 @@ module Datagrid
|
|
69
87
|
# dynamic do
|
70
88
|
# PurchaseCategory.all.each do |category|
|
71
89
|
# column(:"#{category.name.underscore}_sales") do |merchant|
|
72
|
-
# merchant.purchases.where(:
|
90
|
+
# merchant.purchases.where(category_id: category.id).count
|
73
91
|
# end
|
74
92
|
# end
|
75
93
|
# end
|
76
94
|
# end
|
77
95
|
#
|
96
|
+
# ProductCategory.create!(name: 'Swimwear')
|
97
|
+
# ProductCategory.create!(name: 'Sportswear')
|
98
|
+
#
|
78
99
|
# grid = MerchantsGrid.new
|
79
100
|
# grid.data # => [
|
80
101
|
# # [ "Name", "Swimwear Sales", "Sportswear Sales", ... ]
|
@@ -95,6 +116,7 @@ module Datagrid
|
|
95
116
|
end
|
96
117
|
|
97
118
|
protected
|
119
|
+
|
98
120
|
def check_scope_defined!(message = nil)
|
99
121
|
message ||= "#{self}.scope is not defined"
|
100
122
|
raise(Datagrid::ConfigurationError, message) unless scope_value
|
@@ -105,7 +127,7 @@ module Datagrid
|
|
105
127
|
child_class.datagrid_attributes = self.datagrid_attributes.clone
|
106
128
|
end
|
107
129
|
|
108
|
-
end
|
130
|
+
end
|
109
131
|
|
110
132
|
module InstanceMethods
|
111
133
|
|
@@ -122,8 +144,7 @@ module Datagrid
|
|
122
144
|
end
|
123
145
|
end
|
124
146
|
|
125
|
-
#
|
126
|
-
# and ordering values
|
147
|
+
# @return [Hash<Symbol, Object>] grid attributes including filter values and ordering values
|
127
148
|
def attributes
|
128
149
|
result = {}
|
129
150
|
self.datagrid_attributes.each do |name|
|
@@ -132,21 +153,24 @@ module Datagrid
|
|
132
153
|
result
|
133
154
|
end
|
134
155
|
|
135
|
-
#
|
156
|
+
# @return [Object] Any datagrid attribute value
|
136
157
|
def [](attribute)
|
137
158
|
self.send(attribute)
|
138
159
|
end
|
139
160
|
|
161
|
+
# Assigns any datagrid attribute
|
162
|
+
# @param attribute [Symbol, String] Datagrid attribute name
|
163
|
+
# @param value [Object] Datagrid attribute value
|
164
|
+
# @return [void]
|
140
165
|
def []=(attribute, value)
|
141
166
|
self.send(:"#{attribute}=", value)
|
142
167
|
end
|
143
168
|
|
144
|
-
#
|
169
|
+
# @return [Object] a scope relation (e.g ActiveRecord::Relation) with all applied filters
|
145
170
|
def assets
|
146
|
-
|
171
|
+
scope
|
147
172
|
end
|
148
173
|
|
149
|
-
|
150
174
|
# Updates datagrid attributes with a passed hash argument
|
151
175
|
def attributes=(attributes)
|
152
176
|
if respond_to?(:assign_attributes)
|
@@ -163,7 +187,7 @@ module Datagrid
|
|
163
187
|
end
|
164
188
|
|
165
189
|
# Returns serializable query arguments skipping all nil values
|
166
|
-
#
|
190
|
+
# @example
|
167
191
|
# grid = ProductsGrid.new(category: 'dresses', available: true)
|
168
192
|
# grid.as_query # => {category: 'dresses', available: true}
|
169
193
|
def as_query
|
@@ -174,8 +198,8 @@ module Datagrid
|
|
174
198
|
attributes
|
175
199
|
end
|
176
200
|
|
177
|
-
#
|
178
|
-
#
|
201
|
+
# @return [Hash<Symbol, Hash<Symbol, Object>>] query parameters to link this grid from a page
|
202
|
+
# @example
|
179
203
|
# grid = ProductsGrid.new(category: 'dresses', available: true)
|
180
204
|
# Rails.application.routes.url_helpers.products_path(grid.query_params)
|
181
205
|
# # => "/products?products_grid[category]=dresses&products_grid[available]=true"
|
@@ -184,19 +208,18 @@ module Datagrid
|
|
184
208
|
end
|
185
209
|
|
186
210
|
# Redefines scope at instance level
|
187
|
-
#
|
211
|
+
# @example
|
188
212
|
# class MyGrid
|
189
213
|
# scope { Article.order('created_at desc') }
|
190
214
|
# end
|
191
215
|
#
|
192
216
|
# grid = MyGrid.new
|
193
217
|
# grid.scope do |scope|
|
194
|
-
# scope.where(:
|
218
|
+
# scope.where(author_id: current_user.id)
|
195
219
|
# end
|
196
220
|
# grid.assets
|
197
221
|
# # => SELECT * FROM articles WHERE author_id = ?
|
198
222
|
# # ORDER BY created_at desc
|
199
|
-
#
|
200
223
|
def scope(&block)
|
201
224
|
if block_given?
|
202
225
|
current_scope = scope
|
@@ -205,29 +228,39 @@ module Datagrid
|
|
205
228
|
}
|
206
229
|
self
|
207
230
|
else
|
208
|
-
|
209
|
-
|
231
|
+
scope = original_scope
|
232
|
+
driver.to_scope(scope)
|
210
233
|
end
|
211
234
|
end
|
212
235
|
|
236
|
+
# @!visibility private
|
237
|
+
def original_scope
|
238
|
+
check_scope_defined!
|
239
|
+
scope_value.call
|
240
|
+
end
|
241
|
+
|
213
242
|
# Resets current instance scope to default scope defined in a class
|
243
|
+
# @return [void]
|
214
244
|
def reset_scope
|
215
245
|
self.scope_value = self.class.scope_value
|
216
246
|
end
|
217
247
|
|
218
|
-
#
|
248
|
+
# @return [Boolean] true if the scope was redefined for this instance of grid object
|
219
249
|
def redefined_scope?
|
220
250
|
self.class.scope_value != scope_value
|
221
251
|
end
|
222
252
|
|
223
|
-
|
253
|
+
# @!visibility private
|
254
|
+
def driver
|
224
255
|
self.class.driver
|
225
256
|
end
|
226
257
|
|
227
|
-
|
258
|
+
# @!visibility private
|
259
|
+
def check_scope_defined!(message = nil)
|
228
260
|
self.class.send :check_scope_defined!, message
|
229
261
|
end
|
230
262
|
|
263
|
+
# @return [String] a datagrid attributes and their values in inspection form
|
231
264
|
def inspect
|
232
265
|
attrs = attributes.map do |key, value|
|
233
266
|
"#{key}: #{value.inspect}"
|
@@ -240,6 +273,6 @@ module Datagrid
|
|
240
273
|
attributes == other.attributes &&
|
241
274
|
scope == other.scope
|
242
275
|
end
|
243
|
-
end
|
276
|
+
end
|
244
277
|
end
|
245
278
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Datagrid
|
2
2
|
module Drivers
|
3
|
-
|
3
|
+
# @!visibility private
|
4
|
+
class ActiveRecord < AbstractDriver
|
4
5
|
|
5
6
|
def self.match?(scope)
|
6
7
|
return false unless defined?(::ActiveRecord)
|
@@ -18,7 +19,7 @@ module Datagrid
|
|
18
19
|
# We can only reveal it by checking if it respond to some specific
|
19
20
|
# to ActiveRecord method like #scoped
|
20
21
|
if scope.is_a?(Class)
|
21
|
-
|
22
|
+
scope.all
|
22
23
|
elsif scope.respond_to?(:scoped)
|
23
24
|
scope.scoped
|
24
25
|
else
|
@@ -42,7 +43,7 @@ module Datagrid
|
|
42
43
|
end
|
43
44
|
|
44
45
|
def asc(scope, order)
|
45
|
-
#
|
46
|
+
# Relation#order isn't able to override already applied order
|
46
47
|
# Using #reorder instead
|
47
48
|
scope.reorder(order)
|
48
49
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Datagrid
|
2
2
|
module Drivers
|
3
|
-
|
3
|
+
# @!visibility private
|
4
|
+
class Mongoid < AbstractDriver
|
4
5
|
|
5
6
|
def self.match?(scope)
|
6
7
|
return false unless defined?(::Mongoid)
|
@@ -63,7 +64,7 @@ module Datagrid
|
|
63
64
|
return nil unless type
|
64
65
|
{
|
65
66
|
[BigDecimal , String, Symbol, Range, Array, Hash, ] => :string,
|
66
|
-
[Boolean] => :boolean,
|
67
|
+
[::Mongoid::Boolean] => :boolean,
|
67
68
|
|
68
69
|
[Date] => :date,
|
69
70
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Datagrid
|
2
2
|
module Drivers
|
3
|
-
|
3
|
+
# @!visibility private
|
4
|
+
class Sequel < AbstractDriver
|
4
5
|
|
5
6
|
def self.match?(scope)
|
6
7
|
return false unless defined?(::Sequel)
|
@@ -81,8 +82,12 @@ module Datagrid
|
|
81
82
|
end
|
82
83
|
|
83
84
|
def batch_each(scope, batch_size, &block)
|
84
|
-
scope.
|
85
|
-
|
85
|
+
if scope.opts[:limit]
|
86
|
+
scope.each(&block)
|
87
|
+
else
|
88
|
+
scope.extension(:pagination).each_page(batch_size) do |page|
|
89
|
+
page.each(&block)
|
90
|
+
end
|
86
91
|
end
|
87
92
|
end
|
88
93
|
|
data/lib/datagrid/drivers.rb
CHANGED
data/lib/datagrid/engine.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
require "rails/engine"
|
2
2
|
|
3
3
|
module Datagrid
|
4
|
+
# @!private
|
4
5
|
class Engine < ::Rails::Engine
|
5
6
|
initializer "datagrid.helpers" do
|
6
7
|
#TODO: check why it doesn't work
|
7
8
|
ActiveSupport.on_load :action_view do
|
8
9
|
include Datagrid::Helper
|
9
|
-
end
|
10
|
-
end
|
10
|
+
end
|
11
|
+
end
|
11
12
|
end
|
12
13
|
end
|
@@ -1,8 +1,12 @@
|
|
1
|
+
# An error raise when datagrid filter is defined incorrectly and
|
2
|
+
# causes filtering chain to be broken
|
1
3
|
class Datagrid::FilteringError < StandardError
|
2
4
|
end
|
3
5
|
|
4
|
-
|
6
|
+
# @!visibility private
|
7
|
+
class Datagrid::Filters::BaseFilter
|
5
8
|
|
9
|
+
class_attribute :input_helper_name, instance_writer: false
|
6
10
|
attr_accessor :grid_class, :options, :block, :name
|
7
11
|
|
8
12
|
def initialize(grid_class, name, options = {}, &block)
|
@@ -26,7 +30,7 @@ class Datagrid::Filters::BaseFilter #:nodoc:
|
|
26
30
|
result = execute(value, scope, grid_object)
|
27
31
|
|
28
32
|
return scope unless result
|
29
|
-
if result
|
33
|
+
if result == Datagrid::Filters::DEFAULT_FILTER_BLOCK
|
30
34
|
result = default_filter(value, scope, grid_object)
|
31
35
|
end
|
32
36
|
unless grid_object.driver.match?(result)
|
@@ -169,8 +173,8 @@ class Datagrid::Filters::BaseFilter #:nodoc:
|
|
169
173
|
|
170
174
|
def default_filter(value, scope, grid)
|
171
175
|
return nil if dummy?
|
172
|
-
if !driver.has_column?(scope, name) &&
|
173
|
-
|
176
|
+
if !driver.has_column?(scope, name) && scope.respond_to?(name)
|
177
|
+
scope.send(name, value)
|
174
178
|
else
|
175
179
|
default_filter_where(scope, value)
|
176
180
|
end
|
@@ -1,18 +1,19 @@
|
|
1
1
|
module Datagrid
|
2
2
|
module Filters
|
3
|
-
|
3
|
+
# @!visibility private
|
4
|
+
module CompositeFilters
|
4
5
|
|
5
6
|
def self.included(base)
|
6
7
|
base.extend ClassMethods
|
7
8
|
base.class_eval do
|
8
|
-
|
9
9
|
end
|
10
|
-
|
11
|
-
end # self.included
|
10
|
+
end
|
12
11
|
|
12
|
+
# @!visibility private
|
13
13
|
module ClassMethods
|
14
14
|
|
15
15
|
def date_range_filters(field, from_options = {}, to_options = {})
|
16
|
+
Utils.warn_once('date_range_filters is deprecated in favor of range option for date filter')
|
16
17
|
from_options = normalize_composite_filter_options(from_options, field)
|
17
18
|
to_options = normalize_composite_filter_options(to_options, field)
|
18
19
|
|
@@ -25,6 +26,7 @@ module Datagrid
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def integer_range_filters(field, from_options = {}, to_options = {})
|
29
|
+
Utils.warn_once('integer_range_filters is deprecated in favor of range option for integer filter')
|
28
30
|
from_options = normalize_composite_filter_options(from_options, field)
|
29
31
|
to_options = normalize_composite_filter_options(to_options, field)
|
30
32
|
filter(from_options[:name] || :"from_#{field.to_s.tr('.', '_')}", :integer, **from_options) do |value, scope, grid|
|
@@ -37,17 +39,11 @@ module Datagrid
|
|
37
39
|
|
38
40
|
def normalize_composite_filter_options(options, field)
|
39
41
|
if options.is_a?(String) || options.is_a?(Symbol)
|
40
|
-
options = {:
|
42
|
+
options = {name: options}
|
41
43
|
end
|
42
44
|
options
|
43
45
|
end
|
44
|
-
end
|
45
|
-
|
46
|
-
module InstanceMethods
|
47
|
-
|
48
|
-
|
49
|
-
end # InstanceMethods
|
50
|
-
|
46
|
+
end
|
51
47
|
end
|
52
48
|
end
|
53
49
|
end
|
@@ -80,7 +80,7 @@ class Datagrid::Filters::DynamicFilter < Datagrid::Filters::BaseFilter
|
|
80
80
|
|
81
81
|
def operations_select
|
82
82
|
operations.map do |operation|
|
83
|
-
[I18n.t(operation, :
|
83
|
+
[I18n.t(operation, scope: "datagrid.filters.dynamic.operations").html_safe, operation]
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
@@ -1,5 +1,4 @@
|
|
1
1
|
module Datagrid::Filters::SelectOptions
|
2
|
-
|
3
2
|
def select(object)
|
4
3
|
select = self.options[:select]
|
5
4
|
if select.is_a?(Symbol)
|
@@ -11,6 +10,18 @@ module Datagrid::Filters::SelectOptions
|
|
11
10
|
end
|
12
11
|
end
|
13
12
|
|
13
|
+
def select_values(object)
|
14
|
+
options = select(object)
|
15
|
+
groups_used = grouped_choices?(options)
|
16
|
+
options.map do |option|
|
17
|
+
if groups_used
|
18
|
+
option[1].map {|o| option_text_and_value(o)}
|
19
|
+
else
|
20
|
+
option_text_and_value(option)
|
21
|
+
end
|
22
|
+
end.map(&:last)
|
23
|
+
end
|
24
|
+
|
14
25
|
def include_blank
|
15
26
|
unless prompt
|
16
27
|
options.has_key?(:include_blank) ?
|
@@ -21,4 +32,26 @@ module Datagrid::Filters::SelectOptions
|
|
21
32
|
def prompt
|
22
33
|
options.has_key?(:prompt) ? Datagrid::Utils.callable(options[:prompt]) : false
|
23
34
|
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
# Rails built-in method:
|
39
|
+
# https://github.com/rails/rails/blob/94e80269e36caf7b2d22a7ab68e6898d3a824122/actionview/lib/action_view/helpers/form_options_helper.rb#L789
|
40
|
+
# Code reuse is difficult, so it is easier to duplicate it
|
41
|
+
def option_text_and_value(option)
|
42
|
+
# Options are [text, value] pairs or strings used for both.
|
43
|
+
if !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
|
44
|
+
option = option.reject { |e| Hash === e } if Array === option
|
45
|
+
[option.first, option.last]
|
46
|
+
else
|
47
|
+
[option, option]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Rails built-in method:
|
52
|
+
# https://github.com/rails/rails/blob/f95c0b7e96eb36bc3efc0c5beffbb9e84ea664e4/actionview/lib/action_view/helpers/tags/select.rb#L36
|
53
|
+
# Code reuse is difficult, so it is easier to duplicate it
|
54
|
+
def grouped_choices?(choices)
|
55
|
+
!choices.blank? && choices.first.respond_to?(:last) && Array === choices.first.last
|
56
|
+
end
|
24
57
|
end
|