Skip to content
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

Authorization framework with Action Policy #466

Merged
merged 119 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 113 commits
Commits
Show all changes
119 commits
Select commit Hold shift + click to select a range
a98459d
Add action_policy gem
mononoken Jan 24, 2024
8e38388
Apply verify_authorized to require authorization on all controllers e…
mononoken Jan 27, 2024
a0a1ad6
Skip authorization for static pages and root
mononoken Jan 27, 2024
f5f321f
Create AdoptablePetPolicy tests
mononoken Jan 31, 2024
a7b6773
Skip authorization for Organizations::HomeController#index
mononoken Jan 31, 2024
998adc7
Implement AdoptablePetPolicy
mononoken Jan 31, 2024
61bef2f
Fix typos in seed file
mononoken Feb 1, 2024
0054de3
Remove rolify to StaffAccount
mononoken Feb 1, 2024
dd0d25d
Add rolify Role back but paired with User
mononoken Feb 1, 2024
89cb0b9
Remove global authorization temporarily
mononoken Feb 2, 2024
d30ed86
Refactor app from StaffAccount roles to User roles and get existing t…
mononoken Feb 2, 2024
f253c9d
Create Authorizable concern
mononoken Feb 2, 2024
0389f65
Implement authorizable#permissions? on existing policies
mononoken Feb 2, 2024
ea5ced4
Allow user context for Policies by default to allow `nil` as a value …
mononoken Feb 3, 2024
c8df1cf
Create StaffPolicy
mononoken Feb 3, 2024
fbbe23c
Start StaffPolicy rules for activation
mononoken Feb 3, 2024
d1c9a1e
Add test context for when staff user is current_user
mononoken Feb 6, 2024
abfb0c4
Create controller tests for authorization performance
mononoken Feb 6, 2024
2b5cb17
Create default pre_check for verify_authenticated! in ApplicationPolicy
mononoken Feb 6, 2024
f525498
Implement checking organization scope in StaffPolicy with new Applica…
mononoken Feb 6, 2024
8362e49
Complete StaffPolicy for #index and #authorize related actions
mononoken Feb 6, 2024
6fdbee4
Refactor StaffController for new Action Policy authorization
mononoken Feb 6, 2024
cdaaf29
Delete old ActivateStaff tests
mononoken Feb 6, 2024
23fcaf2
Refactor adoptable_pets_controller_test to shoulda-context format
mononoken Feb 7, 2024
b874a16
Create Invitation authorization tests
mononoken Feb 7, 2024
35284d8
Refactor verify_organization! to use id comparison instead
mononoken Feb 7, 2024
8715c83
Create InvitationPolicy
mononoken Feb 7, 2024
257faef
Implement InvitationPolicy in InvitationsController
mononoken Feb 7, 2024
77ef396
Fix possible tenant leakage in test environment
mononoken Feb 8, 2024
027179a
Fix ActsAsTenant implementation in tests
mononoken Feb 8, 2024
14c2e12
Implement Organizations::PetPolicy for resourceful RESTful actions (s…
mononoken Feb 8, 2024
87b7a0d
Create Organizations::PetPolicy tests
mononoken Feb 9, 2024
b72a200
Implement Organizations::PetPolicy in controller
mononoken Feb 9, 2024
3c42de2
Refactor Organizations::StaffPolicy and Organizations::InvitationPoli…
mononoken Feb 9, 2024
b068a8f
Slim down Organizations::PetPolicyTest by introducing new alias asser…
mononoken Feb 9, 2024
a81db07
Move verify_active_staff! to application_policy
mononoken Feb 9, 2024
ac00352
Utilize alias_rule for Organizations::InvitationPolicy and Organizati…
mononoken Feb 9, 2024
764f5a1
Rename test context to better match the factory name and context
mononoken Feb 9, 2024
3ab0898
Sort authorization tests into separate context in InvitationsControll…
mononoken Feb 9, 2024
aec9b89
Remove unnecessary #set_organization calls
mononoken Feb 10, 2024
f158d24
Refactor Authorizable to privately expose the array of permissions so…
mononoken Feb 10, 2024
c00ebcb
Create TaskPolicy with tests
mononoken Feb 10, 2024
d7112ef
Create TasksController authorization tests
mononoken Feb 11, 2024
281ec95
Implement TaskPolicy in Organizations::TasksController
mononoken Feb 11, 2024
0b49040
Use ActionPolicy lookup ability to implicitly find correct Policies w…
mononoken Feb 11, 2024
ef8167c
Move AdoptablePetsController#index scoping to Policy
mononoken Feb 13, 2024
8bc1cad
Move permissions constants into private
mononoken Feb 13, 2024
d86f120
Merge branch 'authorization_framework'
mononoken Feb 13, 2024
f2ff48b
Add authorization to Organizations::OrganizationProfilesController
mononoken Feb 18, 2024
f41ea3f
Remove unused Organizations Dashboard routes
mononoken Feb 22, 2024
fb34c46
Implement Organizations::DashboardPolicy
mononoken Feb 22, 2024
66a7bfe
Add adopter role to User factory and apply User adopter trait to Adop…
mononoken Feb 23, 2024
85efb52
Add application_paused to Pet factory
mononoken Feb 23, 2024
d7324e5
Create AdopterApplicationPolicy
mononoken Feb 23, 2024
8752dd5
Refactor AdopterApplications routes to more standard Rails convention…
mononoken Feb 24, 2024
695cdd3
Refactor AdopterApplication factory to use existing adopter_account o…
mononoken Feb 25, 2024
f26e2cb
Refactor mailer so that it does not fail if no staff exists in the en…
mononoken Feb 25, 2024
87cecff
Add note to clarify the default scoping in ApplicationPolicy
mononoken Feb 25, 2024
c0e41c5
Implement AdopterApplicationPolicy in AdopterApplicationsController
mononoken Feb 25, 2024
efd10d4
Remove deprecated authorization method
mononoken Feb 25, 2024
4c7f024
Refactor AdoptablePetPolicy to check for pet match and application_pa…
mononoken Feb 25, 2024
5214147
Refactor role ordering in tests so that deactivated staff is before a…
mononoken Feb 25, 2024
a9f1060
Remove deprecated #active_staff call from DashboardController
mononoken Feb 25, 2024
bb3786c
Refactor #after_sign_in_path_for to use policy for logic check and fi…
mononoken Feb 25, 2024
0e058d0
Fix factory associations so that AdopterApplicationFactory populates …
mononoken Feb 26, 2024
ff0673a
Refactor UserFactory to use nested factories instead of traits
mononoken Feb 26, 2024
b63c43e
Remove conditional since transient property prevents else case from e…
mononoken Feb 26, 2024
fcbc3d0
Split factories.rb into separate factory files under factories/
mononoken Feb 26, 2024
cc22b7a
Remove unnecessary transient organization attribute from AdopterAccou…
mononoken Feb 26, 2024
3343d95
Remove misleading organization associations from factories
mononoken Feb 26, 2024
1113ce4
Create AdopterProfilePolicy
mononoken Feb 27, 2024
f9f8a40
Implement AdopterProfilePolicy in AdopterProfilesController
mononoken Feb 27, 2024
1dd977f
Add #verify_authorized to all controllers that have had authz framewo…
mononoken Feb 27, 2024
17b7f14
Fix misnamed Test class
mononoken Feb 27, 2024
cec95fe
Add :with_image trait to pet for testing ActiveStorage::Attachments
mononoken Feb 27, 2024
0a03990
Create and implement AttachmentPolicy
mononoken Feb 27, 2024
e1d6102
Namespace AttachmentPolicy under ActiveStorage to assist Action Polic…
mononoken Feb 27, 2024
ca924d5
Adjust test context to more accurate description
mononoken Mar 2, 2024
55606d1
Exclude create_adopter_applications permission from staff role
mononoken Mar 2, 2024
885a8b1
Merge branch 'main' into authorization_framework
mononoken Mar 7, 2024
6c62b65
Create and implement AdopterApplicationReviewPolicy
mononoken Mar 7, 2024
e931de9
Add verify_authorized to Organizations::HomeController
mononoken Mar 9, 2024
9dcf6e6
Add verify_authorized to Organizations::FosterApplicationReviewsContr…
mononoken Mar 9, 2024
dbc6d31
Create Organizations::DefaultPetTaskPolicy
mononoken Mar 9, 2024
5ff96e9
Implement Organizations::DefaultPetTaskPolicy
mononoken Mar 9, 2024
9824476
Remove deprecated organization context authorization method
mononoken Mar 9, 2024
4ff5781
Add verify_authorized to Organizations::OrganizationProfilesController
mononoken Mar 9, 2024
e99f014
Add skip_verify_authorized for ContactsController actions
mononoken Mar 9, 2024
6ad7ca9
Add skip_verify_authorized for DonationsController actions
mononoken Mar 9, 2024
d270267
Create MatchPolicy
mononoken Mar 9, 2024
a3287b5
Refactor matches routes to Rails convention
mononoken Mar 10, 2024
8523e64
Refactor MatchesController to use Rails naming conventions such as de…
mononoken Mar 10, 2024
670d849
Move some business logic from MatchesController into AdopterApplicati…
mononoken Mar 10, 2024
84fae2b
Add tests for new withdrawing and retiring application methods
mononoken Mar 10, 2024
51ea8ea
Add safe navigation in case a match is created where no paired Adopte…
mononoken Mar 10, 2024
d0f6884
Adjust strong params to require match
mononoken Mar 10, 2024
d67c279
Fix params reference in get_pet_id
mononoken Mar 10, 2024
e991d54
Implement MatchPolicy
mononoken Mar 10, 2024
787ae05
Create ProfileReviewPolicy
mononoken Mar 10, 2024
670ebb1
Implement ProfileReviewPolicy
mononoken Mar 10, 2024
d8f9219
Skip authorization on StatesController
mononoken Mar 11, 2024
090a1ff
Skip authorization in ErrorsController
mononoken Mar 11, 2024
f2e4191
Move verify_authorized to ApplicationController to globally apply it
mononoken Mar 11, 2024
754d826
Include PolicyAssertions in all policy unit tests for consistency
mononoken Mar 11, 2024
4f1c7e6
Merge branch 'main' into authorization_framework
mononoken Mar 11, 2024
d3c9cc5
Add rescue to ApplicationController to generically handle unauthorize…
mononoken Mar 11, 2024
39f37e3
Adjust AdoptablePet integration test to match policy
mononoken Mar 11, 2024
7396ffc
Add roles to all users in seeds
mononoken Mar 11, 2024
73a4192
Remove old authorization methods from ApplicationController
mononoken Mar 14, 2024
6a9caba
Remove duplication typo
mononoken Mar 14, 2024
33e8ab8
Rename Organizations::AdopterApplicationReviewPolicy to Organizations…
mononoken Mar 14, 2024
c47110f
Let AdoptionApplicationReviews#index scope use Organizations::PetPoli…
mononoken Mar 14, 2024
3c17dbb
Fix typo
mononoken Mar 14, 2024
4fd2c25
Rename profile precheck method to verify_profile! to decouple frome n…
mononoken Mar 18, 2024
ada4ef4
Redirect staff login to dashboard instead of pets index
mononoken Mar 18, 2024
8ff733c
Merge branch 'main' into authorization_framework
mononoken Mar 18, 2024
8b3eb81
Fix system tests for new staff login redirect
mononoken Mar 18, 2024
c7a9db3
Skip authorization on new cancel action on Tasks
mononoken Mar 18, 2024
0e2fb97
Fix organization contexts to be Current.organization
mononoken Mar 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,6 @@ gem "gretel", "~> 4.5"
gem "ransack"

gem "rails-controller-testing"

# Use Action Policy for authorization framework
gem "action_policy", "~> 0.6.8"
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
GEM
remote: https://rubygems.org/
specs:
action_policy (0.6.8)
ruby-next-core (>= 1.0)
actioncable (7.0.8.1)
actionpack (= 7.0.8.1)
activesupport (= 7.0.8.1)
Expand Down Expand Up @@ -380,6 +382,7 @@ GEM
rubocop-performance (1.18.0)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
ruby-next-core (1.0.0)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
Expand Down Expand Up @@ -470,6 +473,7 @@ PLATFORMS
x86_64-linux

DEPENDENCIES
action_policy (~> 0.6.8)
active_link_to
active_storage_validations
acts_as_tenant
Expand Down
18 changes: 7 additions & 11 deletions app/controllers/adoptable_pets_controller.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
class AdoptablePetsController < Organizations::BaseController
skip_verify_authorized only: %i[index]

def index
@pets = Pet.includes(:adopter_applications, images_attachments: :blob)
.published
.where
.missing(:match)
@pets = authorized_scope(
Pet.includes(:adopter_applications, images_attachments: :blob),
with: AdoptablePetPolicy
)
end

def show
@pet = Pet.find(params[:id])
unless @pet.published
redirect_to adoptable_pets_path, alert: "You can only view published pets."
end
authorize! @pet, with: AdoptablePetPolicy

if current_user&.adopter_account
@adoption_application =
Expand All @@ -22,9 +22,5 @@ def show
adopter_account: current_user.adopter_account
)
end

return unless @pet.match

redirect_to adoptable_pets_path, alert: "You can only view pets that need adoption."
end
end
25 changes: 9 additions & 16 deletions app/controllers/adopter_applications_controller.rb
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
class AdopterApplicationsController < ApplicationController
before_action :authenticate_user!, :adopter_with_profile
before_action :check_pet_app_status, only: [:create]
before_action :authenticate_user!

def index
@applications = AdopterApplication.where(adopter_account_id:
current_user.adopter_account.id)
authorize!

@applications = authorized_scope(AdopterApplication.all)
kasugaijin marked this conversation as resolved.
Show resolved Hide resolved
end

# add check if application already exists
def create
@pet = Pet.find(application_params[:pet_id])
authorize! context: {pet: @pet}

@application = AdopterApplication.new(application_params)

if @application.save
redirect_to adoptable_pet_path(@application.pet),
notice: "Application submitted! #{MessagesHelper.affirmations.sample}"

# mailer
@pet = @application.pet
@org_staff = User.organization_staff(@pet.organization_id)
StaffApplicationNotificationMailer.with(pet: @pet,
organization_staff: @org_staff)
.new_adoption_application.deliver_now
else
redirect_to adoptable_pet_path(@application.pet),
redirect_to adoptable_pet_path(@pet),
alert: "Error. Please try again."
end
end

# update :status to 'withdrawn' or :profile_show to false
def update
@application = AdopterApplication.find(params[:id])
authorize! @application

if @application.update(application_params)
redirect_to adopter_applications_path
Expand All @@ -48,13 +50,4 @@ def application_params
:profile_show
)
end

def check_pet_app_status
pet = Pet.find(params[:adopter_application][:pet_id])

return if pet.application_paused == false

redirect_to adoptable_pet_path(params[:adopter_application][:pet_id]),
alert: "Applications are paused for this pet"
end
end
23 changes: 7 additions & 16 deletions app/controllers/adopter_foster_profiles_controller.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
class AdopterFosterProfilesController < ApplicationController
# staff and admin cannot create a profile
before_action :authenticate_user!
before_action :check_if_adopter, only: [:new, :create, :update, :show]
before_action :authorize!, only: %i[new create]
before_action :set_profile, only: %i[show edit update]

# only allow new profile if one does not exist
# belongs_to for location provides new method build_location
# https://guides.rubyonrails.org/association_basics.html#the-belongs-to-association
def new
if profile_nil?
@adopter_foster_profile = AdopterFosterProfile.new
@adopter_foster_profile.build_location
else
redirect_to profile_path
end
@adopter_foster_profile = AdopterFosterProfile.new
@adopter_foster_profile.build_location
end

def create
Expand All @@ -28,15 +21,12 @@ def create
end

def show
@adopter_foster_profile = current_user.adopter_account.adopter_foster_profile
end

def edit
@adopter_foster_profile = current_user.adopter_account.adopter_foster_profile
end

def update
@adopter_foster_profile = current_user.adopter_account.adopter_foster_profile
respond_to do |format|
if @adopter_foster_profile.update(adopter_foster_profile_params)
format.html { redirect_to profile_path, notice: "Profile updated" }
Expand All @@ -48,8 +38,9 @@ def update

private

def profile_nil?
AdopterFosterProfile.where(adopter_account_id: current_user.adopter_account.id)[0].nil?
def set_profile
@adopter_foster_profile = current_user.adopter_account.adopter_foster_profile
authorize! @adopter_foster_profile
end

def adopter_foster_profile_params
Expand Down
36 changes: 5 additions & 31 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class ApplicationController < ActionController::Base
verify_authorized unless: :devise_controller?

before_action :set_current_user
around_action :switch_locale

Expand All @@ -11,37 +13,9 @@ def switch_locale(&action)
I18n.with_locale(locale, &action)
end

private

def adopter_with_profile
return if current_user.adopter_account&.adopter_foster_profile

redirect_to root_path, alert: "Unauthorized action."
end

def check_if_adopter
return if current_user.adopter_account

redirect_to root_path, alert: "Profiles are for adopters only"
end

def active_staff
return if user_signed_in? &&
current_user.staff_account &&
!current_user.staff_account.deactivated?

redirect_to root_path, alert: "Unauthorized action."
end

def pet_in_same_organization?(org_id)
current_user.staff_account.organization_id == org_id
end

def require_organization_admin
return if user_signed_in? &&
current_user.staff_account &&
current_user.staff_account.has_role?(:admin, current_user.staff_account.organization)
rescue_from ActionPolicy::Unauthorized do |ex|
flash[:alert] = "You are not authorized to perform this action."

redirect_to root_path, alert: "Unauthorized action."
redirect_back fallback_location: root_path
end
end
4 changes: 2 additions & 2 deletions app/controllers/attachments_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
class AttachmentsController < ApplicationController
before_action :active_staff

def purge
@attachment = ActiveStorage::Attachment.find(params[:id])
authorize! @attachment

@attachment.purge
redirect_to request.referrer, notice: "Attachment removed"
end
Expand Down
15 changes: 6 additions & 9 deletions app/controllers/concerns/organization_scopable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,17 @@ def set_tenant
end

def after_sign_in_path_for(resource_or_scope)
if resource_or_scope.staff_account&.deactivated?
adoptable_pets_path
else
if allowed_to?(
:index?, Pet, namespace: Organizations,
context: {organization: Current.organization}
)
pets_path
Copy link
Collaborator

@kasugaijin kasugaijin Mar 17, 2024

Choose a reason for hiding this comment

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

We can now redirect staff to the dashboard_index_path, now we have a solid grasp on what the dashboard looks like. I think we set it to pets_path before the dashboard index was a thing.

Copy link
Collaborator Author

@mononoken mononoken Mar 18, 2024

Choose a reason for hiding this comment

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

Updated the redirect to dashboard now in ada4ef4

else
adoptable_pets_path
end
end

def after_sign_out_path_for(resource_or_scope)
adoptable_pets_path
end

def verify_organization_for_current_user
if current_user.blank? || current_user.organization.id != ActsAsTenant.current_tenant.id
redirect_to root_path, alert: "Wrong organization."
end
end
end
2 changes: 2 additions & 0 deletions app/controllers/contacts_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class ContactsController < ApplicationController
skip_verify_authorized only: %i[new create]

def new
@contact = Contact.new
end
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/donations_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class DonationsController < ApplicationController
skip_verify_authorized only: %i[create]

def create
@mapped_response = PaypalResponseMapper.new(params) if params[:source] == "Paypal"

Expand Down
2 changes: 2 additions & 0 deletions app/controllers/errors_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class ErrorsController < ApplicationController
skip_verify_authorized

def not_found
render status: 404
end
Expand Down
18 changes: 3 additions & 15 deletions app/controllers/matches_controller.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
class MatchesController < ApplicationController
before_action :active_staff, :same_organization?

before_action :set_pet, only: %i[create]
before_action :set_match, only: %i[destroy]

def create
authorize! context: {organization: @pet.organization}
nsiwnf marked this conversation as resolved.
Show resolved Hide resolved

@match = Match.new(match_params.merge(
organization_id: @pet.organization_id
))
Expand Down Expand Up @@ -40,18 +40,6 @@ def set_pet

def set_match
@match = Match.find(params[:id])
end

def get_pet_id
return match_params[:pet_id] if match_params[:pet_id]

Match.find(params[:id]).pet_id
end

# staff and pet in the same org?
def same_organization?
return if current_user.staff_account.organization_id == Pet.find(get_pet_id).organization.id

redirect_to root_path, alert: "Unauthorized action."
authorize! @match
end
end
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
class Organizations::AdoptionApplicationReviewsController < Organizations::BaseController
before_action :active_staff
before_action :set_adopter_application, only: %i[edit update]

layout "dashboard"

def index
@q = Pet.org_pets_with_apps(current_user.staff_account.organization_id).ransack(params[:q])
authorize! AdopterApplication,
context: {organization: current_user.organization}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just noticed this - should we have a before_action authenticate_user or do this in case someone tries to access this page without logging in?
context: {organization: Current.organization}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You are right! That should have been Current.organization and was def a mistake.


@q = authorized_scope(
Pet.org_pets_with_apps(current_user.staff_account.organization_id)
).ransack(params[:q])
@pets_with_applications = @q.result.includes(:adopter_applications)
@pet = selected_pet

Expand All @@ -14,32 +20,17 @@ def index
end
end

def filter_by_application_status(pets_relation, status_filter)
pets_relation.joins(:adopter_applications).where(adopter_applications: {status: status_filter})
end

def edit
@application = AdopterApplication.find(params[:id])

return if pet_in_same_organization?(@application.pet.organization_id)

redirect_to dashboard_index_path,
alert: 'Staff can only edit applications for their organization
pets.'
end

def update
@application = AdopterApplication.find(params[:id])
@applications_tab = request.referrer.include?("applications") # Change table display in pets/applications tab

if pet_in_same_organization?(@application.pet.organization_id) &&
@application.update(application_params)
respond_to do |format|
respond_to do |format|
if @application.update(application_params)
format.html { redirect_to dashboard_index_path }
format.turbo_stream { flash.now[:notice] = "Application was successfully updated." }
end
else
respond_to do |format|
else
format.html { render :edit, status: :unprocessable_entity }
format.turbo_stream { flash.now[:alert] = "Error updating application" }
end
Expand All @@ -52,10 +43,19 @@ def application_params
params.require(:adopter_application).permit(:status, :notes, :profile_show)
end

def set_adopter_application
@application = AdopterApplication.find(params[:id])
authorize! @application
end

# use where to return a relation for the view
def selected_pet
return if !params[:pet_id] || params[:pet_id] == ""

Pet.where(id: params[:pet_id])
end

def filter_by_application_status(pets_relation, status_filter)
pets_relation.joins(:adopter_applications).where(adopter_applications: {status: status_filter})
end
end
Loading