Laravel Eloquent ORM for Social Network - orm

I have the following database setup for my status posts. For each post, users can like the post, comment on the post or can even be tagged in the original post by the author.
I'm trying to setup my Resourceful controller 'Post' to bring back all the data via JSON object but I can't properly find the comment, likes or tags usernames. I'm using Sentry 2 for auth if that makes a difference.
Here's the database setup:
CREATE TABLE Users (
id INT NOT NULL AUTO_INCREMENT,
first_name VARCHAR(30),
last_name VARCHAR(30),
many more...
);
CREATE TABLE Posts (
postID INT NOT NULL AUTO_INCREMENT,
caption VARCHAR(200),
description VARCHAR(200),
fromID INT(10) UNSIGNED NOT NULL,
toID INT(10) UNSIGNED NOT NULL,
icon VARCHAR(200),
link VARCHAR(200),
message TEXT,
storyType INT,
type ENUM ('LINK', 'PHOTO', 'STATUSUPDATE', 'VIDEO' ),
createdTime DATE,
PRIMARY KEY (postID),
FOREIGN KEY (fromID) REFERENCES users (id),
FOREIGN KEY (toID) REFERENCES users (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE Likes (
likeID INT NOT NULL AUTO_INCREMENT,
fromID INT(10) UNSIGNED NOT NULL,
postID INT NOT NULL,
createdDate DATE,
PRIMARY KEY (likeID),
FOREIGN KEY (fromID) REFERENCES users (id),
FOREIGN KEY (postID) REFERENCES Posts (postID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE Comments (
commentID INT NOT NULL AUTO_INCREMENT,
fromID INT(10) UNSIGNED NOT NULL,
postID INT NOT NULL,
comment TEXT,
createdDate DATE,
PRIMARY KEY (commentID),
FOREIGN KEY (fromID) REFERENCES users (id),
FOREIGN KEY (postID) REFERENCES Posts (postID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE Tags (
tagID INT NOT NULL AUTO_INCREMENT,
userID INT(10) UNSIGNED NOT NULL,
postID INT NOT NULL,
PRIMARY KEY (tagID),
FOREIGN KEY (userID) REFERENCES users (id),
FOREIGN KEY (postID) REFERENCES Posts (postID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
My Post Controller, I just have a simple page that spits out everything. I don't want to loop through anything in my view file, I just want to bring back the json full object.
class PostController extends BaseController {
public function show($id)
{
$post = Post::with(array('comments', 'from', 'tags', 'likes'))->find($id);
return View::make('samplepage')->with('data', $post);
}
}
My Post Model:
class Post extends Eloquent {
protected $table = 'Posts';
protected $primaryKey = 'postID';
public function comments()
{
return $this->hasMany('Comment','postID');
}
public function tags()
{
return $this->hasMany('Tag','postID');
}
public function likes()
{
return $this->hasMany('Like','postID');
}
public function from()
{
return $this->belongsTo('User', 'fromID')->select(array('id', 'first_name', 'last_name'));
}
public function users()
{
return $this->belongsTo('User', 'fromID');
}
}
Comment Model:
class Comment extends Eloquent {
protected $table = 'Comments';
protected $primaryKey = 'commentID';
public function post() {
return $this->belongsTo('Post','fromID');
}
public function user() {
return $this->belongsTo('User', 'fromID')->select(array('id', 'first_name', 'last_name'));
}
}
Tag Model:
class Tag extends Eloquent {
protected $table = 'Tags';
protected $primaryKey = 'tagID';
}
I even setup the following in my user model but it makes no difference.
User Model:
public function posts() {
return $this->hasMany('Post','id');
}
public function comments() {
return $this->hasMany('Comment','id');
}
Everything works great with this setup and when I hit posts/2 with this the following code, I get the below object back.
$post = Post::with(array('comments', 'from', 'tags', 'likes'))->find($id);
return View::make('samplepage')->with('data', $post);
{
postID: "2",
toID: "8",
comments: [
{
commentID: "2",
comment: "second comment",
fromID: "1",
postID: "2",
createdDate: "2014-02-15"
}
],
from: {
id: "4",
first_name: Paul,
last_name: Davis
},
tags: [
{
tagID: "1",
userID: "2",
postID: "2"
},
{
tagID: "2",
userID: "3",
postID: "2"
}
],
likes: [
{
likeID: "1",
fromID: "2",
postID: "2",
createdDate: "2013-01-04"
},
{
likeID: "2",
fromID: "3",
postID: "2",
createdDate: "2013-02-05"
}
]
}
But what I want is the following, where for each tag, like and comment to concatenate the first and last name and get them back with the object.
{
postID: "2",
toID: "4",
comments: [
{
commentID: "2",
comment: "second comment",
fromID: "1",
from: {
"name": "Jason Terry",
"id": "721286625"
},
postID: "2",
createdDate: "2014-02-15"
}
],
from: {
id: "4",
first_name: Paul,
last_name: Davis
},
tags: [
{
tagID: "1",
userID: "2",
from: {
"name": "David Lee",
"id": "721286625"
},
postID: "2"
},
{
tagID: "2",
userID: "3",
from: {
"name": "Paul Pierce",
"id": "721286625"
},
postID: "2"
}
],
likes: [
{
likeID: "1",
fromID: "2",
from: {
"name": "David Lee",
"id": "721286625"
},
postID: "2",
createdDate: "2013-01-04"
},
{
likeID: "2",
fromID: "3",
from: {
"name": "Al Davis",
"id": "721286625"
},
postID: "2",
createdDate: "2013-02-05"
}
]
}
I have searched Stackoverflow, countless Laravel blogs, the official documentation for 2 weeks now and I can't seem to solve this. Any help is wonderfully appreciate.
Update:
With Tony's answer below I added
$post = Post::with(array('comments.users', 'from', 'tags.users', 'likes.users'))->find($id);
Then I added
public function users()
{ return $this->belongsTo('User', 'fromID')->select(array('id', 'first_name', 'last_name'));
}
to the comments, tags, and likes model. And the object works great now.
But my debugger shows the following
select `id`, `first_name`, `last_name` from `users` where `users`.`id` in ('1')
select `id`, `first_name`, `last_name` from `users` where `users`.`id` in ('4')
select `id`, `first_name`, `last_name` from `users` where `users`.`id` in ('2', '3')
select `id`, `first_name`, `last_name` from `users` where `users`.`id` in ('2', '3')
In short, it runs 4 queries on my users table. Isn't this redundant? Shouldn't it be doing 1 query to the users table instead of 1 query for the original post user, 1 query for the comments users, 1 query for the tags users, and 1 query for the likes users?

It looks like you want to use nested relationships.
$post = Post::with('comments.from', 'from', 'tags.from', 'likes.from')->find($id);
You would also need the "from" relationship coded into each of those models.
To get your concatenated name; you'd need the following in your User model
protected $appends = array('name');
protected $hidden = array('first_name', 'last_name'); //this is optional
public function getNameAttribute()
{
return $this->attributes['first_name'] . ' ' . $this->attributes['last_name'];
}

it is little old post but you can also do this,
public function from()
{
return $this->belongsTo('User', 'fromID')
->select(array('id',DB::raw("CONCAT(firstname,' ', lastname) as name")));
}

Related

Hasura Graphql does not return all documents when multiple foreign keys exist in a table to the same foreign field

Query:
user (where: { id: {_eq: 104}}) {
connections {
user1
user2
status
}
}
Response:
{
"data": {
"user": [
{
"id": 104,
"connections": [
{
"user1": 104,
"user2": 111,
"status": "pending"
}
]
}
]
}
}
Expected:
{
"data": {
"user": [
{
"id": 104,
"connections": [
{
"user1": 104,
"user2": 111,
"status": "pending"
},
{
"user1": 96,
"user2": 104,
"status": "connected"
},
{
"user1": 112,
"user2": 104,
"status": "pending"
}
]
}
]
}
}
Why are the last two documents not showing up from the first query?
Connection table definition:
CREATE TABLE connections
(
id Integer PRIMARY KEY,
user1 Integer,
user2 Integer
FOREIGN KEY (user1) REFERENCES users (id)
FOREIGN KEY (user2) REFERENCES users (id)
);
I know this could be more of a SQL question, but it would be great if you can show the graphql version too
You have to create two relationships between users table and connections table,
Then query :
query MyQuery {
user(where: {id: {_eq: 104}}) {
connections {
user1
user2
status
}
connectionsByUser2 {
user1
user2
status
}
}
}
Output:

Filtering on related tables using Prisma and Postgres

I have a Prisma model for a Postgres database with a User that can belong to many Organisations, and organizations that can have many users. So I've created a typical many-to-many relationship.
model Organization {
organizationId Int #id #default(autoincrement())
slug String #unique
name String
users UserInOrganization[]
}
model User {
userId Int #id #default(autoincrement())
name String
organizations UserInOrganization[]
}
model UserInOrganization {
fkOrganizationId Int
fkUserId Int
organization Organization #relation(fields: [fkOrganizationId], references: [organizationId], onDelete: Cascade)
user User #relation(fields: [fkUserId], references: [userId], onDelete: Cascade)
role Role #relation(fields: [fkRoleId], references: [roleId])
##id([fkOrganizationId, fkUserId])
}
Next, I need to get all the organizations for a specific user and slug.
If I'd write it in SQL I'd do something like:
select o.name, u."userId" from "Organization" as o
join "UserInOrganization" as uio on o."organizationId" = uio."fkOrganizationId"
join "User" as u on u."userId" = uio."fkUserId"
where u."userId" = 1 and o.slug='slug'
But what would that be in Prisma? Doing something like below seems to give me all the organizations matching the slug (not considering the where on the userId). The where-clause is just filtering the users for each organization. 🥺
const organization = await prisma.organization.findUnique({
where: { slug: slug },
select: {
name: true,
users: { where: { user: { userId: userId } } },
},
});
How can I get just the organizations that have the actual userId?
You can use include with nested select to get organisations that match slug and userId.
Here's the query for it:
const organization = await prisma.organization.findUnique({
where: {
slug: 'prisma',
},
include: {
users: {
where: {
user: {
userId: 1,
},
},
select: {
user: true,
},
},
},
});
console.log(JSON.stringify(organization, null, 2));
Here's the output:
{
"organizationId": 1,
"slug": "prisma",
"name": "Prisma",
"users": [
{
"user": {
"userId": 1,
"name": "John Doe"
}
}
]
}

Prisma insert relation one to many

I read about prisma as an ORM provider and than decided to give it a try. My knowledge in database querying especially postrgres is not very experience at the moment, so I got into a situation where I need some help for a proper solution.
Let's assume the following schema.prisma:
model languages {
language_id Int #id #default(autoincrement())
language_code String #db.VarChar
translations_categories translations_categories[]
}
model categories {
category_id Int #id #default(autoincrement())
category_name String #db.VarChar
translations_categories translations_categories[]
}
model translations_categories {
translation_id Int #id #default(autoincrement())
language_id Int
category_id Int
translation_value String #db.VarChar
categories categories #relation(fields: [category_id], references: [category_id])
language languages #relation(fields: [language_id], references: [language_id])
}
So I try to solve the following update query with some api data:
Add a new category and insert it's category_name
Add the given translations for this category within translations_categories (within this step, the provided information contains the language_code, not the language_id!)
The request body contains the following fields:
{
id: 2
identifier: "TEST-1"
translations: {
de: "german",
en: "english",
fr: "france",
it: "italian"
}
}
The following questions have arisen:
Is it possible to achieve this workflow by a single query?
When inserting the translation, I thought it would be possible by the provided language_code on it's own, isn't it?
I would suggest creating the schema in the following manner:
model languages {
language_id Int #id #default(autoincrement())
language_code String #unique
translations_categories translations_categories[]
}
model categories {
category_id Int #id #default(autoincrement())
category_name String
translations_categories translations_categories[]
}
model translations_categories {
translation_id Int #id #default(autoincrement())
language_id Int
category_id Int
translation_value String
categories categories #relation(fields: [category_id], references: [category_id])
language languages #relation(fields: [language_id], references: [language_id])
##unique([language_id, category_id])
}
The reason for this is that language_code will always be unique for each language so having the #unique would make it much easier to create or update the category.
So you could create/update the category as follows:
let data = {
id: 2,
identifier: 'TEST-1',
translations: {
de: 'german',
en: 'english',
fr: 'france',
it: 'italian',
},
}
let translations = Object.entries(data.translations).map(([key, val]) => ({
language: {
connectOrCreate: {
where: { language_code: key },
create: { language_code: key },
},
},
translation_value: val,
}))
await prisma.categories.upsert({
where: { category_id: data.id },
create: {
category_name: data.identifier,
translations_categories: { create: translations },
},
update: {
category_name: data.identifier,
translations_categories: { create: translations },
},
})

GraphQL field resolver needs contextual information

Maybe my terminology is not accurate. I'm using AWS AppSync. My schema:
type Book {
title: String
author: Author
}
type Author {
name: String
}
type Query {
getBook(title:String!): Book
}
The resolver for getBook returns an object shaped as:
{
title: <string>
authorId: <number>
}
Where authorId is always returned.
What I'd like to do is specify a resolver for the field Book.author that will receive authorId and fetch that object from its own data store. Is this possible?
If what I'm trying to do is not possible, what is the proper way of doing this, where one data store is a table with two columns - { title, authorId }, and a separate store has a table with a list of authors, where the primary key is a column authorId. Since these are two different services, I can't just join the two like a SQL query.
As long as authorId is returned from the getBook resolver, it will be accessible via $ctx.source.authorId when resolving Book.author.
I reproduced your API with local resolvers using your schema:
Query.getBook request mapping template:
{
"version": "2018-05-29",
"payload": {
"title": "$context.arguments.title",
"authorId": "2" ## returned in addition to other fields. It will be used by Book.author resolver.
}
}
Query.getBook response mapping template:
$util.toJson($context.result)
Book.author request mapping template:
{
"version": "2018-05-29",
"payload": {
"name": "author name with authorId: $context.source.authorId"
}
}
Book.author response mapping template:
$util.toJson($context.result)
The following query:
query {
getBook(title:"AWS AppSync") {
title
author {
name
}
}
}
will yield the results:
{
"data": {
"getBook": {
"title": "AWS AppSync",
"author": {
"name": "author name with authorId: 2"
}
}
}
}
You might need to have bookID as parent's ID inside Author:
type Author {
# parent's id
bookID: ID!
# author id
id: ID!
name: String!
}
type Book {
id: ID!
title: String!
author: Author!
}
When Create Resource, just make:
- Book.id as primary key of BookTable
- Author.bookID as primary key and Author.id as sort key of
AuthorTable
You also need to attach resolver for Book.author using $ctx.source.id
After you attach Book.author resolver, you are good to go. You can get result something like below:
getBook(title: "xx") {
id
title
author {
id
name
}
}

Turning JSON into a Table

{
"ID":1,
"KEY1":"",
"ARRAYKEY1":[
{
"ARRAYKEY":""
"OBJECTKEY":{
"OBJ":""
}
}
]
}
Above is a JSON object I have stored in the database and I want to use OPENJSON to create a tabular format. I know I can do the whole OPENJSON WITH ( ) and manually create all the columns. I wanted to know if anyone has any idea of how to recursively or programatically create the table without having to identity each key for the table. I could end up having a couple dozen fields. I'm not worried about excluding any of the items. If I wanted to pull all the records back, where could I even begin with the new 2016 SQL Server?
Here is the official Documentation.
SET #json =
N'[
{ "id" : 2,"info": { "name": "John", "surname": "Smith" }, "age": 25 },
{ "id" : 5,"info": { "name": "Jane", "surname": "Smith" }, "dob": "2005-11-04T12:00:00" }
]'
SELECT *
FROM OPENJSON(#json)
WITH (id int 'strict $.id',
firstName nvarchar(50) '$.info.name', lastName nvarchar(50) '$.info.surname',
age int, dateOfBirth datetime2 '$.dob')