Skip to content

Commit

Permalink
Marked model objects as (non)optional in line with the Core Data model
Browse files Browse the repository at this point in the history
- Uses sensible defaults when possible
- Unit tests are also adjusted
- Populate `ownReactions` and `latestReactions` on MessageDTO when creating a new object, they are non-optinal on the Core Data Model
- Mark `MessageReactionDTO.id` and `MessageDTO.quotedMessage` and `ChannelDto.typeRawValue as non-optional
- Make localStateRaw non optional on both the MessageReactionDTO and the AttachmentDTO.
  • Loading branch information
jeroenleenarts committed Dec 17, 2021
1 parent 74e0873 commit 9237c13
Show file tree
Hide file tree
Showing 14 changed files with 98 additions and 72 deletions.
14 changes: 10 additions & 4 deletions Sources/StreamChat/Database/DTOs/AttachmentDTO.swift
Expand Up @@ -15,17 +15,19 @@ class AttachmentDTO: NSManagedObject {
}

/// An attachment type.
@NSManaged private var type: String
@NSManaged private var type: String?
var attachmentType: AttachmentType {
get { .init(rawValue: type) }
get { AttachmentType(rawValue: type ?? AttachmentType.unknown.rawValue) }
set { type = newValue.rawValue }
}

/// An attachment local state.
@NSManaged private var localStateRaw: String
@NSManaged private var localProgress: Double
var localState: LocalAttachmentState? {
get { LocalAttachmentState(rawValue: localStateRaw, progress: localProgress) }
get {
LocalAttachmentState(rawValue: localStateRaw, progress: localProgress)
}
set {
localStateRaw = newValue?.rawValue ?? ""
localProgress = newValue?.progress ?? 0
Expand Down Expand Up @@ -221,6 +223,8 @@ extension AttachmentDTO {
extension LocalAttachmentState {
var rawValue: String {
switch self {
case .unknown:
return ""
case .pendingUpload:
return "pendingUpload"
case .uploading:
Expand Down Expand Up @@ -251,8 +255,10 @@ extension LocalAttachmentState {
self = .uploadingFailed
case LocalAttachmentState.uploaded.rawValue:
self = .uploaded
case LocalAttachmentState.unknown.rawValue:
self = .unknown
default:
return nil
self = .unknown
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/StreamChat/Database/DTOs/AttachmentDTO_Tests.swift
Expand Up @@ -37,7 +37,7 @@ class AttachmentDTO_Tests: XCTestCase {

// Assert attachment has correct values.
XCTAssertEqual(loadedAttachment.attachmentID, attachmentId)
XCTAssertEqual(loadedAttachment.localState, nil)
XCTAssertEqual(loadedAttachment.localState, .unknown)
XCTAssertEqual(loadedAttachment.attachmentType, attachment.type)
XCTAssertEqual(loadedAttachment.message.id, messageId)
XCTAssertEqual(loadedAttachment.channel.cid, cid.rawValue)
Expand Down Expand Up @@ -72,7 +72,7 @@ class AttachmentDTO_Tests: XCTestCase {

// Assert attachment has correct values.
XCTAssertEqual(loadedAttachment.attachmentID, attachmentId)
XCTAssertEqual(loadedAttachment.localState, nil)
XCTAssertEqual(loadedAttachment.localState, .unknown)
XCTAssertEqual(loadedAttachment.attachmentType, attachment.type)
XCTAssertEqual(loadedAttachment.message.id, messageId)
XCTAssertEqual(loadedAttachment.channel.cid, cid.rawValue)
Expand Down Expand Up @@ -107,7 +107,7 @@ class AttachmentDTO_Tests: XCTestCase {

// Assert attachment has correct values.
XCTAssertEqual(loadedAttachment.attachmentID, attachmentId)
XCTAssertEqual(loadedAttachment.localState, nil)
XCTAssertEqual(loadedAttachment.localState, .unknown)
XCTAssertEqual(loadedAttachment.attachmentType, attachment.type)
XCTAssertEqual(loadedAttachment.message.id, messageId)
XCTAssertEqual(loadedAttachment.channel.cid, cid.rawValue)
Expand Down Expand Up @@ -270,7 +270,7 @@ class AttachmentDTO_Tests: XCTestCase {

// Assert attachment local file URL and state are nil.
XCTAssertNil(loadedAttachment?.localURL)
XCTAssertNil(loadedAttachment?.localState)
XCTAssertEqual(loadedAttachment?.localState, .unknown)
}

func test_attachmentChange_triggerMessageUpdate() throws {
Expand Down
2 changes: 1 addition & 1 deletion Sources/StreamChat/Database/DTOs/ChannelMuteDTO.swift
Expand Up @@ -8,7 +8,7 @@ import Foundation
@objc(ChannelMuteDTO)
final class ChannelMuteDTO: NSManagedObject {
@NSManaged var createdAt: Date
@NSManaged var updatedAt: Date
@NSManaged var updatedAt: Date?
@NSManaged var channel: ChannelDTO
@NSManaged var user: UserDTO

Expand Down
8 changes: 6 additions & 2 deletions Sources/StreamChat/Database/DTOs/ChannelReadDTO.swift
Expand Up @@ -7,7 +7,7 @@ import Foundation

@objc(ChannelReadDTO)
class ChannelReadDTO: NSManagedObject {
@NSManaged var lastReadAt: Date
@NSManaged var lastReadAt: Date?
@NSManaged var unreadMessageCount: Int32

// MARK: - Relationships
Expand Down Expand Up @@ -124,6 +124,10 @@ extension NSManagedObjectContext {

extension ChatChannelRead {
fileprivate static func create(fromDTO dto: ChannelReadDTO) -> ChatChannelRead {
.init(lastReadAt: dto.lastReadAt, unreadMessagesCount: Int(dto.unreadMessageCount), user: dto.user.asModel())
.init(
lastReadAt: dto.lastReadAt ?? Date.distantPast,
unreadMessagesCount: Int(dto.unreadMessageCount),
user: dto.user.asModel()
)
}
}
46 changes: 30 additions & 16 deletions Sources/StreamChat/Database/DTOs/MessageDTO.swift
Expand Up @@ -20,7 +20,7 @@ class MessageDTO: NSManagedObject {
@NSManaged var parentMessageId: MessageId?
@NSManaged var showReplyInChannel: Bool
@NSManaged var replyCount: Int32
@NSManaged var extraData: Data
@NSManaged var extraData: Data?
@NSManaged var isSilent: Bool
@NSManaged var isShadowed: Bool
@NSManaged var reactionScores: [String: Int]
Expand Down Expand Up @@ -280,6 +280,8 @@ class MessageDTO: NSManagedObject {

let new = NSEntityDescription.insertNewObject(forEntityName: entityName, into: context) as! Self
new.id = id
new.latestReactions = []
new.ownReactions = []
return new
}

Expand Down Expand Up @@ -667,15 +669,20 @@ extension MessageDTO {

/// Snapshots the current state of `MessageDTO` and returns its representation for the use in API calls.
func asRequestBody() -> MessageRequestBody {
var extraData: [String: RawJSON]
do {
extraData = try JSONDecoder.default.decode([String: RawJSON].self, from: self.extraData)
} catch {
log.assertionFailure(
"Failed decoding saved extra data with error: \(error). This should never happen because"
+ "the extra data must be a valid JSON to be saved."
)
extraData = [:]
var decodedExtraData: [String: RawJSON]

if let extraData = self.extraData {
do {
decodedExtraData = try JSONDecoder.default.decode([String: RawJSON].self, from: extraData)
} catch {
log.assertionFailure(
"Failed decoding saved extra data with error: \(error). This should never happen because"
+ "the extra data must be a valid JSON to be saved."
)
decodedExtraData = [:]
}
} else {
decodedExtraData = [:]
}

return .init(
Expand All @@ -694,7 +701,7 @@ extension MessageDTO {
mentionedUserIds: mentionedUsers.map(\.id),
pinned: pinned,
pinExpires: pinExpires,
extraData: extraData
extraData: decodedExtraData
)
}
}
Expand All @@ -720,11 +727,18 @@ private extension ChatMessage {
isShadowed = dto.isShadowed
reactionScores = dto.reactionScores.mapKeys { MessageReactionType(rawValue: $0) }
reactionCounts = dto.reactionCounts.mapKeys { MessageReactionType(rawValue: $0) }

do {
extraData = try JSONDecoder.default.decode([String: RawJSON].self, from: dto.extraData)
} catch {
log.error("Failed to decode extra data for Message with id: <\(dto.id)>, using default value instead. Error: \(error)")

if let extraData = dto.extraData, !extraData.isEmpty {
do {
self.extraData = try JSONDecoder.default.decode([String: RawJSON].self, from: extraData)
} catch {
log
.error(
"Failed to decode extra data for Message with id: <\(dto.id)>, using default value instead. Error: \(error)"
)
self.extraData = [:]
}
} else {
extraData = [:]
}

Expand Down
6 changes: 3 additions & 3 deletions Sources/StreamChat/Database/DTOs/MessageDTO_Tests.swift
Expand Up @@ -20,7 +20,7 @@ class MessageDTO_Tests: XCTestCase {
super.tearDown()
}

func test_messagePayload_isStoredAndLoadedFromDB() {
func test_messagePayload_isStoredAndLoadedFromDB() throws {
let userId: UserId = .unique
let messageId: MessageId = .unique
let channelId: ChannelId = .unique
Expand Down Expand Up @@ -143,7 +143,7 @@ class MessageDTO_Tests: XCTestCase {
)
XCTAssertEqual(Int32(messagePayload.replyCount), loadedMessage?.replyCount)
XCTAssertEqual(messagePayload.extraData, loadedMessage.map {
try? JSONDecoder.default.decode([String: RawJSON].self, from: $0.extraData)
try? JSONDecoder.default.decode([String: RawJSON].self, from: $0.extraData!)
})
XCTAssertEqual(messagePayload.reactionScores, loadedMessage?.reactionScores.mapKeys { reaction in
MessageReactionType(rawValue: reaction)
Expand Down Expand Up @@ -228,7 +228,7 @@ class MessageDTO_Tests: XCTestCase {
)
Assert.willBeEqual(Int32(messagePayload.replyCount), loadedMessage?.replyCount)
Assert.willBeEqual(messagePayload.extraData, loadedMessage.map {
try? JSONDecoder.default.decode([String: RawJSON].self, from: $0.extraData)
try? JSONDecoder.default.decode([String: RawJSON].self, from: $0.extraData!)
})
Assert.willBeEqual(messagePayload.reactionScores, loadedMessage?.reactionScores.mapKeys { reaction in
MessageReactionType(rawValue: reaction)
Expand Down
29 changes: 13 additions & 16 deletions Sources/StreamChat/Database/DTOs/MessageReactionDTO.swift
Expand Up @@ -10,12 +10,12 @@ final class MessageReactionDTO: NSManagedObject {
@NSManaged private(set) var id: String

// holds the rawValue of LocalReactionState
@NSManaged fileprivate var localStateRaw: String?
@NSManaged fileprivate var localStateRaw: String
@NSManaged var type: String
@NSManaged var score: Int64
@NSManaged var createdAt: Date?
@NSManaged var updatedAt: Date?
@NSManaged var extraData: Data
@NSManaged var extraData: Data?

@NSManaged var message: MessageDTO
@NSManaged var user: UserDTO
Expand Down Expand Up @@ -47,7 +47,7 @@ extension MessageReactionDTO {

static let notLocallyDeletedPredicates: NSPredicate = {
NSCompoundPredicate(orPredicateWithSubpredicates: [
NSPredicate(format: "localStateRaw == nil"),
NSPredicate(format: "localStateRaw == %@", LocalReactionState.unknown.rawValue),
NSPredicate(format: "localStateRaw == %@", LocalReactionState.sending.rawValue),
NSPredicate(format: "localStateRaw == %@", LocalReactionState.pendingSend.rawValue)
])
Expand Down Expand Up @@ -125,29 +125,26 @@ extension NSManagedObjectContext {
extension MessageReactionDTO {
var localState: LocalReactionState? {
get {
guard let state = localStateRaw else {
return nil
}
return LocalReactionState(rawValue: state)
LocalReactionState(rawValue: localStateRaw)
}
set(state) {
localStateRaw = state?.rawValue
localStateRaw = state?.rawValue ?? LocalReactionState.unknown.rawValue
}
}

/// Snapshots the current state of `MessageReactionDTO` and returns an immutable model object from it.
func asModel() -> ChatMessageReaction {
let extraData: [String: RawJSON]

if self.extraData.isEmpty {
extraData = [:]
} else {
let decodedExtraData: [String: RawJSON]

if let extraData = self.extraData, !extraData.isEmpty {
do {
extraData = try JSONDecoder.default.decode([String: RawJSON].self, from: self.extraData)
decodedExtraData = try JSONDecoder.default.decode([String: RawJSON].self, from: extraData)
} catch {
log.error("Failed decoding saved extra data with error: \(error)")
extraData = [:]
decodedExtraData = [:]
}
} else {
decodedExtraData = [:]
}

return .init(
Expand All @@ -156,7 +153,7 @@ extension MessageReactionDTO {
createdAt: createdAt ?? .init(),
updatedAt: updatedAt ?? .init(),
author: user.asModel(),
extraData: extraData
extraData: decodedExtraData
)
}
}
Expand Up @@ -7,7 +7,7 @@ import CoreData
@objc(MessageSearchQueryDTO)
class MessageSearchQueryDTO: NSManagedObject {
/// Unique identifier of the query/
@NSManaged var filterHash: String
@NSManaged var filterHash: String?

@NSManaged var messages: Set<MessageDTO>

Expand Down

0 comments on commit 9237c13

Please sign in to comment.