-
Notifications
You must be signed in to change notification settings - Fork 162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for ANY/ALL to evaluate conditions for array items #218
Comments
The special syntax which could work for both the array of hashes and array of simple values: With array of simple values like
With array of hashes like
But that I imagine would complicate parsing a lot, as now the expression inside ALL() should no longer be evaluated directly, but needs to be changed first to make sense for each item in array and only then to be evaluated against each item. Also referencing some other value from the context should also work somehow:
|
This sounds like a good addition. The syntax should probably be something like
If you would like to tackle this, that would be great and I can provide support if you have questions. If not, I understand and I will plan to implement at some point, but I'm not sure how soon that would be. |
I would love to tackle this. I have some questions though to help me understand if I'll be able to do it. I like the proposed syntax and thank you for pointing me to calc = Dentaku::Calculator.new
Dentaku::AST::Function.register(:all, :logical, ->(collection, attr, expr) {
collection.all? do |item|
calc.store(Hash[attr, item]) { calc.evaluate(expr) }
end
})
calc.evaluate(
"ALL(users, 'user', 'user.a_score > 1 AND user.b_score < 10')",
users: [{ a_score: 3, b_score: 7 }]
)
# => true Now the questions are:
|
Bad news / good news. The bad news is that (as you pointed out) regarding function access to the calculator -- currently there is no access, so it will take a bit of a refactor to make that work. The good news is that the parser already handles the syntax (without quotes). |
Better news: This turned out to be fairly straightforward! Do you think #220 will work for your use case? |
@rubysolo Oh my! Thank you so much! I'll test it with all possible use cases soon and will let you know. But I think everything would work just fine. |
@rubysolo I've checked #220 with all my current and future use cases and it is working perfectly for me! 🎉 It handles simple cases as shown in specs in #220 : calculator.evaluate!('ANY(users, u, u.age > 33)', users: [
{name: "Bob", age: 44 },
{name: "Jane", age: 27 }
]) It handles more complex expressions (added calculator.evaluate!('ANY(users, u, u.age > 33 AND u.gender = 'female'")', users: [
{name: "Bob", age: 44, gender: "male" },
{name: "Jane", age: 27, gender: "female" }
]) It also allows to nest ALL/ANY (this is a must of course for proper implementation), which was something not possible with my workaround implementation and quoted syntax above. calculator.evaluate!('ANY(users, u, u.age > 33 AND u.gender = 'female' AND ANY(u.hobbies, h, h = 'guitar')")', users: [
{name: "Bob", age: 44, gender: "male", hobbies: ["guitar", "baseball"] },
{name: "Jane", age: 27, gender: "female", ["drawing", "singing"] }
]) I have other functions in mind, once you ship #220 I would be happy to contribute these, if you'll find them generally useful to be included in Dentaku itself. |
FIrst function I have in mind is MAP. My use case is this:
Now in addition to input with single values like Let's say there is an API request with this data:
So at level 1 there will be an expression defined like And on the UI level, inputs which are arrays are handled a bit different from single value, meaning you still have the same UI with 3 text inputs to define condition, but instead of being able to select only For this to work I'm now using an improved module Dentaku
module AST
class Map < Function
def self.min_param_count
3
end
def self.max_param_count
3
end
def deferred_args
[1, 2]
end
def value(context = {})
collection = @args[0].value(context)
item_identifier = @args[1].identifier
expression = @args[2]
collection.map do |item_value|
expression.value(
context.update(
FlatHash.from_hash(item_identifier => item_value)
)
)
end
end
end
end
end
Dentaku::AST::Function.register_class(:map, Dentaku::AST::Map) Do you think this function can be included in Dentaku too or do you feel like this use case is too specific? If you think it should be included in Dentaku I'll submit a PR with tests. |
Another function I have in mind is a The implementation is as follows, but it has a bug: it only works when keys for hashes in array are strings, but doesn't work when symbols are used as keys. And the calc.evaluate("PLUCK(users, age)", users: [{ age: 44 }, { age: 27 }]) # => nil
calc.evaluate("PLUCK(users, age)", users: [{ "age" => 44 }, { "age" => 27 }]) # => [24, 77]
calc.evaluate("MAP(users, u, u.age)", users: [{ age: 44 }, { age: 27 }]) # => [24, 77]
calc.evaluate("MAP(users, u, u.age)", users: [{ "age" => 44 }, { "age" => 27 }]) # => [24, 77] module Dentaku
module AST
class Pluck < Function
def self.min_param_count
2
end
def self.max_param_count
2
end
def deferred_args
[1]
end
def value(context = {})
collection = @args[0].value(context)
property = @args[1]
collection.map do |item|
property.value(
context.update(
FlatHash.from_hash(item)
)
)
end
end
end
end
end
Dentaku::AST::Function.register_class(:pluck, Dentaku::AST::Pluck) I'm not sure yet what would be the correct fix, but what I was actually doing before was not using Dentaku evaluation and instead did this: collection.map do |item|
item[attr.to_sym] || item[attr]
end And it seems to match current Dentaku behavior, but I'm not sure if it covers all cases and I don't want to couple to my expectations/current behavior of Dentaku which might change in the future. Do you think this function can be included in Dentaku too or do you feel like this use case is too specific? If you think it should be included in Dentaku I'll submit a PR with tests. |
Great! I'll merge this. I think |
Thank you for you awesome gem!
I've been using it for years without any issues.
Now I have this new use case where to avoid using external logic to prepare context for calculator first it would be great to be able to do the same things you can do with
Enumerable#all?
andEnumerable#any?
methods in Ruby.First I've checked if hash works and it does:
Then I've added a custom function to evaluate condition for each array item:
It also works, but I want a properly defined and more generic solution, not this quick hack.
calc
instance and callingevaluate
with each item from array as a context.ALL(users, 'a_score > 1 AND b_score < 10')
ALL
function, but it shouldn't be limited to only supporting array of hashes. Array of integers/strings/etc. for example should also work somehow, so the syntaxt needs to be generic and make sense everywhere else in Dentaku.all?
conditions it should be possible to reference not only an array item in conditions but anything from context or maybe calling some a function and so on.So the following should be also possible to describe:
Slightly related to my use case here is #208 as I would sometimes need this to work (and currently it doesn't):
As a workaround I can add
FIRST()
function so that:The text was updated successfully, but these errors were encountered: