datagrid 1.8.2 → 2.0.0

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