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.
Files changed (141) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +413 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/config/refine_rails_manifest.js +0 -0
  5. data/app/assets/javascripts/refine-stimulus.esm.js +2 -0
  6. data/app/assets/javascripts/refine-stimulus.esm.js.map +1 -0
  7. data/app/assets/javascripts/refine-stimulus.js +2 -0
  8. data/app/assets/javascripts/refine-stimulus.js.map +1 -0
  9. data/app/assets/javascripts/refine-stimulus.modern.js +2 -0
  10. data/app/assets/javascripts/refine-stimulus.modern.js.map +1 -0
  11. data/app/assets/javascripts/refine-stimulus.umd.js +2 -0
  12. data/app/assets/javascripts/refine-stimulus.umd.js.map +1 -0
  13. data/app/assets/stylesheets/index.css +1873 -0
  14. data/app/assets/stylesheets/index.tailwind.css +1035 -0
  15. data/app/controllers/refine/blueprints_controller.rb +80 -0
  16. data/app/controllers/refine/filter_application_controller.rb +29 -0
  17. data/app/controllers/refine/inline/criteria_controller.rb +161 -0
  18. data/app/controllers/refine/inline/stored_filters_controller.rb +84 -0
  19. data/app/controllers/refine/stored_filters_controller.rb +69 -0
  20. data/app/javascript/controllers/index.js +66 -0
  21. data/app/javascript/controllers/refine/add-controller.js +42 -0
  22. data/app/javascript/controllers/refine/criterion-form-controller.js +31 -0
  23. data/app/javascript/controllers/refine/date-controller.js +113 -0
  24. data/app/javascript/controllers/refine/defaults-controller.js +32 -0
  25. data/app/javascript/controllers/refine/delete-controller.js +13 -0
  26. data/app/javascript/controllers/refine/filter-pills-controller.js +63 -0
  27. data/app/javascript/controllers/refine/form-controller.js +51 -0
  28. data/app/javascript/controllers/refine/inline-conditions-controller.js +33 -0
  29. data/app/javascript/controllers/refine/popup-controller.js +46 -0
  30. data/app/javascript/controllers/refine/search-filter-controller.js +50 -0
  31. data/app/javascript/controllers/refine/server-refresh-controller.js +43 -0
  32. data/app/javascript/controllers/refine/state-controller.js +220 -0
  33. data/app/javascript/controllers/refine/stored-filter-controller.js +23 -0
  34. data/app/javascript/controllers/refine/submit-form-controller.js +8 -0
  35. data/app/javascript/controllers/refine/toggle-controller.js +12 -0
  36. data/app/javascript/controllers/refine/turbo-stream-form-controller.js +24 -0
  37. data/app/javascript/controllers/refine/turbo-stream-link-controller.js +24 -0
  38. data/app/javascript/controllers/refine/update-controller.js +86 -0
  39. data/app/javascript/index.js +1 -0
  40. data/app/javascript/refine/helpers/index.js +77 -0
  41. data/app/models/refine/blueprints/blueprint.rb +58 -0
  42. data/app/models/refine/blueprints/blueprint_example.json +25 -0
  43. data/app/models/refine/conditions/boolean_condition.rb +112 -0
  44. data/app/models/refine/conditions/clause.rb +38 -0
  45. data/app/models/refine/conditions/clauses.rb +38 -0
  46. data/app/models/refine/conditions/condition.rb +285 -0
  47. data/app/models/refine/conditions/condition_error.rb +1 -0
  48. data/app/models/refine/conditions/date_condition.rb +464 -0
  49. data/app/models/refine/conditions/date_with_time_condition.rb +8 -0
  50. data/app/models/refine/conditions/errors/condition_clause_error.rb +7 -0
  51. data/app/models/refine/conditions/errors/criteria_limit_exceeded_error.rb +2 -0
  52. data/app/models/refine/conditions/errors/option_error.rb +2 -0
  53. data/app/models/refine/conditions/errors/relationship_error.rb +1 -0
  54. data/app/models/refine/conditions/filter_condition.rb +93 -0
  55. data/app/models/refine/conditions/has_clauses.rb +117 -0
  56. data/app/models/refine/conditions/has_meta.rb +10 -0
  57. data/app/models/refine/conditions/has_refinements.rb +156 -0
  58. data/app/models/refine/conditions/numeric_condition.rb +224 -0
  59. data/app/models/refine/conditions/option_condition.rb +260 -0
  60. data/app/models/refine/conditions/text_condition.rb +152 -0
  61. data/app/models/refine/conditions/uses_attributes.rb +168 -0
  62. data/app/models/refine/filter.rb +302 -0
  63. data/app/models/refine/filters/blueprint_editor.rb +102 -0
  64. data/app/models/refine/filters/builder.rb +59 -0
  65. data/app/models/refine/filters/criterion.rb +87 -0
  66. data/app/models/refine/filters/query.rb +82 -0
  67. data/app/models/refine/inline/criteria/input.rb +50 -0
  68. data/app/models/refine/inline/criteria/numeric_refinement.rb +13 -0
  69. data/app/models/refine/inline/criteria/option.rb +2 -0
  70. data/app/models/refine/inline/criterion.rb +141 -0
  71. data/app/models/refine/invalid_filter_error.rb +8 -0
  72. data/app/models/refine/stabilize.rb +29 -0
  73. data/app/models/refine/stabilizers/database_stabilizer.rb +21 -0
  74. data/app/models/refine/stabilizers/errors/url_stabilizer_error.rb +2 -0
  75. data/app/models/refine/stabilizers/url_encoded_stabilizer.rb +21 -0
  76. data/app/models/refine/stored_filter.rb +14 -0
  77. data/app/models/refine/tracks_pending_relationship_subqueries.rb +196 -0
  78. data/app/views/_filter_builder_dropdown.html.erb +63 -0
  79. data/app/views/_filter_pills.html.erb +40 -0
  80. data/app/views/_loading.html.erb +32 -0
  81. data/app/views/refine/blueprints/_add_and.html.erb +25 -0
  82. data/app/views/refine/blueprints/_add_group.html.erb +24 -0
  83. data/app/views/refine/blueprints/_clause_select.html.erb +24 -0
  84. data/app/views/refine/blueprints/_condition_select.html.erb +53 -0
  85. data/app/views/refine/blueprints/_criterion.html.erb +41 -0
  86. data/app/views/refine/blueprints/_criterion_errors.html.erb +7 -0
  87. data/app/views/refine/blueprints/_delete_criterion.html.erb +11 -0
  88. data/app/views/refine/blueprints/_group.html.erb +13 -0
  89. data/app/views/refine/blueprints/_query.html.erb +34 -0
  90. data/app/views/refine/blueprints/_stored_filters.html.erb +23 -0
  91. data/app/views/refine/blueprints/clauses/_date_condition.html.erb +80 -0
  92. data/app/views/refine/blueprints/clauses/_date_picker.html.erb +26 -0
  93. data/app/views/refine/blueprints/clauses/_filter_condition.html.erb +36 -0
  94. data/app/views/refine/blueprints/clauses/_numeric_condition.html.erb +35 -0
  95. data/app/views/refine/blueprints/clauses/_option_condition.html.erb +37 -0
  96. data/app/views/refine/blueprints/clauses/_text_condition.html.erb +13 -0
  97. data/app/views/refine/blueprints/create.turbo_stream.erb +22 -0
  98. data/app/views/refine/blueprints/new.html.erb +7 -0
  99. data/app/views/refine/blueprints/show.html.erb +4 -0
  100. data/app/views/refine/blueprints/show.turbo_stream.erb +22 -0
  101. data/app/views/refine/inline/criteria/_form_fields.html.erb +62 -0
  102. data/app/views/refine/inline/criteria/create.turbo_stream.erb +19 -0
  103. data/app/views/refine/inline/criteria/edit.turbo_stream.erb +26 -0
  104. data/app/views/refine/inline/criteria/index.html.erb +64 -0
  105. data/app/views/refine/inline/criteria/new.turbo_stream.erb +24 -0
  106. data/app/views/refine/inline/filters/_add_first_condition_button.html.erb +19 -0
  107. data/app/views/refine/inline/filters/_and_button.html.erb +26 -0
  108. data/app/views/refine/inline/filters/_criterion.html.erb +23 -0
  109. data/app/views/refine/inline/filters/_group.html.erb +13 -0
  110. data/app/views/refine/inline/filters/_load_button.html.erb +15 -0
  111. data/app/views/refine/inline/filters/_or_button.html.erb +26 -0
  112. data/app/views/refine/inline/filters/_popup.html.erb +26 -0
  113. data/app/views/refine/inline/filters/_save_button.html.erb +15 -0
  114. data/app/views/refine/inline/filters/_show.html.erb +40 -0
  115. data/app/views/refine/inline/inputs/_date_condition.html.erb +7 -0
  116. data/app/views/refine/inline/inputs/_date_condition_days.html.erb +18 -0
  117. data/app/views/refine/inline/inputs/_date_condition_range.html.erb +22 -0
  118. data/app/views/refine/inline/inputs/_date_condition_single.html.erb +9 -0
  119. data/app/views/refine/inline/inputs/_date_picker.html.erb +20 -0
  120. data/app/views/refine/inline/inputs/_numeric_condition.html.erb +23 -0
  121. data/app/views/refine/inline/inputs/_option_condition.html.erb +14 -0
  122. data/app/views/refine/inline/inputs/_text_condition.html.erb +8 -0
  123. data/app/views/refine/inline/stored_filters/find.turbo_stream.erb +19 -0
  124. data/app/views/refine/inline/stored_filters/index.html.erb +28 -0
  125. data/app/views/refine/inline/stored_filters/new.turbo_stream.erb +47 -0
  126. data/app/views/refine/stored_filters/create.turbo_stream.erb +2 -0
  127. data/app/views/refine/stored_filters/find.turbo_stream.erb +5 -0
  128. data/app/views/refine/stored_filters/index.html.erb +39 -0
  129. data/app/views/refine/stored_filters/new.html.erb +29 -0
  130. data/app/views/refine/stored_filters/show.html.erb +1 -0
  131. data/config/locales/en/dates.en.yml +29 -0
  132. data/config/locales/en/en.yml +20 -0
  133. data/config/locales/en/refine.en.yml +187 -0
  134. data/config/routes.rb +17 -0
  135. data/lib/generators/filter/filter_generator.rb +27 -0
  136. data/lib/generators/filter/templates/filter.rb.erb +20 -0
  137. data/lib/refine/rails/engine.rb +15 -0
  138. data/lib/refine/rails/version.rb +5 -0
  139. data/lib/refine/rails.rb +38 -0
  140. data/lib/tasks/refine/rails_tasks.rake +13 -0
  141. 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