Created
January 2, 2025 13:56
-
-
Save lantos1618/1a918b64977b88ef1a790abac5cd880f to your computer and use it in GitHub Desktop.
One shoting nim orm with claud
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import macros, strutils, sequtils, jsony, db_sqlite | |
| from typetraits import name | |
| type | |
| Relation = object | |
| kind: RelationKind | |
| foreignKey: string | |
| throughTable: string | |
| sourceField: string | |
| targetField: string | |
| modelType: string | |
| RelationKind = enum | |
| rkOneToOne | |
| rkOneToMany | |
| rkManyToMany | |
| Hook = enum | |
| beforeCreate | |
| afterCreate | |
| beforeUpdate | |
| afterUpdate | |
| beforeDelete | |
| afterDelete | |
| Model = ref object of RootObj | |
| id: int | |
| createdAt: string | |
| updatedAt: string | |
| template relation(kind: RelationKind, foreignKey="", throughTable="", | |
| sourceField="id", targetField="", modelType=""): Relation = | |
| Relation( | |
| kind: kind, | |
| foreignKey: foreignKey, | |
| throughTable: throughTable, | |
| sourceField: sourceField, | |
| targetField: targetField, | |
| modelType: modelType | |
| ) | |
| template one_to_one(T: typed, foreignKey="", sourceField="id", targetField=""): Relation = | |
| relation(rkOneToOne, foreignKey, "", sourceField, targetField, name(T)) | |
| template one_to_many(T: typed, foreignKey="", sourceField="id", targetField=""): Relation = | |
| relation(rkOneToMany, foreignKey, "", sourceField, targetField, name(T)) | |
| template many_to_many(T: typed, throughTable: string): Relation = | |
| relation(rkManyToMany, "", throughTable, "id", "id", name(T)) | |
| var modelHooks = newTable[string, Table[Hook, seq[NimNode]]]() | |
| macro model*(head, body: untyped): untyped = | |
| ## Main macro to define models | |
| result = newStmtList() | |
| # Extract model name and parent | |
| var | |
| modelName = head | |
| parentType = ident("Model") | |
| if head.kind == nnkInfix and head[0].strVal == "of": | |
| modelName = head[1] | |
| parentType = head[2] | |
| # Create type definition | |
| var typeFields = newNimNode(nnkRecList) | |
| var relations: seq[Relation] | |
| var hooks = initTable[Hook, seq[NimNode]]() | |
| # Process model body | |
| for node in body: | |
| case node.kind | |
| of nnkCall: | |
| if node[0].strVal == "field": | |
| typeFields.add(newIdentDefs( | |
| node[1], | |
| node[2], | |
| newEmptyNode() | |
| )) | |
| of nnkCommand: | |
| # Handle relation definitions | |
| case node[0].strVal: | |
| of "belongs_to": | |
| # Add foreign key field | |
| let fkName = node[1].strVal.toLowerAscii & "Id" | |
| typeFields.add(newIdentDefs( | |
| ident(fkName), | |
| ident("int"), | |
| newEmptyNode() | |
| )) | |
| relations.add(one_to_one(node[1].strVal.capitalizeAscii, fkName)) | |
| of "has_many": | |
| relations.add(one_to_many(node[1].strVal.capitalizeAscii)) | |
| of "many_to_many": | |
| relations.add(many_to_many(node[1].strVal.capitalizeAscii, | |
| node[2].strVal)) | |
| of "hook": | |
| let hookName = parseEnum[Hook](node[1].strVal) | |
| hooks[hookName] = @[node[2]] | |
| else: discard | |
| # Store hooks | |
| modelHooks[$modelName] = hooks | |
| # Create the type | |
| result.add quote do: | |
| type `modelName` = ref object of `parentType` | |
| `typeFields` | |
| # Generate relationship accessors | |
| for rel in relations: | |
| let | |
| targetType = ident(rel.modelType) | |
| methodName = ident(rel.modelType.toLowerAscii) | |
| case rel.kind | |
| of rkOneToOne: | |
| result.add quote do: | |
| proc `methodName`*(m: `modelName`): `targetType` = | |
| let query = "SELECT * FROM " & `rel.modelType` & | |
| " WHERE id = ?" | |
| queryOne(`targetType`, query, m.`ident(rel.foreignKey)`) | |
| of rkOneToMany: | |
| result.add quote do: | |
| proc `methodName`*(m: `modelName`): seq[`targetType`] = | |
| let query = "SELECT * FROM " & `rel.modelType` & | |
| " WHERE " & `rel.foreignKey` & " = ?" | |
| queryMany(`targetType`, query, m.id) | |
| of rkManyToMany: | |
| result.add quote do: | |
| proc `methodName`*(m: `modelName`): seq[`targetType`] = | |
| let query = """ | |
| SELECT t.* FROM """ & `rel.modelType` & """ t | |
| JOIN """ & `rel.throughTable` & """ j | |
| ON j.""" & `rel.modelType.toLowerAscii` & """_id = t.id | |
| WHERE j.""" & `modelName.strVal.toLowerAscii` & "_id = ?" | |
| queryMany(`targetType`, query, m.id) | |
| # Generate CRUD methods | |
| result.add quote do: | |
| proc save*(m: `modelName`) = | |
| if m.id == 0: | |
| # Insert | |
| runHooks(`modelName`, beforeCreate) | |
| m.createdAt = now() | |
| m.updatedAt = now() | |
| m.id = insertInto(`modelName`, m) | |
| runHooks(`modelName`, afterCreate) | |
| else: | |
| # Update | |
| runHooks(`modelName`, beforeUpdate) | |
| m.updatedAt = now() | |
| update(`modelName`, m) | |
| runHooks(`modelName`, afterUpdate) | |
| proc delete*(m: `modelName`) = | |
| runHooks(`modelName`, beforeDelete) | |
| deleteFrom(`modelName`, m.id) | |
| runHooks(`modelName`, afterDelete) | |
| proc find*[T: `modelName`](typ: typedesc[T], id: int): T = | |
| queryOne(T, "SELECT * FROM " & name(T) & " WHERE id = ?", id) | |
| proc all*[T: `modelName`](typ: typedesc[T]): seq[T] = | |
| queryMany(T, "SELECT * FROM " & name(T)) | |
| proc runHooks(T: typedesc, hook: Hook) = | |
| if $T in modelHooks: | |
| let hooks = modelHooks[$T] | |
| if hook in hooks: | |
| for h in hooks[hook]: | |
| h.treerepr.templ | |
| # Example usage: | |
| when isMainModule: | |
| model User: | |
| field name: string | |
| field email: string | |
| posts: one_to_many(Post, on: "id", "userId") | |
| roles: many_to_many(Role, through: "user_roles") | |
| hook beforeCreate: | |
| validate(email, "email") | |
| setDefaults() | |
| model Post: | |
| field title: string | |
| field content: string | |
| user: one_to_one(User, on: "userId", "id") | |
| hook beforeSave: | |
| validate(title, "required") | |
| slugify(title) | |
| # Usage | |
| let user = User.new(name: "John", email: "john@test.com") | |
| user.save() | |
| let post = Post.new( | |
| title: "Hello World", | |
| userId: user.id | |
| ) | |
| post.save() | |
| # Queries | |
| let johns_posts = user.posts() | |
| for post in johns_posts: | |
| echo post.user().name # Access relations | |
| # Find existing | |
| let found = User.find(1) | |
| let all = User.all() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment