Skip to content

Feat/ren/72 custom name ref repository#75

Merged
ren0503 merged 6 commits intomasterfrom
feat/ren/72-custom-name-ref-repository
Aug 31, 2025
Merged

Feat/ren/72 custom name ref repository#75
ren0503 merged 6 commits intomasterfrom
feat/ren/72-custom-name-ref-repository

Conversation

@ren0503
Copy link
Contributor

@ren0503 ren0503 commented Aug 30, 2025

No description provided.

@ren0503 ren0503 added this to the SQL ORM Release v2.3.3 milestone Aug 30, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 30, 2025

Summary by CodeRabbit

  • New Features
    • Repositories can now define a custom display/name via an optional method on the model; dependency injection respects this. Falls back to the model’s struct name when unspecified.
  • Tests
    • Added tests validating custom repository naming and successful injection, increasing coverage and confidence.
  • Chores
    • Continuous integration now always runs tests and uploads coverage, simplifying the workflow and ensuring consistent reporting.

Walkthrough

GetName() and InjectRepository now use reflection to call an optional model method RepositoryName(); if present its return value is used as the repository base name, otherwise code falls back to the struct name. Tests and CI workflow were updated to cover and always run these behaviors.

Changes

Cohort / File(s) Summary
Repository name resolution
.../repository.go, .../module.go
Implemented reflection-based lookup for a RepositoryName() string method on model types; GetName() and InjectRepository call it when present, otherwise fall back to common.GetStructName(model). Added reflect import and runtime name selection logic. No public API signature changes.
Tests: custom name & injection
.../repository_test.go, .../module_test.go
Added test type Abc with embedded sqlorm.Model and RepositoryName() string returning "service". Added TestNameRepo and Test_RefName to verify GetName() and repository injection behavior.
CI workflow simplification
.github/workflows/go.yml
Removed the conditional "Decide if tests should run" step and gating; tests and coverage uploads now run unconditionally and related env/GITHUB_OUTPUT usage was removed.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant Repo as Repository[M]
  participant Reflect as reflect.Type/Value
  participant Model as M.RepositoryName()
  participant Fallback as common.GetStructName

  Caller->>Repo: GetName() / InjectRepository
  Repo->>Reflect: construct model value, look up "RepositoryName"
  alt RepositoryName exists
    Repo->>Model: call RepositoryName()
    Model-->>Repo: "service" (string)
    Repo-->>Caller: return "service"
  else not exists
    Repo->>Fallback: GetStructName(model)
    Fallback-->>Repo: defaultName
    Repo-->>Caller: return defaultName
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I sniffed the code, twitched whiskers twice,
Now names can hop with custom spice.
If models speak, we lend an ear;
If not, the struct will make it clear.
CI runs steady while carrots gleam—repositories hop into the stream. 🥕

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.2.2)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/product/migration-guide for migration instructions

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/ren/72-custom-name-ref-repository

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ren0503 ren0503 linked an issue Aug 30, 2025 that may be closed by this pull request
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
repository_test.go (1)

72-85: Add a test for pointer-receiver RepositoryName()

Ensure GetName() also works when models implement RepositoryName() on a pointer receiver.

Proposed addition:

type Def struct {
	sqlorm.Model `gorm:"embedded"`
	Name         string `gorm:"type:varchar(255);not null"`
}

func (*Def) RepositoryName() string { return "service_ptr" }

func TestNameRepoPtr(t *testing.T) {
	repo := sqlorm.NewRepo(Def{})
	require.Equal(t, "service_ptr", repo.GetName())
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3c93063 and 9ac6d93.

📒 Files selected for processing (2)
  • repository.go (1 hunks)
  • repository_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
repository_test.go (2)
model.go (1)
  • Model (11-16)
repository.go (1)
  • NewRepo (16-18)
🔇 Additional comments (1)
repository_test.go (1)

72-85: Good positive-path coverage

The test clearly validates the custom name path. Looks good.

Comment on lines +26 to +35

ctModel := reflect.ValueOf(&model).Elem()
fnc := ctModel.MethodByName("RepositoryName")
var name string
if fnc.IsValid() {
name = fnc.Call(nil)[0].String()
} else {
name = common.GetStructName(model)
}
return name
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Support pointer receivers and remove panic risks by replacing reflection with a simple interface check

Current code misses methods with pointer receivers and can panic if the method’s return type isn’t string. Prefer a type assertion that handles both value and pointer receivers and falls back cleanly.

Apply this diff:

 func (r *Repository[M]) GetName() string {
   var model M
-  
-  ctModel := reflect.ValueOf(&model).Elem()
-  fnc := ctModel.MethodByName("RepositoryName")
-  var name string
-  if fnc.IsValid() {
-    name = fnc.Call(nil)[0].String()
-  } else {
-    name = common.GetStructName(model)
-  }
-  return name
+  // Prefer interface assertion over reflection; supports both value and pointer receivers.
+  type repositoryNamer interface{ RepositoryName() string }
+  if rn, ok := any(model).(repositoryNamer); ok {
+    if n := rn.RepositoryName(); n != "" {
+      return n
+    }
+  }
+  if rn, ok := any(&model).(repositoryNamer); ok {
+    if n := rn.RepositoryName(); n != "" {
+      return n
+    }
+  }
+  return common.GetStructName(model)
 }

If you prefer to keep reflection, minimally switch to pointer first and validate signature:

- ctModel := reflect.ValueOf(&model).Elem()
- fnc := ctModel.MethodByName("RepositoryName")
+ ptr := reflect.ValueOf(&model)
+ fnc := ptr.MethodByName("RepositoryName")
+ if !fnc.IsValid() {
+   fnc = ptr.Elem().MethodByName("RepositoryName")
+ }
- if fnc.IsValid() {
-   name = fnc.Call(nil)[0].String()
+ if fnc.IsValid() && fnc.Type().NumIn() == 0 && fnc.Type().NumOut() >= 1 && fnc.Type().Out(0).Kind() == reflect.String {
+   out := fnc.Call(nil)
+   if len(out) > 0 {
+     s := out[0].String()
+     if s != "" {
+       return s
+     }
+   }
   } else {
-   name = common.GetStructName(model)
+   return common.GetStructName(model)
   }
- return name
+ return common.GetStructName(model)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ctModel := reflect.ValueOf(&model).Elem()
fnc := ctModel.MethodByName("RepositoryName")
var name string
if fnc.IsValid() {
name = fnc.Call(nil)[0].String()
} else {
name = common.GetStructName(model)
}
return name
func (r *Repository[M]) GetName() string {
var model M
// Prefer interface assertion over reflection; supports both value and pointer receivers.
type repositoryNamer interface{ RepositoryName() string }
if rn, ok := any(model).(repositoryNamer); ok {
if n := rn.RepositoryName(); n != "" {
return n
}
}
if rn, ok := any(&model).(repositoryNamer); ok {
if n := rn.RepositoryName(); n != "" {
return n
}
}
return common.GetStructName(model)
}
🤖 Prompt for AI Agents
In repository.go around lines 26 to 35, the current reflection-based logic
misses methods with pointer receivers and may panic if the method signature
isn't string; replace it by defining a small interface type (e.g.
RepositoryNamer with RepositoryName() string) and perform a type assertion:
first try asserting the concrete model value, then try asserting its pointer
(address) if available; if either assertion succeeds, call RepositoryName() and
use the returned string, otherwise fall back to common.GetStructName(model).
This avoids reflection, supports pointer receivers, and removes the panic risk
by only calling the method after a successful interface assertion.

@codecov-commenter
Copy link

codecov-commenter commented Aug 30, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ren0503 ren0503 merged commit 4d57cc8 into master Aug 31, 2025
3 of 4 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
module.go (2)

81-81: Drop redundant type conversion.

GetRepoName already returns core.Provide.

-	modelName := core.Provide(GetRepoName(name))
+	modelName := GetRepoName(name)

72-80: Alternative: avoid reflection entirely (simpler and faster).

If you’re open to it, interface assertions cover both pointer/value receivers cleanly.

-	ctModel := reflect.ValueOf(&model).Elem()
-	fnc := ctModel.MethodByName("RepositoryName")
-	var name string
-	if fnc.IsValid() {
-		name = fnc.Call(nil)[0].String()
-	} else {
-		name = common.GetStructName(model)
-	}
+	var name string
+	if n, ok := any(&model).(interface{ RepositoryName() string }); ok {
+		name = n.RepositoryName()
+	} else if n, ok := any(model).(interface{ RepositoryName() string }); ok {
+		name = n.RepositoryName()
+	} else {
+		name = common.GetStructName(model)
+	}

If you choose this, remove the reflect import.

module_test.go (1)

249-270: Add a test for pointer-receiver RepositoryName to prevent regressions.

Current code won’t detect pointer receivers; add a test to cover that path once fixed.

func Test_RefName_PtrReceiver(t *testing.T) {
	type P struct {
		sqlorm.Model `gorm:"embedded"`
		Name string
	}
	// Pointer-receiver variant
	func (p *P) RepositoryName() string { return "ptrsvc" }

	require.NotPanics(t, func() { createDatabaseForTest("test_ptr") })
	dsn := "host=localhost user=postgres password=postgres dbname=test_ptr port=5432 sslmode=disable TimeZone=Asia/Shanghai"

	appModule := func() core.Module {
		return core.NewModule(core.NewModuleOptions{
			Imports: []core.Modules{
				sqlorm.ForRoot(sqlorm.Config{
					Dialect: postgres.Open(dsn),
					Models:  []interface{}{&P{}},
				}),
				sqlorm.ForFeature(sqlorm.NewRepo(P{})),
			},
		})
	}
	require.NotNil(t, sqlorm.InjectRepository[P](appModule()))
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9ac6d93 and 7195bdf.

📒 Files selected for processing (3)
  • .github/workflows/go.yml (0 hunks)
  • module.go (2 hunks)
  • module_test.go (1 hunks)
💤 Files with no reviewable changes (1)
  • .github/workflows/go.yml
🧰 Additional context used
🧬 Code graph analysis (1)
module_test.go (3)
module.go (4)
  • ForRoot (31-43)
  • Config (20-27)
  • ForFeature (90-113)
  • InjectRepository (70-88)
repository_test.go (2)
  • Abc (73-76)
  • Abc (78-80)
repository.go (1)
  • NewRepo (16-18)
🔇 Additional comments (2)
module.go (1)

6-6: Import looks correct.

reflect is used below for optional RepositoryName lookup.

module_test.go (1)

249-270: Good coverage for custom repository naming.

Nice sanity check that InjectRepository picks up RepositoryName().

Comment on lines +72 to +81

ctModel := reflect.ValueOf(&model).Elem()
fnc := ctModel.MethodByName("RepositoryName")
var name string
if fnc.IsValid() {
name = fnc.Call(nil)[0].String()
} else {
name = common.GetStructName(model)
}
modelName := core.Provide(GetRepoName(name))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Support pointer receivers and make reflection safe to avoid panics.

RepositoryName implemented with a pointer receiver (e.g., func (T* ) RepositoryName() string)) won’t be discovered, and fnc.Call(nil)[0] can panic if the signature differs. Handle both value/pointer receivers and validate the method signature before calling.

Apply:

-	
-	ctModel := reflect.ValueOf(&model).Elem()
-	fnc := ctModel.MethodByName("RepositoryName")
-	var name string
-	if fnc.IsValid() {
-		name = fnc.Call(nil)[0].String()
-	} else {
-		name = common.GetStructName(model)
-	}
-	modelName := core.Provide(GetRepoName(name))
+	var name string
+	ctModel := reflect.ValueOf(&model).Elem()
+	// Try value receiver first
+	if m := ctModel.MethodByName("RepositoryName"); m.IsValid() &&
+		m.Type().NumIn() == 0 && m.Type().NumOut() >= 1 && m.Type().Out(0).Kind() == reflect.String {
+		name = m.Call(nil)[0].String()
+	} else if m := ctModel.Addr().MethodByName("RepositoryName"); m.IsValid() &&
+		m.Type().NumIn() == 0 && m.Type().NumOut() >= 1 && m.Type().Out(0).Kind() == reflect.String {
+		name = m.Call(nil)[0].String()
+	} else {
+		name = common.GetStructName(model)
+	}
+	modelName := GetRepoName(name)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In module.go around lines 72 to 81, the reflection logic currently misses
methods with pointer receivers and can panic when calling methods with
unexpected signatures; update it to first obtain both the value and pointer
reflect.Values (ensure the value is addressable: v := reflect.ValueOf(model); if
v.Kind()==ptr use v.Elem(); pv := v; if !pv.CanAddr() {pv =
reflect.New(v.Type()) ; pv.Elem().Set(v)} then check for RepositoryName on pv
(pointer) and v (value) in that order), verify the method IsValid and that
method.Type().NumIn() == 0 and method.Type().NumOut() == 1 and
method.Type().Out(0).Kind() == reflect.String before calling, and only call when
those conditions hold; otherwise fall back to common.GetStructName(model) to
avoid panics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow Custom Name Provider By Method Inject Repository

2 participants