Skip to content

Commit

Permalink
Add ability to remove memberships.
Browse files Browse the repository at this point in the history
0160, 0163
0207 test delete when works
0211 test delete when fails

Now that :error is a normal flash type, we can use the shorthand version.

Also, since the error message will give addition optional reasons, we use a regex match to assert the error message.

0226

Allow user to leave a microcosm.

0224, 0225
  • Loading branch information
openbrian committed Apr 12, 2023
1 parent d52b32d commit cc31f21
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 9 deletions.
2 changes: 1 addition & 1 deletion app/abilities/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def initialize(user)
can [:edit, :update], Community, user_is_community_organizer
can [:edit, :create, :destroy, :new, :update], CommunityLink, { :community => user_is_community_organizer }
can [:create, :destroy], CommunityMember, { :user_id => user.id }
can [:edit, :update], CommunityMember, { :community => user_is_community_organizer }
can [:destroy, :edit, :update], CommunityMember, { :community => user_is_community_organizer }

if user.moderator?
can [:hide, :hidecomment], DiaryEntry
Expand Down
14 changes: 13 additions & 1 deletion app/controllers/community_members_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class CommunityMembersController < ApplicationController
layout "site"
before_action :authorize_web
before_action :set_community_member, :only => [:edit, :update]
before_action :set_community_member, :only => [:destroy, :edit, :update]
load_and_authorize_resource :except => [:create, :new]
authorize_resource

Expand Down Expand Up @@ -40,6 +40,18 @@ def update
end
end

def destroy
issues = @community_member.can_be_deleted
if issues.empty? && @community_member.destroy
redirect_to @community_member.community, :notice => t(".success")
else
issues = issues.map { |i| t("activerecord.errors.models.community_member.#{i}") }
issues = issues.to_sentence.capitalize
flash[:error] = "#{t('.failure')} #{issues}."
redirect_to community_community_members_path(@community_member.community)
end
end

private

def set_community_member
Expand Down
2 changes: 1 addition & 1 deletion app/models/community.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class Community < ApplicationRecord
friendly_id :name, :use => :slugged

# Organizers before members, a tad hacky, but works for now.
has_many :community_members, -> { order(:role => :desc) }, :inverse_of => :community
has_many :community_members, -> { order(:user_id) }, :inverse_of => :community
has_many :users, :through => :community_members # TODO: counter_cache

belongs_to :leader, :class_name => "User"
Expand Down
12 changes: 12 additions & 0 deletions app/models/community_member.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,21 @@ module Roles
belongs_to :community
belongs_to :user

scope :organizers, -> { where(:role => Roles::ORGANIZER) }
scope :members, -> { where(:role => Roles::MEMBER) }

validates :community, :associated => true
validates :user, :associated => true
validates :role, :inclusion => { :in => Roles::ALL_ROLES }

# TODO: validate uniqueness of user's role in each community.

# We assume this user already belongs to this community.
def can_be_deleted
issues = []
# The user may also be an organizer under a separate membership.
issues.append(:is_organizer) if CommunityMember.exists?(:community_id => community_id, :user_id => user_id, :role => Roles::ORGANIZER)

issues
end
end
16 changes: 11 additions & 5 deletions app/views/communities/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,17 @@
<% end %>
</div>
<div>
<% if current_user && ! @community.member?(current_user) %>
<%= bootstrap_form_for @current_user_membership do |form| %>
<%= form.hidden_field(:community_id) %>
<%= form.hidden_field(:user_id) %>
<%= form.primary t(".join.action") %>
<% if current_user %>
<% if ! @community.member?(current_user) %>
<%= bootstrap_form_for @current_user_membership do |form| %>
<%= form.hidden_field(:community_id) %>
<%= form.hidden_field(:user_id) %>
<%= form.primary t(".join.action") %>
<% end %>
<% else %>
<%= bootstrap_form_for @current_user_membership, :method => :delete do |form| %>
<%= form.primary t(".leave.action"), :data => { :confirm => t(".leave.confirm", :name => @community.name) } %>
<% end %>
<% end %>
<% else %>
<div>
Expand Down
5 changes: 5 additions & 0 deletions app/views/community_members/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
<%= form.primary t(".edit") %>
<% end %>
</div>
<div class="col-1">
<%= bootstrap_form_for membership, :method => :delete, :url => community_member_path(membership) do |form| %>
<%= form.submit t(".remove") %>
<% end %>
</div>
<% end %>
</div>
</li>
Expand Down
11 changes: 11 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,15 @@ en:
invalid_email_address: does not appear to be a valid e-mail address
email_address_not_routable: is not routable
url: "is not a valid secure URL"
models:
community_member:
is_organizer: "you are an organizer of this community"
# Translates all the model names, which is used in error handling on the website
models:
acl: "Access Control List"
changeset: "Changeset"
changeset_tag: "Changeset Tag"
community: "Community"
country: "Country"
diary_comment: "Diary Comment"
diary_entry: "Diary Entry"
Expand Down Expand Up @@ -531,6 +535,9 @@ en:
join:
action: "Join"
confirm: "Join %{name}?"
leave:
action: "Leave"
confirm: "Are you sure you want to leave %{name}?"
links: "Links"
login_to_join: "Please login to join the community."
members: "Members"
Expand All @@ -550,13 +557,17 @@ en:
create:
failure: The community member could not be created.
success: Member was successfully created.
destroy:
failure: Member was not removed.
success: Member was removed.
edit:
edit_member: Editing Member
index:
edit: edit
member: Member
members: Members
organizers: Organizers
remove: remove
update:
failure: Community Member could not be updated.
success: Community Member was successfully updated.
Expand Down
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@
end
post "/communities/:id/step_up" => "communities#step_up", :as => :step_up, :id => /\d+/
resources :community_links, :only => [:destroy, :edit, :update]
resources :community_members, :only => [:create, :edit, :new, :update]
resources :community_members, :only => [:create, :destroy, :edit, :new, :update]
get "/community_members" => "community_members#create", :as => "login_to_join"

# errors
Expand Down
59 changes: 59 additions & 0 deletions test/controllers/community_members_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,63 @@ def test_update_when_save_fails
assert_response :success
assert_template "community_members/edit"
end

def test_delete_as_a_different_user
# arrange
cm = create(:community_member) # N.b. not an organizer
session_for(create(:user))

# act
delete community_member_url(cm), :xhr => true

# assert
follow_redirect!
assert_response :forbidden
end

def test_delete_when_destroy_works
# arrange
cm1 = create(:community_member, :organizer) # original
session_for(cm1.user)
cm2 = create(:community_member, :community => cm1.community)

# act
delete community_member_url(cm2), :xhr => true

# assert
cm1.reload
assert_not cm1.community.member?(cm2.user)
end

def test_delete_when_destroy_fails
# arrange
cm = create(:community_member, :organizer)
session_for(cm.user)
# Customize this instance so delete returns false.
def cm.destroy
false
end

controller_mock = CommunityMembersController.new
def controller_mock.set_community_member
@community_member = CommunityMember.new
end

def controller_mock.render(_partial, _msg)
# Can't do assert_equal here.
# assert_equal :edit, partial
end

# act
CommunityMembersController.stub :new, controller_mock do
CommunityMember.stub :new, cm do
assert_difference "CommunityMember.count", 0 do
delete community_member_url(cm), :xhr => true
end
end
end

# assert
assert_match(/#{I18n.t("community_members.destroy.failure")}/, flash[:error])
end
end

0 comments on commit cc31f21

Please sign in to comment.