datagrid 1.8.2 → 2.0.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/CHANGELOG.md +11 -1
  4. data/{Readme.markdown → README.md} +44 -29
  5. data/app/assets/stylesheets/datagrid.css +145 -0
  6. data/app/views/datagrid/_enum_checkboxes.html.erb +5 -3
  7. data/app/views/datagrid/_form.html.erb +4 -4
  8. data/app/views/datagrid/_head.html.erb +26 -3
  9. data/app/views/datagrid/_range_filter.html.erb +5 -3
  10. data/app/views/datagrid/_row.html.erb +12 -1
  11. data/app/views/datagrid/_table.html.erb +4 -4
  12. data/datagrid.gemspec +8 -7
  13. data/lib/datagrid/active_model.rb +9 -17
  14. data/lib/datagrid/base.rb +39 -0
  15. data/lib/datagrid/column_names_attribute.rb +9 -11
  16. data/lib/datagrid/columns/column.rb +155 -133
  17. data/lib/datagrid/columns.rb +325 -115
  18. data/lib/datagrid/configuration.rb +33 -4
  19. data/lib/datagrid/core.rb +89 -54
  20. data/lib/datagrid/deprecated_object.rb +20 -0
  21. data/lib/datagrid/drivers/abstract_driver.rb +12 -23
  22. data/lib/datagrid/drivers/active_record.rb +24 -26
  23. data/lib/datagrid/drivers/array.rb +22 -14
  24. data/lib/datagrid/drivers/mongo_mapper.rb +15 -14
  25. data/lib/datagrid/drivers/mongoid.rb +15 -17
  26. data/lib/datagrid/drivers/sequel.rb +14 -19
  27. data/lib/datagrid/drivers.rb +2 -1
  28. data/lib/datagrid/engine.rb +11 -3
  29. data/lib/datagrid/filters/base_filter.rb +166 -143
  30. data/lib/datagrid/filters/boolean_filter.rb +19 -5
  31. data/lib/datagrid/filters/date_filter.rb +33 -35
  32. data/lib/datagrid/filters/date_time_filter.rb +24 -16
  33. data/lib/datagrid/filters/default_filter.rb +9 -3
  34. data/lib/datagrid/filters/dynamic_filter.rb +151 -105
  35. data/lib/datagrid/filters/enum_filter.rb +43 -19
  36. data/lib/datagrid/filters/extended_boolean_filter.rb +39 -31
  37. data/lib/datagrid/filters/float_filter.rb +15 -5
  38. data/lib/datagrid/filters/integer_filter.rb +21 -10
  39. data/lib/datagrid/filters/ranged_filter.rb +66 -45
  40. data/lib/datagrid/filters/select_options.rb +58 -49
  41. data/lib/datagrid/filters/string_filter.rb +9 -4
  42. data/lib/datagrid/filters.rb +204 -79
  43. data/lib/datagrid/form_builder.rb +116 -128
  44. data/lib/datagrid/generators/scaffold.rb +184 -0
  45. data/lib/datagrid/generators/views.rb +20 -0
  46. data/lib/datagrid/helper.rb +436 -69
  47. data/lib/datagrid/ordering.rb +26 -29
  48. data/lib/datagrid/rspec.rb +6 -10
  49. data/lib/datagrid/utils.rb +37 -30
  50. data/lib/datagrid/version.rb +3 -1
  51. data/lib/datagrid.rb +8 -28
  52. data/templates/base.rb.erb +6 -4
  53. data/templates/grid.rb.erb +1 -1
  54. metadata +17 -17
  55. data/app/assets/stylesheets/datagrid.sass +0 -134
  56. data/lib/datagrid/filters/composite_filters.rb +0 -49
  57. data/lib/datagrid/renderer.rb +0 -157
  58. data/lib/datagrid/scaffold.rb +0 -129
  59. data/lib/tasks/datagrid_tasks.rake +0 -15
  60. data/templates/controller.rb.erb +0 -6
  61. data/templates/index.html.erb +0 -5
@@ -1,40 +1,38 @@
1
- require "datagrid/filters/ranged_filter"
2
-
3
- class Datagrid::Filters::DateFilter < Datagrid::Filters::BaseFilter
4
-
5
- include Datagrid::Filters::RangedFilter
6
-
7
- def apply(grid_object, scope, value)
8
- if value.is_a?(Range)
9
- value = value.begin&.beginning_of_day..value.end&.end_of_day
10
- end
11
- super(grid_object, scope, value)
12
- end
13
-
14
- def parse(value)
15
- Datagrid::Utils.parse_date(value)
16
- end
1
+ # frozen_string_literal: true
17
2
 
3
+ require "datagrid/filters/ranged_filter"
18
4
 
19
- def format(value)
20
- if formats.any? && value
21
- value.strftime(formats.first)
22
- else
23
- super
24
- end
25
- end
26
-
27
- def default_filter_where(scope, value)
28
- if driver.is_timestamp?(scope, name)
29
- value = Datagrid::Utils.format_date_as_timestamp(value)
5
+ module Datagrid
6
+ module Filters
7
+ class DateFilter < Datagrid::Filters::BaseFilter
8
+ include Datagrid::Filters::RangedFilter
9
+
10
+ def default_input_options
11
+ { **super, type: "date" }
12
+ end
13
+
14
+ def apply(grid_object, scope, value)
15
+ value = Datagrid::Utils.format_date_as_timestamp(value) if grid_object.driver.timestamp_column?(scope, name)
16
+ super
17
+ end
18
+
19
+ def parse(value)
20
+ Datagrid::Utils.parse_date(value)
21
+ end
22
+
23
+ def format(value)
24
+ if formats.any? && value
25
+ value.strftime(formats.first)
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ protected
32
+
33
+ def formats
34
+ Array(Datagrid.configuration.date_formats)
35
+ end
30
36
  end
31
- super(scope, value)
32
- end
33
-
34
- protected
35
-
36
- def formats
37
- Array(Datagrid.configuration.date_formats)
38
37
  end
39
38
  end
40
-
@@ -1,25 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "datagrid/filters/ranged_filter"
2
4
 
3
- class Datagrid::Filters::DateTimeFilter < Datagrid::Filters::BaseFilter
5
+ module Datagrid
6
+ module Filters
7
+ class DateTimeFilter < Datagrid::Filters::BaseFilter
8
+ include Datagrid::Filters::RangedFilter
4
9
 
5
- include Datagrid::Filters::RangedFilter
10
+ def default_input_options
11
+ { **super, type: "datetime-local" }
12
+ end
6
13
 
7
- def parse(value)
8
- Datagrid::Utils.parse_datetime(value)
9
- end
14
+ def parse(value)
15
+ Datagrid::Utils.parse_datetime(value)
16
+ end
10
17
 
11
- def format(value)
12
- if formats.any? && value
13
- value.strftime(formats.first)
14
- else
15
- super
16
- end
17
- end
18
+ def format(value)
19
+ if formats.any? && value
20
+ value.strftime(formats.first)
21
+ else
22
+ super
23
+ end
24
+ end
18
25
 
19
- protected
26
+ protected
20
27
 
21
- def formats
22
- Array(Datagrid.configuration.datetime_formats)
28
+ def formats
29
+ Array(Datagrid.configuration.datetime_formats)
30
+ end
31
+ end
23
32
  end
24
33
  end
25
-
@@ -1,5 +1,11 @@
1
- class Datagrid::Filters::DefaultFilter < Datagrid::Filters::BaseFilter
2
- def parse(value)
3
- value
1
+ # frozen_string_literal: true
2
+
3
+ module Datagrid
4
+ module Filters
5
+ class DefaultFilter < Datagrid::Filters::BaseFilter
6
+ def parse(value)
7
+ value
8
+ end
9
+ end
4
10
  end
5
11
  end
@@ -1,125 +1,171 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "datagrid/filters/select_options"
2
4
 
3
- class Datagrid::Filters::DynamicFilter < Datagrid::Filters::BaseFilter
4
-
5
- include Datagrid::Filters::SelectOptions
6
-
7
- EQUAL_OPERATION = '='
8
- LIKE_OPERATION = '=~'
9
- MORE_EQUAL_OPERATION = '>='
10
- LESS_EQUAL_OPERATION = '<='
11
- DEFAULT_OPERATIONS = [
12
- EQUAL_OPERATION,
13
- LIKE_OPERATION,
14
- MORE_EQUAL_OPERATION,
15
- LESS_EQUAL_OPERATION,
16
- ]
17
- AVAILABLE_OPERATIONS = %w(= =~ >= <=)
18
-
19
- def initialize(*)
20
- super
21
- options[:select] ||= default_select
22
- options[:operations] ||= DEFAULT_OPERATIONS
23
- unless options.has_key?(:include_blank)
24
- options[:include_blank] = false
25
- end
26
- end
5
+ module Datagrid
6
+ module Filters
7
+ class DynamicFilter < Datagrid::Filters::BaseFilter
8
+ include Datagrid::Filters::SelectOptions
27
9
 
28
- def parse_values(filter)
29
- field, operation, value = filter
10
+ EQUAL_OPERATION = "="
11
+ LIKE_OPERATION = "=~"
12
+ MORE_EQUAL_OPERATION = ">="
13
+ LESS_EQUAL_OPERATION = "<="
14
+ DEFAULT_OPERATIONS = [
15
+ EQUAL_OPERATION,
16
+ LIKE_OPERATION,
17
+ MORE_EQUAL_OPERATION,
18
+ LESS_EQUAL_OPERATION,
19
+ ].freeze
20
+ AVAILABLE_OPERATIONS = %w[= =~ >= <=].freeze
30
21
 
31
- [field, operation, type_cast(field, value)]
32
- end
22
+ def initialize(grid, name, **options, &block)
23
+ options[:select] ||= default_select
24
+ options[:operations] ||= DEFAULT_OPERATIONS
25
+ options[:include_blank] = false unless options.key?(:include_blank)
26
+ super
27
+ end
33
28
 
34
- def unapplicable_value?(filter)
35
- _, _, value = filter
36
- super(value)
37
- end
29
+ def default_input_options
30
+ { **super, type: nil }
31
+ end
38
32
 
39
- def default_filter_where(scope, filter)
40
- field, operation, value = filter
41
- date_conversion = value.is_a?(Date) && driver.is_timestamp?(scope, field)
33
+ def parse_values(filter)
34
+ filter ? FilterValue.new(grid_class, filter) : nil
35
+ end
42
36
 
43
- return scope if field.blank? || operation.blank?
44
- unless operations.include?(operation)
45
- raise Datagrid::FilteringError, "Unknown operation: #{operation.inspect}. Available operations: #{operations.join(' ')}"
46
- end
47
- case operation
48
- when EQUAL_OPERATION
49
- if date_conversion
50
- value = Datagrid::Utils.format_date_as_timestamp(value)
37
+ def unapplicable_value?(filter)
38
+ super(filter&.value)
51
39
  end
52
- driver.where(scope, field, value)
53
- when LIKE_OPERATION
54
- if column_type(field) == :string
55
- driver.contains(scope, field, value)
56
- else
57
- if date_conversion
58
- value = Datagrid::Utils.format_date_as_timestamp(value)
40
+
41
+ def default_filter_where(scope, filter)
42
+ field = filter.field
43
+ operation = filter.operation
44
+ value = filter.value
45
+ date_conversion = value.is_a?(Date) && driver.timestamp_column?(scope, field)
46
+
47
+ return scope if field.blank? || operation.blank?
48
+
49
+ unless operations.include?(operation)
50
+ raise Datagrid::FilteringError,
51
+ "Unknown operation: #{operation.inspect}. Available operations: #{operations.join(' ')}"
52
+ end
53
+
54
+ case operation
55
+ when EQUAL_OPERATION
56
+ value = Datagrid::Utils.format_date_as_timestamp(value) if date_conversion
57
+ driver.where(scope, field, value)
58
+ when LIKE_OPERATION
59
+ if column_type(field) == :string
60
+ driver.contains(scope, field, value)
61
+ else
62
+ value = Datagrid::Utils.format_date_as_timestamp(value) if date_conversion
63
+ driver.where(scope, field, value)
64
+ end
65
+ when MORE_EQUAL_OPERATION
66
+ value = value.beginning_of_day if date_conversion
67
+ driver.greater_equal(scope, field, value)
68
+ when LESS_EQUAL_OPERATION
69
+ value = value.end_of_day if date_conversion
70
+ driver.less_equal(scope, field, value)
71
+ else
72
+ raise Datagrid::FilteringError,
73
+ "Unknown operation: #{operation.inspect}. Use filter block argument to implement operation"
59
74
  end
60
- driver.where(scope, field, value)
61
- end
62
- when MORE_EQUAL_OPERATION
63
- if date_conversion
64
- value = value.beginning_of_day
65
75
  end
66
- driver.greater_equal(scope, field, value)
67
- when LESS_EQUAL_OPERATION
68
- if date_conversion
69
- value = value.end_of_day
76
+
77
+ def operations
78
+ options[:operations]
70
79
  end
71
- driver.less_equal(scope, field, value)
72
- else
73
- raise Datagrid::FilteringError, "Unknown operation: #{operation.inspect}. Use filter block argument to implement operation"
74
- end
75
- end
76
80
 
77
- def operations
78
- options[:operations]
79
- end
81
+ def operations_select
82
+ operations.map do |operation|
83
+ [I18n.t(operation, scope: "datagrid.filters.dynamic.operations"), operation]
84
+ end
85
+ end
80
86
 
81
- def operations_select
82
- operations.map do |operation|
83
- [I18n.t(operation, scope: "datagrid.filters.dynamic.operations").html_safe, operation]
84
- end
85
- end
87
+ protected
86
88
 
87
- protected
89
+ def default_select
90
+ proc { |grid|
91
+ grid.driver.column_names(grid.scope).map do |name|
92
+ # Mongodb/Rails problem:
93
+ # '_id'.humanize returns ''
94
+ [name.gsub(%r{^_}, "").humanize.strip, name]
95
+ end
96
+ }
97
+ end
88
98
 
89
- def default_select
90
- proc {|grid|
91
- grid.driver.column_names(grid.scope).map do |name|
92
- # Mongodb/Rails problem:
93
- # '_id'.humanize returns ''
94
- [name.gsub(/^_/, '').humanize.strip, name]
99
+ def column_type(field)
100
+ grid_class.driver.normalized_column_type(grid_class.scope, field)
95
101
  end
96
- }
97
- end
98
102
 
99
- def type_cast(field, value)
100
- type = column_type(field)
101
- return nil if value.blank?
102
- case type
103
- when :string
104
- value.to_s
105
- when :integer
106
- value.is_a?(Numeric) || value =~ /^\d/ ? value.to_i : nil
107
- when :float
108
- value.is_a?(Numeric) || value =~ /^\d/ ? value.to_f : nil
109
- when :date
110
- Datagrid::Utils.parse_date(value)
111
- when :timestamp
112
- Datagrid::Utils.parse_date(value)
113
- when :boolean
114
- Datagrid::Utils.booleanize(value)
115
- when nil
116
- value
117
- else
118
- raise NotImplementedError, "unknown column type: #{type.inspect}"
119
- end
120
- end
103
+ class FilterValue
104
+ attr_accessor :field, :operation, :value
105
+
106
+ def initialize(grid_class, object = nil)
107
+ super()
108
+
109
+ case object
110
+ when Hash
111
+ object = object.symbolize_keys
112
+ self.field = object[:field]
113
+ self.operation = object[:operation]
114
+ self.value = object[:value]
115
+ when Array
116
+ self.field = object[0]
117
+ self.operation = object[1]
118
+ self.value = object[2]
119
+ else
120
+ raise ArgumentError, object.inspect
121
+ end
122
+ return unless grid_class
123
+
124
+ type = grid_class.driver.normalized_column_type(
125
+ grid_class.scope, field,
126
+ )
127
+ self.value = type_cast(type, value)
128
+ end
129
+
130
+ def inspect
131
+ { field: field, operation: operation, value: value }
132
+ end
121
133
 
122
- def column_type(field)
123
- grid_class.driver.normalized_column_type(grid_class.scope, field)
134
+ def to_ary
135
+ to_a
136
+ end
137
+
138
+ def to_a
139
+ [field, operation, value]
140
+ end
141
+
142
+ def to_h
143
+ { field: field, operation: operation, value: value }
144
+ end
145
+
146
+ protected
147
+
148
+ def type_cast(type, value)
149
+ return nil if value.blank?
150
+
151
+ case type
152
+ when :string
153
+ value.to_s
154
+ when :integer
155
+ value.is_a?(Numeric) || value =~ %r{^\d} ? value.to_i : nil
156
+ when :float
157
+ value.is_a?(Numeric) || value =~ %r{^\d} ? value.to_f : nil
158
+ when :date, :timestamp
159
+ Datagrid::Utils.parse_date(value)
160
+ when :boolean
161
+ Datagrid::Utils.booleanize(value)
162
+ when nil
163
+ value
164
+ else
165
+ raise NotImplementedError, "unknown column type: #{type.inspect}"
166
+ end
167
+ end
168
+ end
169
+ end
124
170
  end
125
171
  end
@@ -1,28 +1,52 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "datagrid/filters/select_options"
2
4
 
3
- class Datagrid::Filters::EnumFilter < Datagrid::Filters::BaseFilter
5
+ module Datagrid
6
+ module Filters
7
+ class EnumFilter < Datagrid::Filters::BaseFilter
8
+ include Datagrid::Filters::SelectOptions
4
9
 
5
- include Datagrid::Filters::SelectOptions
10
+ # @!visibility private
11
+ def initialize(grid, name, **options, &block)
12
+ options[:multiple] = true if options[:checkboxes]
13
+ super
14
+ raise Datagrid::ConfigurationError, ":select option not specified" unless options[:select]
15
+ end
6
16
 
7
- def initialize(*args)
8
- super(*args)
9
- if checkboxes?
10
- options[:multiple] = true
11
- end
12
- raise Datagrid::ConfigurationError, ":select option not specified" unless options[:select]
13
- end
17
+ def parse(value)
18
+ return nil if strict && !select.include?(value)
14
19
 
15
- def parse(value)
16
- return nil if self.strict && !select.include?(value)
17
- value
18
- end
20
+ value
21
+ end
19
22
 
20
- def strict
21
- options[:strict]
22
- end
23
+ def default_input_options
24
+ {
25
+ **super,
26
+ type: enum_checkboxes? ? "checkbox" : "select",
27
+ multiple: multiple?,
28
+ include_hidden: enum_checkboxes? ? false : nil,
29
+ }
30
+ end
23
31
 
24
- def checkboxes?
25
- options[:checkboxes]
26
- end
32
+ def label_options
33
+ if enum_checkboxes?
34
+ # Each checkbox has its own label
35
+ # The main label has no specific input to focus
36
+ # See app/views/datagrid/_enum_checkboxes.html.erb
37
+ { for: nil, **super }
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ def strict
44
+ options[:strict]
45
+ end
27
46
 
47
+ def enum_checkboxes?
48
+ options[:checkboxes]
49
+ end
50
+ end
51
+ end
28
52
  end
@@ -1,40 +1,48 @@
1
- # @!visibility private
2
- class Datagrid::Filters::ExtendedBooleanFilter < Datagrid::Filters::EnumFilter
1
+ # frozen_string_literal: true
3
2
 
4
- YES = "YES"
5
- NO = "NO"
6
- TRUTH_VALUES = [true, 'true', "y", "yes"]
7
- FALSE_VALUES = [false, 'false', "n", "no"]
3
+ module Datagrid
4
+ module Filters
5
+ class ExtendedBooleanFilter < Datagrid::Filters::EnumFilter
6
+ YES = "YES"
7
+ NO = "NO"
8
+ TRUTH_VALUES = [true, "true", "y", "yes"].freeze
9
+ FALSE_VALUES = [false, "false", "n", "no"].freeze
8
10
 
9
- def initialize(report, attribute, options = {}, &block)
10
- options[:select] = -> { boolean_select }
11
- super(report, attribute, options, &block)
12
- end
11
+ def initialize(*args, **options)
12
+ options[:select] = -> { boolean_select }
13
+ super
14
+ end
13
15
 
14
- def execute(value, scope, grid_object)
15
- value = value.blank? ? nil : ::Datagrid::Utils.booleanize(value)
16
- super(value, scope, grid_object)
17
- end
16
+ def execute(value, scope, grid_object)
17
+ value = value.blank? ? nil : ::Datagrid::Utils.booleanize(value)
18
+ super
19
+ end
18
20
 
19
- def parse(value)
20
- value = value.downcase if value.is_a?(String)
21
- case value
22
- when *TRUTH_VALUES
23
- YES
24
- when *FALSE_VALUES
25
- NO
26
- when value.blank?
27
- nil
28
- else
29
- super(value)
30
- end
31
- end
21
+ def default_input_options
22
+ { **super, type: "select" }
23
+ end
24
+
25
+ def parse(value)
26
+ value = value.downcase if value.is_a?(String)
27
+ case value
28
+ when *TRUTH_VALUES
29
+ YES
30
+ when *FALSE_VALUES
31
+ NO
32
+ when value.blank?
33
+ nil
34
+ else
35
+ super
36
+ end
37
+ end
32
38
 
33
- protected
39
+ protected
34
40
 
35
- def boolean_select
36
- [YES, NO].map do |key, value|
37
- [I18n.t("datagrid.filters.xboolean.#{key.downcase}"), key]
41
+ def boolean_select
42
+ [YES, NO].map do |key, _value|
43
+ [I18n.t("datagrid.filters.xboolean.#{key.downcase}"), key]
44
+ end
45
+ end
38
46
  end
39
47
  end
40
48
  end
@@ -1,9 +1,19 @@
1
- class Datagrid::Filters::FloatFilter < Datagrid::Filters::BaseFilter
1
+ # frozen_string_literal: true
2
2
 
3
- include Datagrid::Filters::RangedFilter
3
+ module Datagrid
4
+ module Filters
5
+ class FloatFilter < Datagrid::Filters::BaseFilter
6
+ include Datagrid::Filters::RangedFilter
4
7
 
5
- def parse(value)
6
- return nil if value.blank?
7
- value.to_f
8
+ def default_input_options
9
+ { **super, type: "number", step: "any" }
10
+ end
11
+
12
+ def parse(value)
13
+ return nil if value.blank?
14
+
15
+ value.to_f
16
+ end
17
+ end
8
18
  end
9
19
  end
@@ -1,17 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "datagrid/filters/ranged_filter"
2
4
 
3
- class Datagrid::Filters::IntegerFilter < Datagrid::Filters::BaseFilter
5
+ module Datagrid
6
+ module Filters
7
+ class IntegerFilter < Datagrid::Filters::BaseFilter
8
+ include Datagrid::Filters::RangedFilter
9
+
10
+ def default_input_options
11
+ { **super, type: "number", step: "1" }
12
+ end
4
13
 
5
- include Datagrid::Filters::RangedFilter
14
+ def parse(value)
15
+ return nil if value.blank?
16
+ if defined?(ActiveRecord) && value.is_a?(ActiveRecord::Base) &&
17
+ value.respond_to?(:id) && value.id.is_a?(Integer)
18
+ return value.id
19
+ end
20
+ return value if value.is_a?(Range)
6
21
 
7
- def parse(value)
8
- return nil if value.blank?
9
- if defined?(ActiveRecord) && value.is_a?(ActiveRecord::Base) &&
10
- value.respond_to?(:id) && value.id.is_a?(Integer)
11
- return value.id
22
+ return nil if value.to_i.zero? && value.is_a?(String) && value !~ %r{\A\s*-?0}
23
+
24
+ value.to_i
25
+ end
12
26
  end
13
- return value if value.is_a?(Range)
14
- value.to_i
15
27
  end
16
28
  end
17
-