I am having trouble trying to set up a relationship between tables.
- courses
|- id
|- title
- sessions
|- id
|- course_id
|- title
- users
|-id
|- name
- user_roles
|- user_id
|- course_id
I am able to show all sessions for a given course and all users who are registered for a given session. I would also like to show the users role. Since I could have any number of sessions for any given course, my pivot table only has course_id and the user_id columns.
When I visit a url like this: mysite.com/sessions/1 I want to show the current users for the session. It would look something like this:
"My Course"
"Session One"
"John Doe"
"Admin"
"Jane Doe"
"Editor"
"Contributor"
"Name"
"Foo"
Coming from CodeIgniter, I'd just pass in the course id and user id to find the corresponding roles. Here is what I have that is 90% working.
Course.php
public function sessions()
{
return $this->hasMany(Session::class);
}
Session.php
public function course()
{
return $this->belongsTo(Course::class);
}
User.php
public function user()
{
return $this->belongsTo(Course::class);
}
public function roles()
{
return $this->hasMany(Role::class);
}
Role.php
public function user()
{
return $this->belongsToMany(User::class, 'user_roles');
}
Here is the sample relationship,
Courses.php
//means course has many sessions
public function sessions(){
return $this->hasMany('App\Sessions','course_id', 'id');
}
//course user
public function course_user(){
return $this->belongsToMany('App\Course', 'user_roles', 'user_id', 'course_id');
}
Sessions.php
//means Session belongs to course
public function sessions(){
return $this->belongsTo('App\Course','course_id', 'id');
}
User.php
public function sessions(){
return $this->belongsTo('App\Course','course_id', 'id');
}
//user courses
public function user_course(){
return $this->belongsToMany('App\User','user_roles','courses_id', 'user_id');
}
Note: if you want user role management system, then its better to install and configure the Zizaco Entrust package, its much better instead doing it manually. Entrust Package
Related
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;
}
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
}
}
I would like to know if it is possible to check FK when using SoftDelete with ASP.NET Boilerplate.
Example
Suppose these tables:
Roles: RoleId (PK) - Description
Users: UserId (PK) - Name - RoleId (FK with Roles)
Data:
Roles
1 - admin
2 - guest
Users
1 - admin - 1
2 - john - 2
So RoleId 1 should not be deleted if it was already assigned to an existing User.
Thanks in advance.
Soft delete just sets a flag to mark the record as deleted.
In ABP, you can write your own checks in ApplyAbpConceptsForDeletedEntity of your DbContext:
public class AbpProjectNameDbContext // : ...
{
// ...
protected override void ApplyAbpConceptsForDeletedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport)
{
CheckForeignKeys(entry);
base.ApplyAbpConceptsForDeletedEntity(entry, userId, changeReport);
}
private void CheckForeignKeys(EntityEntry entry)
{
var entity = entry.Entity;
if (!(entity is ISoftDelete))
{
// Foreign key constraints checked by database
return;
}
var role = entity as Role;
if (role != null)
{
if (Users.Any(u => u.Roles.Any(r => r.RoleId == role.Id)))
{
throw new UserFriendlyException("Cannot delete assigned role!");
}
}
}
}
Note that the template's RoleAppService actually removes users from the role before deleting it.
Briefly your goal is non-sense. SoftDelete feature marks the record as deleted and doesn't delete it physically. It's like Recycle Bin in Windows. So that you can undelete it anytime. From the database perspective, it's relationships are consistent. Because there's the data in the table.
Solution; when you prevent users to delete an in-use record you have to validate it yourself against your business rules.
I am working on social app front end Angualar, Backend laravel and with database Mongodb. I have model like:
Hoots
-----------------
- _id
- content
- publish_id
Article
-----------------
- _id
- content
- publish_id
Story
-----------------
- _id
- content
- publish_id
Publish
-----------------
- _id
- post_id
- type
- user_id
Post id in publish belongs to _id in hoots , article and story , where type signify wheather it is hoot , article or story.
I have Model like this
//Article model
class Article extends Eloquent {
public function getpublish(){
return $this->hasMany('Publish','post_id');
}
}
//Story model
class Story extends Eloquent {
public function get_publish(){
return $this->hasMany('Publish','post_id');
}
}
//Hoots model
class Hoots extends Eloquent {
public function get_publ(){
return $this->hasMany('Publish','post_id');
}
}
//Publish model
class Publish extends Eloquent {
public function getdata(){
return $this->BelongsTo('Hoots','Article','Story','publish_id');
}
}
I am using
Publish::with('getdata')->where('user_id',Auth::user()->id)->get();
using this i can only get publish data along with post_id corresponding data in one model i.e hoots only. I want this from all three tables.
I want to fetch publish model data with their corresponding post_id data.How can i accomplish this o single query using eloquent.
I think maybe you haven't got your relationships setup correctly;
//Publish model
class Publish extends Eloquent {
public function hoot(){
return $this->HasMany('Hoots','publish_id');
}
}
public function article(){
return $this->HasMany('article','publish_id');
}
}
public function story(){
return $this->HasMany('stort','publish_id');
}
}
Publish::with(array('hoot', 'story', 'article'))->where('user_id',Auth::user()->id)->get();
If I have two tables, which have an Id, whish is an autogenerated int (seed), anyway I have a many to many relationship between these two tables which requires another table.
Now, I do a "dry run" to generate the items for the first two table before saving them, this works perfect. The problem is when I try to generate the items for the (many-many relationship) in the third table. Before saving the items all Ids in the first two tables will be set to 0, when adding items to the relation table I have no problems, the problems comes when saving the tables because the relationship table will have the Ids of 0.
Is there a way to overcome this problem? like assigning a temp value which will be automatically changed to the real Id in the relationship table before saving it ?
For the same reason, I've chosen not to use default Seed methods (AddOrUpdate) provided by EF, but I'm rather writing my own seed methods.
Now if I want to set up relationships, I'm not explicitly using ID's, but rather use navigational properties.
Imagine the scenario:
public class User
{
public int Id {get;set;}
public string Name {get;set;}
public virtual IList<Roles> Roles { get;set;}
}
public class Role
}
public int Id {get;set;}
public string Name {get;set;}
public virtual IList<User> Users {get;set;}
}
Doing this will seed both values for user and roles and relationships once you hit the save changes:
Role admin = new Role
{
Name = "Administrator"
};
Role basic = new Role
{
Name = "Basic"
};
User user = new User
{
Name = "John",
Roles = new List<Role>()
{
basic,
admin
}
}
db.Users.Add(user);
db.SaveChanges();