Reputation: 1258
I have this model in Rails (trimmed to the relevant parts)
class Session < ActiveRecord::Base
belongs_to :user
before_save :invalidate_existing_sessions
def invalidate_existing_sessions
Session.where(user_id: user.id, current: true).each { |sess| sess.update_attributes(current: false) }
end
end
However, when a record is created and about to be saved, the server goes into an infinite loop.
Here are the server logs
Processing by V1::SessionsController#create as */*
Parameters: {"email"=>"[email protected]", "password"=>"[FILTERED]", "session"=>{}}
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT 1 [["email", "[email protected]"]]
(0.2ms) BEGIN
Session Load (0.7ms) SELECT "sessions".* FROM "sessions" WHERE "sessions"."user_id" = $1 AND "sessions"."current" = $2 [["user_id", 1
], ["current", true]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
CACHE (0.0ms) SELECT "sessions".* FROM "sessions" WHERE "sessions"."user_id" = $1 AND "sessions"."current" = $2 [["user_id", 1], ["cu
rrent", true]]
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
CACHE (0.0ms) SELECT "sessions".* FROM "sessions" WHERE "sessions"."user_id" = $1 AND "sessions"."current" = $2 [["user_id", 1], ["cu
rrent", true]]
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
CACHE (0.0ms) SELECT "sessions".* FROM "sessions" WHERE "sessions"."user_id" = $1 AND "sessions"."current" = $2 [["user_id", 1], ["cu
rrent", true]]
A bit later, this is what the log turns into
app/models/session.rb:12:in `invalidate_existing_sessions'
app/models/session.rb:12:in `block in invalidate_existing_sessions'
app/models/session.rb:12:in `invalidate_existing_sessions'
app/models/session.rb:12:in `block in invalidate_existing_sessions'
app/models/session.rb:12:in `invalidate_existing_sessions'
app/models/session.rb:12:in `block in invalidate_existing_sessions'
app/models/session.rb:12:in `invalidate_existing_sessions'
Any ideas? I'm using Rails 5 alpha.
Upvotes: 0
Views: 1188
Reputation: 954
You're running update_attributes
in before_save
, that means you're saving before save. That's why it goes into an infinite loop.
Upvotes: 1
Reputation: 1258
Even though all of the above answers worked for me, this is what I found simplest and I ended up using.
def invalidate_existing_sessions
Session.where(user_id: user.id, current: true).each { |sess| sess.update_column(:current, false) }
end
Turns out update_column
doesn't call any callbacks, but as an disadvantage it doesn't update updated_at
if you're using timestamps in your model.
Upvotes: 2
Reputation: 36880
It's because your before_save
method does this...
sess.update_attributes(current: false)
Since update_attributes calls before_save
you are (as you say) in an infinite loop.
So you need to skip the callbacks
class Session < ActiveRecord::Base
attr_accessor :skip_callbacks
before_save :invalidate_existing_sessions, unless: :skip_callbacks
def invalidate_existing_sessions
Session.where(user_id: user.id, current: true).each do |sess|
sess.skip_callbacks = true
sess.update_attributes(current: false)
end
end
Upvotes: 2