Drools rule iterate collections and verify property - rules

With Drools Rules "mvel" how to iterate over a collection and verify a property for each object in the collection?

Look for the forall keyword in the reference manual (follow documentation on the drools page).

Here's the code for going over the collection of Interests inside a Person object and checking if one of them is contains an interestName field "Running":
rule "likes running?"
when
$p : Person()
$inter : Interest ( interestName == "Running" ) from $p.interests
then
System.out.println("I like running too.");
end

Related

Firestore: How to query a map of roles inside a subcollection of documents?

I have the following subcollection structure:
/projects/{projectId}/documents/{documentId}
Where each document has an access map used for checking security rules for each document:
{
title: "A Great Story",
content: "Once upon a time ...",
access: {
alice: true,
bob: false,
david: true,
jane: false
// ...
}
}
How the security rules are set:
match /{path=**}/documents/{documentId} {
allow list, get: if resource.data.access[request.auth.uid] == true;
}
When I want to query all documents alice can access across all projects:
db.collectionGroup("documents").where(`access.${uid}`, "==", true);
I get an error telling me that I need to create a collection group index for access.alice, access.david, etc.
How can I overcome this indexing issue with collection group queries?
As a quick overview of indexes, there are two types - a composite index (used for connecting multiple fields together) or a single-field index. By default, single-field indexes are automatically created for each field of a document for ordinary collection queries and disabled for collection group queries. If a field's value is an array, the values of that array will be added to an Array Contains index. If a field's value is primitive (string, number, Timestamp, etc), the document will be added to the Ascending and Descending indexes for that field.
To enable querying of the access field for a collection group query, you must create the index. While you could click the link in the error message you get when making the query, this would only add an index for the user you were querying (e.g. access.alice, access.bob, and so on). Instead, you should use the Firebase Console to tell Cloud Firestore to create the index for access (which will create indexes for each of its children). Once you've got the console open, use the "Add Exemption" button (consider this use of "exemption" to mean "override default settings") to define a new indexing rule:
In the first dialog, use these settings:
Collection ID: "documents"
Field Path: "access"
Collection: Checked ✔
Collection group: Checked ✔
In the second dialog, use these settings:
Collection Scope
Collection Group Scope
Ascending
Enabled
Enabled
Descending
Enabled
Enabled
Array Contains
Enabled
Enabled
In your security rules, you should also check if the target user is in the access map before doing the equality. While accessing a missing property throws a "Property is undefined on object." error which denies access, this will become a problem if you later combine statements together with ||.
To fix this, you can either use:
match /{path=**}/documents/{documentId} {
allow list, get: if request.auth.uid in resource.data.access
&& resource.data.access[request.auth.uid] == true;
}
or provide a fallback value when the desired key is not found:
match /{path=**}/documents/{documentId} {
allow list, get: if resource.data.access.get(request.auth.uid, false) == true;
}
As an example of the rules breaking, let's say you wanted a "staff" user to be able to read a document, even if they aren't in that document's access map.
These rules would always error-out and fail (if the staff member's user ID wasn't in the access map):
match /{path=**}/documents/{documentId} {
allow list, get: if resource.data.access[request.auth.uid] == true
|| get(/databases/$(database)/documents/users/$(request.auth.uid)).data.get("staff", false) == true;
}
whereas, these rules would work:
match /{path=**}/documents/{documentId} {
allow list, get: if resource.data.access.get(request.auth.uid, false) == true
|| get(/databases/$(database)/documents/users/$(request.auth.uid)).data.get("staff", false) == true;
}
As a side note, unless you need to query for documents where a user does not have access to it, it is simpler to simply omit that user from the access map.
{
title: "A Great Story",
content: "Once upon a time ...",
access: {
alice: true,
david: true,
// ...
}
}
As noted on https://cloud.google.com/firestore/docs/query-data/queries#collection-group-query "Before using a collection group query, you must create an index that supports your collection group query. You can create an index through an error message, the console, or the Firebase CLI."
In this case you'll want to enable CollectionGroup queries on the access map (https://cloud.google.com/firestore/docs/query-data/indexing#add_a_single-field_index_exemption)
You would have to create an index for it as mentioned by #Jim.
If you don't want to get much into it, you can just catch the error and it'll have a direct link to to create one as shown below:
db.collectionGroup("documents").where(`access.${uid}`, "==", true).get().then((snap) => {
//
}).catch((e) => console.log(e))
Other ways as mentioned in the documentation include:
Before using a collection group query, you must create an index that
supports your collection group query. You can create an index through
an error message, the console, or the Firebase CLI.

Update an object with notation with a parameter?

In my firebase i have a collection, inside there is a document, and inside there is an object :
object 1
key1:value
key2:value
key3:value
I would like to only update certain keys inside an object say
object1 - key1 and key2.
to do that, i need notation.
the problem is that I pass a parameter to the function that save :
function updateit(product,target)
{
db.collection("Stores").doc(target).update({
product
})
So here if I pass a product that contains only key 1, it will override the previous.
So, I tried to pass this object with notation :
product["product"+".title"] = "xxxxx"; // a new pair in product to pass
and it didn't work, it will save a new object (override) with fields like :
product
product.title=xxxxx
How should you do such a simple thing ?
ok obviously, this is the answer :
db.collection("Stores").doc(targetStore).update(
product // no {} around 'product', not as object!
)
see the comment that explains it all.

How to find if a filter is in the C# GlobalFilter

The ASP.Net MVC has the GlobalFilters.Filters. I want to find if RequireHttpsAttribute is in the GlobalFilters. I tried using GlobalFilters.Filters.Contains() but not knowing what type the RequiredHttpsAtrribute is. Thanks.
You can use some LINQ:
var hasAttribute = GlobalFilters.Filters
.Where(f => f.Instance is RequireHttpsAttribute)
.Any();
Filters collection has a list of System.Web.Mvc.Filter elements. I filtered global filters collection for a filter, whose property Instance is of type RequireHttpsAttribute. Then I used Any to find out if there is at least one element in the collection.
The result hasAttribute is true if global filters collection has RequireHttpsAttribute, and false if not.

Forget doesn't work

If I try to remove an item from this collection
$examples = Example::where('example', '=', $data['example'])->get();
by doing
$examples->forget(20);
it doesn't remove the item from the collection, I still get back all the items that were in there originally. I have read the Laravel documentation and the api docs. And it should work (I think) but it doesn't.
Could someone point me out what I am doing wrong here?
This still returns an object.
$examples->forget(49);
return $examples->find(49);
P.S. Ever other method like push or get works.
Thanks alot!
You did a small mistake, actually you didn't notice that. I did myself :).
Forget use the array key to delete an object item from collection.
Array(0 => 'abc'
1 => 'bcd'
49 => 'aaa'
)
$examples->forget(49);
^^ array key 49
Where as, find use the id to find an object from an collection
table: examples
id example
1 abc
49 bce
$examples->find(49);
^^ `example id`
According to the documentation it says that forget() works by key. The word "key" is ambiguous because what they mean to say is array key also known as "index" and not model key which is the id.
However, in the other methods such as find() or contains() they use the word "key" to mean model key so you can see the confusion.
When you look at the source you can see that the forget() method is found in the Illuminate\Support\Collection class and not in Illuminate\Database\Eloquent\Collection.
My theory is that the support class is supposed to be more generic so it doesn't consider the model keys, but really I don't know.
I just wanted to add on to the Anam's answer. Once you have a collection you can then loop through it like this to delete by ID
function forgetById($collection,$id){
foreach($collection as $key => $item){
if($item->id == $id){
$collection->forget($key);
break;
}
}
return $collection;
}
$examples = Example::where('example', '=', $data['example'])->get();
$examples = forgetById($examples,20);
you could do
$examples->reject(20);
Like most other collection methods, reject returns a new collection instance; it does not modify the collection it is called on.

Get Meteor collection by name

Suppose I write:
new Meteor.Collection("foos");
new Meteor.Collection("bars");
Is there an API for accessing those collections by name? Something like Meteor.Collection.get(name), where name is "foos" or "bars"? I know I could write something like
var MyCollections = {
foos: new Meteor.Collection("foos");
bars: new Meteor.Collection("bars");
}
and then use MyCollections[name], but I'd prefer to use an existing API if one exists.
Based on Shane Donelley's mongoinspector
https://github.com/shanedonnelly1/mongoinspector
getCollection = function (string) {
for (var globalObject in window) {
if (window[globalObject] instanceof Meteor.Collection) {
if (globalObject === string) {
return (window[globalObject]);
break;
};
}
}
return undefined; // if none of the collections match
};
I've just found that package : https://github.com/dburles/mongo-collection-instances/
It allow you to
Foo1 = new Mongo.Collection('foo'); // local
Foo2 = new Mongo.Collection('foo', { connection: connection });
Mongo.Collection.get('foo') // returns instance of Foo1
Mongo.Collection.get('foo', { connection: connection });
// returns instance of Foo2
Hope it will help
This feature was added to Meteor in Feb 2016: "Provide a way to access collections from stores on the client"
It works like this:
Meteor.connection._stores['tasks']._getCollection();
And I was using it as follows to test inserts using the javascript console:
Meteor.connection._stores['tasks']._getCollection().insert({text:'test'});
For the insert it required the insecure package to still be installed otherwise got an access denied message.
As far as I can see in the collection.js source there currently is no way in the api to get an existing Collection by name, once it has already been initialized on the server. It probably wouldn't be hard to add that feature.
So, why not fork Meteor and submit a patch or create a smart package and share it I'm sure there are others out there who'd like the same feature.
With https://github.com/dburles/mongo-collection-instances you can use Mongo.Collection.get('collectionname')
Note that the parameter you're inserting is the same one you use when creating the collection. So if you're using const Products = new Mongo.Collection('products') then you should use get('products') (lowercase).
Note that they have a return value, so you can just do
var Patterns = new Meteor.Collection("patterns");
and use Patterns everywhere.
And when you need to subscribe to server updates, provide "patterns" to Meteor.subscribe().
If you have the same code for multiple collections, the chance is high that you're doing something wrong from a software engineering viewpoint; why not use a single collection with a type field (or something else that differentiates the documents) and use that instead of using multiple collections?
Rather than looking, I've just been doing:
Foos = new Meteor.Collection("foos");
or possibly put it inside another object. I haven't really been making a Collections collection object.
It seems there is no way to get at the wrapped Meteor.Collection object without saving it at creation time, as others have mentioned.
But there is at least a way to list all created collections, and actually access the corresponding Mongo LocalCollection object. They are available from any Meteor Collection object, so to keep it generalistic you can create a dummy collection just for this. Use a method as such (CoffeeScript):
dummy = new Meteor.Collection 'dummy'
getCollection = (name) ->
dummy._driver.collections[name]
These objects do have all the find, findOne, update et al methods, and even some that Meteor doesn't seem to expose, like pauseObservers and resumeObservers which seem interesting. But I haven't tried fiddling with this mongo LocalCollection reference directly to knowif it will update the server collection accordingly.
var bars = new Meteor.Collection("foos");
Judging by what the collection.js does, the line we use to instantiate the collection object opens a connection to the database and looks for the collection matching the name we give. So in this case a connection is made and the collection 'foos' is bound to the Meteor.Collection object 'bars'. See collection.js AND remote_collection_driver.js within the mongo-livedata package.
As is the way with MongoDB, whilst you can, you don't have to explicitly create collections. As stated in the MongoDB documentation:
A collection is created when the first document is inserted.
So, I think what you're after is what you already have - unless I've totally misunderstood what you're intentions are.
You can always roll your own automatic collection getter.
Say you have a couple of collections called "Businesses" and "Clients". Put a reference each into some "collections" object and register a Handlebars helper to access those "collections" by collections["name"].
i.e. put something like this on the client-side main.js:
collections = collections || {};
collections.Businesses = Businesses;
collections.Clients = Clients;
Handlebars.registerHelper("getCollection", function(coll) {
return collections[coll].find();
});
Then in your HTML, just refer to the collection by name:
{{#each getCollection 'Businesses'}}
<div> Business: {{_id}} </div>
{{/each}}
{{#each getCollection 'Clients'}}
<div> Client: {{_id}} </div>
{{/each}}
Look ma, no more generic "list all records" boilerplate js required!

Resources