Skip to content
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

Update "in" operator #18

Merged
merged 3 commits into from
Oct 11, 2024
Merged

Update "in" operator #18

merged 3 commits into from
Oct 11, 2024

Conversation

wildwalks
Copy link
Contributor

Thanks Roberto-- love the project. I made this tweak to my code, think it might help others.

Currently the 'in' operator tests if a value is in an array field. This update still allows that, but also
allows people to test if a string field is in an array of their choosing.
(it allows users to effectively do a simple 'or' test)

eg
$url = "https://dummyjson.com/posts";
$posts = Block
::fromJsonUrl($url)
->getBlock("posts");
//if field id is in this array
$someOddPosts = $posts->where("id", "in", array(1,3,5,7,9));

// if the string "love" in the tags array field
$lovePosts = $posts->where("tags", "in", "love");

(I can update this further, eg testing if a string, should maybe also allow numeric,bool?? or just not an array?? If two arrays, perhaps could get intersection) -- but that was feeling complex

Currently the 'in' operator tests if a value is in an array field 
this update still allows that, but also 
allows people to test if a string field is in an array of their choosing.

eg
$url = "https://dummyjson.com/posts";
$posts = Block
    ::fromJsonUrl($url)
    ->getBlock("posts");
//if field id is in this array
$someOddPosts = $posts->where("id", "in", array(1,3,5,7,9));

// if the string "love" in the tags array field
$lovePosts = $posts->where("tags", "in", "love");

(I can update this further, eg testing if a string, should maybe also allow numeric,bool??  or just not an array??  If two arrays, perhaps could get intersection) -- but that was feeling complex
@roberto-butti
Copy link
Contributor

Hi @wildwalks , thank you for checking the library.
I totally agree, and I understand the value of this implementation. I think it solves a nice use case.
Furthermore, I want to hear your thoughts about this: What if instead of using the in operator (which means "an element is in an array), we could introduce a new operator like the has operator (which means "an array has an element")?

@wildwalks
Copy link
Contributor Author

thanks @roberto-butti, I really appreciate your comments.
Interesting idea with 'has'. I have been playing a bit more with data-block the last day and have a need for a few extra bits of logic. I am mostly thinking;

  • More operators: e.g. 'is not in' !in or !has, etc. There are many naming conventions. I think first place is to cover off all the base PHP operators https://www.php.net/manual/en/language.operators.php. But there are so many more ways of comparing data - I like this list, https://docs.directus.io/reference/filter-rules.html, but I do not think it is intuitive. Maybe you have a list?. Having a planned list might help give some direction. We do not have to do them all at once :), I am happy to help with these, but maybe we come up with a list first to help improve consistency as it grows??

These next few I am not sure about, might not be right but some early ideas. You know the code, if these are separate, maybe we just leave these points below to another time. Scope creep warning :)

  • Expression options: this is probably not the correct term, but basically it seems that data-block allows us to compare a value to a field. I have come across a case where I want to compare two fields, but I tried to do a get() and looking at the code it seems to require a set value? I also wanted to do a where (and sort) against the abs() sum of a value and field. (yes I could probably pre-calcuate these, but it get complex quickly)
    I am not sure of the best way to deal with this. It would be great if we could nest expressions eg ->where("field.total" ">" abs(array_sum(-15, "field.largest"))) -- but I have no idea how to implement that (I am not that nerdy sorry :)

  • Flexible: I wondered if there was also a way to allow users to exec their own logic in some of these. eg
    $return = $data->where(function ($item) {
    if($item->get("pets.dog") == "spot") {return true;)
    if($item->get("pets.cat") == "snowie") {return true;)
    if($item->get("pets")->count > 100) {return true;)
    return false;
    });

so specifically for "has", I guess that can work if we have a clear left side and right side, but I guess that is why I mentioned the expression options idea, does this can confuse this terminology later?? in SQL there is an any() function that is similar as well.
Anyway -- I guess I am not worried about the naming so much -- but it might be helpful to have a full-ish list of wanted operators before we start adding these. We do not want to be changing these down the track :)

@roberto-butti
Copy link
Contributor

Thank you @wildwalks for sharing your thoughts.

In the where method, i would like to keep the basic operators.

Then, I agree to make it more flexible, so this is the reason I'm thinking of introducing a Filter class that can be instantiated and filled by the user for more complex cases (also and/or) and custom functions. And send to the where method (or better I will see if we will use a different method that accepts Filter class).
The Filter class will have a fluent interface in order to chain methods.

At the moment, I think we can add "has" as a "basic" operator.

@wildwalks
Copy link
Contributor Author

Great -- good plan :)
yes, using filter sounds like a good way forward.
'has' works for me.

Shall I update this branch to work something like this?

  1. ->where("data.fieldname" "has" array('list', 'of','possible','matches'))

  2. shall I also add "not has" as well "!has" ->where("data.fieldname" "!has" array('list', 'of','possible','matches'))

  3. and for consistency sake "!in"

  4. and update the readme

@roberto-butti
Copy link
Contributor

i think to keep it easy, an array in the data has to check if has a element (scalar)

->where("field" "has" , "value")

@wildwalks
Copy link
Contributor Author

I am sorry, I am a bit confused. That sounds like the same behaviour as the current implementation of 'in', where we check if an array in the data source contains a scaler? I was proposing we test if a scaler in the data is in an array provided in the where(). (is in the provided array has (contains) the scaler in the data)

I was picturing a change like the following, the opposite too in. But that seems different from your wording and feels a backwards? Your project, your call.
Is this what you want?
'in' => in_array($value, $elementToCheck->get($field)),
'has' => in_array($elementToCheck->get($field), $value),

Sorry, I did not mean to make this complicated.

@roberto-butti
Copy link
Contributor

The project is open source, so all suggestions are welcome.
And, @wildwalks , i appreciated a lot of all your thoughts shared on this thread
My approach is to go on with small steps.
My thought is that first step to implement the 'has' operator is more or less in the way you mentioned:

'in' => in_array($value, $elementToCheck->get($field)),
'has' => in_array($elementToCheck->get($field), $value),

Adding some tests helps to understand the scenario and the problem solved with the new operator.

  • The in operator: check if a scalar (input) is present in an array (in the data structure)
  • The has operator: check if an array (input) contains a scalar (in the data structure)

I think with one PR, we can cover this scenario. If you have in mind an additional scenario, we can open another PR

@roberto-butti
Copy link
Contributor

Hi @wildwalks , I understand where we were having a misunderstanding. The current implementation of the "in" operator is a bit wrong.
Forget the current implementation for a moment, which has to be fixed.

Using your initial example, on the first message of this thread, the expected behavior i would like to achieve is (adjusting the current behavior of in operator and adding a new has operator):

// the id field that is a number (scalar) is checked if it is included in an array
$someOddPosts = $posts->where("id", "in", array(1,3,5,7,9));

// if the string "love" in the `tags` array field
$lovePosts = $posts->where("tags", "has", "love");

@roberto-butti
Copy link
Contributor

I wrote some tests that explain what i have in mind:

test(
    'where method, in operator',
    function () use ($fruitsArray): void {
        $data = Block::make($fruitsArray);
        $greenOrBlack = $data->where("color", "in", ["green", "black"]);
        expect($greenOrBlack)->tohaveCount(1);
        $noResult = $data->where("color", "in", []);
        expect($noResult)->tohaveCount(0);
        $greenOrRed = $data->where("color", "in", ["green", "red"]);
        expect($greenOrRed)->tohaveCount(3);
    },
);

test(
    'where method, has operator',
    function () use ($fruitsArray): void {
        $data = Block::make($fruitsArray);
        $sweet = $data->where("tags", "has", "sweet");
        expect($sweet)->tohaveCount(2);
        $noResult = $data->where("tags", "has", "not-existent");
        expect($noResult)->tohaveCount(0);
        $softFruit = $data->where("tags", "has", "soft");
        expect($softFruit)->tohaveCount(1);
    },
);

Where the "color" field is a string, and the "tags" field is an array of strings

@roberto-butti
Copy link
Contributor

Hi @wildwalks, I would like to apply changes to the "in" and "has" operators. Do you have any feedback about the latest message I shared? I think having your opinion is great here.

@wildwalks
Copy link
Contributor Author

Hi @roberto-butti
Very sorry I went on holidays this week and away from my computer and didn't see your earlier messages.
That all looks good to me -- more intuitive.
We need to note the potential breaking change for people already using the "in" operator and upgrading.
Happy to go ahead with that.
Once that is in I can do !in and !has as a separate PR if you want?
thanks

@roberto-butti
Copy link
Contributor

yeah, let's start with in and has. then we will decide on the next feat request/pr which is the best operator for"not", also for other operators.
most important, enjoy your holiday

@roberto-butti
Copy link
Contributor

There's no pressure at all, @wildwalks. I'm asking to know your plans because I want to create a new release.
I would like to know if I have to wait for your changes, or if I can go ahead. Feel free to take your time :)

@wildwalks
Copy link
Contributor Author

Hi. Sorry. Just got back today. Bit behind on emails. I managed to get offline for a while :)

No worries I can do some tomorrow. But I am a bit lost in this thread sorry.
The changes with In and Has - have you implemented them in another PR or shall I do them here (happy for you Todo it if quicker and easier). I am not worried about credit or anything.

Shall I update this PR with the new "in and Has"?

@roberto-butti
Copy link
Contributor

Hi. Sorry. Just got back today. Bit behind on emails. I managed to get offline for a while :)

No worries I can do some tomorrow. But I am a bit lost in this thread sorry. The changes with In and Has - have you implemented them in another PR or shall I do them here (happy for you Todo it if quicker and easier). I am not worried about credit or anything.

Shall I update this PR with the new "in and Has"?

if you want you can apply the changes you can pick from the other branch in this one, so we can close this one

Change logic for 'in' to be more intuitive and added 'has' (similar to how 'in' was)
@wildwalks
Copy link
Contributor Author

Sorry this took me a while.
Okay I think I have got this right.
added "has" queryblocks
added "in" and "has" unit tests (including adding test data)
Updated the older 'in' queryblocktests to 'has'
and updated readme.
(and updated against the latest branch)

Fingers crossed :)

@roberto-butti
Copy link
Contributor

Thank you @wildwalks i'm going to merge it

@roberto-butti roberto-butti merged commit d0448c1 into Hi-Folks:main Oct 11, 2024
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants