I have a Customer entity that is linked to a Contact entity, in a nullable OneToOne relationship.
When I create a new Customer, the creation of the linked Contact is optional, but it must not be possible to fill in the IRI of an existing Contact. In other words, it must be a new Contact or nothing.
class Customer
{
#[ORM\OneToOne(targetEntity: Contact::class, cascade: ["persist"])]
#[Groups([
'write:Customer:collection', '...'
])]
private $contact;
}
The 'write:Customer:collection' denormalization group is also present on the Contact properties.
With a good request as follow, I can create my Customer and my Contact, no problem with it.
{
"name": "test company",
"contact": [
"firstname" => 'hello',
"lastname" => 'world'
]
}
Problem:
But, and I don't want it, I also can create the new Customer with an existing Contact, like this:
{
"name": "test company",
"contact": "/api/contacts/{id}"
}
As stated in the serialization documentation:
The following rules apply when denormalizing embedded relations:
If an #id key is present in the embedded resource, then the object corresponding to the given URI will be retrieved through the data provider. Any changes in the embedded relation will also be applied to that object.
If no #id key exists, a new object will be created containing data provided in the embedded JSON document.
However, I would like to disable the rule if an #id key is present, for specific validation group.
I thought of creating a custom constraint that would check that the resource does not exist in the database, but I am surprised that no constraint allows to check this.
Am I missing something? Do you have a solution for me? Thanks in advance.
I finally created a custom constraint that checks if the embed resource sent in request is already managed by Doctrine.
The constraint itself:
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
* #Target({"PROPERTY", "METHOD", "ANNOTATION"})
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class AcceptPersisted extends Constraint
{
public bool $expected = false;
public string $mustBePersistMessage = 'Set a new {{ entity }} is invalid. Must be an existing one.';
public string $mustBeNotPersistMessage = 'Set an existing {{ entity }} is invalid. Must be a new one.';
public function __construct(bool $expected = false, $options = null, array $groups = null, $payload = null)
{
parent::__construct($options, $groups, $payload);
$this->expected = $expected;
}
}
And it validator:
namespace App\Validator\Constraints;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class AcceptPersistedValidator extends ConstraintValidator
{
public function __construct(private EntityManagerInterface $entityManager) {}
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof AcceptPersisted) {
throw new UnexpectedTypeException($constraint, AcceptPersisted::class);
}
if ($value === null) {
return;
}
//if current value is/is not manage by doctrine
if ($this->entityManager->contains($value) !== $constraint->expected) {
$entity = (new \ReflectionClass($value))->getShortName();
$message = $constraint->expected ? $constraint->mustBePersistMessage : $constraint->mustBeNotPersistMessage;
$this->context->buildViolation($message)->setParameter("{{ entity }}", $entity)->addViolation();
}
}
}
So, I just had to add the custom constraint on my property:
use App\Validator\Constraints as CustomAssert;
class Customer
{
#[ORM\OneToOne(targetEntity: Contact::class, cascade: ["persist"])]
#[CustomAssert\AcceptPersisted(expected: false)]
//...
private $contact;
}
Related
I'm using GORM for MongoDB in my Grails 3 web-app to manage read/writes from DB.
I have the following 2 domain classes:
class Company {
String id
}
class Team {
String id
Company company
}
For teams, their company is saved on DB as String, and with GORM I can simply use team.company to get an instance of Company domain class.
However, I need to override the getter for company, and I need the raw value for company id (as stored on DB), without GORM getting in the way and performing its magic.
Is there a way to get the raw String value?
Any help is welcome! Thanks in advance
Update (May 27)
Investigating #TaiwaneseDavidCheng suggestion, I updated my code to
class Company {
String id
}
class Team {
String id
Company company
String companyId
static mapping = {
company attr: "company" // optional
companyId attr: "company", insertable: false, updateable: false
}
}
Please note that I'm using GORM for MongoDB, which (citing the manual) tries to be as compatible as possible with GORM for Hibernate, but requires a slightly different implementation.
However I found out (by trial&error) that GORM for MongoDB doesn't support a similar solution, as it seems only one property at a time can be mapped to a MongoDB document property.
In particular the last property in alphabetical order wins, e.g. companyId in my example.
I figured out a way to make the whole thing work, I'm posting my own answer below.
given a non-insertable non-updateable column "companyId" in domain class
class Company {
String id
}
class Team {
String id
Company company
Long companyId
static mapping = {
company column:"companyId"
companyId column:"companyId",insertable: false,updateable: false
}
}
(Follows the edit to my question above)
I defined a custom mapping, and made use of Grails transients by also defining custom getter and setter for team's company.
class Company {
String id
}
class Team {
String id
Company company
String companyId
static mapping = {
companyId attr: "company" // match against MongoDB property
}
static transients = [ 'company' ] // non-persistent property
Company getCompany() {
return Company.get(companyId)
}
void setCompany(Company company) {
companyId = company.id
}
}
Is there a recommended way to go about dealing with documents that don't have the _class field with spring-data-couchbase( if there is one)? Trying it simply just throws an exception as expected.
Edit: Apologies if this was a bit too vague, let me add a bit more context.
I want to fetch data from couchbase for some student by name, let's say . The repository looks something like -
#Repository
public interface StudentRepository extends CouchbaseRepository {
Optional<StudentDocument> findByName(String name);
}
Now the documents in couchbase don't have the _class field OR say if we are entering a different "key" and "value" for _class field as we don't want to rely on it, so this method fails. I sort of hacked a workaround for this using -
`
#Override
public Student getStudent(String name) {
N1qlQuery query = N1qlQuery.simple(String.format("select *, META().id AS _ID, META().cas AS _CAS" +
" from student where name = \'%s\';", name));
return Optional.ofNullable(studentRepository.getCouchbaseOperations()
.findByN1QL(query, StudentWrapper.class)
.get(0))
.map(StudentWrapper::getStudent)
.orElseGet(() -> {
throw new HttpClientErrorException(HttpStatus.NOT_FOUND);
});
}
`
I was wondering if there is an alternate way of achieving this
While using Spring spEL, Couchbase will automatically include the _class (or whatever attribute you have defined as your type) for you:
public interface AreaRepository extends CouchbaseRepository<Area, String> {
//The _class/type is automatically included by Couchbase
List<Area> findByBusinessUnityIdAndRemoved(String businessId, boolean removed);
}
However, if you want to use N1QL, you have to add the #{#n1ql.filter} :
public interface BusinessUnityRepository extends CouchbaseRepository<BusinessUnity, String>{
#Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and companyId = $2 and $1 within #{#n1ql.bucket}")
BusinessUnity findByAreaRefId(String areaRefId, String companyId);
}
the #{#n1ql.filter} will automatically add the filter by type for you.
I'm trying default yii2 api rest calls GET to get some model fields or PUT to update some model fields but I can't find a way to do this. I can only get all the fields or update theme all. Any help to do this? And how can I get the related relational field to this model?
I'm trying like this like
GET localhost/my-website-name/api/web/v1/vendors/
PUT localhost/my-website-name/api/web/v1/vendors/1
one way that I know for customizing fields is overriding fields function in your model like this
public function fields() {
return [
'id',
'iso3' => function() {
return base64_encode($this->iso3);
}
];
}
How to get specific fields and get the relation fields from api calls?
By default, yii\db\ActiveRecord::fields() returns all model attributes which have been populated from DB as fields, while yii\db\ActiveRecord::extraFields() should return the names of model's relations.
Take this model for example:
class Image extends ActiveRecord
{
public function attributeLabels()
{
return [
'id' => 'ID',
'owner_id' => 'Owner ID',
'name' => 'Name',
'url' => 'Url',
'created_at' => 'Created At',
'updated_at' => 'Updated At',
];
}
public function getOwner()
{
return $this->hasOne(Owner::className(), ['id' => 'owner_id']);
}
public function extraFields()
{
return ['owner'];
}
}
Here I did override the extraFields() method to define the owner relationship. Now if I want to retreive all images but selecting id and name fields only and each resource should also hold its related owner data I would simply request this url:
GET example.com/images?fields=id,name&expand=owner
note: you can also use comma separation to expand more than one relation
In case you want to permanently remove some fields like created_at and updated_at you can also override the fields() method:
public function fields()
{
$fields = parent::fields();
unset($fields['created_at'], $fields['updated_at'], $fields['owner_id']);
return $fields;
/*
// or could also be:
return ['id', 'name','url'];
*/
}
this way the following request should only return image's id, name and url fields along with their related owner :
GET example.com/images?expand=owner
If owner's fields should be filtered too then override its fields() method too in its related class or a child class of it that you tie to the image model by using it when defining the relation.
See official documentation for further details.
PUT to update some model fields
Yii only updates dirty attributes. so when doing:
PUT example.com/images/1 {"name": "abc"}
the generated SQL query should only update the name column of id=1 inside database.
Hello i manage to find a solution for this situations
first get some model fields only and with related entities fildes:
public function fields() {
return [
'name',
'phone_number',
'minimum_order_amount',
'time_order_open',
'time_order_close',
'delivery_fee',
'halal',
'featured',
'disable_ordering',
'delivery_duration',
'working_hours',
'longitude',
'latitude',
'image',
'owner' => function() {
$owner = Owners::findOne($this->owner_id);
return array($owner);
}
];
}
if you want to remove some fildes
public function fields()
{
$fields=parent::fields();
unset($fields['id']);
return $fields;
}
update only specific fields
public function beforeSave($insert)
{
$lockedValues = ['name', 'halal', 'featured', 'latitude', 'longitude', 'image', 'status', 'created_at', 'updated_at'];
foreach ($lockedValues as $lockedValue) {
if ($this->attributes[$lockedValue] != $this->oldAttributes[$lockedValue])
throw new ForbiddenHttpException($lockedValue . " can't be changed.");
}
return parent::beforeSave($insert); // TODO: Change the autogenerated stub
}
I have two domain objects,
#Document
public class PracticeQuestion {
private int userId;
private List<Question> questions;
// Getters and setters
}
#Document
public class Question {
private int questionID;
private String type;
// Getters and setters
}
My JSON doc is like this,
{
"_id" : ObjectId("506d9c0ce4b005cb478c2e97"),
"userId" : 1,
"questions" : [
{
"questionID" : 1,
"type" : "optional"
},
{
"questionID" : 3,
"type" : "mandatory"
}
]
}
I have to update the "type" based on userId and questionId, so I have written a findBy query method inside the custom Repository interface,
public interface CustomRepository extends MongoRepository<PracticeQuestion, String> {
List<PracticeQuestion> findByUserIdAndQuestionsQuestionID(int userId,int questionID);
}
My problem is when I execute this method with userId as 1 and questionID as 3, it returns the entire questions list irrespective of the questionID. Is the query method name valid or how should I write the query for nested objects.
Thanks for any suggestion.
Just use the #Query annotation on that method.
public interface CustomRepository extends MongoRepository<PracticeQuestion, String> {
#Query(value = "{ 'userId' : ?0, 'questions.questionID' : ?1 }", fields = "{ 'questions.questionID' : 1 }")
List<PracticeQuestion> findByUserIdAndQuestionsQuestionID(int userId, int questionID);
}
By adding the fields part of the #Query annotation, you are telling Mongo to only return that part of the document. Beware though, it still returns the entire document in the same format - just missing everything you did not specify. So your code will still have to return List<PracticeQuestion> and you will have to do:
foreach (PracticeQuestion pq : practiceQuestions) {
Question q = pq.getQuestions().get(0); // This should be your question.
}
Property expressions
Property expressions can refer only to a direct property of the managed entity, as shown in the preceding example. At query creation time you already make sure that the parsed property is a property of the managed domain class. However, you can also define constraints by traversing nested properties. Assume Persons have Addresses with ZipCodes. In that case a method name of List<Person> findByAddressZipCode(ZipCode zipCode);
creates the property traversal x.address.zipCode. The resolution algorithm starts with interpreting the entire part (AddressZipCode) as the property and checks the domain class for a property with that name (uncapitalized). If the algorithm succeeds it uses that property. If not, the algorithm splits up the source at the camel case parts from the right side into a head and a tail and tries to find the corresponding property, in our example, AddressZip and Code. If the algorithm finds a property with that head it takes the tail and continue building the tree down from there, splitting the tail up in the way just described. If the first split does not match, the algorithm move the split point to the left (Address, ZipCode) and continues.
Although this should work for most cases, it is possible for the algorithm to select the wrong property. Suppose the Person class has an addressZip property as well. The algorithm would match in the first split round already and essentially choose the wrong property and finally fail (as the type of addressZip probably has no code property). To resolve this ambiguity you can use _ inside your method name to manually define traversal points. So our method name would end up like so:
UserDataRepository:
List<UserData> findByAddress_ZipCode(ZipCode zipCode);
UserData findByUserId(String userId);
ProfileRepository:
Profile findByProfileId(String profileId);
UserDataRepositoryImpl:
UserData userData = userDateRepository.findByUserId(userId);
Profile profile = profileRepository.findByProfileId(userData.getProfileId());
userData.setProfile(profile);
Sample Pojo :
public class UserData {
private String userId;
private String status;
private Address address;
private String profileId;
//New Property
private Profile profile;
//TODO:setter & getter
}
public class Profile {
private String email;
private String profileId;
}
For the above Document/POJO in your Repository Class:
UserData findByProfile_Email(String email);
For ref : http://docs.spring.io/spring-data/data-commons/docs/1.6.1.RELEASE/reference/html/repositories.html
You need to use Mongo Aggregation framework :
1) Create custom method for mongo repository : Add custom method to Repository
UnwindOperation unwind = Aggregation.unwind("questions");
MatchOperation match = Aggregation.match(Criteria.where("userId").is(userId).and("questions.questionId").is(questionID));
Aggregation aggregation = Aggregation.newAggregation(unwind,match);
AggregationResults<PracticeQuestionUnwind> results = mongoOperations.aggregate(aggregation, "PracticeQuestion",
PracticeQuestionUnwind.class);
return results.getMappedResults();
2) You need to cretae a class(Because unwind operation has changed the class structure) like below :
public class PracticeQuestionUnwind {
private String userId;
private Question questions;
This will give you only those result which matches the provide userId and questionId
Result for userId: 1 and questionId : 111 :
{
"userId": "1",
"questions": {
"questionId": "111",
"type": "optional"
}
}
i too had similar issue. for that i added $ before the nested class attributes.
try below query
#Query(value = "{ 'userId' : ?0, 'questions.$questionID' : ?1 }") List<PracticeQuestion> findPracticeQuestionByUserIdAndQuestionsQuestionID(int userId, int questionID);
When i use foreign keys in the entity framework the foreign key object is null when i do a POST. I am using MVC3 and EF 4.1. I have two tables, Product and Product Details. I am exposing them using the HTML helpers in a Razor view. When the GET happens, the product details are shown. But when i do a form Submit and post to the server, the Product Details collection is null. I lose all my changes.
Any ideas what i am doing wrong?
Thanks for your help!
The Code (i shortened it because it is fairly lengthy):
Database:
Table Product
{
int Id
varchar Name
}
Table ProductDetails
{
int id,
int ProductId, <- foreign key SQL 2008 to Product Table
varchar Details
}
View:
#model WebSite.Models.Product
#{
ViewBag.Title = "MyLifeSaverStoreInfo";
}
#Html.TextBoxFor(m => m.Product.Name)
#Html.TextBoxFor(m => m.Product.ProductDetails.FirstOrDefault().Description)
Controller:
public ActionResult EditProduct(int productId)
{
var Product = _productRepository.GetProduct(productId);
return View(product);
}
[HttpPost]
public ActionResult EditProduct(Product model)
{
string name = model.Name; <- this update comes through
string description = model.ProductDetails.FirstorDefault().Description;
}
Thanks
Got it!
Instead of creating the "Product" entity on the POST EditProduct method, i use a Form Collection and then set each product detail according to that form collection and then save. I don't understand why it doesn't work the first way. I am manually updating the foreign reference. Maybe i am missing something?
[HttpPost]
public ActionResult EditProduct(int id, FormCollection formCollection)
{
var model = new MyLSstoreInfoViewModel{
StoreCurrent = _profileRepository.GetProduct(Id)
};
var productDetails = model.Product.ProductDetails.Where(p => p.productId == id).Single();
productDetaisl.Details = formCollection["details"];
if (TryUpdateModel(model.StoreCurrent))
{
_profileRepository.Save();
}
}
It's a bit late, but maybe useful for someone in the future:
If you pass the primary key of the referenced child as a hidden input in the view, it will correctly resolve the child model in the action.