SoFunction
Updated on 2025-03-03

Golang Gorm implements custom polymorphic model association query

1. Table structure

CREATE TABLE `orders` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `order_no` varchar(32) NOT NULL DEFAULT '',
  `orderable_id` int unsigned NOT NULL DEFAULT '0',
  `orderable_type` char(30) NOT NULL DEFAULT '',
  `status` tinyint unsigned NOT NULL DEFAULT '0',
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `deleted_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
 
CREATE TABLE `phone` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `phone_name` varchar(50) NOT NULL DEFAULT '',
  `phone_model` varchar(30) NOT NULL DEFAULT '',
  `status` tinyint unsigned NOT NULL DEFAULT '0',
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `deleted_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
 
CREATE TABLE `cars` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `car_name` varchar(50) NOT NULL DEFAULT '',
  `car_model` varchar(30) NOT NULL DEFAULT '',
  `status` tinyint unsigned NOT NULL DEFAULT '0',
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `deleted_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

2. Interface definition

// The LoaderAble interface defines data loading behaviortype LoaderAble interface {
	LoadAble(IDs []int) (map[int]any, error)
}
 
// The LoadAbleItem interface defines a common method for add-onstype LoadAbleItem interface {
	GetAbleType() string        // Get type key value	GetAbleID() int             // Get ID	SetLoadedAbleData(data any) // Set the loaded data}

3. Model definition and implementation of interfaces

type Order struct {
	Id            int            `json:"id"`
	OrderNo       string         `json:"order_no"`
	OrderableId   int            `json:"orderable_id"`
	OrderableType string         `json:"orderable_type"`
	Orderable     any            `json:"orderable" gorm:"-"`
	Status        uint8          `json:"status"`
	CreatedAt     *     `json:"created_at"`
	UpdatedAt     *     `json:"updated_at"`
	DeletedAt      `json:"deleted_at"`
}
 
func (tb *Order) TableName() string {
	return "orders"
}
 
func (tb *Order) GetAbleType() string {
	return 
}
 
func (tb *Order) GetAbleID() int {
	return 
}
 
func (tb *Order) SetLoadedAbleData(data any) {
	 = data
}
 
 
 
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
 
type Car struct {
	Id        int            `json:"id"`
	CarName   string         `json:"car_name"`
	CarModel  string         `json:"car_model"`
	Status    uint8          `json:"status"`
	CreatedAt *     `json:"created_at"`
	UpdatedAt *     `json:"updated_at"`
	DeletedAt  `json:"deleted_at"`
}
 
func (tb *Car) TableName() string {
	return "cars"
}
 
// CarLoaderAble implements the Loader interfacetype CarLoaderAble struct{}
 
// LoadAble implements loading polymorphic association logic// IDs polymorphic association type ID (main parameter)func (loader *CarLoaderAble) LoadAble(IDs []int) (resultMap map[int]any, err error) {
	IDsLen := len(IDs)
	if IDsLen == 0 {
		return
	}
 
	car := make([]*Car, 0, IDsLen)
	err = ().Where("id IN (?) AND status = ?", IDs, 1).Find(&car).Error
	if err != nil {
		return
	}
 
	resultMap = make(map[int]any, IDsLen)
	for _, item := range car {
		resultMap[] = item
	}
	return
}
 
 
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
 
type Phone struct {
	Id         int            `json:"id"`
	PhoneName  string         `json:"phone_name"`
	PhoneModel string         `json:"phone_model"`
	Status     uint8          `json:"status" gorm:"column:status"`
	StatusNew  uint8          `json:"status_new" gorm:"-"`
	CreatedAt  *     `json:"created_at"`
	UpdatedAt  *     `json:"updated_at"`
	DeletedAt   `json:"deleted_at"`
}
 
func (tb *Phone) TableName() string {
	return "phone"
}
 
func (tb *Phone) AfterFind(tx *) (err error) {
	 = 
	return
}
 
// PhoneLoaderAble implements the Loader interfacetype PhoneLoaderAble struct{}
 
// LoadAble implements loading polymorphic association logic// IDs polymorphic association type ID (main parameter)func (loader *PhoneLoaderAble) LoadAble(IDs []int) (resultMap map[int]any, err error) {
	IDsLen := len(IDs)
	if IDsLen == 0 {
		return
	}
 
	phone := make([]*Phone, 0, IDsLen)
	err = ().Where("id IN (?) AND status = ?", IDs, 1).Find(&phone).Error
	if err != nil {
		return
	}
 
	resultMap = make(map[int]any, IDsLen)
	for _, item := range phone {
		resultMap[] = item
	}
	return
}

4. Create a loader preloader

// LoaderAbleFactory is used to manage different types of loaderstype LoaderAbleFactory struct {
	loaders map[string]LoaderAble
}
 
func NewLoaderAbleFactory() *LoaderAbleFactory {
	return &LoaderAbleFactory{
		loaders: make(map[string]LoaderAble),
	}
}
 
func (f *LoaderAbleFactory) RegisterLoader(typeName string, loader LoaderAble) {
	[typeName] = loader
}

5. Register loader preloader service

var (
	loaderAbleFactory *LoaderAbleFactory
)
 
// init Select global loading when the project starts initializationfunc init() {
	loaderAbleFactory = NewLoaderAbleFactory()
	("phone", &PhoneLoaderAble{})
	("car", &CarLoaderAble{})
	("Polymorphic model relationship registration was successful...")
}

6. Implement LoadAble's general loading function

// LoadAble is a common load function that can handle any slice that implements the LoadableItem interfacefunc LoadAble[T LoadAbleItem](items []T) error {
	if len(items) == 0 {
		return nil
	}
 
	// Group IDs by type	typeIDsMap := make(map[string][]int)
	for _, item := range items {
		typeKey := ()
		typeIDsMap[typeKey] = append(typeIDsMap[typeKey], ())
	}
 
	// Use the corresponding loader to load data	typeDataMap := make(map[string]map[int]any)
	for typeName, ids := range typeIDsMap {
		loader, ok := [typeName]
		if !ok {
			continue
		}
 
		resultMap, err := (ids)
		if err != nil {
			return err
		}
		typeDataMap[typeName] = resultMap
	}
 
	// Fill in data	for _, item := range items {
		if dataMap, ok := typeDataMap[()]; ok {
			if data, exists := dataMap[()]; exists {
				(data)
			}
		}
	}
	return nil
}

7. Debugging

  • Prepare data
--orderssurface
INSERT INTO `orders` (`id`, `order_no`, `orderable_id`, `orderable_type`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1, '202411010001', 1002, 'car', 1, '2024-11-01 12:03:03', '2024-11-01 12:03:06', NULL);
INSERT INTO `orders` (`id`, `order_no`, `orderable_id`, `orderable_type`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (2, '202411010002', 1001, 'phone', 1, '2024-11-01 12:03:03', '2024-11-01 12:03:06', NULL);
INSERT INTO `orders` (`id`, `order_no`, `orderable_id`, `orderable_type`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (3, '202411010003', 1000, 'car', 1, '2024-11-01 12:03:03', '2024-11-01 12:03:06', NULL);
INSERT INTO `orders` (`id`, `order_no`, `orderable_id`, `orderable_type`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (4, '202411010004', 1001, 'car', 1, '2024-11-01 12:03:03', '2024-11-01 12:03:06', NULL);
INSERT INTO `orders` (`id`, `order_no`, `orderable_id`, `orderable_type`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (5, '202411010005', 1002, 'phone', 1, '2024-11-01 12:03:03', '2024-11-01 12:03:06', NULL);
 
--phonesurface
INSERT INTO `phone` (`id`, `phone_name`, `phone_model`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1000, 'XiaoMi', '2S', 2, '2024-11-01 11:59:37', '2024-11-01 11:59:40', NULL);
INSERT INTO `phone` (`id`, `phone_name`, `phone_model`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1001, 'HUAWEI', 'mate60', 1, '2024-11-01 11:59:54', '2024-11-01 11:59:57', NULL);
INSERT INTO `phone` (`id`, `phone_name`, `phone_model`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1002, 'Apple', 'iPhone 12 Pro Max', 1, '2024-11-01 12:00:26', '2024-11-01 12:00:28', NULL);
 
--carssurface
INSERT INTO `cars` (`id`, `car_name`, `car_model`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1000, 'Audi', 'A6L', 1, '2024-11-01 11:57:53', '2024-11-01 11:57:55', NULL);
INSERT INTO `cars` (`id`, `car_name`, `car_model`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1001, 'BMW', '5 series', 1, '2024-11-01 11:58:12', '2024-11-01 11:58:15', NULL);
INSERT INTO `cars` (`id`, `car_name`, `car_model`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES (1002, 'Benz', 'E300', 1, '2024-11-01 11:58:53', '2024-11-01 11:58:56', NULL);
  • Writing code
// Gin framework + Gorm as an example("/orders", )
 
// GetOrderList Get the order list interfacefunc GetOrderList(c *) {
	orders := make([]*, 0)
	err := ().Find(&orders).Error
	if err != nil {
		(200, {"error": ()})
		return
	}
	err = (orders)
	if err != nil {
		(200, {"error": ()})
		return
	}
	(200, {"data": orders})
}
  • Make a request
curl '127.0.0.1:16888/api/orders'
{
    "data": [
        {
            "id": 1,
            "order_no": "202411010001",
            "orderable_id": 1002,
            "orderable_type": "car",
            "orderable": {
                "id": 1002,
                "car_name": "Benz",
                "car_model": "E300",
                "status": 1,
                "created_at": "2024-11-01T11:58:53+08:00",
                "updated_at": "2024-11-01T11:58:56+08:00",
                "deleted_at": null
            },
            "status": 1,
            "created_at": "2024-11-01T12:03:03+08:00",
            "updated_at": "2024-11-01T12:03:06+08:00",
            "deleted_at": null
        },
        {
            "id": 2,
            "order_no": "202411010002",
            "orderable_id": 1001,
            "orderable_type": "phone",
            "orderable": {
                "id": 1001,
                "phone_name": "HUAWEI",
                "phone_model": "mate60",
                "status": 1,
                "status_new": 1,
                "created_at": "2024-11-01T11:59:54+08:00",
                "updated_at": "2024-11-01T11:59:57+08:00",
                "deleted_at": null
            },
            "status": 1,
            "created_at": "2024-11-01T12:03:03+08:00",
            "updated_at": "2024-11-01T12:03:06+08:00",
            "deleted_at": null
        },
        {
            "id": 3,
            "order_no": "202411010003",
            "orderable_id": 1000,
            "orderable_type": "car",
            "orderable": {
                "id": 1000,
                "car_name": "Audi",
                "car_model": "A6L",
                "status": 1,
                "created_at": "2024-11-01T11:57:53+08:00",
                "updated_at": "2024-11-01T11:57:55+08:00",
                "deleted_at": null
            },
            "status": 1,
            "created_at": "2024-11-01T12:03:03+08:00",
            "updated_at": "2024-11-01T12:03:06+08:00",
            "deleted_at": null
        },
        {
            "id": 4,
            "order_no": "202411010004",
            "orderable_id": 1001,
            "orderable_type": "car",
            "orderable": {
                "id": 1001,
                "car_name": "BMW",
                "car_model": "5 Series",
                "status": 1,
                "created_at": "2024-11-01T11:58:12+08:00",
                "updated_at": "2024-11-01T11:58:15+08:00",
                "deleted_at": null
            },
            "status": 1,
            "created_at": "2024-11-01T12:03:03+08:00",
            "updated_at": "2024-11-01T12:03:06+08:00",
            "deleted_at": null
        },
        {
            "id": 5,
            "order_no": "202411010005",
            "orderable_id": 1002,
            "orderable_type": "phone",
            "orderable": {
                "id": 1002,
                "phone_name": "Apple",
                "phone_model": "iPhone 12 Pro Max",
                "status": 1,
                "status_new": 1,
                "created_at": "2024-11-01T12:00:26+08:00",
                "updated_at": "2024-11-01T12:00:28+08:00",
                "deleted_at": null
            },
            "status": 1,
            "created_at": "2024-11-01T12:03:03+08:00",
            "updated_at": "2024-11-01T12:03:06+08:00",
            "deleted_at": null
        }
    ]
}

The above is the detailed content of Golang Gorm implementing custom polymorphic model association query. For more information about Golang Gorm association query, please pay attention to my other related articles!