I am attempting to use the acts_as_tenant
gem to scope SQL queries for multiple organizations. RSpec/FactoryGirl are pulling some dirty tricks, however.
A bit of background: in my app, User
and Grant
both belong to an Organization
. Only admin users can create/delete Grant
s.
Like the gem's documentation advises, I have inserted acts_as_tenant :organization
into my Grant
and User
models. I have also set_current_tenant_through_filter
and defined a before_action :set_organization
in my application_controller.rb
. Queries for User
and Grant
are scoped to the current user's Organization
only:
def set_organization
if current_user
current_organization = Organization.find(current_user.organization_id)
set_current_tenant(current_organization)
end
end
All seems well and good. Now to write controller tests:
# grants_controller_spec.rb
describe GrantsController do
let(:organization) { create(:organization) }
let(:admin) { create(:user, admin: true, organization_id: organization.id) }
...
before(:each) { log_in admin }
...
end
The admin part raises an odd error:
Failure/Error: let(:admin) { create(:user, admin: true, organization_id: organization.id) }
ActiveRecord::RecordInvalid:
Validation failed: Organization can't be blank
So, even though I have specifically passed the organization's foreign key to FactoryGirl, it's still having issues identifying the Organization
.
When I comment out the acts_as_tenant
-specific code, the errors go away. How can I make the tests go green for good?
This actually has very little to do with the code I posted above. The culprit was this line, in my User
model:
attr_accessor :organization_id
I believe that line prevented the actual organization_id
database column from being saved with the model. My UsersController
code can clarify this:
def create
@user = User.new(user_params)
organization = Organization.find_by(name: params[:user][:organization_name])
if organization.authenticated?(params[:user][:organization_password])
@user.organization_id = organization.id
@user.save
...
end
So organization_id
was indeed set to organization.id
, but attr_accessor
was evaluated first, so organization_id
became a useless virtual attribute. And the db column organization_id
was saved as nil
, i.e., nothing was passed to it. This in turn caused acts_as_tenant
to complain since I'd set the current_tenant
as the current user's organization, and the current user wasn't getting set up with a foreign key.
My advice to myself and to all others who stumble upon this: check whether your db column names are shadowed by your virtual attributes.