Class: Refine::Conditions::Condition

Inherits:
Object
  • Object
show all
Includes:
ActiveModel::Validations, HasClauses, HasIcon, HasMeta, HasRefinements, HasThroughIdRelationship, SupportsFlatQueries, UsesAttributes, WithForcedIndex
Defined in:
app/models/refine/conditions/condition.rb

Constant Summary collapse

I18N_PREFIX =
"refine.refine_blueprints.condition."

Constants included from SupportsFlatQueries

SupportsFlatQueries::LEFT_JOIN_CLAUSES

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from SupportsFlatQueries

#add_pending_join, #add_pending_joins_if_needed, #apply_flat, #apply_flat_relational_condition, #condition_uses_different_database?, #get_foreign_key_from_relation, #get_through_reflection, #handle_flat_cross_database_condition, #handle_flat_relational_condition

Methods included from HasIcon

#category_icon_class, #category_icon_template, #has_icon?, #icon_class, #icon_container_class, #icon_template, #with_category_icon_class, #with_category_icon_template, #with_icon, #with_icon_class, #with_icon_container_class, #with_icon_template

Methods included from WithForcedIndex

#forced_index, #with_forced_index

Methods included from HasThroughIdRelationship

#through_id_relationship, #with_through_id_relationship

Methods included from HasRefinements

#apply_count_refinement, #apply_date_refinement, #apply_refinements, #define_count_condition, #define_date_condition, #get_count_refinement_condition, #get_date_refinement_condition, #has_any_refinements?, #has_count_refinement?, #has_date_refinement?, #refine_by_count, #refine_by_date, #refine_by_filter, #refinements_allowed?, #refinements_to_array

Methods included from UsesAttributes

#apply_relationship_attribute, #can_use_where_in_relationship_subquery?, #create_pending_has_many_through_subquery, #create_pending_wherein_subquery, #get_reflection_object, #group, #is_relationship_attribute?, #key_1, #key_2, #raw_attribute, #should_inverse_clause?, #with_attribute

Methods included from HasMeta

#meta, #with_meta

Methods included from HasClauses

#before_clause_validation, #boot_has_clauses, #clause_display_map, #custom_clauses, #ensure_clause, #ensure_clauses, #get_clause_by_id, #get_clauses, included, #only_clauses, #remap_clause_displays, #update_show_clauses, #with_clauses, #without_clauses

Constructor Details

#initialize(id = nil, display = nil) ⇒ Condition

Returns a new instance of Condition.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'app/models/refine/conditions/condition.rb', line 23

def initialize(id = nil, display = nil)
  # Capture display value if sent it. Not translated, takes precedence
  # If no display value explicitly sent, use locales to translate in translate_display
  @display = display
  @id = id
  # Optimistically set attribute to the id
  @attribute = id
  @rules = {}

  # Ensurance validations -> every condition must have an id and an attribute evaluated after
  # developer configuration
  @ensurances = []
  add_ensurance(ensure_id)
  add_ensurance(ensure_attribute_configured)

  @before_validations = []

  # Interpolate later in life for each class that needs it - not everyone needs it
  boot_has_clauses

  # Allow each condition to set state post initialization
  boot
  @on_deepest_relationship = false
  @is_refinement = false
  # Refinements variables
  @date_refinement_proc = nil
  @count_refinement_proc = nil
  @filter_refinement_proc = nil
end

Instance Attribute Details

#attribute ⇒ Object

Returns the value of attribute attribute.



19
20
21
# File 'app/models/refine/conditions/condition.rb', line 19

def attribute
  @attribute
end

#before_validations ⇒ Object (readonly)

Returns the value of attribute before_validations.



18
19
20
# File 'app/models/refine/conditions/condition.rb', line 18

def before_validations
  @before_validations
end

#clause ⇒ Object (readonly)

Returns the value of attribute clause.



18
19
20
# File 'app/models/refine/conditions/condition.rb', line 18

def clause
  @clause
end

#display ⇒ Object

Returns the value of attribute display.



19
20
21
# File 'app/models/refine/conditions/condition.rb', line 19

def display
  @display
end

#ensurances ⇒ Object (readonly)

Returns the value of attribute ensurances.



18
19
20
# File 'app/models/refine/conditions/condition.rb', line 18

def ensurances
  @ensurances
end

#filter ⇒ Object (readonly)

Returns the value of attribute filter.



18
19
20
# File 'app/models/refine/conditions/condition.rb', line 18

def filter
  @filter
end

#id ⇒ Object

Returns the value of attribute id.



19
20
21
# File 'app/models/refine/conditions/condition.rb', line 19

def id
  @id
end

#is_flat_query ⇒ Object

Returns the value of attribute is_flat_query.



19
20
21
# File 'app/models/refine/conditions/condition.rb', line 19

def is_flat_query
  @is_flat_query
end

#is_refinement ⇒ Object

Returns the value of attribute is_refinement.



19
20
21
# File 'app/models/refine/conditions/condition.rb', line 19

def is_refinement
  @is_refinement
end

Instance Method Details

#add_ensurance(callable) ⇒ Object



57
58
59
# File 'app/models/refine/conditions/condition.rb', line 57

def add_ensurance(callable)
  @ensurances << callable
end

#add_messages(new_messages) ⇒ Object



101
102
103
104
# File 'app/models/refine/conditions/condition.rb', line 101

def add_messages(new_messages)
  messages.merge!(new_messages)
  self
end

#add_rules(new_rules, new_messages = {}) ⇒ Object



90
91
92
93
94
95
# File 'app/models/refine/conditions/condition.rb', line 90

def add_rules(new_rules, new_messages = {})
  # TODO add messages if desired
  @rules.merge!(new_rules)
  add_messages(new_messages)
  self
end

#apply(input, table, initial_query, inverse_clause = false, through_attribute = nil) ⇒ Arel::Node

Applies the criterion which can be a relationship condition

Parameters:

  • input (Hash) —

    The user's input

  • table (Arel::Table) —

    The arel_table the query is built on

  • initial_query (ActiveRecord::Relation) —

    The base query the query is built on

Returns:

  • (Arel::Node)


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'app/models/refine/conditions/condition.rb', line 128

def apply(input, table, initial_query, inverse_clause=false, through_attribute=nil)
  table ||= filter.table
  # Ensurance validations are checking the developer configured correctly
  run_ensurance_validations
  # Allow developer to modify user input
  # TODO run_before_validate(input) -> what is this for?

  run_before_validate_validations(input)

  # TODO Determine right place to set the clause
  validate_user_input(input)
  if input.dig(:filter_refinement).present?

    filter_condition = call_proc_if_callable(@filter_refinement_proc)
    # Set the filter on the filter_condition to be the current_condition's filter
    filter_condition.set_filter(filter)
    filter_condition.is_refinement = true

    # Applying the filter condition will modify pending relationship subqueries in place
    filter_condition.apply(input.dig(:filter_refinement), table, initial_query)
    input.delete(:filter_refinement)
  end

  if is_relationship_attribute?
    apply_relationship_attribute(input: input, query: initial_query)
    return
  end
  # No longer a relationship attribute, apply condition normally
  self.attribute = through_attribute if through_attribute
  nodes = apply_condition(input, table, inverse_clause)
  if !is_refinement && has_any_refinements?
    refined_node = apply_refinements(input)
    # Count refinement will return nil because it directly modified pending relationship subquery
    nodes = nodes.and(refined_node) if refined_node
  end
  nodes
end

#apply_condition(input, table, _inverse_clause) ⇒ Object

Raises:

  • (NotImplementedError)


215
216
217
# File 'app/models/refine/conditions/condition.rb', line 215

def apply_condition(input, table, _inverse_clause)
  raise NotImplementedError
end

#approved_clauses ⇒ Object



170
171
172
# File 'app/models/refine/conditions/condition.rb', line 170

def approved_clauses
  get_clauses.call
end

#before_validate(callable) ⇒ Object



53
54
55
# File 'app/models/refine/conditions/condition.rb', line 53

def before_validate(callable)
  @before_validations << callable
end

#boot ⇒ Object



87
88
# File 'app/models/refine/conditions/condition.rb', line 87

def boot
end

#boot_traits ⇒ Object

Boot the traits first, so any extended conditions can override the traits if they need to.



84
85
# File 'app/models/refine/conditions/condition.rb', line 84

def boot_traits
end

#call_proc_if_callable(value) ⇒ Object



246
247
248
249
250
251
252
# File 'app/models/refine/conditions/condition.rb', line 246

def call_proc_if_callable(value)
  if value.respond_to? :call
    value.call
  else
    value
  end
end

#clause_display(clause_id) ⇒ Object



278
279
280
# File 'app/models/refine/conditions/condition.rb', line 278

def clause_display(clause_id)
  get_clause_by_id(clause_id)&.display
end

#clause_exists?(input) ⇒ Boolean

Returns:

  • (Boolean)


202
203
204
205
# File 'app/models/refine/conditions/condition.rb', line 202

def clause_exists?(input)
  current_clause = clauses.select { |clause| clause.id == input[:clause] }
  current_clause.present?
end

#clause_in_approved_list? ⇒ Boolean

Returns:

  • (Boolean)


174
175
176
177
178
179
# File 'app/models/refine/conditions/condition.rb', line 174

def clause_in_approved_list?
  # Is the requested clause in the approved list configured by developer?
  unless approved_clauses.map(&:id).include? clause
    errors.add(:base, I18n.t("#{I18N_PREFIX}clause_not_found", clause: clause))
  end
end

#component ⇒ Object

Raises:

  • (NotImplementedError)


207
208
209
# File 'app/models/refine/conditions/condition.rb', line 207

def component
  raise NotImplementedError
end

#ensure_attribute_configured ⇒ Object



66
67
68
69
70
71
72
# File 'app/models/refine/conditions/condition.rb', line 66

def ensure_attribute_configured
  proc do
    if @attribute.nil?
      errors.add(:base, I18n.t("#{I18N_PREFIX}attribute_required"))
    end
  end
end

#ensure_id ⇒ Object



74
75
76
77
78
79
80
# File 'app/models/refine/conditions/condition.rb', line 74

def ensure_id
  proc do
    if @id.nil?
      errors.add(:base, I18n.t("#{I18N_PREFIX}condition_must_have_id"))
    end
  end
end

#evaluated_meta ⇒ Object



242
243
244
# File 'app/models/refine/conditions/condition.rb', line 242

def evaluated_meta
  recursively_evaluate_lazy_enumerable(@meta)
end

#human_readable(input) ⇒ Object

Raises:

  • (NotImplementedError)


211
212
213
# File 'app/models/refine/conditions/condition.rb', line 211

def human_readable(input)
  raise NotImplementedError
end

#messages ⇒ Object



97
98
99
# File 'app/models/refine/conditions/condition.rb', line 97

def messages
  @messages ||= {}
end

#recursively_evaluate_lazy_enumerable(enumerable) ⇒ Object

In HasCallbacks



255
256
257
258
259
260
261
262
263
264
265
# File 'app/models/refine/conditions/condition.rb', line 255

def recursively_evaluate_lazy_enumerable(enumerable)
  if enumerable.is_a? Hash
    enumerable.transform_values! do |value|
      update_value(value)
    end
  elsif enumerable.is_a? Array
    enumerable.map! do |value|
      update_value(value)
    end
  end
end

#run_before_validate_validations(input) ⇒ Object



112
113
114
115
116
117
118
119
120
# File 'app/models/refine/conditions/condition.rb', line 112

def run_before_validate_validations(input)
  before_validations.each do |function|
    if function.respond_to? :call
      function.call(input)
    else
      function
    end
  end
end

#run_ensurance_validations ⇒ Object



106
107
108
109
110
# File 'app/models/refine/conditions/condition.rb', line 106

def run_ensurance_validations
  ensurances.each do |function|
    call_proc_if_callable(function)
  end
end

#set_filter(filter) ⇒ Object



219
220
221
222
# File 'app/models/refine/conditions/condition.rb', line 219

def set_filter(filter)
  @filter = filter
  self
end

#set_input_parameters(input) ⇒ Object



166
167
168
# File 'app/models/refine/conditions/condition.rb', line 166

def set_input_parameters(input)
  # Placeholder for conditions that do not need this method
end

#to_array(allow_errors: false) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'app/models/refine/conditions/condition.rb', line 224

def to_array(allow_errors: false)
  # Has clauses has already been called, so meta is populated with possible closures to evaluate
  # Run ensurance validations will populate the errors array on the object
  run_ensurance_validations

  if errors.any? && !allow_errors
    raise ConditionError, errors.full_messages.join(". ")
  end

  {
    id: id,
    component: component,
    display: @display,
    meta: evaluated_meta,
    refinements: refinements_to_array
  }
end

#update_value(value) ⇒ Object



267
268
269
270
271
272
273
274
275
276
# File 'app/models/refine/conditions/condition.rb', line 267

def update_value(value)
  value = call_proc_if_callable(value)
  if value.respond_to? :to_array
    value = value.to_array
  end
  if value.is_a? Enumerable
    recursively_evaluate_lazy_enumerable(value)
  end
  value
end

#validate_user_input(input) ⇒ Object



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'app/models/refine/conditions/condition.rb', line 181

def validate_user_input(input)
  evaluated_rules = recursively_evaluate_lazy_enumerable(@rules)
  # Set input parameters on the condition in order to use condition level validations
  # TODO set this somewhere more obvious 
  @clause = input[:clause]
  set_input_parameters(input)
  evaluated_rules.each_pair do |k, v|
    if input[k].blank?
      errors.add(:base, I18n.t("#{I18N_PREFIX}a_is_required", k: k))
    end
  end
  # Errors added to the errors array by individual conditions and standard rails validations
  if errors.any? || !valid?
    # When the error is rescued, it will stringify the message.
    raise Errors::ConditionClauseError.new(
      errors.attribute_names.map{|name| errors[name].join}.to_sentence,
      errors: errors
    )
  end
end

#with_display(value) ⇒ Object



61
62
63
64
# File 'app/models/refine/conditions/condition.rb', line 61

def with_display(value)
  @display = value
  self
end