Skip to content
Merged
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
24 changes: 24 additions & 0 deletions fastapi_startkit/src/fastapi_startkit/masoniteorm/models/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import TYPE_CHECKING

import inflection
import pendulum

from fastapi_startkit.carbon import Carbon
from fastapi_startkit.masoniteorm.collection import Collection
Expand Down Expand Up @@ -347,6 +348,29 @@ async def update(self, attributes: dict) -> bool:

return await self.fill(attributes).save()

async def touch(self) -> bool:
"""Bump ``updated_at`` to now and persist only that column.

Mirrors Laravel's instance-level ``touch()``: any other dirty
attributes on the model are left untouched and unsaved. A no-op for
models with timestamps disabled (``__timestamps__`` falsy) or that
haven't been persisted yet, matching how ``save()`` already treats
those cases.
"""
if not self.__timestamps__ or not self._exists:
return False

value = self.caster.set("updated_at", pendulum.now("UTC"))

query = self.new_query()
pk_value = self.get_attribute(self.__primary_key__)
await query.where(self.__primary_key__, pk_value).update({"updated_at": value})

self._attributes["updated_at"] = value
self._original["updated_at"] = value

return True

def fill(self, attributes: dict) -> "Model":
for key, value in attributes.items():
if key in self.__fillable__:
Expand Down
82 changes: 82 additions & 0 deletions fastapi_startkit/tests/masoniteorm/models/test_model_touch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""
Tests for the Laravel-style instance-level ``Model.touch()`` method.

Scope is deliberately narrow: bump ``updated_at`` on a single persisted
instance and leave everything else alone. No relationship cascading and no
bulk query-builder touch — those are separate, out-of-scope features.
"""

from tests.masoniteorm.fixtures.model import Profile, User
from tests.masoniteorm.sqlite.test_case import TestCase


class TestTouch(TestCase):
async def test_touch_returns_true_on_success(self):
user = await User.create({"name": "Alex", "email": "alex@gmail.com"})

self.assertTrue(await user.touch())

async def test_touch_sets_updated_at_on_instance(self):
user = await User.create({"name": "Alex", "email": "alex@gmail.com"})
self.assertIsNone(user.updated_at)

await user.touch()

self.assertIsNotNone(user.updated_at)

async def test_touch_persists_updated_at_to_the_database(self):
user = await User.create({"name": "Alex", "email": "alex@gmail.com"})

await user.touch()

fetched = await User.find(user.id)
self.assertEqual(fetched.updated_at, user.updated_at)

async def test_touch_advances_an_already_set_updated_at(self):
user = await User.create({"name": "Alex", "email": "alex@gmail.com"})
await user.touch()
first_touch = user.updated_at

await user.touch()

self.assertGreaterEqual(user.updated_at, first_touch)

async def test_touch_does_not_persist_other_dirty_attributes(self):
user = await User.create({"name": "Alex", "email": "alex@gmail.com"})
user.name = "Changed"

await user.touch()

fetched = await User.find(user.id)
self.assertEqual(fetched.name, "Alex")

async def test_touch_leaves_other_dirty_attributes_dirty_on_the_instance(self):
user = await User.create({"name": "Alex", "email": "alex@gmail.com"})
user.name = "Changed"

await user.touch()

self.assertTrue(user.is_dirty())
self.assertIn("name", user.get_dirty())

async def test_touch_does_not_mark_updated_at_as_dirty(self):
user = await User.create({"name": "Alex", "email": "alex@gmail.com"})

await user.touch()

self.assertNotIn("updated_at", user.get_dirty())

async def test_touch_is_noop_when_timestamps_disabled(self):
profile = await Profile.create({"name": "P1", "user_id": 1})

result = await profile.touch()

self.assertFalse(result)

async def test_touch_is_noop_on_an_unsaved_model(self):
user = User(name="Alex", email="alex@gmail.com")

result = await user.touch()

self.assertFalse(result)
self.assertIsNone(user.updated_at)
Loading