I'm using Laravel to create a basic site with the following function: A user can follow certain topics. I have a 'users' table, a 'topics' table, and as a pivot table, I have a 'following' table.
users
----
user_id (primary)
user_first_name
etc..
following
---------
follow_id
user_id (foreign)
topic_id (foreign)
topics
------
topic_id (primary)
topic_name
etc...
I'm trying to create a page that displays all of the topics, but for the topics that the current user is following, I need to show an overlay on the box.
I have a User Model with the following function:
public function follows() {
return $this->belongsToMany('Topic', 'following', 'user_id', 'user_id');
}
However, I'm not too sure where to go from here (or whether this is right!)
Would be hugely grateful for any help.
First of all, you made a mistake on your follows method.
You have the same variable name on local and foreign id 'user_id'.
Then,
Did you already add a topic to an user ?
If yes, it would be great if you do the same as in your User model on the Topic model
public function followedBy()
{
return $this->belongsToMany('User', 'following', 'topic_id', 'user_'id');
}
From here, you can add a following topic to users by doing
$user->following()->attach($topic_id);
Or
$user->following()->attach([$first_topic, $second, $third, ...]);
You can use the sync method too, but that will delete all previous relationship between user and the topics which are not in the array.
To retrieve all information you can simply do the following:
foreach ($user->following as $topic) {};
/!\ Do not add parentheses to following otherwise you will get a QueryBuilder instead of a collection of the topics. /!\
If you want to add more filters (for example only active topics)
foreach ($user->following()->active()->get() as $topic) {}
Notice that here I added the parentheses which are necessaries because I do not directly want the topics but a QueryBuilder to filter the results.
Call the ->get() method when you are done filtering.
(This suppose you have a method called scopeActive() in your model)
See Laravel scope to do so : http://laravel.com/docs/eloquent#query-scopes
You can do the opposite on the topic side by doing :
foreach ($topic->followedBy as $user) {}
PS: sorry for my English, If you misunderstood something. Not my native language.
You sustain the following function in User Model
public function follows() {
return $this->belongsToMany('Topic', 'following');
}
and use below statement to retrieve the all topics of any user
$topics = User::find(1)->follows;
Where 1 is the user id for particular user.
1 In your setup you use non-default primary keys (topic_id / user_id / follow_id instead of id) so be sure to set:
protected $primaryKey = 'topic_id';
on each of your models accordingly.
2 I would suggest renaming the relation - follows is ambiguous, unless you have literally 3 models there.
3 Your relation for your current setup, like already suggested by #ChainList:
public function follows()
{
return $this->belongsToMany('Topic', 'following', 'user_id', 'topic_id');
}
4 In order to check if a user already follows given topic, do this:
// I Assume logged in user
$user = Auth::user();
$topics = Topic::all();
// somewhere in your view
#foreach ($topics as $topic)
{{ $topic->name }}
#if ($user->follows->contains($topic->id))
Already following this topic
#else
Follow this topic (put a link here or whatever)
#endif
#endforeach
With this you run just a single query for user's topics, when you call $user->follows for the first time.
My suggestion would be:
// leave id as primary key, it will make your life easier
// rename relation to
public function topics()
{
// rename table name to something meaningful
return $this->belongsToMany('Topic', 'topic_user'); // laravel convention
// or if you like topics_followers
}
Almost there, the follows() relationship needs to be like this:
public function follows() {
return $this->belongsToMany('Topic', 'following', 'user_id', 'topic_id');
}
Then you should be able to grab the topics associated to the current user by doing this:
$topicsUserIsFollowing = Auth::user()->follows;
To start following a topic, you can do this (assuming you have the topic's ID):
Auth::user()->follows()->attach($topic_id);
Edit
If you want to see if a topic is followed by someone, then in your Topic model put a function like this:
public function isFollowedBy($user)
{
return $user->follows->contains($this->topic_id);
}
So then you can do something like this:
$currentUser = Auth::user();
$topics = Topics::all();
foreach($topics as $topic) {
echo $topic->topic_name;
if( $topic->isFollowedBy($currentUser) ){
echo ' - [Already following this topic]';
} else {
echo ' - [Follow this topic]';
}
}
You'd want to put the loop in your view, this is just for illustration
Related
i have three models
Article
id
title
Comment
id
title
user_id
article_id
User
id
name
what i wanna achieve is to select one article based on its id with comments and user info that made that comment
like that :
$article = Article::find($id -- say 1)->with('comments' -- this is a relation in Article Model)->get();
this gives me article with related comments as an array of objects say comment one - comment two etc ....
what i want instead of user_id in comment object i wanna it to be a user object
see this pic thats what i reached so far
using laravel 5.4
You can use following:
$articles = Article::find($id)->with('comments', 'comments.user')->get();
Here 'user' is the relationship you mentioned in the comments model for User.
If you have defined the foreign key relationship in Schemas, you can define functions for Eloquent Relationship as defined in following reference link -
Laravel - Eloquent Relationships.
You can define functions in models as follows -
Article -
class Article extends Model
{
...
public function comments(){
// Accessing comments posted to that article.
return $this->hasMany(\App\Comment::class);
}
// Create a foreign key to refer the user who created the article. I've referred it here as 'created_by'. That would keep relationship circle complete. You may ignore it if you want.
public define user(){
// Accessing user who posted the article
return $this->hasOne(\App\User::class, 'id', 'created_by');
}
}
Comment -
class Comment extends Model
{
...
public function article(){
// Accessing article to which the particular comment was posted
return $this->hasOne(\App\Article::class, 'id', 'article_id');
}
public function user(){
// Accessing user who posted the comment
return $this->hasOne(\App\User::class, 'id', 'user_id');
}
}
User -
class User extends Models
{
...
public function articles(){
// Accessing articles posted by a user
return $this->hasMany(\App\Article::class);
}
public function comments(){
// Accessing comments posted by a user
return $this->hasMany(\App\Comment::class);
}
}
Now you can use like following -
$article = Article::findOrFail($id);
$comments = $article->comments;
$article_user = $article->user;
$comment_user = Comment::findOrFail($commnet_id)->user;
$users_comments = User::findOrFail($user_id)->comments;
$users_articles = User::findOrFail($user_id)->articles;
and so on...
It is far better to use ->find() at last instead of ->get() because get() returns a Collection.
This way you will get a single object which you want instead of a Collection.
For example:
$commentableObj = Post::with(['comments'])
->withCount(['comments'])
->findOrFail($commentable->id);
I get all items owned by authenticated user.
$items=Auth::user()->with('items')->get();
In my view i can access items collection, but instead of title_id want to retrieve item_title value.
Currently i'm able to get item_title value using code below:
$item->title->title
Is it possible to simplify it to retrieve title like this:
$item->title
?
Here is my models and relations:
Users
id
username
Item
id
user_id
title_id
Item_Titles
id
title
User model:
public function items()
{
return $this->hasMany('Item', 'user_id', 'id');
}
Item model:
public function user(){
return $this->belongsTo('User', 'user_id', 'id');
}
public function title()
{
return $this->belongsTo('ItemTitle','title_id','id');
}
ItemTitle model:
public function item()
{
return $this->hasMany('Item', 'title_id', 'id');
}
UPDATE
Excuse me probably I wasn't clear. To be precise - I just want to find Eloquent alternative to:
$items=Item::where('user_id','=',Auth::id())->leftJoin('item_titles', 'item.title_id', '=', 'item_titles.id')->get();
#foreach ($items as $item)
{{ $item->title }}
#endforeach
Just change your relationship function to
Item model:
public function title()
{
return $this->belongsTo('ItemTitle','title_id','id')->first()->title;
}
You will need to call it as $item->title() unless you also do
public function getTitleAttribute(){
return $this->title();
}
You might get some funny stuff with everything being called 'title' but with this $item->title should also work I think
Yes it is. It looks like you setup a many-to-many relationship with the Item model being the pivot table.
User Model
public function titles()
{
return $this->belongsToMany('ItemTitle', 'items');
}
Note: Change ItemTitle to the correct namespace. Also, change items to the Item model's table name.
You can also define the inverse relationship like this:
ItemTitle Model
public function users()
{
return $this->belongsToMany('User', 'items');
}
From there, you can get all the authenticated user's ItemTitles like this:
$titles = Auth::user()->titles;
Link to the documentation: https://laravel.com/docs/5.1/eloquent-relationships#many-to-many
Editing based on the comments below (thanks to #ash for helping clarify and for his suggestion):
The other answer is more along the lines of what you are trying to achieve so I would recommend taking a look at that. However, there is an error in your question. This does not return items:
$items=Auth::user()->with('items')->get();
That returns all users with their items eager loaded. To see proof of this, if you dd($items), you will most likely see every single user in the database.
That is most likely not what you want to do. To get all items for the authenticated users, you should do this:
$items = Auth::user()->items;
It's that simple to get a collection of items. This will run 2 queries - 1 to get the user and another to get all of his items.
For example I have:
// Returns all projects
$projects = Projects::all();
To return categories, belonging to project, I use relationships and can do something like:
foreach($projects as $project) { ...show $project->categories... }
I need only specific projects, which are followed by specific user. On my projects_followers table I have user_id and project_id.
To retrieve projects which were followed I have this peace of code:
$projects = Project::rightJoin(DB::raw('(select * from projects_followers group by project_id) projects_followers'),'projects_followers.project_id','=','projects.id')->get();
// Note: This code doesn't include specifuc user_id.
It does retrieve specific rows, but the problem with this code is that laravel relationhips dont work on them. For example $project->categories return empty.
// Relationship
public function categories()
{
return $this->belongsToMany('App\Category');
}
How do I retrieve my model specific rows and make relationships to work?
Actually your question is:
How do I get projects liked/followed by Auth/Logged in User ?
Unfortunately you described it in such a way that it looks something else, anyways. Lets try to find the solution and for this I would like to use something like this:
$projects = Auth::user()->favorite_projects;
So how we can implement this to work, first of all the User Model should contain the method favoriteProjects so lets create it:
public function favoriteProjects()
{
return $this->belongsToMany(
'App\Project',
'projects_followers', // This table already exists as you mentioned
'user_id',
'project_id'
);
}
That's it. You will be able to load the projects followed by the current user and other relationship methods will work on every single project as well.
My workaround:
// I don't know how to create empty collection, so impossible conditions here.
$projects = Project::where('id', 0)->get();
$follows = DB::table('projects_followers')->where('follower_id', Auth::user()->id)->get();
foreach($follows as $follow) {
$project = Project::where('id', $follow->project_id)->first();
$projects = $projects->add($project);
}
In Laravel I just started with models and I have this database structure:
users
id | username | password | group | //(group is the id of the group)
groups
id | groupname |
group_pages
group_id | page_id | create | read | update | delete
pages
id | pagename
I am trying to check if the user can create/read/update/delete on the page he's on.
So I have 4 Models for this at the moment: Users, Pages,Group_pages and Groups. So in the models, I define the relationships like so:
User model:
public function group()
{
return $this->belongsTo('group', 'group', 'id');
}
Group Model:
public function users()
{
return $this->hasMany('users', 'group', 'id');
}
public function group_pages()
{
return $this->hasMany('group_pages', 'group_id', 'id');
}
I am using this in my controller like this:
$group_id = User::find(Session::get('user_id'));
$crud = Group::find($group_id->group)->group_pages()->first();
As described in the documentation.
but this is giving me the error:
Class group_pages not found
What is going wrong here?
I'm not sure about assigning the keys in the relationships.
I know this:
One to One Inverse:
return $this->belongsTo('class', 'local_key', 'parent_key');
One to Many:
return $this->hasMany('class', 'foreign_key', 'local_key');
I dont know about the One to Many Inverse. I know it's: return $this->belongsTo('table');, but I dont know about the keys.
Group_pages model:
class Group_pages extends Eloquent {
public function pages()
{
return $this->belongsTo('pages', 'id', 'group_id');
}
public function group()
{
return $this->belongsTo('group', 'id', 'group_id');
}
}
Model files should be named singularly and in camel-case, i.e. User, Page, Group. A model representing the join between users and groups isn’t necessary.
Then when it comes to defining the relationships, the first parameter is the class name of the model:
class User {
public function group()
{
return $this->belongsTo('Group', 'local_key', 'parent_key');
}
}
You’re making life difficult for yourself by going against Laravel’s conventions.
If you name your columns as per Laravel’s conventions, you then don’t need to specify them in your relationship definitions either. So your users table should have a column named group_id that’s a foreign key referencing the id column in your groups table. Your relationship can then be expressed like this:
class User {
public function group()
{
return $this->belongsTo('Group');
}
}
A lot more succinct and easier to read, and you don’t have to remember which way around the local and foreign column names go.
You can read more about the conventions Laravel uses for model and relation names in the official documentation: http://laravel.com/docs/master/eloquent#relationships
You defined your relationship with a model-class that does not exists.
To solve this, create a group_page-model (or even better GroupPage) and change the corresponding relationship (return $this->hasMany('GroupPage', 'group_id', 'id'); within your Group-model.
Then fix the relationship in your User-model:
public function group() // typo! not groep..
{
return $this->belongsTo('group', 'group'); // remove id, you do not need it
}
Then there is a problem with your controller code which might be fixable like that:
$group_id = User::find(Session::get('user_id'))->group()->id;
$crud = Group::find($group_id)->group_pages()->first();
I always like to recommend Laracasts to peopel who are new to Laravel (i hope you do not know this yet). The basic screencasts are all free (laravel 4 from scratch and laravel 5 fundamendals) and you will lern very fast in no time! Specifically, have a look at the episode on Eloquent Relationsships.
I also strongly recommend sticking to conventions
use the column-name group_id on the users-table for the group-foreign-key).
Classnames should be PascalCase -> Group, not group, and when commiting them as parametes, stick to it (belongsTo('Group'))...
This makes life much easier!
Finally
Be aware that there might be packages for what you are trying to achieve. One that comes to my mind is Entrust.
You're making your life hard with this code and thus you can't make it work.
Check this out first:
// This is User model, NOT group_id
$group_id = User::find(Session::get('user_id'));
Next:
public function group() // I suppose groep was typo, right?
{
// having relation with same name as the column
// makes Eloquent unable to use the relation in fact
return $this->belongsTo('group', 'group', 'id');
}
So, here's what you need:
// User model
public function group()
{
return $this->belongsTo('Group', 'group_id'); // rename column to group_id
}
// Group model
public function pages()
{
return $this->belongsToMany('Page', 'group_pages')
->withPivot(['create', 'read', 'update', 'delete']);
}
Then:
$user = User::find(Session::get('user_id')); // or use Auth for this
$page = $user->group->pages()->find($currentPageId);
// now you can access pivot fields:
$page->pivot->create;
$page->pivot->update;
... and so on
I'm implementing relationships in Eloquent, and I'm facing the following problem:
An article can have many followers (users), and a user can follow many articles (by follow I mean, the users get notifications when a followed article is updated).
Defining such a relationship is easy:
class User extends Eloquent {
public function followedArticles()
{
return $this->belongsToMany('Article', 'article_followers');
}
}
also
class Article extends Eloquent {
public function followers()
{
return $this->belongsToMany('User', 'article_followers');
}
}
Now, when listing articles I want to show an extra information about each article: if the current user is or is not following it.
So for each article I would have:
article_id
title
content
etc.
is_following (extra field)
What I am doing now is this:
$articles = Article::with(array(
'followers' => function($query) use ($userId) {
$query->where('article_followers.user_id', '=', $userId);
}
)
);
This way I have an extra field for each article: 'followers` containing an array with a single user, if the user is following the article, or an empty array if he is not following it.
In my controller I can process this data to have the form I want, but I feel this kind of a hack.
I would love to have a simple is_following field with a boolean (whether the user following the article).
Is there a simple way of doing this?
One way of doing this would be to create an accessor for the custom field:
class Article extends Eloquent {
protected $appends = array('is_following');
public function followers()
{
return $this->belongsToMany('User', 'article_followers');
}
public function getIsFollowingAttribute() {
// Insert code here to determine if the
// current instance is related to the current user
}
}
What this will do is create a new field named 'is_following' which will automatically be added to the returned json object or model.
The code to determine whether or not the currently logged in user is following the article would depend upon your application.
Something like this should work:
return $this->followers()->contains($user->id);