refine-rails 2.9.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 +7 -0
- data/README.md +413 -0
- data/Rakefile +8 -0
- data/app/assets/config/refine_rails_manifest.js +0 -0
- data/app/assets/javascripts/refine-stimulus.esm.js +2 -0
- data/app/assets/javascripts/refine-stimulus.esm.js.map +1 -0
- data/app/assets/javascripts/refine-stimulus.js +2 -0
- data/app/assets/javascripts/refine-stimulus.js.map +1 -0
- data/app/assets/javascripts/refine-stimulus.modern.js +2 -0
- data/app/assets/javascripts/refine-stimulus.modern.js.map +1 -0
- data/app/assets/javascripts/refine-stimulus.umd.js +2 -0
- data/app/assets/javascripts/refine-stimulus.umd.js.map +1 -0
- data/app/assets/stylesheets/index.css +1873 -0
- data/app/assets/stylesheets/index.tailwind.css +1035 -0
- data/app/controllers/refine/blueprints_controller.rb +80 -0
- data/app/controllers/refine/filter_application_controller.rb +29 -0
- data/app/controllers/refine/inline/criteria_controller.rb +161 -0
- data/app/controllers/refine/inline/stored_filters_controller.rb +84 -0
- data/app/controllers/refine/stored_filters_controller.rb +69 -0
- data/app/javascript/controllers/index.js +66 -0
- data/app/javascript/controllers/refine/add-controller.js +42 -0
- data/app/javascript/controllers/refine/criterion-form-controller.js +31 -0
- data/app/javascript/controllers/refine/date-controller.js +113 -0
- data/app/javascript/controllers/refine/defaults-controller.js +32 -0
- data/app/javascript/controllers/refine/delete-controller.js +13 -0
- data/app/javascript/controllers/refine/filter-pills-controller.js +63 -0
- data/app/javascript/controllers/refine/form-controller.js +51 -0
- data/app/javascript/controllers/refine/inline-conditions-controller.js +33 -0
- data/app/javascript/controllers/refine/popup-controller.js +46 -0
- data/app/javascript/controllers/refine/search-filter-controller.js +50 -0
- data/app/javascript/controllers/refine/server-refresh-controller.js +43 -0
- data/app/javascript/controllers/refine/state-controller.js +220 -0
- data/app/javascript/controllers/refine/stored-filter-controller.js +23 -0
- data/app/javascript/controllers/refine/submit-form-controller.js +8 -0
- data/app/javascript/controllers/refine/toggle-controller.js +12 -0
- data/app/javascript/controllers/refine/turbo-stream-form-controller.js +24 -0
- data/app/javascript/controllers/refine/turbo-stream-link-controller.js +24 -0
- data/app/javascript/controllers/refine/update-controller.js +86 -0
- data/app/javascript/index.js +1 -0
- data/app/javascript/refine/helpers/index.js +77 -0
- data/app/models/refine/blueprints/blueprint.rb +58 -0
- data/app/models/refine/blueprints/blueprint_example.json +25 -0
- data/app/models/refine/conditions/boolean_condition.rb +112 -0
- data/app/models/refine/conditions/clause.rb +38 -0
- data/app/models/refine/conditions/clauses.rb +38 -0
- data/app/models/refine/conditions/condition.rb +285 -0
- data/app/models/refine/conditions/condition_error.rb +1 -0
- data/app/models/refine/conditions/date_condition.rb +464 -0
- data/app/models/refine/conditions/date_with_time_condition.rb +8 -0
- data/app/models/refine/conditions/errors/condition_clause_error.rb +7 -0
- data/app/models/refine/conditions/errors/criteria_limit_exceeded_error.rb +2 -0
- data/app/models/refine/conditions/errors/option_error.rb +2 -0
- data/app/models/refine/conditions/errors/relationship_error.rb +1 -0
- data/app/models/refine/conditions/filter_condition.rb +93 -0
- data/app/models/refine/conditions/has_clauses.rb +117 -0
- data/app/models/refine/conditions/has_meta.rb +10 -0
- data/app/models/refine/conditions/has_refinements.rb +156 -0
- data/app/models/refine/conditions/numeric_condition.rb +224 -0
- data/app/models/refine/conditions/option_condition.rb +260 -0
- data/app/models/refine/conditions/text_condition.rb +152 -0
- data/app/models/refine/conditions/uses_attributes.rb +168 -0
- data/app/models/refine/filter.rb +302 -0
- data/app/models/refine/filters/blueprint_editor.rb +102 -0
- data/app/models/refine/filters/builder.rb +59 -0
- data/app/models/refine/filters/criterion.rb +87 -0
- data/app/models/refine/filters/query.rb +82 -0
- data/app/models/refine/inline/criteria/input.rb +50 -0
- data/app/models/refine/inline/criteria/numeric_refinement.rb +13 -0
- data/app/models/refine/inline/criteria/option.rb +2 -0
- data/app/models/refine/inline/criterion.rb +141 -0
- data/app/models/refine/invalid_filter_error.rb +8 -0
- data/app/models/refine/stabilize.rb +29 -0
- data/app/models/refine/stabilizers/database_stabilizer.rb +21 -0
- data/app/models/refine/stabilizers/errors/url_stabilizer_error.rb +2 -0
- data/app/models/refine/stabilizers/url_encoded_stabilizer.rb +21 -0
- data/app/models/refine/stored_filter.rb +14 -0
- data/app/models/refine/tracks_pending_relationship_subqueries.rb +196 -0
- data/app/views/_filter_builder_dropdown.html.erb +63 -0
- data/app/views/_filter_pills.html.erb +40 -0
- data/app/views/_loading.html.erb +32 -0
- data/app/views/refine/blueprints/_add_and.html.erb +25 -0
- data/app/views/refine/blueprints/_add_group.html.erb +24 -0
- data/app/views/refine/blueprints/_clause_select.html.erb +24 -0
- data/app/views/refine/blueprints/_condition_select.html.erb +53 -0
- data/app/views/refine/blueprints/_criterion.html.erb +41 -0
- data/app/views/refine/blueprints/_criterion_errors.html.erb +7 -0
- data/app/views/refine/blueprints/_delete_criterion.html.erb +11 -0
- data/app/views/refine/blueprints/_group.html.erb +13 -0
- data/app/views/refine/blueprints/_query.html.erb +34 -0
- data/app/views/refine/blueprints/_stored_filters.html.erb +23 -0
- data/app/views/refine/blueprints/clauses/_date_condition.html.erb +80 -0
- data/app/views/refine/blueprints/clauses/_date_picker.html.erb +26 -0
- data/app/views/refine/blueprints/clauses/_filter_condition.html.erb +36 -0
- data/app/views/refine/blueprints/clauses/_numeric_condition.html.erb +35 -0
- data/app/views/refine/blueprints/clauses/_option_condition.html.erb +37 -0
- data/app/views/refine/blueprints/clauses/_text_condition.html.erb +13 -0
- data/app/views/refine/blueprints/create.turbo_stream.erb +22 -0
- data/app/views/refine/blueprints/new.html.erb +7 -0
- data/app/views/refine/blueprints/show.html.erb +4 -0
- data/app/views/refine/blueprints/show.turbo_stream.erb +22 -0
- data/app/views/refine/inline/criteria/_form_fields.html.erb +62 -0
- data/app/views/refine/inline/criteria/create.turbo_stream.erb +19 -0
- data/app/views/refine/inline/criteria/edit.turbo_stream.erb +26 -0
- data/app/views/refine/inline/criteria/index.html.erb +64 -0
- data/app/views/refine/inline/criteria/new.turbo_stream.erb +24 -0
- data/app/views/refine/inline/filters/_add_first_condition_button.html.erb +19 -0
- data/app/views/refine/inline/filters/_and_button.html.erb +26 -0
- data/app/views/refine/inline/filters/_criterion.html.erb +23 -0
- data/app/views/refine/inline/filters/_group.html.erb +13 -0
- data/app/views/refine/inline/filters/_load_button.html.erb +15 -0
- data/app/views/refine/inline/filters/_or_button.html.erb +26 -0
- data/app/views/refine/inline/filters/_popup.html.erb +26 -0
- data/app/views/refine/inline/filters/_save_button.html.erb +15 -0
- data/app/views/refine/inline/filters/_show.html.erb +40 -0
- data/app/views/refine/inline/inputs/_date_condition.html.erb +7 -0
- data/app/views/refine/inline/inputs/_date_condition_days.html.erb +18 -0
- data/app/views/refine/inline/inputs/_date_condition_range.html.erb +22 -0
- data/app/views/refine/inline/inputs/_date_condition_single.html.erb +9 -0
- data/app/views/refine/inline/inputs/_date_picker.html.erb +20 -0
- data/app/views/refine/inline/inputs/_numeric_condition.html.erb +23 -0
- data/app/views/refine/inline/inputs/_option_condition.html.erb +14 -0
- data/app/views/refine/inline/inputs/_text_condition.html.erb +8 -0
- data/app/views/refine/inline/stored_filters/find.turbo_stream.erb +19 -0
- data/app/views/refine/inline/stored_filters/index.html.erb +28 -0
- data/app/views/refine/inline/stored_filters/new.turbo_stream.erb +47 -0
- data/app/views/refine/stored_filters/create.turbo_stream.erb +2 -0
- data/app/views/refine/stored_filters/find.turbo_stream.erb +5 -0
- data/app/views/refine/stored_filters/index.html.erb +39 -0
- data/app/views/refine/stored_filters/new.html.erb +29 -0
- data/app/views/refine/stored_filters/show.html.erb +1 -0
- data/config/locales/en/dates.en.yml +29 -0
- data/config/locales/en/en.yml +20 -0
- data/config/locales/en/refine.en.yml +187 -0
- data/config/routes.rb +17 -0
- data/lib/generators/filter/filter_generator.rb +27 -0
- data/lib/generators/filter/templates/filter.rb.erb +20 -0
- data/lib/refine/rails/engine.rb +15 -0
- data/lib/refine/rails/version.rb +5 -0
- data/lib/refine/rails.rb +38 -0
- data/lib/tasks/refine/rails_tasks.rake +13 -0
- metadata +202 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
module Refine::Conditions
|
|
2
|
+
class Condition
|
|
3
|
+
include ActiveModel::Validations
|
|
4
|
+
|
|
5
|
+
validates :clause, presence: true
|
|
6
|
+
validate :clause_in_approved_list?
|
|
7
|
+
|
|
8
|
+
# TODO remove hasclauses here, rename boot_has_clauses
|
|
9
|
+
include HasClauses
|
|
10
|
+
include HasMeta
|
|
11
|
+
include UsesAttributes
|
|
12
|
+
include HasRefinements
|
|
13
|
+
|
|
14
|
+
attr_reader :ensurances, :before_validations, :clause, :filter
|
|
15
|
+
attr_accessor :display, :id, :is_refinement, :attribute
|
|
16
|
+
|
|
17
|
+
I18N_PREFIX = "refine.refine_blueprints.condition."
|
|
18
|
+
|
|
19
|
+
def initialize(id = nil, display = nil)
|
|
20
|
+
# Capture display value if sent it. Not translated, takes precedence
|
|
21
|
+
# If no display value explicitly sent, use locales to translate in translate_display
|
|
22
|
+
@display = display
|
|
23
|
+
@id = id
|
|
24
|
+
# Optimistically set attribute to the id
|
|
25
|
+
@attribute = id
|
|
26
|
+
@rules = {}
|
|
27
|
+
|
|
28
|
+
# Ensurance validations -> every condition must have an id and an attribute evaluated after
|
|
29
|
+
# developer configuration
|
|
30
|
+
@ensurances = []
|
|
31
|
+
add_ensurance(ensure_id)
|
|
32
|
+
add_ensurance(ensure_attribute_configured)
|
|
33
|
+
|
|
34
|
+
@before_validations = []
|
|
35
|
+
|
|
36
|
+
# Interpolate later in life for each class that needs it - not everyone needs it
|
|
37
|
+
boot_has_clauses
|
|
38
|
+
|
|
39
|
+
# Allow each condition to set state post initialization
|
|
40
|
+
boot
|
|
41
|
+
@on_deepest_relationship = false
|
|
42
|
+
@is_refinement = false
|
|
43
|
+
# Refinements variables
|
|
44
|
+
@date_refinement_proc = nil
|
|
45
|
+
@count_refinement_proc = nil
|
|
46
|
+
@filter_refinement_proc = nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def before_validate(callable)
|
|
50
|
+
@before_validations << callable
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def add_ensurance(callable)
|
|
54
|
+
@ensurances << callable
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def with_display(value)
|
|
58
|
+
@display = value
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def ensure_attribute_configured
|
|
63
|
+
proc do
|
|
64
|
+
if @attribute.nil?
|
|
65
|
+
errors.add(:base, I18n.t("#{I18N_PREFIX}attribute_required"))
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def ensure_id
|
|
71
|
+
proc do
|
|
72
|
+
if @id.nil?
|
|
73
|
+
errors.add(:base, I18n.t("#{I18N_PREFIX}condition_must_have_id"))
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Boot the traits first, so any extended conditions
|
|
79
|
+
# can override the traits if they need to.
|
|
80
|
+
def boot_traits
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def boot
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def add_rules(new_rules, new_messages = {})
|
|
87
|
+
# TODO add messages if desired
|
|
88
|
+
@rules.merge!(new_rules)
|
|
89
|
+
add_messages(new_messages)
|
|
90
|
+
self
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def messages
|
|
94
|
+
@messages ||= {}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def add_messages(new_messages)
|
|
98
|
+
messages.merge!(new_messages)
|
|
99
|
+
self
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def run_ensurance_validations
|
|
103
|
+
ensurances.each do |function|
|
|
104
|
+
call_proc_if_callable(function)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def run_before_validate_validations(input)
|
|
109
|
+
before_validations.each do |function|
|
|
110
|
+
if function.respond_to? :call
|
|
111
|
+
function.call(input)
|
|
112
|
+
else
|
|
113
|
+
function
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Applies the criterion which can be a relationship condition
|
|
119
|
+
#
|
|
120
|
+
# @param [Hash] input The user's input
|
|
121
|
+
# @param [Arel::Table] table The arel_table the query is built on
|
|
122
|
+
# @param [ActiveRecord::Relation] initial_query The base query the query is built on
|
|
123
|
+
# @return [Arel::Node]
|
|
124
|
+
def apply(input, table, initial_query, inverse_clause=false)
|
|
125
|
+
table ||= filter.table
|
|
126
|
+
# Ensurance validations are checking the developer configured correctly
|
|
127
|
+
run_ensurance_validations
|
|
128
|
+
# Allow developer to modify user input
|
|
129
|
+
# TODO run_before_validate(input) -> what is this for?
|
|
130
|
+
|
|
131
|
+
run_before_validate_validations(input)
|
|
132
|
+
|
|
133
|
+
# TODO Determine right place to set the clause
|
|
134
|
+
validate_user_input(input)
|
|
135
|
+
if input.dig(:filter_refinement).present?
|
|
136
|
+
|
|
137
|
+
filter_condition = call_proc_if_callable(@filter_refinement_proc)
|
|
138
|
+
# Set the filter on the filter_condition to be the current_condition's filter
|
|
139
|
+
filter_condition.set_filter(filter)
|
|
140
|
+
filter_condition.is_refinement = true
|
|
141
|
+
|
|
142
|
+
# Applying the filter condition will modify pending relationship subqueries in place
|
|
143
|
+
filter_condition.apply(input.dig(:filter_refinement), table, initial_query)
|
|
144
|
+
input.delete(:filter_refinement)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
if is_relationship_attribute?
|
|
148
|
+
apply_relationship_attribute(input: input, query: initial_query)
|
|
149
|
+
return
|
|
150
|
+
end
|
|
151
|
+
# No longer a relationship attribute, apply condition normally
|
|
152
|
+
nodes = apply_condition(input, table, inverse_clause)
|
|
153
|
+
if !is_refinement && has_any_refinements?
|
|
154
|
+
refined_node = apply_refinements(input)
|
|
155
|
+
# Count refinement will return nil because it directly modified pending relationship subquery
|
|
156
|
+
nodes = nodes.and(refined_node) if refined_node
|
|
157
|
+
end
|
|
158
|
+
nodes
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def set_input_parameters(input)
|
|
162
|
+
# Placeholder for conditions that do not need this method
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def approved_clauses
|
|
166
|
+
get_clauses.call
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def clause_in_approved_list?
|
|
170
|
+
# Is the requested clause in the approved list configured by developer?
|
|
171
|
+
unless approved_clauses.map(&:id).include? clause
|
|
172
|
+
errors.add(:base, I18n.t("#{I18N_PREFIX}clause_not_found", clause: clause))
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def validate_user_input(input)
|
|
177
|
+
evaluated_rules = recursively_evaluate_lazy_enumerable(@rules)
|
|
178
|
+
# Set input parameters on the condition in order to use condition level validations
|
|
179
|
+
# TODO set this somewhere more obvious
|
|
180
|
+
@clause = input[:clause]
|
|
181
|
+
set_input_parameters(input)
|
|
182
|
+
evaluated_rules.each_pair do |k, v|
|
|
183
|
+
if input[k].blank?
|
|
184
|
+
errors.add(:base, I18n.t("#{I18N_PREFIX}a_is_required", k: k))
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
# Errors added to the errors array by individual conditions and standard rails validations
|
|
188
|
+
if errors.any? || !valid?
|
|
189
|
+
# When the error is rescued, it will stringify the message.
|
|
190
|
+
raise Errors::ConditionClauseError.new(
|
|
191
|
+
errors.attribute_names.map{|name| errors[name].join}.to_sentence,
|
|
192
|
+
errors: errors
|
|
193
|
+
)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def clause_exists?(input)
|
|
198
|
+
current_clause = clauses.select { |clause| clause.id == input[:clause] }
|
|
199
|
+
current_clause.present?
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def component
|
|
203
|
+
raise NotImplementedError
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def human_readable(input)
|
|
207
|
+
raise NotImplementedError
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def apply_condition(input, table, _inverse_clause)
|
|
211
|
+
raise NotImplementedError
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def set_filter(filter)
|
|
215
|
+
@filter = filter
|
|
216
|
+
self
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def to_array(allow_errors: false)
|
|
220
|
+
# Has clauses has already been called, so meta is populated with possible closures to evaluate
|
|
221
|
+
# Run ensurance validations will populate the errors array on the object
|
|
222
|
+
run_ensurance_validations
|
|
223
|
+
|
|
224
|
+
if errors.any? && !allow_errors
|
|
225
|
+
raise ConditionError, errors.full_messages.join(". ")
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
{
|
|
229
|
+
id: id,
|
|
230
|
+
component: component,
|
|
231
|
+
display: @display,
|
|
232
|
+
meta: evaluated_meta,
|
|
233
|
+
refinements: refinements_to_array
|
|
234
|
+
}
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def evaluated_meta
|
|
238
|
+
recursively_evaluate_lazy_enumerable(@meta)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def call_proc_if_callable(value)
|
|
242
|
+
if value.respond_to? :call
|
|
243
|
+
value.call
|
|
244
|
+
else
|
|
245
|
+
value
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# In HasCallbacks
|
|
250
|
+
def recursively_evaluate_lazy_enumerable(enumerable)
|
|
251
|
+
if enumerable.is_a? Hash
|
|
252
|
+
enumerable.transform_values! do |value|
|
|
253
|
+
update_value(value)
|
|
254
|
+
end
|
|
255
|
+
elsif enumerable.is_a? Array
|
|
256
|
+
enumerable.map! do |value|
|
|
257
|
+
update_value(value)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def update_value(value)
|
|
263
|
+
value = call_proc_if_callable(value)
|
|
264
|
+
if value.respond_to? :to_array
|
|
265
|
+
value = value.to_array
|
|
266
|
+
end
|
|
267
|
+
if value.is_a? Enumerable
|
|
268
|
+
recursively_evaluate_lazy_enumerable(value)
|
|
269
|
+
end
|
|
270
|
+
value
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def clause_display(clause_id)
|
|
274
|
+
get_clause_by_id(clause_id)&.display
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
private
|
|
278
|
+
|
|
279
|
+
def arel_attribute(table)
|
|
280
|
+
return @attribute if @attribute.is_a? Arel::Nodes::SqlLiteral
|
|
281
|
+
|
|
282
|
+
table[:"#{attribute}"]
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class Refine::Conditions::ConditionError < StandardError; end
|