Kinship
Schema‑inferred relationship graphs for Ruby & Rails
Kinship discovers relationships between your models automatically by inspecting columns like user_id, project_id, etc. From that single fact, it builds a full graph of your domain — parents, children, families, paths — without has_many, belongs_to, or configuration.
It exists to solve a very real problem:
Rails knows your schema, but not your graph.
Kinship makes the graph explicit.
Why Kinship exists
Most Rails apps:
- Define associations manually
- Forget to preload
- Accidentally ship N+1 queries
- Duplicate schema knowledge in code
Example of a very common bad query:
User.all.each do |user|
user.projects.each do |project|
project.tasks.each do |task|
task.comments.each do |comment|
comment.reactions.each do |reaction|
reaction.kind
end
end
end
end
endRails allows this. It fails silently. Performance collapses.
Kinship’s philosophy:
If the database already encodes relationships, your application should be able to reason about them automatically.
What Kinship gives you
From nothing more than foreign keys, Kinship builds:
-
parents(model)– one layer up -
children(model)– one layer down -
families– connected components -
path(from, to)– shortest relationship path -
to_dot– visual graph output
No macros. No DSL. No annotations.
Installation
Ruby / Rails
gem install kinshipor in your Gemfile:
gem "kinship"Rails usage (recommended setup)
Create an initializer:
# config/initializers/kinship.rb
Rails.application.config.after_initialize do
Rails.application.eager_load!
KINSHIP = Kinship.build(
models: ApplicationRecord.descendants,
attribute_provider: ->(model) { model.column_names }
)
endThat’s it.
Kinship now understands your entire domain graph.
Example domain
Given these tables:
users-
projects(user_id) -
tasks(project_id) -
comments(task_id) -
reactions(comment_id)
No associations defined.
Core API
Parents
KINSHIP.parents(Project)
# => { user: User }Children
KINSHIP.children(Project)
# => { tasks: Task }Families
KINSHIP.families
# => [[User, Project, Task, Comment, Reaction]]Path discovery
KINSHIP.path(User, Reaction)
# => [User, Project, Task, Comment, Reaction]This is the missing abstraction Rails never gave you.
Graph visualization
puts KINSHIP.to_dotOutput:
digraph Kinship {
"User";
"Project";
"Task";
"Comment";
"Reaction";
"User" -> "Project";
"Project" -> "Task";
"Task" -> "Comment";
"Comment" -> "Reaction";
}You can render this with Graphviz to see your data model.
What Kinship intentionally does not do (yet)
- Execute SQL
- Replace ActiveRecord
- Magically rewrite queries
Instead, it provides the missing layer of understanding required to:
- detect N+1 queries
- generate preload plans
- reason about deep filters
- visualize data flow
Kinship is the map, avoiding wrong turns.
Framework integrations
Kinship is framework‑agnostic.
It can be embedded into:
- Rails (today)
- Jetski (in progress)
- Custom ORMs
- Data tooling
Once embedded, all downstream users benefit automatically.
Roadmap (vision)
- Declarative query intent
- Automatic preload inference
- N+1 detection hooks
- Query planning helpers
- Console inspection tools
Kinship is designed to cover the 80% of relationship problems that cause 95% of performance bugs.
Philosophy
Databases already encode relationships.
Kinship makes them visible, navigable, and reason‑able.
License
MIT
Built by Kurt Tamulonis