Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/masoniteorm/schema/Blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,15 +383,22 @@ def timestamp(self, column, nullable=False, now=False):

return self

def timestamps(self):
def timestamps(self, created_at="created_at", updated_at="updated_at"):
"""Creates `created_at` and `updated_at` timestamp columns.

Returns:
self
"""
self.datetime("created_at", nullable=True, now=True)
if not created_at and not updated_at:
raise Exception(
"Invalid 'timestamps' arguments, 'created_at' and/or 'updated_at' parameters are required"
)

if created_at:
self.datetime(created_at, nullable=True, now=True)

self.datetime("updated_at", nullable=True, now=True)
if updated_at:
self.datetime(updated_at, nullable=True, now=True)

return self

Expand Down
18 changes: 10 additions & 8 deletions src/masoniteorm/schema/platforms/PostgresPlatform.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,6 @@ def compile_alter_sql(self, table):
columns=", ".join(dropped_sql),
)
)

if table.changed_columns:
changed_sql = []

Expand All @@ -297,18 +296,18 @@ def compile_alter_sql(self, table):
if column.column_type == "enum":
values = ", ".join(f"'{x}'" for x in column.values)
column_constraint = f" CHECK({column.name} IN ({values}))"
length = ""
if column.length:
length = self.create_column_length(
column.column_type
).format(length=column.length)
changed_sql.append(
self.modify_column_string()
.format(
name=self.wrap_column(name),
data_type=self.type_map.get(column.column_type),
nullable="NULL" if column.is_null else "NOT NULL",
length=(
"(" + str(column.length) + ")"
if column.column_type
not in self.types_without_lengths
else ""
),
length=length,
column_constraint=column_constraint,
constraint="PRIMARY KEY" if column.primary else "",
)
Expand All @@ -325,8 +324,11 @@ def compile_alter_sql(self, table):
)

if column.default is not None:
default = f" DEFAULT {column.default}"
if column.default in self.premapped_defaults:
default = self.premapped_defaults[column.default]
changed_sql.append(
f"ALTER COLUMN {self.wrap_column(name)} SET DEFAULT {column.default}"
f"ALTER COLUMN {self.wrap_column(name)} SET{default}"
)

sql.append(
Expand Down
27 changes: 27 additions & 0 deletions src/masoniteorm/scopes/SoftDeletesMixin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from typing import TYPE_CHECKING

from .SoftDeleteScope import SoftDeleteScope

if TYPE_CHECKING:
from ..query import QueryBuilder


class SoftDeletesMixin:
"""Global scope class to add soft deleting to models."""
Expand All @@ -9,5 +14,27 @@ class SoftDeletesMixin:
def boot_SoftDeletesMixin(self, builder):
builder.set_global_scope(SoftDeleteScope(self.__deleted_at__))

if TYPE_CHECKING:

@classmethod
def with_trashed(cls) -> QueryBuilder:
"""Include records flagged as deleted"""
...

@classmethod
def only_trashed(cls) -> QueryBuilder:
"""Filter for records marked as deleted"""
...

@classmethod
def force_delete(cls) -> QueryBuilder:
"""Remove the record from the table"""
...

@classmethod
def restore(cls) -> QueryBuilder:
"""Mark the record as not deleted"""
...

def get_deleted_at_column(self):
return self.__deleted_at__
12 changes: 11 additions & 1 deletion src/masoniteorm/scopes/TimeStampsMixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@


class TimeStampsMixin:
"""Global scope class to add soft deleting to models."""
"""Global scope class to add automatic timestamps to models."""

def boot_TimeStampsMixin(self, builder):
if not self.__timestamps__:
return

if not self.date_created_at and not self.date_updated_at:
raise AttributeError(
"Timestamps are enabled but not defined in the model. "
"Please define at least one of the 'date_created_at' or 'date_updated_at' attributes. "
"If you want to disable timestamps, set the '__timestamps__' attribute to False."
)

builder.set_global_scope(TimeStampsScope())

def activate_timestamps(self, boolean=True):
Expand Down
58 changes: 40 additions & 18 deletions src/masoniteorm/scopes/TimeStampsScope.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,67 @@


class TimeStampsScope(BaseScope):
"""Global scope class to add soft deleting to models."""
"""Global scope class to add automatic timestamps to a builder query."""

def on_boot(self, builder):
builder.set_global_scope(
"_timestamps", self.set_timestamp_create, action="insert"
)
if not builder._model.__timestamps__:
return

builder.set_global_scope(
"_timestamp_update", self.set_timestamp_update, action="update"
)
created_column = builder._model.date_created_at
updated_column = builder._model.date_updated_at
if created_column or updated_column:
builder.set_global_scope(
"_timestamps", self.set_timestamp_create, action="insert"
)

if updated_column:
builder.set_global_scope(
"_timestamp_update", self.set_timestamp_update, action="update"
)

def on_remove(self, builder):
pass

def set_timestamp(owner_cls, query):
owner_cls.updated_at = "now"
if owner_cls.date_updated_at:
column = owner_cls.date_updated_at
setattr(owner_cls, column, "now")

def set_timestamp_create(self, builder):
if not builder._model.__timestamps__:
return builder
return

builder._creates.update(
{
builder._model.date_updated_at: builder._model.get_new_date().to_datetime_string(),
builder._model.date_created_at: builder._model.get_new_date().to_datetime_string(),
}
)
columns = {}
created_column = builder._model.date_created_at
if created_column:
columns[created_column] = (
builder._model.get_new_date().to_datetime_string()
)

updated_column = builder._model.date_updated_at
if updated_column:
columns[updated_column] = (
builder._model.get_new_date().to_datetime_string()
)

builder._creates.update(columns)

def set_timestamp_update(self, builder):
if not builder._model.__timestamps__:
return builder
return

updated_column = builder._model.date_updated_at
if not updated_column:
return

for update in builder._updates:
if builder._model.date_updated_at in update.column:
if updated_column in update.column:
return

builder._updates += (
UpdateQueryExpression(
{
builder._model.date_updated_at: builder._model.get_new_date().to_datetime_string()
updated_column: builder._model.get_new_date().to_datetime_string()
}
),
)
13 changes: 10 additions & 3 deletions tests/mysql/schema/test_mysql_schema_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ def test_can_advanced_table_creation(self):
blueprint.string("option").default("ADMIN")
blueprint.string("remember_token").nullable()
blueprint.timestamp("verified_at").nullable()
blueprint.timestamps()
blueprint.timestamps(updated_at=False)

self.assertEqual(len(blueprint.table.added_columns), 14)
self.assertEqual(len(blueprint.table.added_columns), 13)
self.assertEqual(
blueprint.to_sql(),
[
Expand All @@ -151,11 +151,18 @@ def test_can_advanced_table_creation(self):
"`name` VARCHAR(255) NOT NULL, `active` TINYINT(1) NOT NULL, `email` VARCHAR(255) NOT NULL, `gender` ENUM('male', 'female') NOT NULL, "
"`password` VARCHAR(255) NOT NULL, `money` DECIMAL(17, 6) NOT NULL, "
"`admin` INT(11) NOT NULL DEFAULT 0, `option` VARCHAR(255) NOT NULL DEFAULT 'ADMIN', `remember_token` VARCHAR(255) NULL, `verified_at` TIMESTAMP NULL, "
"`created_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP, "
"`created_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP, "
"CONSTRAINT users_email_unique UNIQUE (email))"
],
)

def test_raise_on_empty_timestamps(self):
with self.assertRaises(Exception):
with self.schema.create("users") as blueprint:
blueprint.increments("id")
blueprint.string("name")
blueprint.timestamps(created_at=False, updated_at=False)

def test_can_add_primary_constraint_without_column_name(self):
with self.schema.create("users") as blueprint:
blueprint.integer("user_id").primary()
Expand Down
23 changes: 3 additions & 20 deletions tests/mysql/scopes/test_can_use_global_scopes.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,7 @@ def test_can_use_global_scopes_on_select(self):
query_sql = UserSoft.where("name", "joe").to_sql()
self.assertEqual(query_sql, expected_sql)

# def test_can_use_global_scopes_on_delete(self):
# expected_sql = "UPDATE `users` SET `users`.`deleted_at` = 'now' WHERE `users`.`name` = 'joe'"
# self.assertEqual(
# expected_sql,
# User.apply_scope(SoftDeletes)
# .where("name", "joe")
# .delete(query=True)
# .to_sql(),
# )

def test_can_use_global_scopes_on_time(self):
expected_sql = "INSERT INTO `users` (`users`.`name`, `users`.`updated_at`, `users`.`created_at`) VALUES ('Joe'"
self.assertTrue(
User.create({"name": "Joe"}, query=True)
.to_sql()
.startswith(expected_sql)
)

# def test_can_use_global_scopes_on_inherit(self):
# sql = "SELECT * FROM `user_softs` WHERE `user_softs`.`deleted_at` IS NULL"
# self.assertEqual(sql, UserSoft.all(query=True))
expected_sql = "INSERT INTO `users` (`users`.`name`, `users`.`created_at`, `users`.`updated_at`) VALUES ('Joe'"
query_sql = User.create({"name": "Joe"}, query=True).to_sql()
self.assertTrue(query_sql.startswith(expected_sql))
13 changes: 13 additions & 0 deletions tests/postgres/schema/test_postgres_schema_builder_alter.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,19 @@ def test_timestamp_alter_add_nullable_column(self):
query_sql = blueprint.to_sql()
self.assertEqual(query_sql, expected_sql)

def test_alter_existing_timestamp(self):
with self.schema.table("users") as blueprint:
blueprint.timestamp("created_at", now=True).change()
query_sql = blueprint.to_sql()
expected_sql = [
'ALTER TABLE "users" '
'ALTER COLUMN "created_at" TYPE TIMESTAMP, '
'ALTER COLUMN "created_at" SET NOT NULL, '
'ALTER COLUMN "created_at" SET DEFAULT CURRENT_TIMESTAMP'
]

self.assertEqual(query_sql, expected_sql)

def test_alter_drop_on_table_schema_table(self):
with self.schema.table("table_schema") as blueprint:
blueprint.drop_column("name")
Expand Down
Loading