Skip to content

Commit dcd10d3

Browse files
dadachiclaude
andcommitted
Bridge Device to ApplicationPushDevice
Drop the standalone Device model and consolidate push-token registration onto ApplicationPushDevice (subclass of ActionPushNative::Device) so deliver_by :action_push_native actually fires for tokens registered via POST /api/v1/shopkeeper/devices. - Drop devices table; rebuild action_push_native_devices with UUID primary key + UUID polymorphic owner + bundle_id / last_active_at columns + unique (platform, token) index - Move validations / active scope / last_active_at touching from Device onto ApplicationPushDevice - Shopkeeper has_many :application_push_devices, as: :owner - DevicesController now manages ApplicationPushDevice; JSONAPI type stays "device" via set_type :device on the new serializer - API contract change (pre-mobile-client): device.platform enum is now [apple, google] (matches Action Push Native's APNs/FCM convention). PRs #3-5 (iOS/Android substrate clients) will register with apple or google. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 52a5283 commit dcd10d3

14 files changed

Lines changed: 177 additions & 141 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [Unreleased]
44

5+
- Drop the standalone `Device` model and consolidate push-token registration onto `ApplicationPushDevice` (subclass of `ActionPushNative::Device`) so `deliver_by :action_push_native` actually fires for tokens registered via `POST /api/v1/shopkeeper/devices`. The custom `devices` table is dropped; `action_push_native_devices` is rebuilt with UUID primary key + UUID polymorphic owner + `bundle_id` / `last_active_at` columns + unique `(platform, token)` index.
6+
- API contract change (still pre-mobile-client): `device.platform` enum is now `[apple, google]` (matches Action Push Native's APNs/FCM service convention) instead of `[ios, android]`. Mobile substrate clients (PRs #3-5) will register with `apple` or `google`.
7+
- Renames: `Device``ApplicationPushDevice`, `Api::Shopkeeper::DevicePolicy``Api::Shopkeeper::ApplicationPushDevicePolicy`, `DeviceSerializer``ApplicationPushDeviceSerializer` (JSONAPI `type` stays `device`); `Shopkeeper has_many :devices``has_many :application_push_devices, as: :owner`.
8+
59
## 2026-05-10
610

711
- Add push notifications scaffolding via `noticed` v2 (#58)

app/controllers/api/v1/shopkeeper/devices_controller.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@ class Api::V1::Shopkeeper::DevicesController < Api::V1::Shopkeeper::BaseControll
88
# token to a different shopkeeper (e.g. user signed out + new user
99
# signed in on same device) reassigns the device row.
1010
def create
11-
authorize Device
11+
authorize ApplicationPushDevice
1212

13-
device = Device.find_or_initialize_by(
13+
device = ApplicationPushDevice.find_or_initialize_by(
1414
platform: device_params[:platform],
1515
token: device_params[:token]
1616
)
17-
device.shopkeeper = current_shopkeeper
17+
device.owner = current_shopkeeper
1818
device.bundle_id = device_params[:bundle_id]
1919
device.last_active_at = Time.current
2020

2121
if device.save
22-
render json: DeviceSerializer.new(device).serializable_hash, status: device.previously_new_record? ? :created : :ok
22+
render json: ApplicationPushDeviceSerializer.new(device).serializable_hash, status: device.previously_new_record? ? :created : :ok
2323
else
2424
render_validation_error(device)
2525
end
@@ -36,7 +36,7 @@ def destroy
3636
private
3737

3838
def set_device
39-
@device = current_shopkeeper.devices.find(params[:id])
39+
@device = current_shopkeeper.application_push_devices.find(params[:id])
4040
end
4141

4242
def device_params
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
11
class ApplicationPushDevice < ActionPushNative::Device
2+
# Inherits `enum :platform, {apple: "apple", google: "google"}, validate: true`
3+
# from ActionPushNative::Device (apple = APNs, google = FCM).
4+
5+
validates :token, presence: true, uniqueness: {scope: :platform}
6+
7+
scope :active, -> { where("last_active_at > ?", 90.days.ago) }
8+
9+
before_validation :touch_last_active_at, on: :create
10+
211
# Customize TokenError handling (default: destroy!)
312
# rescue_from (ActionPushNative::TokenError) { Rails.logger.error("Device #{id} token is invalid") }
13+
14+
private
15+
16+
def touch_last_active_at
17+
self.last_active_at ||= Time.current
18+
end
419
end

app/models/device.rb

Lines changed: 0 additions & 18 deletions
This file was deleted.

app/models/shopkeeper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Shopkeeper < ApplicationRecord
1212
has_many :created_shops, class_name: "Shop", foreign_key: :created_by_id, inverse_of: :created_by
1313
has_many :created_item_tags, class_name: "ItemTag", foreign_key: :created_by_id, inverse_of: :created_by, dependent: :nullify
1414
has_many :completed_item_tags, class_name: "ItemTag", foreign_key: :completed_by_id, inverse_of: :completed_by, dependent: :nullify
15-
has_many :devices, dependent: :destroy
15+
has_many :application_push_devices, as: :owner, class_name: "ApplicationPushDevice", dependent: :destroy
1616
has_many :notifications, as: :recipient, dependent: :destroy, class_name: "Noticed::Notification"
1717

1818
attribute :token, :string
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class Api::Shopkeeper::ApplicationPushDevicePolicy < Api::Shopkeeper::BasePolicy
2+
def create?
3+
true
4+
end
5+
6+
def destroy?
7+
record.owner_type == "Shopkeeper" && record.owner_id == accounts_shopkeeper.shopkeeper_id
8+
end
9+
end

app/policies/api/shopkeeper/device_policy.rb

Lines changed: 0 additions & 9 deletions
This file was deleted.

app/serializers/device_serializer.rb renamed to app/serializers/application_push_device_serializer.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
class DeviceSerializer
1+
class ApplicationPushDeviceSerializer
22
include JSONAPI::Serializer
33

4+
set_type :device
5+
46
attributes :token,
57
:platform,
68
:bundle_id,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
class ConsolidateDevicesIntoApplicationPushDevices < ActiveRecord::Migration[8.1]
2+
def up
3+
drop_table :devices
4+
5+
drop_table :action_push_native_devices
6+
7+
create_table :action_push_native_devices, id: :uuid, default: -> { "gen_random_uuid()" } do |t|
8+
t.string :name
9+
t.string :platform, null: false
10+
t.string :token, null: false
11+
t.string :bundle_id
12+
t.datetime :last_active_at
13+
t.references :owner, polymorphic: true, type: :uuid
14+
t.timestamps
15+
t.index [:platform, :token], unique: true
16+
end
17+
end
18+
19+
def down
20+
drop_table :action_push_native_devices
21+
22+
create_table :action_push_native_devices do |t|
23+
t.string :name
24+
t.string :platform, null: false
25+
t.string :token, null: false
26+
t.references :owner, polymorphic: true
27+
t.timestamps
28+
end
29+
30+
create_table :devices, id: :uuid, default: -> { "gen_random_uuid()" } do |t|
31+
t.string :bundle_id
32+
t.datetime :last_active_at
33+
t.string :platform, null: false
34+
t.uuid :shopkeeper_id, null: false
35+
t.string :token, null: false
36+
t.timestamps
37+
t.index [:platform, :token], unique: true
38+
t.index [:shopkeeper_id]
39+
end
40+
add_foreign_key :devices, :shopkeepers
41+
end
42+
end

db/schema.rb

Lines changed: 6 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)