Eloquent Model with nested model issue - eloquent

I have got a couple of tables looking like this:
1) article
id
name
2) article_options
id
article_id
option_id
3) option
id
name
An article can have none, one or many options and I want to return in the RestAPI a JSON structure that looks like this:
[{
"id": 1,
"name": "Article1",
"options": [10]
},
{
"id": 2,
"name": "Article2",
"options": [3, 10]
},
{
"id": 3,
"name": "Article3",
"options": []
}]
whereby 3 and 10 would be the option ids.
I tried a few solutions but none worked. Here is what I currently have:
Article Model:
class Article extends Model
{
# protected $with = ['Article_Option'];
public function options()
{
return $this->hasMany('App\Article_Option');
}
}
Article Controller:
class ArticleController extends Controller
{
public function index()
{
$articles = Article::all();
return response()->json(
$articles
, 200);
}
}
I just can't get my head around how the model and the controller should be configured to achieve this.
Please help.
Thanks
Goppi
[UPDATE]
Thanks to Hides help. With some modification I got it to work the way I wanted. Here is all the code...
Article model:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
public function options()
{
return $this->belongsToMany('App\Option');
}
}
ArticleController:
namespace App\Http\Controllers;
use App\Article;
use App\Http\Resources\Article as ArticleResource;
use Illuminate\Http\Request;
class ArticleController extends Controller
{
public function index()
{
$articles = ArticleResource::collection(Article::all());
return response()->json ($articles, 200);
}
}
and the Resource:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class Article extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'options' => $this->options->pluck('id')->all()
];
}
}

article_options is your pivot table and the relationship between article and options is a many to many relationship which laravel provides a function for here. So all you will have to do is add the relationship to your article model.
public function options() {
return $this->belongsToMany(Option::class);
}
then when getting the articles you can call that relationship.
$articles = Article::with('options')->get();
This will grab all articles with all option relations and format it in the way that you desired. Laravel can automatically work out which fields it needs to use to form the relationship between the tables so don't worry about that though it does have an option to supply them in the belongsToMany function.
If you are only wanting the id you can pass specific columns to the with function as below.
$articles = Articles::with('options:id')->get()
To then return in a json format you can use resources. Resources help better format the collection of the model which can be used to achieve what you want. You will need to create a resource for articles. In the article resource you can format it how you wanted and to format the options you can use collection methods to transform it into an array of ids by plucking the field from the collection.
Article Resource
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'options' => $this->options->pluck('id')->all(),
];
}
So once you have got the articles you can return them like so
return new ArticlesResource::collection($articles);
If later on you can wanting to pass additional columns through for options you can create an options resource and pass that to the options key in you article resource.
'options' => new OptionsResource::collection($this->options)

Related

Create a Notification System Based on User Preferences

I am trying to develop a notification system, but I am not sure if I do certain parts correctly. To simplify the case I will use some generic naming.
How the system should work:
A registered user can subscribe for notifications based on chosen filters from a data table grid. ( for example, notify me when the quantity of an item is X, or have multiple filters set up like set1.slug.quantity > X, some_value = false and some_int = 52)
How I store such preferences:
Example object
"O:8:"stdClass":2:{s:9:"set1.slug";a:1:{s:3:"$eq";s:10:"item_slug1";}s:13:"set1.quantity";a:1:{s:4:"$gte";i:1;}}"
Generation simplified
$object = new \stdClass();
$object->{'set1.slug'} = ['$eq' => 'item_slug1'];
$object->{'set1.quantity'} = ['$gte' => 1];
$object = serialize($object);
It attaches also the user_id and all the data serialized from the form to a partial MongoDB raw query.
Database stored object - predefined filter set for a user. Md is an md5 hash of the object for easier access and edit.
+----+-----------------------------------------------------------------------------------------------------------------+---------+----------------------------------+
| id | object | user_id | md |
+----+-----------------------------------------------------------------------------------------------------------------+---------+----------------------------------+
| 1 | O:8:"stdClass":2:{s:9:"set1.slug";a:1:{s:3:"$eq";s:10:"item_slug1";}s:13:"set1.quantity";a:1:{s:4:"$gte";i:1;}} | 22 | d5003ba3227c4db3189827329815b053 |
+----+-----------------------------------------------------------------------------------------------------------------+---------+----------------------------------+
This is how I would use it - my vision of how it would work.
I would call findByFilterMD in the API parser in a loop, while the table Items is populated.
// MongoDB query ( items database )
protected function executeUserFilter($array)
{
$list = Items::raw(function ($collection) use (&$array) {
return $collection->find(
$array, ["typeMap" => ['root' => 'array', 'document' => 'array']])
->toArray();
});
return $list;
}
// MySQL query - stored filters
protected function findByFilterMD($id)
{
$user = Auth::user();
$filter = PredefinedFilters::where('md', '=', $id)->first();
$deserialize = unserialize($filter->object);
$results = $this->executeUserFilter($deserialize);
// here would probably be a notification function like pusher or onesignal
}
I am aware that my attempt of achieving this might be totally wrong and I might reinvent the wheel since some tools might do that already.
Here is an example Item MongoDB object
{
"_id": {
"$oid": "5c0406abdc04e7007f17f4ef"
},
"name": "myObject",
"inner_ID": "0db0b19a-9c01-4c21-8e10-6879dbcb37f1",
"some_value": false,
"some_int": 52,
"set1": [
{
"slug": "item_slug1",
"quantity": 88,
"extra": {
"value": 0
}
},
],
"set2": [
{
"slug": "item_slug2",
"quantity": 88,
"extra": {
"value": 0
}
},
{
"slug": "item_slug3",
"quantity": 88,
"extra": {
"value": 0
}
}
],
"expires": "2018-12-02 22:21:30"
}
Here comes my questions
Is this way of doing it proper?
Where should the notification system kick in? I assume it might be in the place where I parse the api of items, then I should loop over the user filter data and run the stored object query - or should it be a separate system called with cron?
I am open to any suggestions, redesigns.
I developed an app kinda like this. My approach is to make use of Laravel notification found here:
https://laravel.com/docs/5.7/notifications#creating-notifications
Let's say, in your case, if someone modify/create data, the other users who subscribe will get notification.
Create notification
php artisan make:notification UserUpdateQuantity
Make User model notifiable, also create scope that subscribe for something
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
public function scopeSubscribe($query)
{
return $query->where('subscribe', true);
}
}
the $query in method scopeSubscribe needs to be adjusted based on your business logic
Send notification
$subscriber = User::subscribe()->get();
Notification::send($subscriber, new UserUpdateQuantity());
Create event & listener
You can find the event and listener here https://laravel.com/docs/5.7/events
In the EventServiceProvider
protected $listen = [
'App\Events\QuantityUpdated' => [
'App\Listeners\SendUpdateQuantiryNotification',
],
];
Then run the command php artisan event:generate
Event listener
In the event listener, we send notification
public function handle(QuantityUpdated $event)
{
$subscriber = User::subscribe()->get();
Notification::send($subscriber, new UserUpdateQuantity());
}
Eloquent event
Add event on your eloquent model, so when someone update quantity, it triggers event and the listener will send notification to subscribed users
// In your model
protected $dispatchesEvents = [
'updated' => App\Events\QuantityUpdated::class
];

How to search for list of keyword with Spring Data Elasticsearch?

I am using ElasticsearchRepository and I want to search some keywords. What I want to query is like;
//Get all results which contains at least one of these keywords
public List<Student> searchInBody(List<String> keywords);
I have already created a query for single keyword and It works but I don't know how to create a query for multiple keywords. Is there any way to do this?
#Repository
public interface StudentRepository extends
ElasticsearchRepository<Student, String> {
public List<Student> findByNameOrderByCreateDate(String name);
#Query("{\"query\" : {\"match\" : {\"_all\" : \"?0\"}}}")
List<ParsedContent> searchInBody(String keyword);
}
Yes, You can pass an array of String objects in ElasticsearchRepository.
Elasticsearch provides terms query for that.
Also You have to use JSONArray instead of List<String> i.e. you have to convert your List<String> to JsonArray. (Reason: check syntax of elastic query provided below)
Here is how you can use it in your code:
#Query("{\"bool\": {\"must\": {\"terms\": {\"your_field_name\":?0}}}}")
List<ParsedContent> searchInBody(JSONArray keyword);
Result will contain objects with atleast one keyword provided in your keyword array.
Following is rest request representation of above java code that you can use in your kibana console or in terminal:
GET your_index_name/_search
{
"query" : {
"bool": {
"must": {
"terms": {
"your_field_name":["keyword_1", "keyword_2"]
}
}
}
}
}
Note: For more options, You can check terms-set-query

Laravel relationship with nested columns?

I am using Laravel with mongodb, i have little understanding in laravel eloquent relationship,currently my collection structure is as follows
collection name:general_details
{
"id": 01,
"personal_details":[
[
"emp_id":10,
"blood_group":"B+ve"
],
[
"emp_id":11,
"blood_group":"B+ve"
]
]
}
collection name:employee_details
{
"emp_id":10,
"emp_name":"Alex"
},
{
"emp_id":11,
"emp_name":"Ramesh"
}
i want to create eloquent relationship between two collections for "emp_id", please suggest any solution?
In GenDetails model
public function empdetails(){
return $this->hasOne('App\EmpDetails');
}
In EmpDetails Model
public function genDetails(){
return $this->belongsTo('App\GenDetails');
}
here is one to one relationship between GenDetails and EmpDetails model. So learn more from laravel documentation for Eloquent Relationship.
In GenDetail Model put this relationship
public function empDetails(){
return $this->hasMany('App\EmpDetails','emp_id','personal_details.emp_id');
}
i think this relationship will defiantly work for you.

How to implement Role-based REST API

I have two different roles in my project: ROLE_USER and ROLE_ADMIN.
I want to get list of all users through REST API's url '/users', but some fields (for example email) can see only those person, who authenticated with ROLE_ADMIN.
So, I have generally 2 questions:
1) On which abstraction level (in MVC pattern) should I decide which information can be returned based on ROLE
2) Which is the best way to implement such a Role-based REST API in Symfony?
Thanks
If you are using JMSSerializer you can use groups to decide what can be seen or not. Then in your controller, or where ever, you could set the group based on the role.
For example with the mapping (in YAML)..
Fully\Qualified\Class\Name:
exclusion_policy: ALL
properties:
id:
groups: [user]
userAndAdmin:
groups: [user]
adminOnly:
groups: [admin]
And then in your controller you would set the group like...
public function getUsersAction(Request $request)
{
$users = $this->getRepository()->findAll();
$serializer = $this->get('jms_serializer.serializer');
$json = $serializer->serialize(
$users,
'json',
SerializationContext::create()->setGroups($this->generateGroups())
);
return new Response($json);
// If you are using FOSRestBundle, which I would recommend, then you would just need to do...
$view = $this
->view($this->getRepository()->findAll();)
->setExclusionGroups($this->generateGroups())
;
return $this->handleView($view);
}
private function generateGroups()
{
$securityContext = $this->get('security.context');
$groups = array();
if ($securityContext->isGranted('ROLE_USER')) {
$groups[] = 'user';
}
if ($securityContext->isGranted('ROLE_ADMIN')) {
$groups[] = 'admin';
}
return $groups;
}
Although the whole "generateGroups" and setting the groups would be better placed in a customer view handler or response generator.
Assuming your hierarchy has ROLE_ADMIN as a parent of ROLE_USER you would get the following results.
ROLE_USER
{
"users": [
{
"id": 1,
"userAndAdmin": "val"
}
]
}
ROLE_ADMIN
{
"users": [
{
"id": 1,
"userAndAdmin": "val",
"adminOnly": "val"
}
]
}
Since the API is dependent on user who is making the request, each request will have to carry the information about the current user. Usually all authorization related tasks are processed within the controller. So, answer to your first question is that you should process the roles in the controller and based on the roles, you should filter out the fields from the data returned from the repository. For example,
//users is the array of user objects returned by your repository
data = [];
if ($this->get('security.context')->isGranted('ROLE_ADMIN')) {
foreach($users as $user){
// Add ROLE_ADMIN specific data
data[][] = array(
'name' => $user->getName(),
'email' => $user->getEmail(),
);
}
}
if ($this->get('security.context')->isGranted('ROLE_USER')) {
foreach($users as $user){
// Add ROLE_USER specific data
data[][] = array(
'name' => $user->getName(),
'address' => $user->getAddress(),
);
}
}
then, JSON encode the data array and return as the response. To reduce the number of queries, you can add a __toArray method in your User class.

Designing output object of RESTful service

I am creating a new project using a REStful service. I need to send a single object containing
collection of object containing data for UI
object User( Name, role etc)
Collection of error occured(If any).
So I have designed my class like this
public class ServiceREsponse
{
Collection<ServiceError> errorCollection { get; private set; }
Collection<object> objectCollection { get; set; }
User user { get; set; }
}
How do I populate values in this class, or how do I use this class in my service?
I don't know exactly the technologies / frameworks you use as client to the RESTful service. In fact, you need to instantiate your class and populate with data you want to send. Then you need to convert this object into a structure that can be put into the payload of the HTTP request.
Here is a sample:
PUT /myresource
(some headers like Content-Type: application/json)
{
"objectCollection": [
(...)
],
"user": {
(...)
}
}
The service may not support all structures but formats like JSON, XML or YAML are commonly supported. With JSON, we will have something like that:
{
"objectCollection": [
{ "field1": (...) },
{ "field1": (...) },
{ "field1": (...) },
(...)
],
"user": {
"name": "my name",
(...)
}
}
You can notice that collections must be converted into arrays in the structure.
Moreover the generated structure must match the structure expected by the service.
With Java, you have some great REST client like Restlet (http://restlet.com/products/restlet-framework/) and object to/from JSON / XML / YAML like Jackson (http://wiki.fasterxml.com/JacksonHome).
Hope it helps,
Thierry