Skip to content

Commit 4f592e2

Browse files
authored
[Local Catalog] Update schema with many-to-many images (#16222)
2 parents cf01d8c + 887104e commit 4f592e2

13 files changed

+328
-218
lines changed

Modules/Sources/Storage/GRDB/Migrations/V001InitialSchema.swift

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ struct V001InitialSchema {
1010
try createSiteTable(db)
1111
try createProductTable(db)
1212
try createProductAttributeTable(db)
13+
try createImageTable(db)
1314
try createProductImageTable(db)
1415
try createProductVariationTable(db)
1516
try createProductVariationAttributeTable(db)
@@ -74,23 +75,39 @@ struct V001InitialSchema {
7475
}
7576
}
7677

78+
private static func createImageTable(_ db: Database) throws {
79+
// Single image table shared by products and variations
80+
try db.create(table: "image") { imageTable in
81+
imageTable.column("id", .integer).notNull()
82+
imageTable.primaryKey(["siteID", "id"]) // SiteID column created by belongsTo relationship
83+
imageTable.belongsTo("site", onDelete: .cascade)
84+
85+
imageTable.column("dateCreated", .datetime).notNull()
86+
imageTable.column("dateModified", .datetime)
87+
88+
imageTable.column("src", .text).notNull()
89+
imageTable.column("name", .text)
90+
imageTable.column("alt", .text)
91+
}
92+
}
93+
7794
private static func createProductImageTable(_ db: Database) throws {
95+
// Join table for many-to-many relationship between products and images
7896
try db.create(table: "productImage") { productImageTable in
7997
productImageTable.column("siteID", .integer).notNull()
80-
productImageTable.column("id", .integer).notNull()
81-
productImageTable.primaryKey(["siteID", "id"])
8298
productImageTable.column("productID", .integer).notNull()
99+
productImageTable.column("imageID", .integer).notNull()
100+
productImageTable.primaryKey(["siteID", "productID", "imageID"])
101+
83102
productImageTable.foreignKey(["siteID", "productID"],
84103
references: "product",
85104
columns: ["siteID", "id"],
86105
onDelete: .cascade)
87106

88-
productImageTable.column("dateCreated", .datetime).notNull()
89-
productImageTable.column("dateModified", .datetime)
90-
91-
productImageTable.column("src", .text).notNull()
92-
productImageTable.column("name", .text)
93-
productImageTable.column("alt", .text)
107+
productImageTable.foreignKey(["siteID", "imageID"],
108+
references: "image",
109+
columns: ["siteID", "id"],
110+
onDelete: .cascade)
94111
}
95112
}
96113

@@ -138,22 +155,22 @@ struct V001InitialSchema {
138155
}
139156

140157
private static func createProductVariationImageTable(_ db: Database) throws {
158+
// Join table for many-to-many relationship between product variations and images
141159
try db.create(table: "productVariationImage") { productVariationImageTable in
142160
productVariationImageTable.column("siteID", .integer).notNull()
143-
productVariationImageTable.column("id", .integer).notNull()
144-
productVariationImageTable.primaryKey(["siteID", "id"])
145161
productVariationImageTable.column("productVariationID", .integer).notNull()
162+
productVariationImageTable.column("imageID", .integer).notNull()
163+
productVariationImageTable.primaryKey(["siteID", "productVariationID", "imageID"])
164+
146165
productVariationImageTable.foreignKey(["siteID", "productVariationID"],
147166
references: "productVariation",
148167
columns: ["siteID", "id"],
149168
onDelete: .cascade)
150169

151-
productVariationImageTable.column("dateCreated", .datetime).notNull()
152-
productVariationImageTable.column("dateModified", .datetime)
153-
154-
productVariationImageTable.column("src", .text).notNull()
155-
productVariationImageTable.column("name", .text)
156-
productVariationImageTable.column("alt", .text)
170+
productVariationImageTable.foreignKey(["siteID", "imageID"],
171+
references: "image",
172+
columns: ["siteID", "id"],
173+
onDelete: .cascade)
157174
}
158175
}
159176
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import Foundation
2+
import GRDB
3+
4+
// periphery:ignore
5+
public struct PersistedImage: Codable {
6+
public let siteID: Int64
7+
public let id: Int64
8+
public let dateCreated: Date
9+
public let dateModified: Date?
10+
public let src: String
11+
public let name: String?
12+
public let alt: String?
13+
14+
public init(siteID: Int64,
15+
id: Int64,
16+
dateCreated: Date,
17+
dateModified: Date?,
18+
src: String,
19+
name: String?,
20+
alt: String?) {
21+
self.siteID = siteID
22+
self.id = id
23+
self.dateCreated = dateCreated
24+
self.dateModified = dateModified
25+
self.src = src
26+
self.name = name
27+
self.alt = alt
28+
}
29+
}
30+
31+
// periphery:ignore - TODO: remove ignore when populating database
32+
extension PersistedImage: FetchableRecord, PersistableRecord {
33+
public static var databaseTableName: String { "image" }
34+
35+
public static var primaryKey: [String] { [CodingKeys.siteID.stringValue, CodingKeys.id.stringValue] }
36+
37+
public enum Columns {
38+
public static let siteID = Column(CodingKeys.siteID)
39+
public static let id = Column(CodingKeys.id)
40+
public static let dateCreated = Column(CodingKeys.dateCreated)
41+
public static let dateModified = Column(CodingKeys.dateModified)
42+
public static let src = Column(CodingKeys.src)
43+
public static let name = Column(CodingKeys.name)
44+
public static let alt = Column(CodingKeys.alt)
45+
}
46+
}
47+
48+
49+
// periphery:ignore - TODO: remove ignore when populating database
50+
extension PersistedImage {
51+
enum CodingKeys: String, CodingKey {
52+
case siteID
53+
case id
54+
case dateCreated
55+
case dateModified
56+
case src
57+
case name
58+
case alt
59+
}
60+
}

Modules/Sources/Storage/GRDB/Model/PersistedProduct.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,17 @@ extension PersistedProduct: FetchableRecord, PersistableRecord {
7272
public static let stockStatusKey = Column(CodingKeys.stockStatusKey)
7373
}
7474

75-
public static let images = hasMany(PersistedProductImage.self,
76-
using: ForeignKey([PersistedProductImage.CodingKeys.siteID.stringValue,
77-
PersistedProductImage.CodingKeys.productID.stringValue],
78-
to: primaryKey))
75+
// Join table association (internal - used by 'images' through association)
76+
private static let productImages = hasMany(PersistedProductImage.self,
77+
using: ForeignKey([PersistedProductImage.CodingKeys.siteID.stringValue,
78+
PersistedProductImage.CodingKeys.productID.stringValue],
79+
to: primaryKey))
80+
81+
// Through association to access actual images via join table (use this to fetch images)
82+
public static let images = hasMany(PersistedImage.self,
83+
through: productImages,
84+
using: PersistedProductImage.image)
85+
7986
public static let attributes = hasMany(PersistedProductAttribute.self,
8087
using: ForeignKey([PersistedProductAttribute.CodingKeys.siteID.stringValue,
8188
PersistedProductAttribute.CodingKeys.productID.stringValue],

Modules/Sources/Storage/GRDB/Model/PersistedProductImage.swift

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,48 @@ import Foundation
22
import GRDB
33

44
// periphery:ignore - TODO: remove ignore when populating database
5+
/// Join table linking products to images (many-to-many relationship)
56
public struct PersistedProductImage: Codable {
67
public let siteID: Int64
7-
public let id: Int64
88
public let productID: Int64
9-
public let dateCreated: Date
10-
public let dateModified: Date?
11-
public let src: String
12-
public let name: String?
13-
public let alt: String?
9+
public let imageID: Int64
1410

1511
public init(siteID: Int64,
16-
id: Int64,
1712
productID: Int64,
18-
dateCreated: Date,
19-
dateModified: Date?,
20-
src: String,
21-
name: String?,
22-
alt: String?) {
13+
imageID: Int64) {
2314
self.siteID = siteID
24-
self.id = id
2515
self.productID = productID
26-
self.dateCreated = dateCreated
27-
self.dateModified = dateModified
28-
self.src = src
29-
self.name = name
30-
self.alt = alt
16+
self.imageID = imageID
3117
}
3218
}
3319

3420
// periphery:ignore - TODO: remove ignore when populating database
3521
extension PersistedProductImage: FetchableRecord, PersistableRecord {
3622
public static var databaseTableName: String { "productImage" }
3723

38-
public static var primaryKey: [String] { [CodingKeys.siteID.stringValue, CodingKeys.id.stringValue] }
24+
public static var primaryKey: [String] {
25+
[CodingKeys.siteID.stringValue, CodingKeys.productID.stringValue, CodingKeys.imageID.stringValue]
26+
}
3927

4028
public enum Columns {
4129
public static let siteID = Column(CodingKeys.siteID)
42-
public static let id = Column(CodingKeys.id)
4330
public static let productID = Column(CodingKeys.productID)
44-
public static let dateCreated = Column(CodingKeys.dateCreated)
45-
public static let dateModified = Column(CodingKeys.dateModified)
46-
public static let src = Column(CodingKeys.src)
47-
public static let name = Column(CodingKeys.name)
48-
public static let alt = Column(CodingKeys.alt)
31+
public static let imageID = Column(CodingKeys.imageID)
4932
}
33+
34+
// Association to the actual image
35+
public static let image = belongsTo(PersistedImage.self,
36+
using: ForeignKey([CodingKeys.siteID.stringValue,
37+
CodingKeys.imageID.stringValue],
38+
to: PersistedImage.primaryKey))
5039
}
5140

5241

5342
// periphery:ignore - TODO: remove ignore when populating database
5443
extension PersistedProductImage {
5544
enum CodingKeys: String, CodingKey {
5645
case siteID
57-
case id
5846
case productID
59-
case dateCreated
60-
case dateModified
61-
case src
62-
case name
63-
case alt
47+
case imageID
6448
}
6549
}

Modules/Sources/Storage/GRDB/Model/PersistedProductVariation.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,19 @@ extension PersistedProductVariation: FetchableRecord, PersistableRecord {
6565
using: ForeignKey([PersistedProductVariationAttribute.CodingKeys.siteID.stringValue,
6666
PersistedProductVariationAttribute.CodingKeys.productVariationID.stringValue],
6767
to: primaryKey))
68-
public static let image = hasOne(PersistedProductVariationImage.self,
69-
key: "image",
70-
using: ForeignKey([PersistedProductVariationImage.CodingKeys.siteID.stringValue,
71-
PersistedProductVariationImage.CodingKeys.productVariationID.stringValue],
72-
to: primaryKey))
68+
69+
// Join table association (internal - used by 'image' through association)
70+
private static let productVariationImage = hasOne(PersistedProductVariationImage.self,
71+
key: "productVariationImage",
72+
using: ForeignKey([PersistedProductVariationImage.CodingKeys.siteID.stringValue,
73+
PersistedProductVariationImage.CodingKeys.productVariationID.stringValue],
74+
to: primaryKey))
75+
76+
// Through association to access actual image via join table (use this to fetch image)
77+
public static let image = hasOne(PersistedImage.self,
78+
through: productVariationImage,
79+
using: PersistedProductVariationImage.image,
80+
key: "image")
7381
}
7482

7583

Modules/Sources/Storage/GRDB/Model/PersistedProductVariationImage.swift

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,48 @@ import Foundation
22
import GRDB
33

44
// periphery:ignore - TODO: remove ignore when populating database
5+
/// Join table linking product variations to images (many-to-many relationship)
56
public struct PersistedProductVariationImage: Codable {
67
public let siteID: Int64
7-
public let id: Int64
88
public let productVariationID: Int64
9-
public let dateCreated: Date
10-
public let dateModified: Date?
11-
public let src: String
12-
public let name: String?
13-
public let alt: String?
9+
public let imageID: Int64
1410

1511
public init(siteID: Int64,
16-
id: Int64,
1712
productVariationID: Int64,
18-
dateCreated: Date,
19-
dateModified: Date?,
20-
src: String,
21-
name: String?,
22-
alt: String?) {
13+
imageID: Int64) {
2314
self.siteID = siteID
24-
self.id = id
2515
self.productVariationID = productVariationID
26-
self.dateCreated = dateCreated
27-
self.dateModified = dateModified
28-
self.src = src
29-
self.name = name
30-
self.alt = alt
16+
self.imageID = imageID
3117
}
3218
}
3319

3420
// periphery:ignore - TODO: remove ignore when populating database
3521
extension PersistedProductVariationImage: FetchableRecord, PersistableRecord {
3622
public static var databaseTableName: String { "productVariationImage" }
3723

38-
public static var primaryKey: [String] { [CodingKeys.siteID.stringValue, CodingKeys.id.stringValue] }
24+
public static var primaryKey: [String] {
25+
[CodingKeys.siteID.stringValue, CodingKeys.productVariationID.stringValue, CodingKeys.imageID.stringValue]
26+
}
3927

4028
public enum Columns {
4129
public static let siteID = Column(CodingKeys.siteID)
42-
public static let id = Column(CodingKeys.id)
4330
public static let productVariationID = Column(CodingKeys.productVariationID)
44-
public static let dateCreated = Column(CodingKeys.dateCreated)
45-
public static let dateModified = Column(CodingKeys.dateModified)
46-
public static let src = Column(CodingKeys.src)
47-
public static let name = Column(CodingKeys.name)
48-
public static let alt = Column(CodingKeys.alt)
31+
public static let imageID = Column(CodingKeys.imageID)
4932
}
33+
34+
// Association to the actual image
35+
public static let image = belongsTo(PersistedImage.self,
36+
using: ForeignKey([CodingKeys.siteID.stringValue,
37+
CodingKeys.imageID.stringValue],
38+
to: PersistedImage.primaryKey))
5039
}
5140

5241

5342
// periphery:ignore - TODO: remove ignore when populating database
5443
extension PersistedProductVariationImage {
5544
enum CodingKeys: String, CodingKey {
5645
case siteID
57-
case id
5846
case productVariationID
59-
case dateCreated
60-
case dateModified
61-
case src
62-
case name
63-
case alt
47+
case imageID
6448
}
6549
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Foundation
2+
import Storage
3+
4+
// MARK: - PersistedImage Conversions
5+
public extension PersistedImage {
6+
/// Create a PersistedImage from a ProductImage
7+
static func make(from productImage: ProductImage, siteID: Int64) -> PersistedImage {
8+
return PersistedImage(
9+
siteID: siteID,
10+
id: productImage.imageID,
11+
dateCreated: productImage.dateCreated,
12+
dateModified: productImage.dateModified,
13+
src: productImage.src,
14+
name: productImage.name,
15+
alt: productImage.alt
16+
)
17+
}
18+
19+
func toProductImage() -> ProductImage {
20+
return ProductImage(
21+
imageID: id,
22+
dateCreated: dateCreated,
23+
dateModified: dateModified,
24+
src: src,
25+
name: name,
26+
alt: alt
27+
)
28+
}
29+
}

0 commit comments

Comments
 (0)