organizations 0.4.1 → 0.4.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a2f3a64510fcf3f08245894e49fbdd1a2aae8e76b9b9fb17fa2a6baa212c3e9
4
- data.tar.gz: 593fdc944621beb95f6bb318ade8ae88e4933a81f25bd273253704ab9f491f05
3
+ metadata.gz: 41b5d77cf4271b72a242ed018982a20a62de1aa65be8b44aeb0a1bdca6ca095f
4
+ data.tar.gz: 47da19b07cb78b00d63e3c682389d3b9cce80c10860883af947d25fb75ad4fa1
5
5
  SHA512:
6
- metadata.gz: 3c5327208fd86db81f8255bf95405736abc01e374e72a190041f31eb4ced7cff63bf2cb7b040d3a4dfc32fcdde7d11282ad6949c70a282eaed1454f318dc9908
7
- data.tar.gz: 7537c69f044a08a8e8422549b4b9272768c412ef3c13a97600808bb8b1a5d1102479c7d9c5c5d61e062ecec84be92132a687405f751033fdfc58f29fc2ca0227
6
+ metadata.gz: 21cd1f525064c45d471ec8a8e271ddb524aa21843130f2124c0e1b2f903aed5edfb8cbe0a3376f1d699983191eabfdada1304b391244ed65f5e1f7292420d693
7
+ data.tar.gz: f8b6512de051110d180d894a3e23c49f6b16f50b7bb81f0ba8f4d79257f79063ee345f23332d1d923bc3baff0f7feeac56f62f726594a4750a6b7b6522a57acc
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## [0.4.3] - 2026-03-19
2
+
3
+ - Added `can_view_billing?` and `can_manage_billing?` view helpers for billing permission checks
4
+ - Refactored `can_manage_organization?` and `can_invite_members?` to use shared permission predicate
5
+ - Fixed `pricing_plans` integration examples to use `current_pricing_plan` (effective plan API)
6
+ - Clarified that billing permissions are authorization checks only, not subscription state indicators
7
+
8
+ ## [0.4.2] - 2026-03-19
9
+
10
+ - Added `should_create_personal_organization?` predicate as extension seam for conditional personal org creation
11
+ - DSL `create_personal_organization` setting now accepts procs for dynamic evaluation
12
+ - Added `DELETE /memberships/leave` route for users to leave organizations
13
+ - Updated owner deletion guard message to clarify transfer/delete solution
14
+ - Documentation: added "Pattern 4: Hybrid Onboarding" to README
15
+
1
16
  ## [0.4.1] - 2026-03-17
2
17
 
3
18
  - Fixed `memberships_count` counter cache writes on `Organizations::Membership.create!`
data/README.md CHANGED
@@ -7,9 +7,9 @@
7
7
 
8
8
  `organizations` adds organizations with members to any Rails app. It handles team invites, user memberships, roles, and permissions.
9
9
 
10
- **🎮 [Try the live demo →](https://organizations.rameerez.com)**
10
+ <img src="docs/organizations-invitation-accept-create-account.webp" width="500" />
11
11
 
12
- [TODO: invitation / member management gif]
12
+ **🎮 [Try the live demo ](https://organizations.rameerez.com)**
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
 
@@ -41,6 +41,8 @@ current_user.is_organization_owner? # => true
41
41
  current_user.is_organization_admin? # => true (owners inherit admin permissions)
42
42
  ```
43
43
 
44
+ https://github.com/user-attachments/assets/2eddafe2-025b-4670-af9f-e0d5480508c5
45
+
44
46
  ## Installation
45
47
 
46
48
  Add to your Gemfile:
@@ -74,13 +76,13 @@ That's the simplest setup. You can also configure per-model options:
74
76
  class User < ApplicationRecord
75
77
  has_organizations do
76
78
  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)
79
+ create_personal_org true # Auto-create org on signup (default: false). Can also be a proc.
78
80
  require_organization true # Require users to have at least one org (default: false)
79
81
  end
80
82
  end
81
83
  ```
82
84
 
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.
85
+ > **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
86
 
85
87
  Mount the engine in your routes:
86
88
 
@@ -108,6 +110,8 @@ org = current_user.create_organization!("Acme Corp")
108
110
 
109
111
  ### Invite teammates
110
112
 
113
+ <img src="docs/organizations-team-members.webp" width="500" />
114
+
111
115
  ```ruby
112
116
  current_user.send_organization_invite_to!("teammate@example.com")
113
117
  # Sends invitation email: "John invited you to join Acme Corp"
@@ -122,6 +126,8 @@ current_user.send_organization_invite_to!("teammate@example.com", organization:
122
126
 
123
127
  ### Check roles and permissions
124
128
 
129
+ <img src="docs/organizations-member-permissions.webp" width="500" />
130
+
125
131
  ```ruby
126
132
  # Quick role checks (in current organization)
127
133
  current_user.is_organization_owner? # => true/false
@@ -141,6 +147,8 @@ current_user.is_member_of?(@org)
141
147
 
142
148
  ### Switch between organizations
143
149
 
150
+ <img src="docs/organizations-switcher.webp" width="400" />
151
+
144
152
  ```ruby
145
153
  # User belongs to multiple organizations? No problem.
146
154
  current_user.organizations # => [acme, startup_co, personal]
@@ -160,7 +168,7 @@ end
160
168
 
161
169
  > **Note:** This is an integration pattern, not built-in functionality. You implement the limit checks in your callbacks.
162
170
 
163
- If you're using [`pricing_plans`](https://github.com/rameerez/pricing_plans), you can limit how many members an organization can have based on their subscription using callbacks:
171
+ If you're using [`pricing_plans`](https://github.com/rameerez/pricing_plans), you can limit how many members an organization can have based on their effective pricing plan using callbacks:
164
172
 
165
173
  ```ruby
166
174
  # config/initializers/pricing_plans.rb
@@ -180,7 +188,7 @@ Then hook into the `on_member_invited` callback to enforce limits. **This callba
180
188
  Organizations.configure do |config|
181
189
  config.on_member_invited do |ctx|
182
190
  org = ctx.organization
183
- limit = org.current_plan.limit_for(:organization_members)
191
+ limit = org.current_pricing_plan.limit_for(:organization_members)
184
192
 
185
193
  if limit && org.member_count >= limit
186
194
  raise Organizations::InvitationError, "Member limit reached. Please upgrade your plan."
@@ -428,6 +436,8 @@ class SettingsController < ApplicationController
428
436
  end
429
437
  ```
430
438
 
439
+ `manage_billing` and `view_billing` are authorization checks only. They control who in the organization can access your billing UI, but they do not imply an active Stripe subscription or determine the effective pricing plan.
440
+
431
441
  ### Handling unauthorized access
432
442
 
433
443
  Configure how unauthorized access is handled:
@@ -652,6 +662,8 @@ org.send_invite_to!("teammate@example.com", invited_by: current_user)
652
662
 
653
663
  The gem handles **both existing users and new signups** with a single invitation link:
654
664
 
665
+ <img src="docs/organizations-invitation-accept-create-account.webp" width="500" />
666
+
655
667
  **For existing users:**
656
668
  1. Invitation created → Email sent with unique link
657
669
  2. User clicks link → Sees invitation details (org name, inviter, role)
@@ -888,6 +900,165 @@ Organizations.configure do |config|
888
900
  end
889
901
  ```
890
902
 
903
+ ## User Onboarding Patterns
904
+
905
+ 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.
906
+
907
+ ### Pattern 1: Instant Access (auto-create)
908
+
909
+ **Think:** Notion, Slack, Trello — "Sign up and start using it in 10 seconds"
910
+
911
+ ```ruby
912
+ config.always_create_personal_organization_for_each_user = true
913
+ config.default_organization_name = "My Workspace"
914
+ ```
915
+
916
+ ```
917
+ ┌─────────────────────────────────────────────────────────────┐
918
+ │ User signs up → "My Workspace" created → Dashboard 🎉 │
919
+ └─────────────────────────────────────────────────────────────┘
920
+ ```
921
+
922
+ Users land in the app immediately. Zero friction. They can invite teammates later.
923
+
924
+ ```ruby
925
+ # config/initializers/organizations.rb
926
+ Organizations.configure do |config|
927
+ config.always_create_personal_organization_for_each_user = true
928
+ config.default_organization_name = "My Workspace"
929
+ # Or personalize it:
930
+ # config.default_organization_name = ->(user) { "#{user.email.split('@').first}'s Workspace" }
931
+ end
932
+ ```
933
+
934
+ Best for: productivity tools, note apps, personal SaaS, anything where "just let me in" matters.
935
+
936
+ ### Pattern 2: Guided Onboarding (manual create)
937
+
938
+ **Think:** Stripe, HubSpot, Salesforce — "Tell us about your company first"
939
+
940
+ ```ruby
941
+ config.always_create_personal_organization_for_each_user = false
942
+ config.redirect_path_when_no_organization = "/onboarding/create_organization"
943
+ ```
944
+
945
+ ```
946
+ ┌──────────────────────────────────────────────────────────────────────────┐
947
+ │ User signs up → Onboarding wizard → "Create your company" → App │
948
+ │ (collect company name, billing email, etc.) │
949
+ └──────────────────────────────────────────────────────────────────────────┘
950
+ ```
951
+
952
+ You control the experience. Collect whatever info you need before they enter.
953
+
954
+ ```ruby
955
+ # config/initializers/organizations.rb
956
+ Organizations.configure do |config|
957
+ config.always_create_personal_organization_for_each_user = false
958
+ config.redirect_path_when_no_organization = "/onboarding/create_organization"
959
+ config.no_organization_notice = "Let's set up your company first."
960
+ config.additional_organization_params = [:billing_email, :company_size, :industry]
961
+ end
962
+ ```
963
+
964
+ ```ruby
965
+ # app/controllers/onboarding_controller.rb
966
+ def create_organization
967
+ @organization = current_user.create_organization!(
968
+ name: params[:company_name],
969
+ billing_email: params[:billing_email]
970
+ )
971
+ redirect_to dashboard_path
972
+ end
973
+ ```
974
+
975
+ Best for: B2B SaaS, enterprise tools, apps that need company details for billing/compliance.
976
+
977
+ ### Pattern 3: Invitation-Only
978
+
979
+ **Think:** Internal company tools, private beta, enterprise deployments
980
+
981
+ ```ruby
982
+ config.always_create_personal_organization_for_each_user = false
983
+ config.redirect_path_when_no_organization = "/waiting_room"
984
+ ```
985
+
986
+ ```
987
+ ┌───────────────────────────────────────────────────────────────────────┐
988
+ │ User signs up → "Waiting for invitation" page → (nothing yet) │
989
+ │ │
990
+ │ Admin invites user → User accepts → Joins org → Dashboard 🎉 │
991
+ └───────────────────────────────────────────────────────────────────────┘
992
+ ```
993
+
994
+ Users can't do anything until an admin invites them. Full control over who gets in.
995
+
996
+ Best for: internal tools, private beta programs, enterprise B2B where orgs are pre-provisioned.
997
+
998
+ ### Pattern 4: Hybrid Onboarding
999
+
1000
+ **Think:** Best of both worlds — instant access for direct signups, no clutter for invited users
1001
+
1002
+ 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.
1003
+
1004
+ ```
1005
+ ┌──────────────────────────────────────────────────────────────────────────────┐
1006
+ │ Direct signup → "My Workspace" auto-created → Dashboard 🎉 │
1007
+ │ │
1008
+ │ Invited signup → No personal org → Joins invited org → Dashboard 🎉 │
1009
+ └──────────────────────────────────────────────────────────────────────────────┘
1010
+ ```
1011
+
1012
+ Implementation requires overriding the `should_create_personal_organization?` method on your User model:
1013
+
1014
+ ```ruby
1015
+ # app/models/user.rb
1016
+ class User < ApplicationRecord
1017
+ has_organizations
1018
+
1019
+ # Skip personal org creation for users signing up via invitation
1020
+ attr_accessor :skip_personal_organization
1021
+
1022
+ def should_create_personal_organization?
1023
+ return false if skip_personal_organization
1024
+ super
1025
+ end
1026
+ end
1027
+ ```
1028
+
1029
+ Then set the flag before the user is persisted. With Devise, override `build_resource`:
1030
+
1031
+ ```ruby
1032
+ # app/controllers/users/registrations_controller.rb
1033
+ class Users::RegistrationsController < Devise::RegistrationsController
1034
+ protected
1035
+
1036
+ def build_resource(hash = {})
1037
+ super
1038
+ # Skip personal org for users signing up via invitation
1039
+ resource.skip_personal_organization = true if pending_organization_invitation?
1040
+ end
1041
+ end
1042
+ ```
1043
+
1044
+ The `should_create_personal_organization?` method is the official extension seam. It evaluates:
1045
+ 1. Your method override (if defined)
1046
+ 2. The `create_personal_org` setting (boolean or proc)
1047
+ 3. The global `always_create_personal_organization_for_each_user` config
1048
+
1049
+ You can also use a proc for the setting itself:
1050
+
1051
+ ```ruby
1052
+ # In has_organizations block
1053
+ has_organizations do
1054
+ create_personal_org ->(user) { !user.skip_personal_organization }
1055
+ end
1056
+ ```
1057
+
1058
+ Best for: SaaS products that want instant onboarding for individual users but clean team onboarding for invited members.
1059
+
1060
+ ---
1061
+
891
1062
  ## Configuration
892
1063
 
893
1064
  Full configuration options:
@@ -1048,7 +1219,7 @@ end
1048
1219
 
1049
1220
  ### Integrates with pricing_plans
1050
1221
 
1051
- Enforce member limits based on pricing plans using callbacks:
1222
+ Enforce member limits based on the effective pricing plan using callbacks:
1052
1223
 
1053
1224
  ```ruby
1054
1225
  # In your Organization model
@@ -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
@@ -15,6 +15,9 @@ Organizations::Engine.routes.draw do
15
15
  member do
16
16
  post :transfer_ownership
17
17
  end
18
+ collection do
19
+ delete :leave
20
+ end
18
21
  end
19
22
 
20
23
  # Invitation management (scoped to current_organization)
data/context7.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "url": "https://context7.com/rameerez/organizations",
3
+ "public_key": "pk_HibNJE5rTFvy1txHHXUot"
4
+ }
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 after the user (e.g., "John's Organization").
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
- # Create personal organization on user signup
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
- after_create :create_personal_organization_if_configured, if: -> {
115
- self.class.organization_settings[:create_personal_org]
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Organizations
4
- VERSION = "0.4.1"
4
+ VERSION = "0.4.3"
5
5
  end
@@ -213,10 +213,7 @@ module Organizations
213
213
  # @param organization [Organizations::Organization] The organization
214
214
  # @return [Boolean]
215
215
  def can_manage_organization?(user, organization)
216
- return false unless user && organization
217
-
218
- role = user.role_in(organization)
219
- role && Roles.has_permission?(role, :manage_settings)
216
+ user_has_permission_in_org?(user, organization, :manage_settings)
220
217
  end
221
218
 
222
219
  # Check if current user can invite members
@@ -225,10 +222,25 @@ module Organizations
225
222
  # @param organization [Organizations::Organization] The organization
226
223
  # @return [Boolean]
227
224
  def can_invite_members?(user, organization)
228
- return false unless user && organization
225
+ user_has_permission_in_org?(user, organization, :invite_members)
226
+ end
229
227
 
230
- role = user.role_in(organization)
231
- role && Roles.has_permission?(role, :invite_members)
228
+ # Check if current user can view billing information
229
+ # Uses permission-based check to respect custom role configurations
230
+ # @param user [User] The user
231
+ # @param organization [Organizations::Organization] The organization
232
+ # @return [Boolean]
233
+ def can_view_billing?(user, organization)
234
+ user_has_permission_in_org?(user, organization, :view_billing)
235
+ end
236
+
237
+ # Check if current user can manage billing
238
+ # Uses permission-based check to respect custom role configurations
239
+ # @param user [User] The user
240
+ # @param organization [Organizations::Organization] The organization
241
+ # @return [Boolean]
242
+ def can_manage_billing?(user, organization)
243
+ user_has_permission_in_org?(user, organization, :manage_billing)
232
244
  end
233
245
 
234
246
  # Check if current user can remove a member
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.1
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - rameerez
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-03-17 00:00:00.000000000 Z
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