Mongoid behavior when discriminator key is not found - mongodb

Looking at mongoid inheritance documentation it says:
Similarly, when querying by parent classes (Canvas in this example), any documents in the collection that do not have a discriminator value, or whose discriminator value does not map to either the parent or any of its descendants, will be returned as instances of the parent class.
Using the example classes on that page:
class Canvas
include Mongoid::Document
field :name, type: String
embeds_many :shapes
end
class Browser < Canvas
field :version, type: Integer
scope :recent, ->{ where(:version.gt => 3) }
end
If I insert a document into the Canvas collection like so:
{
"_id": { "$oid": "612d5bd10170cb02ad9bfbac" },
"_type":"SpecialCanvas"
}
And then query for that document like so:
Canvas.find_by(id: '612d5bd10170cb02ad9bfbac')
I get this error:
Mongoid::Errors::UnknownModel:
message:
Attempted to instantiate an object of the unknown Model 'SpecialCanvas'.
summary:
A document with the value 'SpecialCanvas' at the key '_type' was used to instantiate a model object but Mongoid cannot find this Class.
resolution:
The _type field is a reserved one used by Mongoid to determine the class for instantiating an object. Please don't save data in this field or ensure that any values in this field correspond to valid Models.
from /usr/local/bundle/gems/mongoid-7.2.5/lib/mongoid/factory.rb:87:in `rescue in from_db'
Caused by NameError: uninitialized constant SpecialCanvas
from /usr/local/bundle/gems/activesupport-5.2.6/lib/active_support/inflector/methods.rb:283:in `block in constantize'
But, based on the documentation, I would expect it to just return an instance of the parent class (Canvas). Am I misunderstanding this?

Seems like a documentation bug to me. Please report it to https://jira.mongodb.org/browse/MONGOID.

Related

MongoDB: Can _id be used to reference another object in a One-to-one relationship?

Let's suppose we have two classes, Class A and Class B. Each Class A is created with a random ObjectID, generated by MongoDB. It can only have a ClassB associated, but can also have none aswell.
Class B on the other hand, when created, must be associated with a existing Class A, and can only have one Class A associated.
In this case, should the _id of Class B be the same as class A, or that is bad pratice, and just like class A, the _id should be generated by MongoDB and another field should reference to the associated to Class A?
In other words, what is correct, example 1 or 2?
Class A
{
"_id": "5ec6f2b8df6300002e004292", // ID generated by MongoDB
"extra info": "extra info goes here"
}
Example 1
Class B
{
"_id": "5ec6f2b8df6300002e004292", // Associated ClassA is referenced by _id
"extra info": "extra info goes here"
}
Example 2
Class B
{
"_id": "5ec6f2e0df6300002e004293", // ID generated by MongoDB
"classA: "5ec6f2b8df6300002e004292", // Class A is referenced by another field
"extra info": "extra info goes here"
}
Both approaches are possible.
The issue with example 1 is it won't be (easily) possible to decouple A and B if you decide to have them separate down the road for whatever reason.

Is there a Deconstruct Mongo Response to DTO short cut?

If I have a table in a mongoDB with five properties and I only want to return four of them and none of the mongo added info such as v1 I can map the reposne to a dto like so,
const product = await this.productModel.findById(productId).exec()
return { id: product.id, title: product.title }
Is there a deconstruct shortcut for the return, to extract every field from an interface (Product) from the product response, to save typing each property out ? If for example im retunring 127 properties from a table of entires with 140.
interface Product {
id: string
title: string
...
}
Unfortunately no, typescript interfaces do not really exist when your program compiles
Interface is a structure that defines the contract in your application. It defines the syntax for classes to follow. Classes that are derived from an interface must follow the structure provided by their interface.
The TypeScript compiler does not convert interface to JavaScript. It
uses interface for type checking. This is also known as "duck typing"
or "structural subtyping".
So, you can't really read interface fields and then write some logic (you can maybe achieve this through reflection but it's a bad practice)
An alternative is to explicitly define what fields are to include/or exclude from your object
Suppose that I have an object with this interface:
interface Foo {
field1: string;
field2: string;
field3: string;
.....
field140: string;
}
What you can do here is to define what properties you want to exclude (you take the exclude approach here since you are returning 127 fields of 140)
// This isn't an implementation with mongoose (if you are using it),
// it's just to give you the idea
const FIELDS_TO_EXCLUDE = ["field128", "field129", "field130", ..., "field140"];
productModel.toDTO(){
const documentData = this;
FIELDS_TO_EXCLUDE.forEach(x => delete documentData[x]);
return documentData;
}
In this way, when you will execute the toDTO function your manipulate itself excluding (or including) the fields you want

MongoDB id remains null after InsertOneAsync

I have a base class Entity that has a string Id member and a derived class A.
But when creating a new instance of the derived class and using InsertOneAsync to add it to my collection, the document is added to the database with null as the Id value.
Using an ObjectId as the Id does seem to work, but I'm trying to prevent MongoDB dependency in my models.
I also experimented with the following code, but the results are the same:
BsonClassMap.RegisterClassMap<Entity>(cm =>
{
cm.MapIdField(x => x.Id).SetSerializer(new StringSerializer(BsonType.ObjectId));
});
I had the same issue and finally got it to work (with the 2.0 driver) with these attributes:
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[BsonIgnoreIfDefault]
Or the equivalent BsonClassMap fluent interface:
BsonClassMap.RegisterClassMap<Entity>(cm =>
{
cm.MapIdField(x => x.Id)
.SetSerializer(new StringSerializer(BsonType.ObjectId))
.SetIgnoreIfDefault(true);
});
I tried to get the same working with .ReplaceOneAsync with Upsert on but that always leaves the id still null
Are you trying not to use ObjectID in your model. The ID doesn't have to be an objectID. You can decorate a string field as ID.
I have used string property with [BsonId] attribute without any issues.

Mongoid/MongoDB: Order a query by the value of an embedded document?

I am attempting to order the results of a query by the value of a specific embedded document, but even with what seems to be a valid set of options and using the $elemMatch operator, my results are coming back in natural order.
My model is composed of Cards, which embeds_many :card_attributes, which in turn reference a specific CardAttributeField and contain an Integer value. I would like to be able to order a collection of Cards by that value.
I am able to isolate a collection of Cards which have a CardAttribute referencing a specific CardAttributeField like this:
cards = Card.where(:card_attributes.elem_match => {
:card_attribute_field_id => card_attribute_field.id
})
If I knew the order in which the card_attributes were set, I could use MongoDB array notation, like this:
cards.order_by(['card_attributes.0.value', :asc])
This does deliver my expected results in test scenarios, but it won't work in the real world.
After much messing around, I found a syntax which I thought would allow me to match a field without using array notation:
cards.asc(:'card_attributes.value'.elem_match => {
:card_attribute_field_id => card_attribute_field.id
})
This produced a set of options on the resulting Mongoid::Criteria which looked like:
{:sort=>{"{#<Origin::Key:0x2b897548 #expanded=nil, #operator=\"$elemMatch\", #name=:\"card_attributes.value\", #strategy=:__override__, #block=nil>=>{:card_attribute_field_id=>\"54c6c6fe2617f55611000068\"}}"=>1}}
However, the results here came back in the same order regardless or whether I called asc() or desc().
Is there any way to do what I'm after? Am I taking the wrong approach, or do I have a mistake in my implementation? Thanks.
Simplified, my model is:
class Card
include Mongoid::Document
# various other fields
has_many :card_attribute_fields
embeds_many :card_attributes do
def for_attribute_field card_attribute_field
where(:card_attribute_field_id => card_attribute_field.id)
end
end
end
class CardAttributeField
include Mongoid::Document
belongs_to :card
field :name, type: String
field :default_value, type: String
field :description, type: String
end
class CardAttribute
include Mongoid::Document
embedded_in :card
field :card_attribute_field_id, type: Moped::BSON::ObjectId
field :value, type: Integer
end

Possible bug in breeze 1.4.14

I haven't tested this against the 1.4.16 release that came out a couple of weeks ago but there is nothing in the release notes about it.
The problem occurs with predicates where the value you are comparing is identical to the name of a property on any entity that breeze knows about. A simple test case is :
var query = breeze.EntityQuery.from('Items');
var pred = breeze.Predicate.create('name', breeze.FilterQueryOp.Contains, searchTerm);
query = query.where(pred);
Where searchTerm is equal to any string other than "name" this produces an oData query as below:
Items?$filter=(substringof(%27somevalue%27%2CName)%20eq%20true)
but if searchTerm = "name" then it produces the following query
Items?$filter=(substringof(Name%2CName)%20eq%20true)
Which istead of comparing the string 'name' against the property Name, it compares the property Name with itself.
I have not tested every operator but as far as I can tell it does not matter which you use you get the same behaviour.
You also get the same problem when querying navigation properties but it usually results in an invalid query. Below is a predicate for the same entity but against a navigation property tags that contains a collection of ItemTag entities that have a "Tag" property on them.
breeze.Predicate.create('tags', breeze.filterQueryOp.Any, 'tag', breeze.filterQueryOp.Contains, searchTerm)
It works fine for any searchTerm other than "tag" where it produces an oData request as below:
Items?$filter=Tags%2Fany(x1%3A%20substringof(%27somevalue%27%2Cx1%2FTag)%20eq%20true)
but if the searchTerm is "tag" then it requests:
Items?$filter=Tags%2Fany(x1%3A%20substringof(Tag%2Cx1%2FTag)%20eq%20true)
which produces an error of "Could not find a property named 'Tag' on type 'Item'" because the property Tag exists on the ItemTag entity.
In short breeze seems to infer that any search term that is identical to the name of a property it knows about, refers to that property rather than being a string literal value.
Has anyone else encountered this?
Is this a bug, or is there a way to explicitly tell breeze to interpret that value as a string literal and not a reference to a property?
I am not sure it is relevant as the server seems to be responding correctly to the requests and it is breeze that is creating incorrect requests but on the server side I am using Web API oData controllers with EF as ORM data layer.
Try
var pred = breeze.Predicate.create('name', breeze.FilterQueryOp.Contains,
{ value: searchTerm, isLiteral: true} );
This is described here ( under the explanation of the value parameter):
http://www.breezejs.com/sites/all/apidocs/classes/Predicate.html#method_create
if the value can be interpreted as a property expression it will be, otherwise it will be treated as a literal.
In most cases this works well, but you can also force the interpretation by making the value argument itself an object with a 'value' property and an 'isLiteral' property set to either true or false.
Breeze also tries to infer the dataType of any literal based on context, if this fails you can force this inference by making the value argument an object with a 'value' property and a 'dataType'property set
to one of the breeze.DataType enumeration instances.
The reason for this logic is to allow expressions where both sides of the expression are properties. For example to query for employees with the same first and last name you'd do this:
var q = EntityQuery.from("Employees")
.where("lastName", "==", "firstName");
whereas if you wanted employees with a lastName of 'firstName' you'd do this:
var q = EntityQuery.from("Employees")
.where("lastName", "startsWith", { value: "firstName", isLiteral: true })