Skip to content

Commit e9d9f18

Browse files
committed
Condition#do_eval takes into account the current record
This allows us to use `rec` in the expression and not have to post evaluate the to_ruby output Skipped over the associations and the find clauses Also skipped the hash contexts
1 parent 12d5b91 commit e9d9f18

File tree

4 files changed

+131
-116
lines changed

4 files changed

+131
-116
lines changed

app/models/condition.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,11 @@ def self.evaluate(cond, rec, _inputs = {}, attr = :expression)
8282
# similar to MiqExpression#evaluate
8383
# @return [Boolean] true if the expression matches the record
8484
def self.subst_matches?(expr, rec)
85-
do_eval(subst(expr, rec))
85+
do_eval(subst(expr, rec), rec)
8686
end
8787

88-
def self.do_eval(expr)
89-
!!eval(expr)
88+
def self.do_eval(expr, rec)
89+
!!eval(expr, binding)
9090
end
9191
private_class_method :do_eval
9292

@@ -167,7 +167,7 @@ def self._subst_find(rec, expr)
167167
value = MiqExpression.quote(obj.send(attr), opts[:type]&.to_sym)
168168
value = value.gsub("\\", '\&\&') if value.kind_of?(String)
169169
e = search.gsub(/<value[^>]*>.+<\/value>/im, value.to_s)
170-
obj if do_eval(e)
170+
obj if do_eval(e, obj)
171171
end.compact
172172

173173
MiqPolicy.logger.debug("MIQ(condition-_subst_find): Search Expression returned: [#{list.length}] records")
@@ -207,7 +207,7 @@ def self._subst_find(rec, expr)
207207
e = check.gsub(/<value[^>]*>.+<\/value>/im, value.to_s)
208208
MiqPolicy.logger.debug("MIQ(condition-_subst_find): Check Expression after substitution: [#{e}]")
209209

210-
result = do_eval(e)
210+
result = do_eval(e, obj)
211211

212212
return true if result && checkmode == "any"
213213
return false if !result && checkmode == "all"

lib/miq_expression.rb

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def to_ruby(timezone = nil, prune_sql: false)
190190
end
191191
end
192192

193-
def self._to_ruby(exp, context_type, tz)
193+
def self._to_ruby(exp, context_type, tz, obj_name: :rec)
194194
return exp unless exp.kind_of?(Hash)
195195

196196
operator = exp.keys.first
@@ -200,29 +200,29 @@ def self._to_ruby(exp, context_type, tz)
200200

201201
case operator
202202
when "equal", "=", "<", ">", ">=", "<=", "!="
203-
operands = operands2rubyvalue(operator, op_args, context_type)
203+
operands = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
204204
clause = operands.join(" #{normalize_ruby_operator(operator)} ")
205205
when "before"
206206
col_type = Target.parse(col_name).column_type if col_name
207-
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type)
207+
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type, :obj_name => obj_name)
208208
val = op_args["value"]
209209
clause = ruby_for_date_compare(col_ruby, col_type, tz, "<", val)
210210
when "after"
211211
col_type = Target.parse(col_name).column_type if col_name
212-
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type)
212+
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type, :obj_name => obj_name)
213213
val = op_args["value"]
214214
clause = ruby_for_date_compare(col_ruby, col_type, tz, nil, nil, ">", val)
215215
when "includes all"
216-
operands = operands2rubyvalue(operator, op_args, context_type)
216+
operands = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
217217
clause = "(#{operands[0]} & #{operands[1]}) == #{operands[1]}"
218218
when "includes any"
219-
operands = operands2rubyvalue(operator, op_args, context_type)
219+
operands = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
220220
clause = "(#{operands[1]} - #{operands[0]}) != #{operands[1]}"
221221
when "includes only", "limited to"
222-
operands = operands2rubyvalue(operator, op_args, context_type)
222+
operands = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
223223
clause = "(#{operands[0]} - #{operands[1]}) == []"
224224
when "like", "not like", "starts with", "ends with", "includes"
225-
operands = operands2rubyvalue(operator, op_args, context_type)
225+
operands = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
226226
operands[1] =
227227
case operator
228228
when "starts with"
@@ -235,7 +235,7 @@ def self._to_ruby(exp, context_type, tz)
235235
clause = operands.join(" #{normalize_ruby_operator(operator)} ")
236236
clause = "!(" + clause + ")" if operator == "not like"
237237
when "regular expression matches", "regular expression does not match"
238-
operands = operands2rubyvalue(operator, op_args, context_type)
238+
operands = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
239239

240240
# If it looks like a regular expression, sanitize from forward
241241
# slashes and interpolation
@@ -255,11 +255,11 @@ def self._to_ruby(exp, context_type, tz)
255255
end
256256
clause = operands.join(" #{normalize_ruby_operator(operator)} ")
257257
when "and", "or"
258-
clause = "(" + op_args.collect { |operand| _to_ruby(operand, context_type, tz) }.join(" #{normalize_ruby_operator(operator)} ") + ")"
258+
clause = "(" + op_args.collect { |operand| _to_ruby(operand, context_type, tz, :obj_name => obj_name) }.join(" #{normalize_ruby_operator(operator)} ") + ")"
259259
when "not", "!"
260-
clause = normalize_ruby_operator(operator) + "(" + _to_ruby(op_args, context_type, tz) + ")"
260+
clause = normalize_ruby_operator(operator) + "(" + _to_ruby(op_args, context_type, tz, :obj_name => obj_name) + ")"
261261
when "is null", "is not null", "is empty", "is not empty"
262-
operands = operands2rubyvalue(operator, op_args, context_type)
262+
operands = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
263263
clause = operands.join(" #{normalize_ruby_operator(operator)} ")
264264
when "contains"
265265
op_args["tag"] ||= col_name
@@ -294,14 +294,14 @@ def self._to_ruby(exp, context_type, tz)
294294

295295
check =~ /^check(.*)$/
296296
mode = $1.downcase
297-
clause = "<find><search>" + _to_ruby(op_args["search"], context_type, tz) + "</search>" \
298-
"<check mode=#{mode}>" + _to_ruby(op_args[check], context_type, tz) + "</check></find>"
297+
clause = "<find><search>" + _to_ruby(op_args["search"], context_type, tz, :obj_name => nil) + "</search>" \
298+
"<check mode=#{mode}>" + _to_ruby(op_args[check], context_type, tz, :obj_name => nil) + "</check></find>"
299299
when "key exists"
300-
clause, = operands2rubyvalue(operator, op_args, context_type)
300+
clause, = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
301301
when "value exists"
302-
clause, = operands2rubyvalue(operator, op_args, context_type)
302+
clause, = operands2rubyvalue(operator, op_args, context_type, :obj_name => obj_name)
303303
when "is"
304-
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type)
304+
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type, :obj_name => obj_name)
305305
col_type = Target.parse(col_name).column_type
306306
value = op_args["value"]
307307
clause = if col_type == :date && !RelativeDatetime.relative?(value)
@@ -310,7 +310,7 @@ def self._to_ruby(exp, context_type, tz)
310310
ruby_for_date_compare(col_ruby, col_type, tz, ">=", value, "<=", value)
311311
end
312312
when "from"
313-
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type)
313+
col_ruby, _value = operands2rubyvalue(operator, {"field" => col_name}, context_type, :obj_name => obj_name)
314314
col_type = Target.parse(col_name).column_type
315315

316316
start_val, end_val = op_args["value"]
@@ -703,7 +703,7 @@ def self.quote_by(operator, value, column_type = nil)
703703
end
704704
end
705705

706-
def self.operands2rubyvalue(operator, ops, context_type)
706+
def self.operands2rubyvalue(operator, ops, context_type, obj_name: nil)
707707
if ops["field"]
708708
if ops["field"] == "<count>"
709709
["<count>", quote(ops["value"], :integer)]
@@ -713,6 +713,9 @@ def self.operands2rubyvalue(operator, ops, context_type)
713713

714714
[if context_type == "hash"
715715
"<value type=#{col_type}>#{ops["field"].split(".").last.split("-").join(".")}</value>"
716+
elsif obj_name && !virtual_custom_attribute?(target.column) && target.associations.empty?
717+
# TODO: handle value for fields with associations (they could be habtm, has_one, has_many, belongs_to)
718+
"#{obj_name}.#{target.column}"
716719
else
717720
"<value ref=#{target.model.to_s.downcase}, type=#{col_type}>#{target.tag_path_with}</value>"
718721
end, quote_by(operator, ops["value"], col_type)]
@@ -838,6 +841,10 @@ def self.sanitize_regular_expression(string)
838841
string.gsub(%r{\\*/}, "\\/").gsub(/\\*#/, "\\#")
839842
end
840843

844+
def self.virtual_custom_attribute?(attribute)
845+
attribute.include?(CustomAttributeMixin::CUSTOM_ATTRIBUTES_PREFIX)
846+
end
847+
841848
def self.escape_virtual_custom_attribute(attribute)
842849
if attribute.include?(CustomAttributeMixin::CUSTOM_ATTRIBUTES_PREFIX)
843850
uri_parser = URI::RFC2396_Parser.new

0 commit comments

Comments
 (0)