organizations 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +170 -3
- data/app/controllers/organizations/memberships_controller.rb +25 -0
- data/config/routes.rb +3 -0
- data/context7.json +4 -0
- data/docs/organizations-invitation-accept-create-account.webp +0 -0
- data/docs/organizations-member-permissions.webp +0 -0
- data/docs/organizations-switcher.webp +0 -0
- data/docs/organizations-team-members.webp +0 -0
- data/lib/generators/organizations/install/templates/initializer.rb +23 -1
- data/lib/organizations/configuration.rb +33 -2
- data/lib/organizations/models/concerns/has_organizations.rb +47 -6
- data/lib/organizations/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 916bef6e3c027111a6da0cfa08cf015f2d872de614d55ab8978ce41e2a8ab8aa
|
|
4
|
+
data.tar.gz: 6d0fb70f4292c7a7635d16acde00f7b88cdbc9fdcc8d4268d76b65010bf16667
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 78deb6016d5e62f432c1949ee9b8f700ce370e75a682451ace35a6923eb208e8f83800312cb525631fc1ee150a832f2748a67ab05b7f714873ca4b09b4e67adc
|
|
7
|
+
data.tar.gz: 529a7721cf5eef70b856a6b6e357e6d8881caf5b9108f31651cc2be7d83ea4258bde159b9a39d787ea78d71ed2b80d61939e1e5576c575df9416719729582c2f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [0.4.2] - 2026-03-19
|
|
2
|
+
|
|
3
|
+
- Added `should_create_personal_organization?` predicate as extension seam for conditional personal org creation
|
|
4
|
+
- DSL `create_personal_organization` setting now accepts procs for dynamic evaluation
|
|
5
|
+
- Added `DELETE /memberships/leave` route for users to leave organizations
|
|
6
|
+
- Updated owner deletion guard message to clarify transfer/delete solution
|
|
7
|
+
- Documentation: added "Pattern 4: Hybrid Onboarding" to README
|
|
8
|
+
|
|
1
9
|
## [0.4.1] - 2026-03-17
|
|
2
10
|
|
|
3
11
|
- Fixed `memberships_count` counter cache writes on `Organizations::Membership.create!`
|
data/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
**🎮 [Try the live demo →](https://organizations.rameerez.com)**
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
https://github.com/user-attachments/assets/2eddafe2-025b-4670-af9f-e0d5480508c5
|
|
13
13
|
|
|
14
14
|
It's everything you need to turn a `User`-based app into a multi-tenant, `Organization`-based B2B SaaS (users belong in organizations, and organizations share resources and billing, etc.)
|
|
15
15
|
|
|
@@ -74,13 +74,13 @@ That's the simplest setup. You can also configure per-model options:
|
|
|
74
74
|
class User < ApplicationRecord
|
|
75
75
|
has_organizations do
|
|
76
76
|
max_organizations 5 # Limit how many orgs a user can own (nil = unlimited)
|
|
77
|
-
create_personal_org true # Auto-create org on signup (default: false)
|
|
77
|
+
create_personal_org true # Auto-create org on signup (default: false). Can also be a proc.
|
|
78
78
|
require_organization true # Require users to have at least one org (default: false)
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
> **Note:** By default, users can exist without any organization (invite-to-join flow). Set `create_personal_org true` if you want to auto-create a personal organization when users sign up.
|
|
83
|
+
> **Note:** By default, users can exist without any organization (invite-to-join flow). Set `create_personal_org true` if you want to auto-create a personal organization when users sign up. For conditional creation, use a proc: `create_personal_org ->(user) { ... }` or override `should_create_personal_organization?` on your User model (see Pattern 4: Hybrid Onboarding).
|
|
84
84
|
|
|
85
85
|
Mount the engine in your routes:
|
|
86
86
|
|
|
@@ -108,6 +108,8 @@ org = current_user.create_organization!("Acme Corp")
|
|
|
108
108
|
|
|
109
109
|
### Invite teammates
|
|
110
110
|
|
|
111
|
+
<img src="docs/organizations-team-members.webp" width="500" />
|
|
112
|
+
|
|
111
113
|
```ruby
|
|
112
114
|
current_user.send_organization_invite_to!("teammate@example.com")
|
|
113
115
|
# Sends invitation email: "John invited you to join Acme Corp"
|
|
@@ -122,6 +124,8 @@ current_user.send_organization_invite_to!("teammate@example.com", organization:
|
|
|
122
124
|
|
|
123
125
|
### Check roles and permissions
|
|
124
126
|
|
|
127
|
+
<img src="docs/organizations-member-permissions.webp" width="500" />
|
|
128
|
+
|
|
125
129
|
```ruby
|
|
126
130
|
# Quick role checks (in current organization)
|
|
127
131
|
current_user.is_organization_owner? # => true/false
|
|
@@ -141,6 +145,8 @@ current_user.is_member_of?(@org)
|
|
|
141
145
|
|
|
142
146
|
### Switch between organizations
|
|
143
147
|
|
|
148
|
+
<img src="docs/organizations-switcher.webp" width="400" />
|
|
149
|
+
|
|
144
150
|
```ruby
|
|
145
151
|
# User belongs to multiple organizations? No problem.
|
|
146
152
|
current_user.organizations # => [acme, startup_co, personal]
|
|
@@ -652,6 +658,8 @@ org.send_invite_to!("teammate@example.com", invited_by: current_user)
|
|
|
652
658
|
|
|
653
659
|
The gem handles **both existing users and new signups** with a single invitation link:
|
|
654
660
|
|
|
661
|
+
<img src="docs/organizations-invitation-accept-create-account.webp" width="500" />
|
|
662
|
+
|
|
655
663
|
**For existing users:**
|
|
656
664
|
1. Invitation created → Email sent with unique link
|
|
657
665
|
2. User clicks link → Sees invitation details (org name, inviter, role)
|
|
@@ -888,6 +896,165 @@ Organizations.configure do |config|
|
|
|
888
896
|
end
|
|
889
897
|
```
|
|
890
898
|
|
|
899
|
+
## User Onboarding Patterns
|
|
900
|
+
|
|
901
|
+
The `always_create_personal_organization_for_each_user` setting controls how new users get their first organization. This is one of the most important decisions when integrating the gem.
|
|
902
|
+
|
|
903
|
+
### Pattern 1: Instant Access (auto-create)
|
|
904
|
+
|
|
905
|
+
**Think:** Notion, Slack, Trello — "Sign up and start using it in 10 seconds"
|
|
906
|
+
|
|
907
|
+
```ruby
|
|
908
|
+
config.always_create_personal_organization_for_each_user = true
|
|
909
|
+
config.default_organization_name = "My Workspace"
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
```
|
|
913
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
914
|
+
│ User signs up → "My Workspace" created → Dashboard 🎉 │
|
|
915
|
+
└─────────────────────────────────────────────────────────────┘
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
Users land in the app immediately. Zero friction. They can invite teammates later.
|
|
919
|
+
|
|
920
|
+
```ruby
|
|
921
|
+
# config/initializers/organizations.rb
|
|
922
|
+
Organizations.configure do |config|
|
|
923
|
+
config.always_create_personal_organization_for_each_user = true
|
|
924
|
+
config.default_organization_name = "My Workspace"
|
|
925
|
+
# Or personalize it:
|
|
926
|
+
# config.default_organization_name = ->(user) { "#{user.email.split('@').first}'s Workspace" }
|
|
927
|
+
end
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
Best for: productivity tools, note apps, personal SaaS, anything where "just let me in" matters.
|
|
931
|
+
|
|
932
|
+
### Pattern 2: Guided Onboarding (manual create)
|
|
933
|
+
|
|
934
|
+
**Think:** Stripe, HubSpot, Salesforce — "Tell us about your company first"
|
|
935
|
+
|
|
936
|
+
```ruby
|
|
937
|
+
config.always_create_personal_organization_for_each_user = false
|
|
938
|
+
config.redirect_path_when_no_organization = "/onboarding/create_organization"
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
```
|
|
942
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
943
|
+
│ User signs up → Onboarding wizard → "Create your company" → App │
|
|
944
|
+
│ (collect company name, billing email, etc.) │
|
|
945
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
You control the experience. Collect whatever info you need before they enter.
|
|
949
|
+
|
|
950
|
+
```ruby
|
|
951
|
+
# config/initializers/organizations.rb
|
|
952
|
+
Organizations.configure do |config|
|
|
953
|
+
config.always_create_personal_organization_for_each_user = false
|
|
954
|
+
config.redirect_path_when_no_organization = "/onboarding/create_organization"
|
|
955
|
+
config.no_organization_notice = "Let's set up your company first."
|
|
956
|
+
config.additional_organization_params = [:billing_email, :company_size, :industry]
|
|
957
|
+
end
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
```ruby
|
|
961
|
+
# app/controllers/onboarding_controller.rb
|
|
962
|
+
def create_organization
|
|
963
|
+
@organization = current_user.create_organization!(
|
|
964
|
+
name: params[:company_name],
|
|
965
|
+
billing_email: params[:billing_email]
|
|
966
|
+
)
|
|
967
|
+
redirect_to dashboard_path
|
|
968
|
+
end
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
Best for: B2B SaaS, enterprise tools, apps that need company details for billing/compliance.
|
|
972
|
+
|
|
973
|
+
### Pattern 3: Invitation-Only
|
|
974
|
+
|
|
975
|
+
**Think:** Internal company tools, private beta, enterprise deployments
|
|
976
|
+
|
|
977
|
+
```ruby
|
|
978
|
+
config.always_create_personal_organization_for_each_user = false
|
|
979
|
+
config.redirect_path_when_no_organization = "/waiting_room"
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
```
|
|
983
|
+
┌───────────────────────────────────────────────────────────────────────┐
|
|
984
|
+
│ User signs up → "Waiting for invitation" page → (nothing yet) │
|
|
985
|
+
│ │
|
|
986
|
+
│ Admin invites user → User accepts → Joins org → Dashboard 🎉 │
|
|
987
|
+
└───────────────────────────────────────────────────────────────────────┘
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
Users can't do anything until an admin invites them. Full control over who gets in.
|
|
991
|
+
|
|
992
|
+
Best for: internal tools, private beta programs, enterprise B2B where orgs are pre-provisioned.
|
|
993
|
+
|
|
994
|
+
### Pattern 4: Hybrid Onboarding
|
|
995
|
+
|
|
996
|
+
**Think:** Best of both worlds — instant access for direct signups, no clutter for invited users
|
|
997
|
+
|
|
998
|
+
Direct signups get a personal workspace immediately. Invited users skip the personal workspace and join the organization that invited them directly. This avoids creating unnecessary "My Workspace" organizations for users who are joining an existing team.
|
|
999
|
+
|
|
1000
|
+
```
|
|
1001
|
+
┌──────────────────────────────────────────────────────────────────────────────┐
|
|
1002
|
+
│ Direct signup → "My Workspace" auto-created → Dashboard 🎉 │
|
|
1003
|
+
│ │
|
|
1004
|
+
│ Invited signup → No personal org → Joins invited org → Dashboard 🎉 │
|
|
1005
|
+
└──────────────────────────────────────────────────────────────────────────────┘
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
Implementation requires overriding the `should_create_personal_organization?` method on your User model:
|
|
1009
|
+
|
|
1010
|
+
```ruby
|
|
1011
|
+
# app/models/user.rb
|
|
1012
|
+
class User < ApplicationRecord
|
|
1013
|
+
has_organizations
|
|
1014
|
+
|
|
1015
|
+
# Skip personal org creation for users signing up via invitation
|
|
1016
|
+
attr_accessor :skip_personal_organization
|
|
1017
|
+
|
|
1018
|
+
def should_create_personal_organization?
|
|
1019
|
+
return false if skip_personal_organization
|
|
1020
|
+
super
|
|
1021
|
+
end
|
|
1022
|
+
end
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
Then set the flag before the user is persisted. With Devise, override `build_resource`:
|
|
1026
|
+
|
|
1027
|
+
```ruby
|
|
1028
|
+
# app/controllers/users/registrations_controller.rb
|
|
1029
|
+
class Users::RegistrationsController < Devise::RegistrationsController
|
|
1030
|
+
protected
|
|
1031
|
+
|
|
1032
|
+
def build_resource(hash = {})
|
|
1033
|
+
super
|
|
1034
|
+
# Skip personal org for users signing up via invitation
|
|
1035
|
+
resource.skip_personal_organization = true if pending_organization_invitation?
|
|
1036
|
+
end
|
|
1037
|
+
end
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
The `should_create_personal_organization?` method is the official extension seam. It evaluates:
|
|
1041
|
+
1. Your method override (if defined)
|
|
1042
|
+
2. The `create_personal_org` setting (boolean or proc)
|
|
1043
|
+
3. The global `always_create_personal_organization_for_each_user` config
|
|
1044
|
+
|
|
1045
|
+
You can also use a proc for the setting itself:
|
|
1046
|
+
|
|
1047
|
+
```ruby
|
|
1048
|
+
# In has_organizations block
|
|
1049
|
+
has_organizations do
|
|
1050
|
+
create_personal_org ->(user) { !user.skip_personal_organization }
|
|
1051
|
+
end
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
Best for: SaaS products that want instant onboarding for individual users but clean team onboarding for invited members.
|
|
1055
|
+
|
|
1056
|
+
---
|
|
1057
|
+
|
|
891
1058
|
## Configuration
|
|
892
1059
|
|
|
893
1060
|
Full configuration options:
|
|
@@ -79,6 +79,31 @@ module Organizations
|
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
+
# DELETE /memberships/leave
|
|
83
|
+
# Leave the current organization (current user removes themselves)
|
|
84
|
+
# Note: No permission guard needed — users can only leave themselves, and
|
|
85
|
+
# require_organization! ensures valid org context. Domain rules (can't leave
|
|
86
|
+
# as last owner, etc.) are enforced by leave_organization!.
|
|
87
|
+
def leave
|
|
88
|
+
org_name = current_organization.name
|
|
89
|
+
|
|
90
|
+
begin
|
|
91
|
+
current_user.leave_organization!(current_organization)
|
|
92
|
+
|
|
93
|
+
respond_to do |format|
|
|
94
|
+
format.html { redirect_to organizations_path, notice: "You have left #{org_name}." }
|
|
95
|
+
format.json { head :no_content }
|
|
96
|
+
end
|
|
97
|
+
rescue Models::Concerns::HasOrganizations::CannotLeaveLastOrganization,
|
|
98
|
+
Models::Concerns::HasOrganizations::CannotLeaveAsLastOwner,
|
|
99
|
+
Error => e
|
|
100
|
+
respond_to do |format|
|
|
101
|
+
format.html { redirect_back fallback_location: organizations_path, alert: e.message }
|
|
102
|
+
format.json { render json: { error: e.message }, status: :unprocessable_entity }
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
82
107
|
# POST /memberships/:id/transfer_ownership
|
|
83
108
|
# Transfer organization ownership to another member
|
|
84
109
|
def transfer_ownership
|
data/config/routes.rb
CHANGED
data/context7.json
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -6,11 +6,33 @@ Organizations.configure do |config|
|
|
|
6
6
|
# ============================================================================
|
|
7
7
|
|
|
8
8
|
# Automatically create a personal organization when a user signs up.
|
|
9
|
-
# The organization will be named
|
|
9
|
+
# The organization will be named using default_organization_name (below).
|
|
10
10
|
# Set to true if you want every user to have their own organization on signup.
|
|
11
11
|
# Default: false (invite-to-join flow)
|
|
12
12
|
# config.always_create_personal_organization_for_each_user = false
|
|
13
13
|
|
|
14
|
+
# NOTE: This is a coarse, global default. For conditional creation (e.g., skip
|
|
15
|
+
# personal org for invited signups), override should_create_personal_organization?
|
|
16
|
+
# on your User model instead:
|
|
17
|
+
#
|
|
18
|
+
# class User < ApplicationRecord
|
|
19
|
+
# has_organizations
|
|
20
|
+
# attr_accessor :skip_personal_organization
|
|
21
|
+
#
|
|
22
|
+
# def should_create_personal_organization?
|
|
23
|
+
# return false if skip_personal_organization
|
|
24
|
+
# super
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# See "Pattern 4: Hybrid Onboarding" in the README for the full pattern.
|
|
29
|
+
|
|
30
|
+
# Name for auto-created organizations.
|
|
31
|
+
# Can be a string or a proc that receives the user.
|
|
32
|
+
# Default: "Personal"
|
|
33
|
+
# config.default_organization_name = "My Workspace"
|
|
34
|
+
# config.default_organization_name = ->(user) { "#{user.name}'s Workspace" }
|
|
35
|
+
|
|
14
36
|
# ============================================================================
|
|
15
37
|
# ORGANIZATION REQUIREMENTS
|
|
16
38
|
# ============================================================================
|
|
@@ -33,10 +33,41 @@ module Organizations
|
|
|
33
33
|
attr_accessor :authenticate_user_method
|
|
34
34
|
|
|
35
35
|
# === Auto-creation ===
|
|
36
|
-
#
|
|
36
|
+
#
|
|
37
|
+
# Create personal organization automatically on user signup.
|
|
38
|
+
#
|
|
39
|
+
# This setting controls the user's first-time experience:
|
|
40
|
+
#
|
|
41
|
+
# ┌─────────────────────────────────────────────────────────────────────────┐
|
|
42
|
+
# │ true → "Instant access" pattern │
|
|
43
|
+
# │ │
|
|
44
|
+
# │ User signs up → auto-created workspace → lands in app immediately │
|
|
45
|
+
# │ │
|
|
46
|
+
# │ Think: Notion, Slack, Trello │
|
|
47
|
+
# │ "Sign up and start using it in seconds" │
|
|
48
|
+
# │ │
|
|
49
|
+
# │ Best for: productivity tools, note apps, simple SaaS │
|
|
50
|
+
# └─────────────────────────────────────────────────────────────────────────┘
|
|
51
|
+
#
|
|
52
|
+
# ┌─────────────────────────────────────────────────────────────────────────┐
|
|
53
|
+
# │ false → "Guided onboarding" pattern │
|
|
54
|
+
# │ │
|
|
55
|
+
# │ User signs up → onboarding wizard → enters company info → dashboard │
|
|
56
|
+
# │ │
|
|
57
|
+
# │ Think: Stripe, HubSpot, enterprise B2B tools │
|
|
58
|
+
# │ "Tell us about your company before you start" │
|
|
59
|
+
# │ │
|
|
60
|
+
# │ Best for: B2B SaaS needing company details, billing info, etc. │
|
|
61
|
+
# └─────────────────────────────────────────────────────────────────────────┘
|
|
62
|
+
#
|
|
63
|
+
# Related settings:
|
|
64
|
+
# - default_organization_name: Name for auto-created orgs (only when true)
|
|
65
|
+
# - redirect_path_when_no_organization: Where to send users without an org
|
|
66
|
+
# - always_require_users_to_belong_to_one_organization: Prevent leaving last org
|
|
67
|
+
#
|
|
37
68
|
attr_accessor :always_create_personal_organization_for_each_user
|
|
38
69
|
|
|
39
|
-
# Name for auto-created organizations
|
|
70
|
+
# Name for auto-created organizations (only used when always_create_personal_organization_for_each_user = true)
|
|
40
71
|
# Can be a String or a Proc/Lambda: ->(user) { "#{user.name}'s Workspace" }
|
|
41
72
|
attr_accessor :default_organization_name
|
|
42
73
|
|
|
@@ -37,7 +37,8 @@ module Organizations
|
|
|
37
37
|
# Enable organization support on this model
|
|
38
38
|
# @param options [Hash] Configuration options
|
|
39
39
|
# @option options [Integer, nil] :max_organizations Maximum orgs user can own
|
|
40
|
-
# @option options [Boolean] :create_personal_org Create personal org on signup
|
|
40
|
+
# @option options [Boolean, Proc] :create_personal_org Create personal org on signup.
|
|
41
|
+
# Can be a boolean or a proc that receives the user instance for conditional creation.
|
|
41
42
|
# @option options [Boolean] :require_organization Require user to have an org
|
|
42
43
|
# @yield Configuration block using DSL
|
|
43
44
|
def has_organizations(**options, &block)
|
|
@@ -111,9 +112,10 @@ module Organizations
|
|
|
111
112
|
end
|
|
112
113
|
|
|
113
114
|
def setup_personal_org_callback
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
# Callback that creates personal organization.
|
|
116
|
+
# The condition is checked inside the callback to ensure it's evaluated
|
|
117
|
+
# at the right time with the correct instance state.
|
|
118
|
+
after_create :create_personal_organization_if_configured
|
|
117
119
|
end
|
|
118
120
|
|
|
119
121
|
def setup_owner_deletion_guard
|
|
@@ -136,7 +138,14 @@ module Organizations
|
|
|
136
138
|
end
|
|
137
139
|
|
|
138
140
|
# Enable/disable personal organization creation on signup
|
|
139
|
-
# @param value [Boolean]
|
|
141
|
+
# @param value [Boolean, Proc] Boolean or proc that receives the user instance
|
|
142
|
+
#
|
|
143
|
+
# @example Boolean (backward-compatible)
|
|
144
|
+
# create_personal_org true
|
|
145
|
+
#
|
|
146
|
+
# @example Proc for conditional creation
|
|
147
|
+
# create_personal_org ->(user) { !user.skip_personal_organization }
|
|
148
|
+
#
|
|
140
149
|
def create_personal_org(value)
|
|
141
150
|
@settings[:create_personal_org] = value
|
|
142
151
|
end
|
|
@@ -499,9 +508,41 @@ module Organizations
|
|
|
499
508
|
org.send_invite_to!(email, invited_by: self, role: role)
|
|
500
509
|
end
|
|
501
510
|
|
|
511
|
+
# Extension seam for personal organization creation.
|
|
512
|
+
# Override this method in your User model to implement custom logic.
|
|
513
|
+
#
|
|
514
|
+
# @return [Boolean] true if a personal organization should be created
|
|
515
|
+
#
|
|
516
|
+
# @example Skip personal org for invited signups
|
|
517
|
+
# class User < ApplicationRecord
|
|
518
|
+
# has_organizations
|
|
519
|
+
# attr_accessor :skip_personal_organization
|
|
520
|
+
#
|
|
521
|
+
# def should_create_personal_organization?
|
|
522
|
+
# return false if skip_personal_organization
|
|
523
|
+
# super
|
|
524
|
+
# end
|
|
525
|
+
# end
|
|
526
|
+
#
|
|
527
|
+
def should_create_personal_organization?
|
|
528
|
+
setting = self.class.organization_settings[:create_personal_org]
|
|
529
|
+
|
|
530
|
+
case setting
|
|
531
|
+
when Proc
|
|
532
|
+
setting.call(self)
|
|
533
|
+
else
|
|
534
|
+
!!setting
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
|
|
502
538
|
private
|
|
503
539
|
|
|
504
540
|
def create_personal_organization_if_configured
|
|
541
|
+
# Check condition inside callback (not via `if:` option) to ensure
|
|
542
|
+
# proper evaluation of instance state, including singleton methods
|
|
543
|
+
# and dynamically-set attributes.
|
|
544
|
+
return unless should_create_personal_organization?
|
|
545
|
+
|
|
505
546
|
org_name = Organizations.configuration.resolve_default_organization_name(self)
|
|
506
547
|
create_organization!(org_name)
|
|
507
548
|
rescue StandardError => e
|
|
@@ -512,7 +553,7 @@ module Organizations
|
|
|
512
553
|
def prevent_deletion_while_owning_organizations
|
|
513
554
|
return unless memberships.where(role: "owner").exists?
|
|
514
555
|
|
|
515
|
-
errors.add(:base, "Cannot delete a user who still owns organizations. Transfer ownership first.")
|
|
556
|
+
errors.add(:base, "Cannot delete a user who still owns organizations. Transfer ownership or delete those organizations first.")
|
|
516
557
|
throw(:abort)
|
|
517
558
|
end
|
|
518
559
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: organizations
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rameerez
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-03-
|
|
10
|
+
date: 2026-03-19 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: railties
|
|
@@ -103,6 +103,11 @@ files:
|
|
|
103
103
|
- app/views/organizations/invitation_mailer/invitation_email.html.erb
|
|
104
104
|
- app/views/organizations/invitation_mailer/invitation_email.text.erb
|
|
105
105
|
- config/routes.rb
|
|
106
|
+
- context7.json
|
|
107
|
+
- docs/organizations-invitation-accept-create-account.webp
|
|
108
|
+
- docs/organizations-member-permissions.webp
|
|
109
|
+
- docs/organizations-switcher.webp
|
|
110
|
+
- docs/organizations-team-members.webp
|
|
106
111
|
- gemfiles/rails_7.2.gemfile
|
|
107
112
|
- gemfiles/rails_8.1.gemfile
|
|
108
113
|
- lib/generators/organizations/install/install_generator.rb
|