Is it possible for revisionable to track changes to one-to-many relationships? For example:
Model: User.
Model: Posts. User model uses Venturecraft\Revisionable\RevisionableTrait; and have a hasMany relationship to Posts. If a post is added or updated, can this be tracked by revisionable under the User which the post belongs to?
Thanks in advance
I was able to come up with something. However it's not the most elegant solution, so it'd be great if somebody would help me to clean this up (especially that unset is bothering me).
My solution will create duplicates in the table the Model belongs to. I don't know if this is what you wanted
The first thing you need to do is to add a nullable datetime revision_at column in the appropriate table.
The trait itself is pretty simple. We make use of the models boot() method to register the models updating event. It will fire whenever a model is about to update. That's exactly what we need, since we don't want a revision the first time we are creating the model.
<?php
trait RevisionableTrait {
public static function boot()
{
parent::boot();
static::updating(function( $model ){
// Grab the original Model and unset the id
// from it, so that we don't get duplicate
// entries when we create a new model.
$attributes = $model->getOriginal();
unset( $attributes['id'] );
// Next we need to add the date the revision
// was created
$attributes['revision_at'] = new DateTime();
$revision = new static($attributes);
$revision->save();
});
}
}
The only thing we do here is to grab the original model before the new fields were assigned, unset the id to make sure we don't create a duplicate entry, set the current time for the revision_at field and save the model.
That's it basically.
can this be tracked by revisionable under the User which the post belongs to?
This is automatically done since the new revision model still belongs to the respective user,
If you want to fine-tune it you could create a dedicated table for revisions where a reference to the model is stored. However storing the properties might get a little bit harder (Maybe you could store them serialized).
Another possible improvement would be to modify the getter methods of the model in the trait. For example let all() only return models that are no revisions. Then add a withRevisions() method to grab them too. You can extract the logic from it if you take a look how Laravel handles Soft Deletes. It's exactly the same.
why not use this?
$user = User::first();
$user->posts->map(function($post) {
return $post->revisionHistory;
});
Related
I've been having this question for quite a long time. I guess it's more of a design problem. So I need to create a User with a mentor role that has many tutorships, but in the create user view I want to be able to add as many tutorships as I want. Once the tutorships are created, I want to be able to perform CRUD operations on them.
However, I want to be able to do this before actually saving its parent model (User) in the database. What is the common approach here? Am I supposed to create and then persist each Tutorship with an empty foreign key until I save the user? Or is it better if all the Tutorships are "floating around" until I save my user?
I worked through this earlier. I had created a trait I used on my models to allow setting relations and then I had to override the save and push on the model to work properly.
Set hasOne relation to new instance of child model
I have a many to many relationship in the database that looks like
User -> user_companies -> company.
My issue is that in terms of our business logic this should be a one to one relationship however correcting this would take a lot of work. In the pivot table there is only ever one entry of a User.
Currently on the user model I am doing this but it returns a model and not a relationship instance.
// returns relationship instance
public function companies()
{
return $this->belongsToMany('App/Company', 'user_companies');
}
// returns model
public function company()
{
return $this->companies()->first();
}
Is it possible for me to mimic the behaviour of a one to one relationship whilst the database is set up as many to many?
UPDATE:
Laravel has now added a HasOneThrough relationship out of the box. https://laravel.com/docs/5.8/eloquent-relationships#has-one-through
With this, it's possible but you'd really have to dig into the core and build your own custom solution (Relationship Query Builder).
The first thing that comes to mind is a hasOneThrough relationship method. Unfortunately, this does not exist out of the box. My recommendation is to find a reliable package to accomplish this.
In fact, I was able to find this package: https://github.com/riesjart/relaquent
Otherwise, unless your DB Schema is a complete mess, adding a company_id to your users table and then writing the sql migration script would not be complicated in the least bit. Hope this helps.
So I have a lot of controllers that will be created by one user. So on every save/create/update I want the user's ID to be saved the resource's user_id column in the database.
I know that before the actual database update I could go like
$resource->user_id = Auth::user()->id;
but this seems pretty unclean and I don't wanna do this for all the create/update actions I have spread across multiple controllers.
What would be the best and cleanest way to approach this issue?
If you are using Eloquent ORM to define $resource you can define Events for that model, that will be executed (if you wish) after or before every create, update, save, delete or restore action on that model. You can see the documentation here: Laravel 5.1 Model Events Documentation
Create a trait that hooks into the model’s saving event, and set the user ID there.
I use Eloquent to implement my models. It has a lot of methods that makes working with models much easier. However, in some cases I have to implement functionality that is accessing the database, but doesn't return an Eloquent model.
For example, imagine a common User model. Eloquent helps me a lot in CRUD operations for the User model, but what if I have to implement a method that returns some statistics about users (ex. how many total users, how many active users, etc.).
Where should I implement this functionality? In the Eloquent model, as a public static function, (e.g. User::getStats()), or should have a different class for this.
It seems a little bit unnatural to have these methods on a Eloquent model, because they are not Eloquent related.
It's really up to you, but it's a good idea to decide on a convention and stick to it.
My rule of thumb is this:
if it's related to a single item that is normally represented by an Eloquent model, do it as a public static method on that model. For example, in my current project, I have a Contract model. I needed a method that would generate a contract ID string (for compatibility with legacy systems) for new contracts. This is related to a single item (a contract) but for technical reasons needed to be generated separately from the Eloquent model (ie. it was based on a separate database with a different connection). So I created a public static method: public static function generateContractIdentifier($id, $salesRep). If you're using a repository for access to your database, you could put it there.
If it's more general (ie. not tied to an instance of the Eloquent model), I put it into a separate library. For example, in the same application I have a queue for processing items for printing (an industrial process). The application has a dashboard and I needed to show the current queue status for management. For this I created a ProcessStatus library and implemented a method: public function getStatus() which runs a query and provides the results as an array for display in a view. This is not related to any particular item in the queue, so I put it in a separate library.
As always, it depends on your project and what role the user plays in it.
But basically, no, I don't think the logic for building reports belongs on the user model. While it may be related to the user, regarding the SOLID-principle the User class should only have one responsibility, which in this case is to handle the User entity.
This contains getting and setting properties on an instance, in a simple project it's probably also fine to define some scopes on the model, e.g. to select only active users, like User::getActive();
But as your project grows, you should consider using more specific classes.
For instance, you could abstract the Eloquent functionality into a User-Repository. So now you have a handler for operations on the entitiy itself, like
$userRepo->getAll();
$userRepo->getActive();
$userRepo->getInactive();
and a handler for a User instance:
$user->getName();
$user->setStatus();
Creating reports and statistics is yet a completely different topic. So you could have something like a UserReportBuilder oder UserStatisticsService:
$userStats->getMostActive();
$userStats->getRegistrationsPerDay();
A simple example:
// UserRepository:
class UserRepository
{
protected $model = $model;
public function __construct($model)
{
// you pass in an instance of the actual Eloquent model
// so you have the whole power of eloquent in here
$this->model = $model;
}
public function getActive()
{
// this returns a collection of models
return $this->model->where('status', 'active')->get();
}
}
$userRepo = new UserRepo(new User);
And that's pretty much it. You can still work with Eloquent, but you have separated the functionality in parts with a clewar responsibility. So your UserStats class would only be resposible for building user statistics:
class UserStats
{
// You could pass in the Repository through the constructor
// or just use the Eloquent model directly
public function getRegistrationsPerDay()
{
return User::groupBy('day')->get(
[
DB::raw('DATE(created_at) as day'),
DB::raw('count(*) as registration_count')
]
);
}
}
The User instance or the UserStats-builder do not need to know how to fetch all users, and the User instance or the UserRepository do not need to know how to calculate registrations per day, so it makes sense to split that functionality into separate, independent parts that do exactly one thing.
I think you get the idea and I hope it makes sense. Maybe you should make yourself more familiar with the SOLID-principles and try to keep them in mind when you get stuck on problems like that.
Let's say I have a Widget table and the Yii Model class that goes with it.
I want to be able to instantiate it ($tempWidget = new Widget) but somehow make sure it cannot be saved to the database. I want to use the model just for the user to test things, simulate...
Obviously, I could just avoid to call $tempWidget->save() but I'd like some kind of flag that would prevent save from saving, in case some other part of the code tries to do so.
There are a few ways to accomplish what you want. The easiest way is to modify the models beforeSave() method to prevent the model from being able to save by unsetting all the attributes using the CModel unsetAttributes method , example:
public function beforeSave(){
$this->attributes = $this->unsetAttributes();
}
This will work only if you have rules associated with this model that have required fields (at least one required field), otherwise this would create an entry in your table consisting only of the primary key (assuming PK is auto increment).