Can Laravel's Eloquent update a creation time but not an update time? - laravel

I'm presently learning Laravel and Eloquent, and have set up some initial migrations to play with. One of my tables only needs a creation time, since once a row is inserted there, it will never be updated:
// The run table only needs a creation timestamp, not an updated timestamp
Schema::create('runs', function (Blueprint $table) {
$table->increments('id');
$table->timestamp('created_at');
});
I understand that Eloquent by default expects both a created_at and an updated_at column to be present, and that this feature can be turned off entirely. From the manual:
By default, Eloquent expects created_at and updated_at columns to exist on your tables. If you do not wish to have these columns automatically managed by Eloquent, set the $timestamps property on your model to false.
However, is it possible for Eloquent to be asked to automatically set a creation time and not an update time? I realise I can do this manually, but it would be nice if Eloquent could do this "for free".

Short answer: no.
I had a look at framework/src/Illuminate/Database/Eloquent/Model.php which handles the timestamps and it's not directly possible.
You could override a bunch of methods to make it work:
public function setUpdatedAt($value)
anywhere there's a reference to static::UPDATED_AT
anywhere that depends on the value of usesTimestamps()
and certainly other places
At best, this would be vulnerable to future code changes so I don't recommend it.

you can just use
$model->touch();

Related

Recommended / Standard handling of Laravel Data Migrations

Laravel ships with database migrations for managing CRUD operations regarding the structure of a database, but what is the appropriate/recommended/standardized way to handle migration of actual data?
My question is, should the data migration take place directly inside the database migration file? Should it be a seeder? Should it be a job that is dispatched from within the database migration? Where should such logic go. Sometimes these data migrations can become incredibly complex depending on what the database migration does, and in the spirit of maximizing readability and keeping responsibilities separate, I feel like the logic belongs somewhere else.
This question, I suppose, is more attributable to OOP programming structure and practice as a whole, rather than laravel specific, but Laravel is the framework I'm working in right now so framing my question in that regard.
I've done this several times, and I do it right there in the migration up() and down() functions unless we're talking about millions of records. I agree with you, it feels like there should be a clearly defined function in the migration for this. We want the data changed before another migration on the table is triggered, so I feel it needs to be done right away.
Using your example, this is what a simple migration would look like for splitting the name into a first_name and last_name in the up() function:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
class Test extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('last_name')->after('name');
$table->string('first_name')->after('name');
});
DB::statement("UPDATE users SET first_name = SUBSTRING_INDEX(name, ' ', 1), last_name = SUBSTRING(name from instr(name, ' ') + 1)");
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('name');
});
}
...
If you have complex data changes, take a look at the $table->temporary(); option to create temporary tables to do data manipulation with SQL, and/or make command scripts which are called within the migration using the Artisan::call().
$table->temporary(): https://laravel.com/docs/8.x/migrations#database-connection-table-options
Artisan::call(): https://laravel.com/docs/8.x/artisan#programmatically-executing-commands
I prefer to separate data and structure migrations. I think that migration files should include only schema related queries.
Conditionally migration could contain data changes if:
Data is dependant on the time of deployment/migration (Can't really think of a case, but I am sure there are some :)).
We are making a schema change that directly affects the data. For example: changing the type of column or creating a new key that has to be seeded before future migrations take place.
Additional reasons why I prefer to have data in seeder files:
Running migrations on productions always carries certain risks. You can lower the risks of losing your data by testing the deployment process and using some fancy CD processes, but the risk is always present.
Static data that you think will never change, will change. For example, you start a new project in 2010 and the project's database contains table 'countries', which contains a list of countries and their properties. But after 2011 you get a new country: South Sudan. Will you create new migration or just update the seeder?
Adding to the answers by #jon__o and you can find more information here. Also, I will recommend that you refer to this link where they used temporary tables based on hashed_id where temporary tables are basically identical to the normal tables in the database. It has many features that are useful for migrations.
Schema::create('temp_mappings', function (Blueprint $table) {
$table->temporary(); // thanks, Laravel
$table->integer('id')->primary();
$table->string('hash_id');
});

What is the best way to copy data from one field to another when creating a migration of a new field?

I have a database table having a field that has a boolean field type. Now, as per the new requirement, the field should be changed to the small integer type.
In order to achieve it, I created a migration and added the script in the same migration file to copy the value from the old field to the new field. However, I think this is not the best approach that I have followed. Can someone please help and advise about the best way to handle this scenario.
public function up()
{
Schema::table('skills', function (Blueprint $table) {
$table->tinyInteger('skill_type_id')->nullable()->comment = '1 for advisory skills, 2 for tools, 3 for language & framework';
});
$skill_object = (new \App\Model\Skill());
$skills = $skill_object->get();
if (_count($skills)) {
foreach($skills as $skill) {
$skill_type = 1;
if ($skill->is_tool) {
$skill_type = 2;
}
$skill_object->whereId($skill->id)->update(['skill_type_id' => $skill_type]);
}
}
}
You can do it with 02 migrations, the first one is to create the new field, as already did. The second is create a migration with raw statement to copy value from old field to new field.
If you don't need anymore old field, you can create a third migration deleting the old field.
public function up()
{
Schema::table('skills', function (Blueprint $table) {
DB::statement('UPDATE skills SET skill_type_id = IF(is_tool, 2, 1)');
}
}
You can do this(update the data) from the following way in your scenario.
Create separate routes and update the data after the migrations.
Create seeder(having the same query as above in migrations file) run the seeder.
But above both solutions are little risky if you are trying to do this with your production database. If someone mistakenly hit URL and run seeder multiple time, It's difficult to manage.
I believe the best way to solve your problem by seed(modify) the data on the same migrations file after modifying the schema because migrations won't run again (even mistakenly), Once it migrated.
You are doing the correct way as I believe.
You are free to develop your own way to achieve this task, but as far as migrations are concerned, these are meant for controlling and sharing the application's database schema among the team, not the actual data ;)
You can create separate seeder for this task.
It will keep your migration clean and easy to rollback if needed.
NOTE: Don't include this seeder class in DatabaseSeeder.
These kind of seeder class are only meant for update the existing data after fixing the current functionality(I am taking into consideration, you have already fixed the code as per your new requirement). So, there is not need to worry about re run the same seeder class.
Considering (laracast, stack-overflow), i will prefer to go by your way over the suggestions provided above as neither i have to maintain extra route nor additional migration (03).
The only improvement i can suggest here is you can use databse-transaction something like this :
// create new column
DB::transaction(function () {
update new column
delete old column
});

Laravel migration default value

I didn't understand what is the effect of the default option in the migrations.
I can see that the column in the database is defined with default value, but the models are ignore it completely. Say I have a Book model that reflect the books table in the database. I have migration to create the books table:
Schema::create('books', function (Blueprint $table) {
$table->increments('id');
->string('author');
->string('title');
->decimal('price', 4, 1)->default(100);
->timestamps();
});
When I create a new instance of Book model I see:
$book = new Book();
var_dump($book->price); //Always 0...
The default value is ignored and the attribute is not sets correctly.
Ok, I can get it, because it is a new object and it shouldn't get the default values from the DB. But if I tries to save model like:
$book = new Book();
$book->author = 'Test'
$book->title = 'Test'
$book->save();
It is saves 0 in the field price in the database!
So what is the point of the default option in the migrations?
By the way...
It wasn't be better if the model see inside the migration (if exists) what are the fields types and behavior instead to define it manually in the model and the migration? And moreover, even to create a validator automatically for the model. I think that it was possible with small change of the migration structure, so why it is not like that?
Put the default value in single quote and it will work as intended. An example of migration:
$table->increments('id');
$table->string('name');
$table->string('url');
$table->string('country');
$table->tinyInteger('status')->default('1');
$table->timestamps();
EDIT : in your case ->default('100.0');
In Laravel 6 you have to add 'change' to your migrations file as follows:
$table->enum('is_approved', array('0','1'))->default('0')->change();
You can simple put the default value using default(). See the example
$table->enum('is_approved', array('0','1'))->default('0');
I have used enum here and the default value is 0.
Might be a little too late to the party, but hope this helps someone with similar issue.
The reason why your default value doesnt't work is because the migration file sets up the default value in your database (MySQL or PostgreSQL or whatever), and not in your Laravel application.
Let me illustrate with an example.
This line means Laravel is generating a new Book instance, as specified in your model. The new Book object will have properties according to the table associated with the model. Up until this point, nothing is written on the database.
$book = new Book();
Now the following lines are setting up the values of each property of the Book object. Same still, nothing is written on the database yet.
$book->author = 'Test'
$book->title = 'Test'
This line is the one writing to the database. After passing on the object to the database, then the empty fields will be filled by the database (may be default value, may be null, or whatever you specify on your migration file).
$book->save();
And thus, the default value will not pop up before you save it to the database.
But, that is not enough. If you try to access $book->price, it will still be null (or 0, i'm not sure). Saving it is only adding the defaults to the record in the database, and it won't affect the Object you are carrying around.
So, to get the instance with filled-in default values, you have to re-fetch the instance. You may use the
Book::find($book->id);
Or, a more sophisticated way by refreshing the instance
$book->refresh();
And then, the next time you try to access the object, it will be filled with the default values.
The problem you face is not about the migration file. The value turns into 0 every time because you didn't change $fillable on your model. When you forget to add recently added column to $fillable of its model, Laravel inserts 0.

Laravel5: How are Eloquent model relationships expressed in the database?

There's a missing link I fail to understand.
I use migrations to create database tables and I define the relationships there. meaning.. if I have a person table and a job table and I need a one to many relationship between the person and jobs, I'd have the job table contain a "person_id".
When I seed data or add it in my app, I do all the work of adding the records setting the *_id = values etc.
but somehow I feel Laravel has a better way of doing this.
if I define that one to many relationship with the oneToMany Laravel Eloquent suports:
in my Person model.....
public function jobs()
{
return $this->hasMany('Jobs);
}
what's done on the database level? how do I create the migration for such table? Is Laravel automagically doing the "expected" thing here? like looking for a Jobs table, and having a "person_id" there?
Yep, Laravel is doing what you guess in your last paragraph.
From the Laravel documentation for Eloquent Relationships (with the relevant paragraph in bold):
For example, a User model might have one Phone. We can define this
relation in Eloquent:
class User extends Model {
public function phone()
{
return $this->hasOne('App\Phone');
}
}
The first argument passed to the hasOne method is the name of the
related model. Once the relationship is defined, we may retrieve it
using Eloquent's dynamic properties:
$phone = User::find(1)->phone;
The SQL performed by this statement
will be as follows:
select * from users where id = 1
select * from phones where user_id = 1
Take note that Eloquent assumes the foreign key of the relationship based on the model name. In this case, Phone model is assumed to use a user_id foreign key.
Also note that you don't actually have to explicitly set the foreign key indexes in your database (just having those "foreign key" columns with the same data type as the parent key columns is enough for Laravel to accept the relationship), although you should probably have those indexes for the sake of database integrity.
There is indeed support to create foreign key relationships inside migration blueprints and it's very simple too.
Here is a simple example migration where we define a jobs table that has a user_id column that references the id column on users table.
Schema::create('jobs', function($table)
{
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');
});
You can also use some other methods that laravel provides such as onDelete() or onUpdate
Of course to understand better the options that are available to you please read the documentation here.
Edit:
Keep in mind that Eloquent is just using fluent SQL wrapper and behind the scenes there are just raw sql queries, nothing magical is happening, fluent just makes your life a lot easier and helpers you write maintainable code.
Take a look here about the Query Builder and how it works and also, as #Martin Charchar stated , here about Eloquent and relationships.

Adding new columns to an Existing Doctrine Model

First of all Hats of to StackOverflow for their great service and to you guys for taking your time to answer our questions.
I am using Doctrine ORM 1.2.4 with CodeIgniter 1.7.3. I created a Site with some required tables and pumped in with datas only to realize at a later point of time that a specific table needs to have one more column.
The way i created the tables was by writing the model as php classes which extend the Doctrine_Record.
Now i am wondering if i need to just add the column in the model that requires a new column in the setTableDefinition() method and recreate that table or is there any other way that easily does this. The former method i've mentioned requires me to drop the current table along with the datas and recreate the table which i do not wish. Since doctrine seems to be a very well architect-ed database framework, i believe it is lack of my knowledge but surely should exist a way to add new columns easily.
PS: I am not trying to alter a column with relations to other tables, but just add a new column which is not related to any other table. Also i create the tables in the database using Doctrine::createTablesFromModels(); When i alter a table with a new column and run this method it shows errors.
Since you don't want to drop & recreate, use a Doctrine Migration.
The official docs here show many examples:
http://www.doctrine-project.org/projects/orm/1.2/docs/manual/migrations/en
Since you just want to add a field, look at their second code example as being the most relevant which is like this:
// migrations/2_add_column.php
class AddColumn extends Doctrine_Migration_Base
{
public function up()
{
$this->addColumn('migration_test', 'field2', 'string');
}
public function down()
{
$this->removeColumn('migration_test', 'field2');
}
}

Resources