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,102 @@
|
|
|
1
|
+
class Refine::Filters::BlueprintEditor
|
|
2
|
+
# Editor that will transform a given blueprint IN PLACE
|
|
3
|
+
# Designed to support operations for the v2 inline filter builder via ConditionsController
|
|
4
|
+
|
|
5
|
+
attr_reader :blueprint
|
|
6
|
+
|
|
7
|
+
INPUT_ATTRS = %i[
|
|
8
|
+
clause
|
|
9
|
+
date1
|
|
10
|
+
date2
|
|
11
|
+
days
|
|
12
|
+
modifier
|
|
13
|
+
selected
|
|
14
|
+
value
|
|
15
|
+
value1
|
|
16
|
+
value2
|
|
17
|
+
count_refinement
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
def initialize(blueprint)
|
|
21
|
+
@blueprint = blueprint
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# add a criteria at the specified position (defaults to the end)
|
|
25
|
+
def add(position: -1, conjunction: "and", criterion:)
|
|
26
|
+
# TODO support multiple input conditions, refinements, etc
|
|
27
|
+
|
|
28
|
+
conjunction_depth = case conjunction
|
|
29
|
+
in "and" then 1
|
|
30
|
+
in "or" then 0
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# extract data from criterion
|
|
34
|
+
condition_id = criterion[:condition_id]
|
|
35
|
+
input = criterion[:input].slice(*INPUT_ATTRS)
|
|
36
|
+
|
|
37
|
+
nodes_to_insert = []
|
|
38
|
+
nodes_to_insert << {
|
|
39
|
+
depth: conjunction_depth,
|
|
40
|
+
type: "conjunction",
|
|
41
|
+
word: conjunction,
|
|
42
|
+
} if blueprint[(position.negative? ? position + 1 : position - 1)]
|
|
43
|
+
|
|
44
|
+
nodes_to_insert << {
|
|
45
|
+
depth: 1,
|
|
46
|
+
type: "criterion",
|
|
47
|
+
condition_id: condition_id,
|
|
48
|
+
input: input
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
blueprint.insert position, *nodes_to_insert
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def update(index, criterion:)
|
|
55
|
+
# extract data from criterion
|
|
56
|
+
input = criterion[:input].slice(*INPUT_ATTRS)
|
|
57
|
+
|
|
58
|
+
blueprint[index][:input] = input
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def delete(index)
|
|
62
|
+
# To support 'groups' there is some complicated logic for deleting criterion.
|
|
63
|
+
#
|
|
64
|
+
# Imagine this simplified blueprint: [eq, and, sw, or, eq]
|
|
65
|
+
#
|
|
66
|
+
# User clicks to delete the last eq. We also have to delete the preceding or
|
|
67
|
+
# otherwise we're left with a hanging empty group
|
|
68
|
+
#
|
|
69
|
+
# What if the user deletes the sw? We have to clean up the preceding and.
|
|
70
|
+
#
|
|
71
|
+
# Imagine another scenario: [eq or sw and ew]
|
|
72
|
+
# Now we delete the first eq but this time we need to clean up the or.
|
|
73
|
+
#
|
|
74
|
+
# These conditionals cover these cases.
|
|
75
|
+
|
|
76
|
+
previous_entry = index.zero? ? nil : blueprint[index - 1]
|
|
77
|
+
next_entry = blueprint[index + 1]
|
|
78
|
+
|
|
79
|
+
next_is_or = next_entry && next_entry[:word] == 'or'
|
|
80
|
+
previous_is_or = previous_entry && previous_entry[:word] == 'or'
|
|
81
|
+
|
|
82
|
+
next_is_right_paren = next_is_or || !next_entry
|
|
83
|
+
previous_is_left_paren = previous_is_or || !previous_entry
|
|
84
|
+
|
|
85
|
+
is_first_in_group = previous_is_left_paren && !next_is_right_paren
|
|
86
|
+
is_last_in_group = previous_is_left_paren && next_is_right_paren
|
|
87
|
+
is_last_criterion = !previous_entry && !next_entry
|
|
88
|
+
|
|
89
|
+
if is_last_criterion
|
|
90
|
+
blueprint.slice!(index)
|
|
91
|
+
elsif is_last_in_group && previous_is_or
|
|
92
|
+
blueprint.slice!((index - 1)..index)
|
|
93
|
+
elsif is_last_in_group && !previous_entry
|
|
94
|
+
blueprint.slice!(index..(index + 1))
|
|
95
|
+
elsif is_first_in_group
|
|
96
|
+
blueprint.slice!(index..(index + 1))
|
|
97
|
+
else
|
|
98
|
+
blueprint.slice!((index - 1)..index)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
class Refine::Filters::Builder
|
|
2
|
+
# View Model that holds all aspects of the client state related to the filter builder
|
|
3
|
+
# This includes auxiliary state like client_id and selected stored filter id
|
|
4
|
+
#
|
|
5
|
+
# State for the main query builder form is held in #query which is an instance of
|
|
6
|
+
# Refine::Filters::Query
|
|
7
|
+
include ActiveModel::Model
|
|
8
|
+
|
|
9
|
+
attr_accessor :blueprint_json,
|
|
10
|
+
:filter_class,
|
|
11
|
+
:stable_id,
|
|
12
|
+
:stored_filter_id,
|
|
13
|
+
:client_id,
|
|
14
|
+
:initial_query
|
|
15
|
+
|
|
16
|
+
attr_reader :blueprint, :refine_filter, :query
|
|
17
|
+
|
|
18
|
+
def initialize(attrs = {})
|
|
19
|
+
super
|
|
20
|
+
@client_id ||= SecureRandom.uuid
|
|
21
|
+
set_refine_filter_and_blueprint!
|
|
22
|
+
set_query!
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_params
|
|
26
|
+
{
|
|
27
|
+
refine_filters_builder: {
|
|
28
|
+
blueprint_json: blueprint.to_json,
|
|
29
|
+
stable_id: stable_id,
|
|
30
|
+
stored_filter_id: stored_filter_id,
|
|
31
|
+
client_id: client_id,
|
|
32
|
+
filter_class: filter_class
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# For use with the Rails dom_id helper
|
|
38
|
+
def to_key
|
|
39
|
+
[client_id]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def set_refine_filter_and_blueprint!
|
|
45
|
+
if stable_id.present?
|
|
46
|
+
@refine_filter = Refine::Rails.configuration.stabilizer_classes[:url].new.from_stable_id(id: stable_id, initial_query: initial_query)
|
|
47
|
+
@blueprint = @refine_filter.blueprint
|
|
48
|
+
else
|
|
49
|
+
json = blueprint_json || "[]"
|
|
50
|
+
@blueprint = JSON.parse(json).map(&:deep_symbolize_keys)
|
|
51
|
+
@refine_filter = filter_class.constantize.new(blueprint, initial_query)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def set_query!
|
|
56
|
+
@query = Refine::Filters::Query.new(@refine_filter)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
class Refine::Filters::Criterion
|
|
2
|
+
# View model that holds the state of individual criteria within the Filter query
|
|
3
|
+
include ActiveModel::Model
|
|
4
|
+
include ActiveModel::Attributes
|
|
5
|
+
|
|
6
|
+
attribute :depth, :integer
|
|
7
|
+
attribute :criterion, :string
|
|
8
|
+
attribute :condition_id, :string
|
|
9
|
+
attribute :input
|
|
10
|
+
attribute :word, :string
|
|
11
|
+
attribute :type, :string
|
|
12
|
+
attribute :position, :integer
|
|
13
|
+
attribute :uid, :string
|
|
14
|
+
|
|
15
|
+
attr_accessor :query
|
|
16
|
+
delegate :filter, to: :query, allow_nil: true
|
|
17
|
+
|
|
18
|
+
attr_reader :condition
|
|
19
|
+
|
|
20
|
+
def initialize(**attrs)
|
|
21
|
+
super
|
|
22
|
+
initialize_condition!
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def validate!
|
|
26
|
+
return if (input&.has_key?(:count_refinement) || input&.has_key?(:date_refinement))
|
|
27
|
+
errors.clear
|
|
28
|
+
return true if type == "conjunction"
|
|
29
|
+
begin
|
|
30
|
+
query_for_validate = filter.initial_query || filter.model.all
|
|
31
|
+
condition&.apply(input, filter.table, query_for_validate)
|
|
32
|
+
rescue Refine::Conditions::Errors::ConditionClauseError => e
|
|
33
|
+
e.errors.each do |error|
|
|
34
|
+
errors.add(:base, error.full_message)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def condition_attributes
|
|
40
|
+
result = condition&.to_array(allow_errors: true)
|
|
41
|
+
result.to_h
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def meta
|
|
45
|
+
condition_attributes[:meta]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def selected_clause
|
|
49
|
+
input[:clause]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def selected_clause_meta
|
|
53
|
+
meta[:clauses].find {|c| c[:id] == selected_clause }[:meta]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def refinements
|
|
57
|
+
condition.refinements_to_array
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def meta_for_refinement_clause(refinement)
|
|
61
|
+
refinement_meta = refinement[:meta]
|
|
62
|
+
selected_clause_id = input[refinement[:id].to_sym][:clause]
|
|
63
|
+
clauses = refinement_meta[:clauses]
|
|
64
|
+
selected_clause = clauses.find { |clause| clause[:id] == selected_clause_id }
|
|
65
|
+
selected_clause[:meta]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def component
|
|
69
|
+
condition.component.underscore
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def initialize_condition!
|
|
75
|
+
@condition = query
|
|
76
|
+
.available_conditions
|
|
77
|
+
.find { |condition| condition.id == condition_id }
|
|
78
|
+
.dup
|
|
79
|
+
|
|
80
|
+
if @condition
|
|
81
|
+
@condition.set_filter(filter)
|
|
82
|
+
filter.translate_display(@condition)
|
|
83
|
+
elsif type != "conjunction"
|
|
84
|
+
raise Refine::InvalidFilterError
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
class Refine::Filters::Query
|
|
2
|
+
include ActiveModel::Model
|
|
3
|
+
# View Model for the main section of the filter builder- the dynamic query form
|
|
4
|
+
|
|
5
|
+
attr_reader :filter
|
|
6
|
+
|
|
7
|
+
def initialize(filter)
|
|
8
|
+
@filter = filter
|
|
9
|
+
add_criteria!
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def validate!
|
|
13
|
+
@criteria.each(&:validate!)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def valid?
|
|
17
|
+
validate!
|
|
18
|
+
@criteria.all? { |c| c.errors.empty? }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def clear_errors
|
|
22
|
+
@criteria.each { |c| c.errors.clear }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def grouped_criteria
|
|
26
|
+
# Allow for an empty blueprint
|
|
27
|
+
return [] if @criteria.blank?
|
|
28
|
+
[].tap do |result|
|
|
29
|
+
# start with an empty group
|
|
30
|
+
result.push []
|
|
31
|
+
@criteria.each_with_index do |criterion, index|
|
|
32
|
+
case criterion.word
|
|
33
|
+
when "or"
|
|
34
|
+
result.push []
|
|
35
|
+
when "and"
|
|
36
|
+
next
|
|
37
|
+
else
|
|
38
|
+
result.last.push criterion
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def available_conditions
|
|
45
|
+
@filter.conditions
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def blueprint
|
|
49
|
+
if @filter.blueprint&.any?
|
|
50
|
+
@filter.blueprint
|
|
51
|
+
else
|
|
52
|
+
[]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def configuration
|
|
57
|
+
@filter.configuration
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# The json representation of conditions that is sent to the front end.
|
|
62
|
+
def available_conditions_attributes
|
|
63
|
+
configuration[:conditions]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# TODO make this a more useful data with pointers to which criteria
|
|
67
|
+
# each error goes with
|
|
68
|
+
def error_messages
|
|
69
|
+
@criteria.flat_map {|c| c.errors.full_messages }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def add_criteria!
|
|
75
|
+
@criteria = []
|
|
76
|
+
blueprint.each.with_index do |criterion_attrs, index|
|
|
77
|
+
@criteria << Refine::Filters::Criterion.new(
|
|
78
|
+
**criterion_attrs.merge(query: self, uid: index, position: index)
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class Refine::Inline::Criteria::Input
|
|
2
|
+
include ActiveModel::Model
|
|
3
|
+
|
|
4
|
+
MODIFIERS = {
|
|
5
|
+
ago: "days ago",
|
|
6
|
+
from_now: "days from now"
|
|
7
|
+
}.freeze
|
|
8
|
+
|
|
9
|
+
attr_accessor :clause,
|
|
10
|
+
:date1,
|
|
11
|
+
:date2,
|
|
12
|
+
:days,
|
|
13
|
+
:modifier,
|
|
14
|
+
:selected,
|
|
15
|
+
:value,
|
|
16
|
+
:value1,
|
|
17
|
+
:value2,
|
|
18
|
+
:count_refinement
|
|
19
|
+
|
|
20
|
+
def attributes
|
|
21
|
+
{
|
|
22
|
+
clause: clause,
|
|
23
|
+
date1: date1,
|
|
24
|
+
date2: date2,
|
|
25
|
+
days: days,
|
|
26
|
+
modifier: modifier,
|
|
27
|
+
selected: selected,
|
|
28
|
+
value: value,
|
|
29
|
+
value1: value1,
|
|
30
|
+
value2: value2,
|
|
31
|
+
count_refinement_attributes: count_refinement_attributes.presence
|
|
32
|
+
}.compact
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def count_refinement
|
|
36
|
+
@count_refinement ||= Refine::Inline::Criteria::NumericRefinement.new
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def count_refinement_attributes
|
|
40
|
+
count_refinement.attributes
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def count_refinement_attributes=(attrs = {})
|
|
44
|
+
count_refinement.attributes = attrs.to_h
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def selected=(value)
|
|
48
|
+
@selected = Array.wrap(value)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
class Refine::Inline::Criterion
|
|
2
|
+
include ActiveModel::Model
|
|
3
|
+
|
|
4
|
+
attr_accessor :stable_id,
|
|
5
|
+
:client_id,
|
|
6
|
+
:condition_id,
|
|
7
|
+
:input,
|
|
8
|
+
:position,
|
|
9
|
+
:conjunction,
|
|
10
|
+
:refine_filter
|
|
11
|
+
|
|
12
|
+
# initialize a Crtierion object from a blueprint node
|
|
13
|
+
def self.from_blueprint_node(node, **additional_attrs)
|
|
14
|
+
attrs = node.deep_dup.merge(additional_attrs)
|
|
15
|
+
|
|
16
|
+
# delete some attributes we don't need
|
|
17
|
+
attrs.delete(:depth)
|
|
18
|
+
attrs.delete(:type)
|
|
19
|
+
|
|
20
|
+
# suffix nested hash keys with '_attributes' to initialize objects
|
|
21
|
+
attrs[:input_attributes] = attrs.delete(:input)
|
|
22
|
+
if input_attrs = attrs[:input_attributes]
|
|
23
|
+
input_attrs[:count_refinement_attributes] = input_attrs.delete(:count_refinement)
|
|
24
|
+
end
|
|
25
|
+
new(attrs)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
#
|
|
29
|
+
# Returns a nested array of Criterion objects reflecting the grouping of the OR groups in a filter's blueprint
|
|
30
|
+
def self.groups_from_filter(refine_filter, **attrs)
|
|
31
|
+
return [] unless refine_filter&.blueprint.present?
|
|
32
|
+
[].tap do |result|
|
|
33
|
+
result.push([])
|
|
34
|
+
refine_filter.blueprint.each_with_index do |node, i|
|
|
35
|
+
case node[:word]
|
|
36
|
+
when "or"
|
|
37
|
+
result.push []
|
|
38
|
+
when "and"
|
|
39
|
+
next
|
|
40
|
+
else
|
|
41
|
+
criterion = from_blueprint_node(node, **attrs.merge(refine_filter: refine_filter, position: i))
|
|
42
|
+
result.last.push criterion
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def attributes
|
|
49
|
+
{
|
|
50
|
+
stable_id: stable_id,
|
|
51
|
+
client_id: client_id,
|
|
52
|
+
condition_id: condition_id,
|
|
53
|
+
position: position,
|
|
54
|
+
conjunction: conjunction,
|
|
55
|
+
input_attributes: input_attributes
|
|
56
|
+
}.compact
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_params
|
|
60
|
+
{refine_inline_criterion: attributes}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def input
|
|
64
|
+
@input ||= Refine::Inline::Criteria::Input.new
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def input_attributes
|
|
68
|
+
input.attributes
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def input_attributes=(attrs = {})
|
|
72
|
+
input.attributes = attrs.to_h
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def to_key
|
|
76
|
+
[client_id, position, conjunction].map(&:presence).compact
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def condition
|
|
80
|
+
@condition ||= begin
|
|
81
|
+
@refine_filter
|
|
82
|
+
.instantiated_conditions
|
|
83
|
+
.find { |c| c.id == condition_id }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def input_partial
|
|
88
|
+
"refine/inline/inputs/#{condition.component}".underscore
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def to_blueprint_node
|
|
92
|
+
result = attributes.slice(:condition_id, :input_attributes)
|
|
93
|
+
result[:input] = result.delete(:input_attributes)
|
|
94
|
+
if input_attrs = result[:input]
|
|
95
|
+
input_attrs[:count_refinement] = input_attrs.delete(:count_refinement_attributes)
|
|
96
|
+
end
|
|
97
|
+
result
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def human_readable_value
|
|
101
|
+
condition.human_readable_value(to_blueprint_node[:input])
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def condition_display
|
|
105
|
+
condition&.display
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def clause_display
|
|
109
|
+
condition&.clause_display(input&.clause)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def options
|
|
113
|
+
if condition.respond_to? :get_options
|
|
114
|
+
condition.get_options.call.map {|option_hash| Refine::Inline::Criteria::Option.new(**option_hash)}
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def multiple?
|
|
119
|
+
selected_clause = condition.clauses.detect {|c| c.id == input.clause } || condition.clauses.first
|
|
120
|
+
selected_clause.meta[:multiple].present?
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def validate!
|
|
124
|
+
# TODO figure out how to validate inputs with count refinements
|
|
125
|
+
return if input.count_refinement.attributes.present?
|
|
126
|
+
errors.clear
|
|
127
|
+
begin
|
|
128
|
+
query_for_validate = refine_filter.initial_query || refine_filter.model.all
|
|
129
|
+
condition&.apply(input_attributes, refine_filter.table, query_for_validate)
|
|
130
|
+
rescue Refine::Conditions::Errors::ConditionClauseError => e
|
|
131
|
+
e.errors.each do |error|
|
|
132
|
+
errors.add(:base, error.full_message)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def valid?
|
|
138
|
+
validate!
|
|
139
|
+
errors.empty?
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Refine
|
|
2
|
+
module Stabilize
|
|
3
|
+
|
|
4
|
+
def automatically_stabilize?
|
|
5
|
+
false
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def automatic_stable_id_generator
|
|
9
|
+
self.class.default_stabilizer
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_optional_stable_id(stabilizer=nil)
|
|
13
|
+
create_stable_id(stabilizer) if automatically_stabilize?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create_stable_id(stabilizer=nil)
|
|
17
|
+
make_stable_id_generator(stabilizer).new.to_stable_id(filter: self)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def make_stable_id_generator(stabilizer = nil)
|
|
21
|
+
generator = stabilizer || automatic_stable_id_generator
|
|
22
|
+
if generator.blank?
|
|
23
|
+
raise ArgumentError.new('No stable id class set. Set using the default_stable_id_generator method')
|
|
24
|
+
end
|
|
25
|
+
generator
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Refine
|
|
2
|
+
module Stabilizers
|
|
3
|
+
class DatabaseStabilizer
|
|
4
|
+
|
|
5
|
+
def to_stable_id(filter:, name: nil)
|
|
6
|
+
# Serialize the filter class and blueprint. Reference via id.
|
|
7
|
+
model.find_or_create_by!(state: filter.state, name: name).id
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def from_stable_id(id:, initial_query: nil)
|
|
11
|
+
# Find the associated StoredFilter by id and return state. Decode to create blueprint
|
|
12
|
+
state = ActiveSupport::JSON.decode(model.find(id).state).deep_symbolize_keys
|
|
13
|
+
Refine::Filter.from_state(state, initial_query)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def model
|
|
17
|
+
Refine::StoredFilter
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Refine
|
|
2
|
+
module Stabilizers
|
|
3
|
+
class UrlEncodedStabilizer
|
|
4
|
+
|
|
5
|
+
def to_stable_id(filter:)
|
|
6
|
+
compressed_state = ActiveSupport::Gzip.compress(filter.state)
|
|
7
|
+
encoded_state = Base64.encode64(compressed_state)
|
|
8
|
+
CGI.escape(encoded_state)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def from_stable_id(id:, initial_query: nil)
|
|
12
|
+
raise Refine::Stabilizers::Errors::UrlStabilizerError if id.blank?
|
|
13
|
+
url_decoded = CGI.unescape(id)
|
|
14
|
+
base_64_decoded = Base64.decode64(url_decoded)
|
|
15
|
+
uncompress = ActiveSupport::Gzip.decompress(base_64_decoded)
|
|
16
|
+
state = ActiveSupport::JSON.decode(uncompress).deep_symbolize_keys
|
|
17
|
+
Refine::Filter.from_state(state, initial_query)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Refine
|
|
2
|
+
class StoredFilter < ApplicationRecord
|
|
3
|
+
validates_presence_of :state
|
|
4
|
+
self.table_name = "refine_stored_filters"
|
|
5
|
+
|
|
6
|
+
def refine_filter(initial_query: nil)
|
|
7
|
+
Refine::Rails.configuration.stabilizer_classes[:db].new.from_stable_id(id: id, initial_query: initial_query)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def blueprint
|
|
11
|
+
JSON.parse(state)["blueprint"]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|