janko / rodauth-rails

Rails integration for Rodauth authentication framework
https://github.com/jeremyevans/rodauth
MIT License
565 stars 40 forks source link

RodauthApp.rodauth.create_account does not clear query cache #204

Closed petergoldstein closed 1 year ago

petergoldstein commented 1 year ago

I'm seeing an interaction (or failure to interact) between my call to create_account and the Rails query cache. Specifically I've got a class whose trimmed down and simplified representation looks something like this:

class AccountCreator
  include ActiveModel::Validations

  validate :no_account_exists

  def initialize(account_options)
    @account_options = account_options
  end

  def create
    return nil unless valid?

    ActiveRecord::Base.transaction do
      create_account
      <do other stuff>
      ...
      account
    end    
  end

  def create_account
    RodauthApp.rodauth.create_account(account_options)
  end

  def no_account_exists
    return if account.blank?

    errors.add(:login, "already exists")
  end

  def account
    Account.find_by(email: @account_options[:login])
  end
end

I expect create to return the newly created Account object in the success case. It doesn't. Instead it returns nil.

If I remove the initial validation (by removing the return nil unless valid? line, for example) then it correctly returns the newly created Account object.

If I add a line ActiveRecord::Base.connection.clear_query_cache where the ... is, then it correctly returns the newly created Account object.

From this I've surmised that the RodauthApp.rodauth.create_account is not properly updating the Rails query cache, so the second call to account gets the cached query value.

Not sure if this is 'fixable' given that I'm calling a Rodauth internal method, but it was definitely confusing for a bit.

janko commented 1 year ago

Thanks for the report, I was able to reproduce this with the following script:

require "rodauth"
require "sequel"
require "active_record"

ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.connection.enable_query_cache!

DB = Sequel.sqlite(extensions: :activerecord_connection)
DB.create_table :accounts do
  primary_key :id
  Integer :status_id, default: 1
  String :email
end

class Account < ActiveRecord::Base
end

rodauth = Rodauth.lib do
  enable :create_account
  create_account_set_password? false
end

p Account.find_by(email: "foo@example.com")
rodauth.create_account(login: "foo@example.com")
p Account.find_by(email: "foo@example.com")

I think Sequel's activerecord_connection extension should automatically clear Active Record's query cache after modifications. I will try to get this fixed.

janko commented 1 year ago

I just released version 1.3.1 of the sequel-activerecord_connection gem, which clears Active Record's query cache after Sequel executes SQL statements.

Unfortunately, there didn't appear to be an easy way to only do this on write queries, so cache will be cleared on SELECT queries as well. But I don't think Active Record's query cache makes much of a difference performance-wise anyway; Sequel doesn't have this functionality and I don't remember anyone ever requesting it 🤷🏻‍♂️