ruby-on-railsrspecfactory-botacts-as-tenant

acts_as_tenant gem is creating errors using RSpec/FactoryGirl


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 Grants.

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?


Solution

  • 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.