Laravel | Check that user belongs to all relationships BelongsToMany - laravel

I am trying to create a policy for updating a user to ensure that either the person updating or the user being updated belongs to a place.
I have the current database structure for the users
| id | first_name | last_name | email | password | created_at | updated_at |
and this is the places
| id | name | description | created_at | updated_at |
I then have a pivot table which is
| user_id | place_id |
Everything links up correctly and I can pass and associate data into each models.
What I am trying to achieve is that when I send in a request according to the JSON API spec which looks like the following:
{
"type": "users",
"attributes": {
...
},
"relationships": {
"places": {
"data": [
{
"type": "places",
"id": "1"
},
{
"type": "places",
"id": "2"
}
]
}
}
}
Inside of the updated policy, I want to check that either the authenticated user or the user being updated belongs to that place.
I have a method on the User model which is the following:
public function belongsToPlaces($placeIds)
{
return $this->places()->whereIn('id', $placeIds)->count() === count($placeIds);
}
This seems to work but it really doesn't feel very secure as realistically I should check each of the IDs. I'm just wondering if there is a built-in method to do this?

I would do a diff on the placeIds you already have with the one on the user. It will return all place ids there is not in the request, if it's not present on the users places. So check if the diff is empty, then everything is fine, otherwise the id's are invalid.
public function belongsToPlaces($placeIds)
{
return collect($placeIds)->diff($this->places()->pluck('id'))->isEmpty();
}

Related

Updating JSONB values while keeping not matching one

I have a project of Laravel with two tables
Variants
id | name | data (jsonb)
Devices
id | name | variants_id | data (jsonb)
Some of the values in Devices tables used from variant but it also has it's own values too.
So when we create a new device we use variant data and save it with devices along with it's own data.
Now when I want to update variant and only update those fields in all the devices without replacing non-matching values.
Is there a better way than running a foreach loop on all devices.
This is what I'm doing right now.
foreach($variant->devices as $device)
{
$data = $device->data;
foreach($variant->data as $key => $value)
{
$data[$key] = $value;
}
$device->data = $data;
$device->save();
}
This is the data in Variant
{"depth": 66.9, "width": 58.2, "height": 100.8, "pit_id": "1234", "variable": false, "liters_per_cm": 3.88194, "measurement_unit": "cm"}
This is the data in Devices
{"lat": "16.636192", "lng": "19.238912", "depth": 66.9, "width": 58.2, "height": 100.8, "pit_id": "1234", "total_liters": 391.299552, "liters_per_cm": 3.88194, "measurement_unit": "cm"}
I hope this makes sense and help will be highly appreciated
i believe you can achieve this by where condition
$variant // you already have
Device::where('variants_id',$variant->id)->update([
'data'=> $variant->data
]);
is this you want ?

Preload with additional column

So I have a table of users where each user is associated with a main tool and tools in their backpacks. Each tool also have a "quality". But I have no idea of how to fetch this value so I appreciate any help on how to do this.
Databases
users
+----+------+--------------+
| id | name | main_tool_id |
+----+------+--------------+
| 1 | adam | 1 |
+----+------+--------------+
tools
+----+-------------+
| id | name |
+----+-------------+
| 1 | hammer |
| 2 | screwdriver |
+----+-------------+
user_tools
+----+---------+---------+---------+
| id | user_id | tool_id | quality |
+----+---------+---------+---------+
| 1 | 1 | 1 | LOW |
| 2 | 1 | 2 | HIGH |
+----+---------+---------+---------+
Models
type User struct {
Id int64 `json:"-"`
Name string `json:"name"`
MainToolId int64 `json:"-"`
MainTool Tool `json:"main_tool"`
ToolsInBackpack []Tool `json:"tools_in_backpack" gorm:"many2many:user_tools"`
}
type Tool struct {
Id int64 `json:"-"`
Name string `json:"name"`
Quality string `json:"quality"`
}
Code
var users []User
DB.Preload("MainTool").Preload("ToolsInBackpack").Find(&users) // <-- Modify this (I guess)
Real result
{
"name": "adam",
"main_tool": {
"name": "hammer",
"quality": ""
},
"tools_in_backpack": [
{
"name": "hammer",
"quality": ""
},
{
"name": "screwdriver",
"quality": ""
},
]
}
Desired result
{
"name": "adam",
"main_tool": {
"name": "hammer",
"quality": "LOW"
},
"tools_in_backpack": [
{
"name": "hammer",
"quality": "LOW"
},
{
"name": "screwdriver",
"quality": "HIGH"
},
]
}
Thanks a lot for your help!
If you do not require MainTool to return an object.
maybe you uses it
type User struct {
ID uint `json:"-"`
Name string
MainTool int64
ToolsInBackpack string
Tools []*Tool
}
type Tool struct {
ID uint`json:"-"`
Name string
UserID uint
}
User{MainTool: 1, ToolsInBackpack: "[1,2]", Tools: []*Tool{&t1, &t2}}
if you don't wana to use string at ToolsInBackpack string you can't use datatypes.JSON at gorm.io/datatypes
for example
ToolsInBackpack datatypes.JSON
This isn't supported in Gorm so there won't be an easy way to just Preload and it's done.
Here are a few techniques you could look at:
Use a Custom Join Table Model that contains the fields that you need
type UserTool struct {
UserID int64 `gorm:"primaryKey"`
ToolID int64 `gorm:"primaryKey"`
Quality string
}
err := db.SetupJoinTable(&User{}, "ToolsInBackpack", &UserTool{})
// handle error
(I left out the UserTools.ID field, Gorm doesn't need it, and you only need it if a user can have the same tool twice, in which case put it back on as part of the primaryKey. I don't know whether Gorm will be happy with it but try it out).
Then you can use this model to query the field as any other model:
userTools := []UserTool
err := db.Where("user_id = ?", user.ID).Find(&userTools).Error
// handle error
// now userTools[i].Quality is filled, you could use it to update the corresponding users[j].ToolsInBackpack[k].Quality
It's a pain because you need to match IDs as a post-processing step.
Use a Has-Many/Belongs-To hybrid relation to model the Many-To-Many Join Table:
Here the User Has Many UserTools and a Tool belongs to one or more UserTools. This effectively models the meaning of Many-to-Many (in ERD, a relationship like [User]>--<[Tool] can be decomposed into [User]--<[UserTool]>--[Tool]).
type User struct {
ID int64
Name string
MainToolId int64
MainTool Tool
ToolsInBackpack []UserTool
}
type UserTool struct {
UserID int64 `gorm:"primaryKey"`
ToolID int64 `gorm:"primaryKey"`
Tool Tool
Quality string
}
type Tool struct {
ID int64
Name string
}
Now you can Preload this association like this:
err := db.Model(&User{}).Preload("ToolsInBackpack.Tool").Find(&users).Error
// handle error
// now users[i].ToolsInBackpack[j].Quality and
// users[i].ToolsInBackpack[j].Tool.Name will be set correctly.
Only problem now is that you've got a weird shape in the model that you're then trying to marshal into JSON (most likely to be used in an API). My advice here is to split the DB model from the JSON API model, and have a mapping layer in your code. The DB model and the API Messages invariably diverge in important ways and trying to reuse one model for both soon leads to pain.

Fetch complete objects including children using Golang and Gorm

I'm using Gorm and Golang to fetch data from my database. Is it possible to make Gorm fetch also the objects children (foreign keys)?
Database tables
users
+----+---------+------------+
| id | name | country_id |
+----+---------+------------+
| 1 | Adam | 1 |
| 2 | Bertil | 1 |
| 3 | Charlie | 2 |
+----+---------+------------+
countries
+----+--------+
| id | name |
+----+--------+
| 1 | Sweden |
| 2 | Norway |
+----+--------+
Models
type User struct {
Id int64 `json:"-"`
Name string `json:"name"`
CountryId int64 `json:"-"`
Country Country `json:"country"`
}
type Country struct {
Id int64 `json:"-"`
Name string `json:"name"`
}
Code to fetch all users
var users []User
DB.Find(&users) // Question: How should this be modified to automatically fetch the Country?
Actual result
[
{
"name": "Adam",
"country" : {
"name": "",
}
},
...
]
Desired result
[
{
"name": "Adam",
"country" : {
"name": "Sweden",
}
},
...
]
Thanks a lot for you input!
/Klarre
Yes it is possible, it's called Preloading.
users := make([]User,0)
DB.Preload("Country").Find(&users)

How to define values specific to a connection of two nodes in GraphQL?

I have two types in my schema:
type Resident = { type Visitor = {
id id
name name
} }
In my database:
Residents and Visitors Tables:
+--------+-------+ +--------+---------+
| res_id | name | | vis_id | name |
+--------+-------+ +--------+---------+
| 1 | Alice | | 1 | Charlie |
| 2 | Bob | +--------+---------+
+--------+-------+
And then a table that shows which visitor belongs to which resident:
+--------+--------+--------------+
| res_id | vis_id | relationship |
+--------+--------+--------------+
| 1 | 1 | fam/fri |
| 2 | 1 | contractor |
+--------+--------+--------------+
Each visitor could either be a "fam/fri" or a "contractor" to a resident. So Charlie is Alice's visitor as her family or friend. However, Charlie is also a visitor to Bob, but instead as a contractor.
Question: How do I structure my schema so that when I query Alice, Charlie returns as a fam/fri, and when I query Bob, Charlie is returned as a contractor? I imagine this:
{
Resident(id: 1) { "Resident" {
name "Alice"
Visitor { "Visitor" {
id ===> "1"
name "Charlie"
relationship "fam/fri"
} }
} }
}
and also:
{
Resident(id: 2) { "Resident" {
name "Bob"
Visitor { "Visitor" {
id ===> "1"
name "Charlie"
relationship "contractor"
} }
} }
}
Something like:
type Query {
resident(id: Int): Resident
}
type Resident {
id: Int!
name: String!
visitors: [Visitor!]!
}
type Vistor {
id: Int!
name: String!
relationship: VisitorRelationship!
}
enum VisitorRelationship {
CONTRACTOR
FAMILY_OR_FRIEND
}
Note that by convention field names should be camelCase and type names should be in PascalCase. If the data returned from your data source (whether that's a database, API, or whatever) is not in the same shape as what you want to return from your GraphQL service, then you should transform the data before returning it inside your resolver, for example:
const relationshipMap = {
'fam/fri': 'FAMILY_OR_FRIEND',
'contractor': 'CONTRACTOR',
}
const resolvers = {
Query: {
resident: (root, args) => {
const resident = await Resident.findById(args.id)
// assuming resident has a property named joinTable that's
// an array and each relationship has a property named visitor
return {
...resident,
visitors: resident.joinTable.map(relationship => {
return {
...joinTable.visitor,
relationship: relationshipMap[joinTable.relationship],
}
})
}
},
},
}
You can also map enums to custom values this way.

Ember adds empty row to data with slug as id

I have a resource where I list all my projects from server /projects. You can visit specific project going to /projects/:slug.
When I visit projects resource I see this data in Ember Inspector:
/projects
id | Slug | Title |
-------------------------------------------------
1 | first-project | First project |
-------------------------------------------------
2 | second-project | Second project |
-------------------------------------------------
3 | third-project | Third project |
When I visit a project from list of projects I get the same data without new ajax request and everything is working fine. /projects/first-project
The problem comes when I refresh /projects/first-project page. Ember makes an ajax request to fetch data from server but It also inserts an empty row with slug as id to data.
id | Slug | Title |
-------------------------------------------------------------
first-project | | |
-------------------------------------------------------------
1 | first-project | First project |
Now visiting project list It is showing a list of projects but at the top of the list is this empty row. Why it is inserting this slug row to my data? Maybe my logic is wrong.
<li>
<a id="ember451" class="ember-view" href="/projects/undefined">
<script id="metamorph-13-start" type="text/x-placeholder"></script>
<script id="metamorph-13-end" type="text/x-placeholder"></script>
</a>
</li>
My code for projects:
App.Router.map(function() {
this.resource('project', { path: '/projects' }, function() {
this.resource('project.show', { path: ":post_slug"});
});
});
App.Project = DS.Model.extend({
slug: DS.attr("string"),
title: DS.attr("string")
});
App.ProjectIndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('project').then(function(data) {
return data;
});
}
});
App.ProjectShowRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('project', params.post_slug).then(function(data) {
return data;
});
},
serialize: function(model) {
return { post_slug: model.get("slug") };
}
});
I'm using:
DEBUG: -------------------------------
DEBUG: Ember : 1.1.2
DEBUG: Ember Data : 1.0.0-beta.3
DEBUG: Handlebars : 1.0.0
DEBUG: jQuery : 1.9.1
DEBUG: -------------------------------
Adding this helps. now primary key is slug for Project model and it doesn't duplicate rows.
App.ProjectSerializer = DS.RESTSerializer.extend({
normalize: function(type, hash, property) {
hash.id = hash.slug;
return this._super(type, hash, property);
}
});
By calling
store.find('project', 'first-project')
you're treating the slug as the primary key. I guess that's the reason that a new record is initialized (you have one with the actual primary key of the first project which is probably a number, and one with 'first-project' as the primary key.
Try changing the model hook by calling the store's find() method with a hash of search options, in your case:
store.find('project', { slug: 'first-project' })
There are two other ways to solve this problem without extending the RESTSerializer.
First method: If you can make sure that the slug str is unique across the whole app, and you don't really care about id numbers. Then you can set the ids for each project using the slug str because ids don't have to be numbers.
{ id: 'first-project', content: 'xxx' }
{ id: 'second-project', content: 'yyy' }
Second method. use the modelFor method. example code below:
App.ProjectShowRoute = Ember.Route.extend({
model: function(params) {
return this.modelFor('its_parent_route').findBy('slug', params.post_slug);
},
serialize: function(model) {
return { post_slug: model.get("slug") };
}
});
By calling modelFor here, it won't make duplicate calls to the server since you're using ember data.
Hope it helps.

Resources