mensa 0.9.5 → 0.9.6

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.
@@ -16,7 +16,7 @@ module Mensa
16
16
 
17
17
  # Types that ILIKE works on, nil is an attribute without a type
18
18
  TEXT_TYPES = [:string, :text, :citext, nil].freeze
19
- # Types with a meaningful order, for gt, gteq, lt and lteq
19
+ # Types with a meaningful order, for gt, gteq, lt, lteq and between
20
20
  RANGE_TYPES = %i[integer bigint float decimal money date datetime timestamp timestamptz time interval].freeze
21
21
  # Types where an exact match on a value is of little use
22
22
  POINT_IN_TIME_TYPES = %i[date datetime timestamp timestamptz].freeze
@@ -34,12 +34,24 @@ module Mensa
34
34
  [:gteq, I18n.t("mensa.operators.gteq"), true],
35
35
  [:lt, I18n.t("mensa.operators.lt"), true],
36
36
  [:lteq, I18n.t("mensa.operators.lteq"), true],
37
+ [:between, I18n.t("mensa.operators.between"), true],
37
38
  [:is_duplicate, I18n.t("mensa.operators.is_duplicate"), false],
38
39
  [:is_current, I18n.t("mensa.operators.is_current"), false],
39
40
  [:is_empty, I18n.t("mensa.operators.is_empty"), false],
40
41
  [:isnt_empty, I18n.t("mensa.operators.isnt_empty"), false]
41
42
  ].freeze
42
43
  end
44
+
45
+ # A value in a form to compare filters with, e.g. a view's with the
46
+ # current one. The ends of a range keep their place, other values are a set.
47
+ def comparable_value(operator, value)
48
+ if operator&.to_sym == :between
49
+ from, to = Array(value)
50
+ return [from.to_s, to.to_s]
51
+ end
52
+
53
+ Array(value).map(&:to_s).reject(&:blank?).sort
54
+ end
43
55
  end
44
56
 
45
57
  def initialize(column:, config:, table:)
@@ -80,7 +92,13 @@ module Mensa
80
92
  def formatted_value
81
93
  options = collection_options
82
94
 
83
- if value.is_a?(Array)
95
+ if operator == :between
96
+ from, to = range_values.map { |entry| label_for_value(entry, options) }
97
+ return from if to.blank?
98
+ return to if from.blank?
99
+
100
+ I18n.t("mensa.filters.between_value", from: from, to: to)
101
+ elsif value.is_a?(Array)
84
102
  value.map { |entry| label_for_value(entry, options) }.join(", ")
85
103
  else
86
104
  label_for_value(value, options)
@@ -112,11 +130,11 @@ module Mensa
112
130
  else
113
131
  operators.delete_if { |op| op[0] == :is_current } unless Current.method_defined?(column.name, false)
114
132
  type = column.type
115
- operators.delete_if { |op| [:matches, :does_not_match].include?(op[0]) } if collection.present?
133
+ operators.delete_if { |op| [:matches, :does_not_match, :between].include?(op[0]) } if collection.present?
116
134
  # Only these operators work for the column's type in SQL, the others
117
135
  # raise, e.g. LIKE on a boolean
118
136
  operators.delete_if { |op| [:matches, :does_not_match].include?(op[0]) } unless TEXT_TYPES.include?(type)
119
- operators.delete_if { |op| [:gt, :lt, :gteq, :lteq].include?(op[0]) } unless RANGE_TYPES.include?(type)
137
+ operators.delete_if { |op| [:gt, :lt, :gteq, :lteq, :between].include?(op[0]) } unless RANGE_TYPES.include?(type)
120
138
  operators.delete_if { |op| [:is, :isnt].include?(op[0]) } if POINT_IN_TIME_TYPES.include?(type) || STRUCTURED_TYPES.include?(type)
121
139
  # json has no equality, so duplicates can't be grouped
122
140
  operators.delete_if { |op| op[0] == :is_duplicate } if type == :json
@@ -127,6 +145,8 @@ module Mensa
127
145
  # Used in the where clause
128
146
  def query_and_hash_for_operator
129
147
  col = "(#{column.attribute_for_condition})"
148
+ return range_query_and_hash(col) if operator == :between
149
+
130
150
  hash = {value: normalize(value)}
131
151
  hash[:value] = hash[:value].compact_blank if hash[:value].is_a?(Array)
132
152
 
@@ -163,8 +183,19 @@ module Mensa
163
183
  [query, hash]
164
184
  end
165
185
 
186
+ # A range with one end open reads as the comparison it is
166
187
  def operator_label
167
- Mensa::Filter.OPERATORS.find { |op| op[0] == operator }[1]
188
+ label_operator = operator
189
+ if operator == :between
190
+ from, to = range_values
191
+ label_operator = :gteq if to.blank? && from.present?
192
+ label_operator = :lteq if from.blank? && to.present?
193
+ end
194
+ Mensa::Filter.OPERATORS.find { |op| op[0] == label_operator }[1]
195
+ end
196
+
197
+ def comparable_value
198
+ self.class.comparable_value(operator, value)
168
199
  end
169
200
 
170
201
  def operator_with_value?
@@ -188,6 +219,21 @@ module Mensa
188
219
 
189
220
  private
190
221
 
222
+ # The from and to of a between filter, either can be blank for an open end
223
+ def range_values
224
+ from, to = Array(value)
225
+ [from.presence, to.presence]
226
+ end
227
+
228
+ # Both ends are inclusive, a blank end leaves that side open
229
+ def range_query_and_hash(col)
230
+ from, to = range_values.map { |entry| normalize(entry) }
231
+ conditions = []
232
+ conditions << "#{col} >= :from" if from.present?
233
+ conditions << "#{col} <= :to" if to.present?
234
+ [conditions.join(" AND "), {from: from, to: to}]
235
+ end
236
+
191
237
  def validate_operators!
192
238
  valid_keys = Mensa::Filter.OPERATORS.map(&:first)
193
239
 
@@ -28,14 +28,21 @@
28
28
  <% end %>
29
29
  </ul>
30
30
  <% else %>
31
- <input class="mensa-table__add_filter__popover_container__input"
32
- type="<%= @column.filter.input_type %>"
33
- value="<%= @values.first %>"
34
- autocomplete="off"
35
- inputmode="<%= @column.type == :integer ? 'numeric' : nil %>"
36
- step="<%= @column.type == :integer ? 1 : (@column.type == :datetime ? 1 : nil) %>"
37
- data-mensa-add-filter-target="value"
38
- data-action="input->mensa-add-filter#manualValueChanged keydown.enter->mensa-add-filter#applyManualValue">
31
+ <% range = @column.filter.operators.any? { |operator_name, *| operator_name == :between } %>
32
+ <% between = range && @operator == "between" %>
33
+ <div class="mensa-table__add_filter__popover_container__range" data-range="<%= 'true' if between %>">
34
+ <% [[:value, @values.first, t("mensa.filters.from")], ([:valueTo, (@values.second if between), t("mensa.filters.to")] if range)].compact.each do |target, value, placeholder| %>
35
+ <input class="mensa-table__add_filter__popover_container__input"
36
+ type="<%= @column.filter.input_type %>"
37
+ value="<%= value %>"
38
+ autocomplete="off"
39
+ inputmode="<%= @column.type == :integer ? 'numeric' : nil %>"
40
+ step="<%= @column.type == :integer ? 1 : (@column.type == :datetime ? 1 : nil) %>"
41
+ <% if range %>placeholder="<%= placeholder if target == :valueTo || between %>" data-range-placeholder="<%= placeholder %>"<% end %>
42
+ data-mensa-add-filter-target="<%= target %>"
43
+ data-action="input->mensa-add-filter#manualValueChanged keydown.enter->mensa-add-filter#applyManualValue">
44
+ <% end %>
45
+ </div>
39
46
  <% end %>
40
47
  <hr class="mensa-table__add_filter__popover_container__separator">
41
48
  <% end %>