Skip to content

Gem Transfers #5758

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jul 3, 2025
Merged

Gem Transfers #5758

merged 16 commits into from
Jul 3, 2025

Conversation

colby-swandale
Copy link
Member

@colby-swandale colby-swandale commented Jun 23, 2025

This Pull Request is the last remaining feature needed to begin private beta testing of Organizations in rubygems.org! This Pull Request introduces a system for a Rubygem to transition from being owned by a set of Users to being owned by an Organization.

Gem transfers work very similarly to Organization Onboardings in that they take a user through a multi-step process that asks for information like:

  • What Organization will the gem move to
  • What roles should be assigned to each of the gem owners?
  • Asking the User to confirm everything entered and perform the migration

Breaking Change: OrganizationOnboardingInvite

Back in #5201, I introduced a table/model called OrganizationOnboardingInvite, which captured the User and associated role for the Organization Onboarding system. This model works great, and I've reused the same functionality here.

To reduce duplication, I'm deprecating and deleting (the empty) the OrganizationOnboardingInvite model and moving everything to OrganizationInduction that will work for both Organization Onboardings & Gem Transfers.

Screenshots

User Management

Screenshot 2025-06-23 at 11 11 42 pm

Select an Organization

Screenshot 2025-06-23 at 11 11 35 pm

Gem Transfer Button/Link

Screenshot 2025-06-23 at 11 11 32 pm

Confirmation Screen

Screenshot 2025-06-23 at 11 11 44 pm

Copy link

codecov bot commented Jun 23, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.18%. Comparing base (5979c67) to head (296edf0).
Report is 11 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5758      +/-   ##
==========================================
+ Coverage   97.15%   97.18%   +0.03%     
==========================================
  Files         460      469       +9     
  Lines        9559     9676     +117     
==========================================
+ Hits         9287     9404     +117     
  Misses        272      272              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member

@landongrindheim landongrindheim left a comment

Choose a reason for hiding this comment

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

Leaving comments which I'll return to as I continue this review.

Comment on lines 4 to 7
t.string :status, null: false, default: "pending"
t.references :organization, null: true, foreign_key: { to_table: :organizations }
t.references :created_by, null: false, foreign_key: { to_table: :users }
t.references :rubygem, null: false, foreign_key: { to_table: :rubygems }
Copy link
Member

Choose a reason for hiding this comment

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

🤔 My understanding of a transfer is that it's updating ownership from one entity to another. In that model, I might be using a from_organization and a to_organization. This data could be used for auditing. What do you think of capturing this information? Does it fit our needs?

Comment on lines +1 to +2
<% @title = "Create an Organization" %>

Copy link
Member

Choose a reason for hiding this comment

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

🔎 There are a few instances of @title being set in views. What do you think of moving assignment to the controller?

self.invites = users_for_rubygem.map { |user| existing_invites[user.id] || OrganizationInduction.new(user: user) }
end

def rubygem_ownership
Copy link
Member

Choose a reason for hiding this comment

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

💅 What do you think of #authorized_to_give??

Copy link
Member Author

@colby-swandale colby-swandale Jul 1, 2025

Choose a reason for hiding this comment

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

The purpose of the rubygem_ownership is a bit different from what you would expect a predicate method to do. Maybe we can move the policy check into it's own method, but that feels unnecessary right now?

errors.add(:rubygem, "does not have permission to transfer this gem")
end

def organization_ownership
Copy link
Member

Choose a reason for hiding this comment

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

💅 What do you think of #authorized_to_receive??

Copy link
Member Author

Choose a reason for hiding this comment

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

Same thought as above

Copy link
Member

@landongrindheim landongrindheim left a comment

Choose a reason for hiding this comment

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

I've been thinking about this a bunch and wanted to propose a model which I think might cover the needs we have and extend beyond them nicely. There are still a couple of cases that would need to be ironed out, but I think they're within reach.

Ownership Model

  • A Rubygem is owned by a User OR an Organization
  • An Organization has many members (Users)
    • These members have access based on their roles in the org
  • A Rubygem optionally has contributors (via Contributorships)
    • contributors have a default set of permissions, though they could be extended
    • The owner of the Rubygem would manage contributors

Assumptions

  • If a Rubygem is owned by a User, the user has all permissions (admin, owner, etc)
  • Permissions in an organization are dependent on the role within the organization
  • Permissions for contributors are limited and do not (?) overlap with organization membership permissions

Further work

  • If a contributor joins an organization:
    • Leave contributorship as is? (cascade permissions, so we never have to worry about hitting this)
    • Soft-delete contributorship?
  • If a member leaves an organization:
    • Organization owner determines whether to retain contributorship
erDiagram
    User {
        int id
        string name
        string email
    }
    
    Organization {
        int id
        string name
        string description
    }
    
    Rubygem {
        int id
        string name
        string version
        int owner_id
        string owner_type
    }
    
    Membership {
        int user_id
        int organization_id
        string role
        datetime joined_at
    }
    
    Contributorship {
        int user_id
        int rubygem_id
        string permission_level
        datetime granted_at
    }
    
    %% Core ownership relationships
    User ||--o{ Rubygem : "owns (individual)"
    Organization ||--o{ Rubygem : "owns (organizational)"
    
    %% Organization structure
    User ||--o{ Membership : "belongs to"
    Organization ||--o{ Membership : "has members"
    
    %% Gem contributorships (outside contributors only)
    User ||--o{ Contributorship : "contributes to"
    Rubygem ||--o{ Contributorship : "has contributors"
    
    %% Notes on roles and permissions
    %% Membership.role: owner, admin, member
    %% Contributorship.permission_level: maintainer, contributor
    %% Rubygem.owner_type: User or Organization
Loading


before_save :sync_invites, if: :organization_changed?

def transfer!
Copy link
Member

Choose a reason for hiding this comment

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

🤔 I wonder about #finalize! or #complete! as a name here. Since the transfer process is multi-step, the name seems incomplete (and potentially redundant with RubygemTransfer being the class). What do you think?

Copy link
Member Author

@colby-swandale colby-swandale Jul 1, 2025

Choose a reason for hiding this comment

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

transfer! to me communicates the intent of what this big method does well. You don't have to dive into the 50 lines of code to figure out what it's doing.

@simi
Copy link
Member

simi commented Jun 30, 2025

@landongrindheim ad the Ownership Model, can't we make it simpler and make implicit organization per user so gem is always owned by organziation? This will make later planned changes like namespacing easier to introduce, since there will be only one way to own gem.

A Rubygem optionally has contributors (via Contributorships)

I don't think that's good name. Contributors is term used for users contributing to the codebase (like on GitHub) not related to release process. I think "maintainer" or "release manager" would be better name.

@landongrindheim
Copy link
Member

@landongrindheim ad the Ownership Model, can't we make it simpler and make implicit organization per user so gem is always owned by organziation? This will make later planned changes like namespacing easier to introduce, since there will be only one way to own gem.

@simi This was my initial thought. Something like community could be a default organization, which could become the new home of ownerships. Migrating data will be necessary to achieve this, so it's inherently a non-trivial task.

A Rubygem optionally has contributors (via Contributorships)

I don't think that's good name. Contributors is term used for users contributing to the codebase (like on GitHub) not related to release process. I think "maintainer" or "release manager" would be better name.

I agree the name is lacking. My assumption is that organizations will typically map to businesses or similar, and that there will be a need for including someone who doesn't belong to the business and shouldn't have the ability to take actions for all the organization's resources. I'm trying to capture that role. Is that a good fit for "maintainer" or "release manager"? If not, do you know of another term?

@simi
Copy link
Member

simi commented Jun 30, 2025

This was my initial thought. Something like community could be a default organization, which could become the new home of ownerships. Migrating data will be necessary to achieve this, so it's inherently a non-trivial task.

@landongrindheim I think we can just make implicit organization for everyone named same as profile name (handle). We can also make implicit organization per gem 🤔.

The original problem was related to fact, you were able to only add owner to gem and owner had all permission, so in theory the new owner was able to remove you as original owner and fully take over the gem. Usually gem owners were looking for someone to help (be able to release new versions) without risk of losing control over the gem. That's how Maintainer role was born (#4883).

The second problem was you need to do this gem by gem. Some projects do often maintain big amount of gems (like here, each folder is separate gem https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/75bab36aa8901b40bf2bc5017581302d581ed0ea/instrumentation) and it is hard to maintain permissions in bulk. That's what Organizations should fix.

And the final part is to ensure it all works together. In my thinking the organization is more like entity owning group of gems + their maintainers and owners under one umbrella. OpenTelemetry Ruby projects is hosting 2 kind of gems (per my understanding), core gems and contrib gems. I assume they will create 2 organizations for this on RubyGems.org to be able to add different maintainers per each type.

@colby-swandale colby-swandale marked this pull request as ready for review July 1, 2025 07:02
Copy link
Member

@landongrindheim landongrindheim left a comment

Choose a reason for hiding this comment

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

I added some tests to satisfy the CodeCov build. At the least I don't think they hurt. I think it's in a good place to move forward.

@colby-swandale colby-swandale merged commit 23fa363 into master Jul 3, 2025
20 of 21 checks passed
@colby-swandale colby-swandale deleted the colby/gem-transfers branch July 3, 2025 03:14
@github-project-automation github-project-automation bot moved this from Ready to Merged in RubyGems.org Pull Requests Jul 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

Successfully merging this pull request may close these issues.

3 participants