Repository: MasoniteFramework/orm

Issue:
Cannot remove / filter out object (where_not_in) from Collection
**Describe the bug**
when a Collection is populated with a `list` of `objects` there is no way to remove  obects that meet a criteria.
It seems that there is no `delete()` method on `Collection` that prunes out the filtered objects from the collection.
All methods that take a `key` work on an index which is not going to work in this case.

I tried `.reject()` but could not get that to work. 

Example:
```python
users_data = [
    {'first': 'Pat', 'last': 'Knox', 'email': 'imperial1821@example.com'}, 
    {'first': 'Von', 'last': 'Daugherty', 'email': 'hoped1802@duck.com'}, 
    {'first': 'Merrie', 'last': 'Hart', 'email': 'tourist1956@yandex.com'}
]
users = Collection(users_data)
emails_to_delete = ["hoped1802@duck.com"]

# filter for the objects to remove
cleaned_users = users.where_in("email", emails_to_remove).xxx() # <-- what goes here?

# this does not work
filtered_users = users.reject(lambda item: item["email"] in emails_to_delete ) 

```

**Expected behavior**
There should be a `delete()` method for use on collections of objects after filtering them.

**Desktop (please complete the following information):**
 - OS: Mac OSX
 - Version 12.7

**What database are you using?**
 - Type: N/A
 - Version N/A
 - Masonite ORM : 2.24.0

**Additional context**
N/A

Hints from issue comments:
@circulon: After thinking about this overnight.... in my sleep ;)

I realised that instead of a `delete()` method a `where_not_in()` method would provide the same results.
Working on a PR for this now ;)

Solution patch:
diff --git a/src/masoniteorm/collection/Collection.py b/src/masoniteorm/collection/Collection.py
index e238db8a..f0c81eff 100644
--- a/src/masoniteorm/collection/Collection.py
+++ b/src/masoniteorm/collection/Collection.py
@@ -430,6 +430,13 @@ class Collection:
         return self.__class__(attributes)
 
     def where_in(self, key, args: list) -> "Collection":
+        # Compatibility patch - allow numeric strings to match integers
+        # (if all args are numeric strings)
+        if all(
+                [isinstance(arg, str) and arg.isnumeric() for arg in args]
+        ):
+            return self.where_in(key, [int(arg) for arg in args])
+
         attributes = []
 
         for item in self._items:
@@ -445,12 +452,30 @@ class Collection:
             if comparison in args:
                 attributes.append(item)
 
+        return self.__class__(attributes)
+
+    def where_not_in(self, key, args: list) -> "Collection":
         # Compatibility patch - allow numeric strings to match integers
-        # (if all args are numeric strings and no matches were found)
-        if len(attributes) == 0 and all(
-            [isinstance(arg, str) and arg.isnumeric() for arg in args]
+        # (if all args are numeric strings)
+        if all(
+                [isinstance(arg, str) and arg.isnumeric() for arg in args]
         ):
-            return self.where_in(key, [int(arg) for arg in args])
+            return self.where_not_in(key, [int(arg) for arg in args])
+
+        attributes = []
+
+        for item in self._items:
+            if isinstance(item, dict):
+                if key not in item:
+                    continue
+                comparison = item.get(key)
+            else:
+                if not hasattr(item, key):
+                    continue
+                comparison = getattr(item, key)
+
+            if comparison not in args:
+                attributes.append(item)
 
         return self.__class__(attributes)
 


Test patch:
diff --git a/tests/collection/test_collection.py b/tests/collection/test_collection.py
index 02bab4cf..f1822be1 100644
--- a/tests/collection/test_collection.py
+++ b/tests/collection/test_collection.py
@@ -64,6 +64,26 @@ class TestCollection(unittest.TestCase):
         self.assertEqual(len(collection.where_in("id", ["3"])), 1)
         self.assertEqual(len(collection.where_in("id", ["4"])), 0)
 
+        self.assertEqual(len(collection.where_in("name", ["Joe"])), 2)
+
+    def test_where_not_in(self):
+        collection = Collection(
+            [
+                {"id": 1, "name": "Joe"},
+                {"id": 2, "name": "Joe"},
+                {"id": 3, "name": "Bob"},
+            ]
+        )
+        self.assertEqual(len(collection.where_not_in("id", [1, 2])), 1)
+        self.assertEqual(len(collection.where_not_in("id", [3])), 2)
+        self.assertEqual(len(collection.where_not_in("id", [4])), 3)
+
+        self.assertEqual(len(collection.where_not_in("id", ["1", "2"])), 1)
+        self.assertEqual(len(collection.where_not_in("id", ["3"])), 2)
+        self.assertEqual(len(collection.where_not_in("id", ["4"])), 3)
+
+        self.assertEqual(len(collection.where_not_in("name", ["Joe"])), 1)
+
     def test_where_in_bool(self):
         nested_collection = Collection(
             [