Using Eloquent for this kind of relationship - database

This is what my table relationship is like:
One user may have multiple groups. One group may have multiple users.
One message maybe be only by one user and group.
I have three models in laravel.
User,Message, and Group. And my pivot table is mssg_group which stores which user sent which message to which group.
(if this design is incorrect, do tell me the better way).
How do I write relationships to access all messages before a certain time by supplying a groupID?

I think your searching for the hasManyThrough relationship.
You need a Group model with a relationship to messages. See the Laravel Docs for more info.
/**
* app/Group.php
*/
public function messages(){
return $this->hasManyThrough('App\Message','App\MessageGroup');
}
In your Message Group Model you need to create the messages relation.
/*
* app/MessageGroup.php
*/
protected $table = "mssg_grp";
public function messages(){
return $this->hasMany('App\Message','id','mssg_id');
}
In your Message model be sure to define your custom table name.
/*
* app/Message.php
*/
class Message extends Model {
protected $table = 'mssg';
}
In your controller you can eager load the date requirement.
$group_messages = Group::where('id', $group_id)->with(function($query)
{
return $query->where('created_at','>',$date);
})->get()
I haven't tested it, so this may need to be tweaked a little bit, but it should get you close to where you want to be. I haven't address retrieving users, but it should be nearly the same as getting the messages synced up. Let me know if this helps.

Related

Laravel multiple user id tables

I'm pretty new to laravel and have a really basic question related to relationships.
Here is an example of my question:
I have a migration called money_transfers.
The migration contains the following things:
user_id (transfer sent by)
sentTo_id (transfer sent to)
amount
sent_at
BOTH user_id and sentTo_id refer to a User ID.
Now, what I want to do is the following:
Fetch the user the money was sent TO the same way as the user the money was sent BY. Just like in the example below:
$transfer->sentTo->name
or
$transfer->sentTo->id
You get what I mean. Thanks in advance :)
If you defined your foreign keys correctly in your migration table, Then it's just a matter of defining the right relationship:
class MoneyTransfer extends Model
{
public function sentBy()
{
return $this->belongsTo(User::class,'user_id');
}
public function sentTo()
{
return $this->belongsTo(User::class,'sentTo_id');
}
}
This way you can access the receiver attribute like this:
$transfer->sentTo->name;
And the sender attribute like this:
$transfer->sentBy->name;

Laravel 5.4 accessor with relationship returns related model

I probably missed something really obvious, but have had no luck thus far. I have two related models, Messages and Users. I'm trying to append only the 'username' of the user who created the message when retrieving all messages. I am getting a fully serialized related user model however. I understand I could $hidden in my User model, but would like to avoid that.
In my Messages model, I've created a relationship
public function createdBy()
{
return $this->belongsTo('App\User', 'created_by');
}
Then I've added
protected $appends = ['username'];
Along with a custom attribute
public function getUsernameAttribute($value)
{
$username = null;
if ($this->createdBy){
$username = $this->createdBy->username;
}
return $username;
}
When I'm getting messages through another relationship such as
return $channel->messages()->orderBy('id','desc')->get();
I end up with the following response:
[{"id":97,"to_id":"12345","message":"This is the message content","created_at":"2017-02-25 08:58:22","created_by":{"id":1,"email":"REDACTED","name":"REDACTED","username":"myusername","created_at":"2016-09-26 16:00:00","created_by":null,"updated_at":"2017-02-20 00:33:06","updated_by":null},"updated_at":"2017-02-25 08:58:22","updated_by":null,"deleted_at":null,"username":"myusername"}, ...
What am I doing wrong, or is there a better way to accomplish what I'm trying to do?
I didn't realize I was able to hide the relationship in the original model. I ended up hiding the createdBy relationship in the Messages model.
protected $hidden = [
'createdBy'
];

CakePHP: how do I provide hard coded data for a model instead of a table to provide it?

I presently have a model called UserLevels which defines some user levels (user, premium user, admin, etc) and some properties of them (number, description, color, etc).
I've decided I would rather hard code this, and be able to use the __() method on the names and descriptions.
How do I go about providing data to a model so that it doesn't use a database? Is there a better way to approach this?
Thank you.
Update: Full multi-field records in a model without a database
After the author made his goal more clear, this should be the solution:
How to use models without database on CakePHP and have associations?
Don't get the title wrong, read it first, the answer describes exactly what would be the solution here as well.
Solution for single field, enum like data:
If you don't use a DB table and entries are limited it is always good to use constants because you can't do a typo without causing an error somewhere and UserLevel::USER is much more clear than a random 'user' string somewhere that could mean anything.
class UserLevel extends AppModel {
public $useTable = false;
const ADMIN = 'admin';
const USER = 'user';
/* ... */
public function getUserLevels() {
return [
UserLevel::ADMIN => __('Admin'),
/* ... */
];
}
}

Playframework Siena Filtering and Ordering

This is my first question on any of these websites so pardon my unprofessionalism.
I use playframework with SIENA module (with GAE) and I came accross the following problem:
Given 3 entities:
public class Meeting extends Model{
#Id
public Long id;
public String place;
#Owned
Many<MeetingUser> users;
.
.
.
}
public class User extends Model{
#Id
public Long id;
public String firstName;
public String lastName;
#Owned
Many<MeetingUser> meetings;
.
.
.
}
public class MeetingUser extends Model{
#Id
public Long id;
public Meeting meeting;
public User user;
.
.
.
public User getUser(){
return Model.all(User.class).filter("id", user).get();
}
public Meeting getMeeting(){
return Model.all(Meeting.class).filter("id", meeting).get();
}
}
For instance I am listing a meeting and all their users:
public static void meetingInfo(Long meetingId){
Meeting meeting = Models.all(Meeting.class).filter("id",meetingId);
List<MeetingUser> meetingusers = meeting.asList();
List<User> users = new ArrayList<User>();
for(MeetingUser mu: meetingusers){
users.add(mu.getUser());
}
render(users);
}
This is done(is there any better way here?) however when it comes to filtering (especially dynamic filtering for many many fields) I can not use the Query's filter method on the MeetingUser as I need to filter on a MeetingUser's field's field (firstName). The same problem arise for ordering. I need the solution for both problems.
I hope my problem is clear and I appreciate any kind of help here.
Remember that you are in GAE which is a NoSQL DB.
So you can't do Join request as in RDBMS.
Yet, this is not really the pb you have so this was just to be sure you are aware of it ;)
So if you want to find the person having given firstname in a given meeting, can you try the following:
List<MeetingUser> meetingusers = meeting.users.asQuery().filter("firstname", "XXX");
(you can also order)
Nevertheless, knowing that you can't join, remember that you can't write a query searching for a meeting in which there are users whose firstname is XXX as it would require some joins and it doesn't exist in GAE. In this case, you need to change your model following NoSQL philosophy but this is another subject
regards
Let's try to give a way to do what you want...
Your relation is a Many-to-Many which is always the worst case :)
You want to filter Meeting by User's firstname.
It requires a join request which is not possible in GAE. In this case, you must change your model by denormalizing it (sometimes use redundancy also) and manage the join by yourself. Actually, you must do the job of the RDBMS by yourself. It seems overkill but in fact, it's quite easy. The only drawback is that you must perform several requests to the DB. NoSQL means No Schema (& No Join) so there are a few drawbacks but it allows to scale and to manage huge data load... it depends on your needs :)
The choice you did to create the MeetingUser which is a "joined" table and a kind of denormalization is good in GAE because it allows to manage the join yourself.
Solution:
// fetch users by firstname
List<User> users = users.all().filter("firstName", "John").fetch();
// fetch meetingusers associated to these users (verify the "IN" operator works because I didn't use that for a long time and don't remember if it works with this syntax)
List<MeetingUser> meetingusers = MeetingUser.all().filter("user IN", users);
// now you must fetch the whole meeting because in MeetingUser, only the Meeting ID is stored (other fields are Null or O)
List<Meeting> meetings = new ArrayList<Meeting>()
for(MeetingUsers mu:meetingusers) {
meetings.add(meetingusers.meeting);
}
// use the batch feature to fetch all objects
Meeting.batch(Meeting.class).get(meetings);
// you have your meetings
Hope this helps!

Problem with altering model attributes in controller

Today I've got a problem when I tried using following code to alter the model attribute in the controller
function userlist($trigger = 1)
{
if($trigger == 1)
{
$this->User->useTable = 'betausers'; //'betausers' is completely the same structure as table 'users'
}
$users = $this->User->find('all');
debug($users);
}
And the model file is
class User extends AppModel
{
var $name = "User";
//var $useTable = 'betausers';
function beforeFind() //only for debug
{
debug($this->useTable);
}
}
The debug message in the model showed the userTable attribute had been changed to betausers.And It was supposed to show all records in table betausers.However,I still got the data in the users,which quite confused me.And I hope someone can show me some directions to solve this problem.
Regards
Model::useTable is only consulted during model instantiation (see the API documentation for Model::__construct). If you want to change the model's table on the fly, you should use Model::setSource:
if ( $trigger == 1 ) {
$this->User->setSource('betausers');
}
The table to use is "fixed" when the model is loaded/instantiated. At that time a DB connection object is created, the table schema is being checked and a lot of other things happen. You can change that variable later all you want, Cake is not looking at it anymore after that point.
One model should be associated with one table, and that association shouldn't change during runtime. You'll need to make another model BetaUser and dynamically change the model you're using. Or rethink your database schema, a simple flag to distinguish beta users from regular users within the users table may be better than a whole new table.

Resources