I am upgrading an app to rails 4.1.1 and authlogic 3.4.2 and encountered a problem with a race condition in an integration test.
I have a page that issues two ajax requests upon loading. Both requests cause authlogic to try to update the logged in user record's last_request_at column. I do not always get the same exception, but something is always raised when the app tries to update the same user record back to back.
This was not an issue on rails 3 because they used Rack::Lock in the test environment.
Here is a snippet of the logs:
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1 [50/1923]
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
(1.5ms) BEGIN
(0.2ms) BEGIN
SQL (0.3ms) UPDATE "users" SET "last_request_at" = $1, "perishable_token" = $2, "updated_at" = $3 WHERE "users"."type" IN ('MedicalProfessional') AND "user
s"."id" = 2 [["last_request_at", "2014-05-18 00:19:46.564174"], ["perishable_token", "awlh6mBgHl2mdU2uboi"], ["updated_at", "2014-05-18 00:19:46.566315"]]
PG::Error: another command is already in progress
: UPDATE "users" SET "last_request_at" = $1, "perishable_token" = $2, "updated_at" = $3 WHERE "users"."type" IN ('MedicalProfessional') AND "users"."id" = 2
SQL (1.7ms) UPDATE "users" SET "last_request_at" = $1, "perishable_token" = $2, "updated_at" = $3 WHERE "users"."type" IN ('MedicalProfessional') AND "user
s"."id" = 2 [["last_request_at", "2014-05-18 00:19:46.562637"], ["perishable_token", "SoXbKuCMs0Zu3vtxKGqn"], ["updated_at", "2014-05-18 00:19:46.564956"]]
(0.2ms) ROLLBACK
Completed 500 Internal Server Error in 132ms
(0.5ms) COMMIT
ActiveRecord::StatementInvalid (PG::Error: another command is already in progress
: UPDATE "users" SET "last_request_at" = $1, "perishable_token" = $2, "updated_at" = $3 WHERE "users"."type" IN ('MedicalProfessional') AND "users"."id" = 2):
app/helpers/auth_helper.rb:3:in `current_user_session'
app/helpers/auth_helper.rb:7:in `current_user'
How should/can I resolve this issue?
Turns out someone else on my team had monkey patched ActiveRecord so that all threads use the same DB connection in order to speed up tests. He got the idea from this: http://blog.plataformatec.com.br/2011/12/three-tips-to-improve-the-performance-of-your-test-suite/
I was able to work around the issue by setting ActiveRecord::Base.shared_connection = nil at the start of the failing tests.
Related
I added this lines of code to create action:
def create
super
#card = Card.find(params[:card_id])
#card.update(:user_id=>current_user)
end
And everything works fine, user gets created, card gets updated, but after redirect this happens:
Couldn't find Card with 'id'=
Extracted source (around line #14):
def create
super
#card = Card.find(params[:card_id])
#card.update(:user_id=>current_user)
end
I checked my terminal to find out the reason why this happens, and it seems that create action triggers twice for no reason:
Started POST "/users" for ::1 at 2020-08-12 11:04:34 +0300
Processing by Users::RegistrationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"q1W0+ZhzK85uHTcp1x4jKHvCG0ukIgj2JxZuAy6vuLQl/vPqJVu6eXSEWviYTnWC4cXAJk2xCJhl8mgoWzXIAA==", "user"=>{"name"=>"Терл Кабот", "email"=>"tafff1#gmail.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "card_id"=>"2000012606"}, "commit"=>"Sign up"}
Card Load (1.0ms) SELECT "cards".* FROM "cards" WHERE "cards"."id" = $1 LIMIT $2 [["id", 2000012606], ["LIMIT", 1]]
(0.0ms)
BEGIN
User Exists (1.0ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2 [["email", "tafff1#gmail.com"], ["LIMIT", 1]]
SQL (1.0ms) INSERT INTO "users" ("email", "encrypted_password", "name", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["email", "tafff1#gmail.com"], ["encrypted_password", "$2a$12$qTrv/zFUxULi9sqWgYlY/uPjQoJsZxB8PJK2ae/e6YfAFT40ci47e"], ["name", "Терл Кабот"], ["created_at", "2020-08-12 08:04:35.174621"], ["updated_at", "2020-08-12 08:04:35.174621"]]
SQL (1.0ms) UPDATE "cards" SET "user_id" = $1, "updated_at" = $2 WHERE "cards"."id" = $3 [["user_id", 17], ["updated_at", "2020-08-12 08:04:35.178626"], ["id", 2000012606]]
(1.0ms) COMMIT
Redirected to http://localhost:3000/
Card Load (0.0ms) SELECT "cards".* FROM "cards" WHERE "cards"."id" = $1 LIMIT $2 [["id", nil], ["LIMIT", 1]]
Completed 404 Not Found in 378ms (ActiveRecord: 6.0ms)
ActiveRecord::RecordNotFound (Couldn't find Card with 'id'=):
is there any solution for this?
EDIT: I gave up and just changed card and user logic, now user belongs to card, so I dont have to update cards user_id from devises create action.
The card_id is nested in the user key, so it will be: params[:user][:card_id]
I have
class CustomSessionsController < Devise::SessionsController
def create
#user = resource # needed for Merit
super
end
protected
def after_sign_in_path_for(resource)
#user = resource # needed for Merit
resource.update_streak
super
And
grant_on 'custom_sessions#create', badge: :streak, level: 3, temporary: true, model_name: 'User' do |user|
puts user.inspect
user.streak.count >= 3
end
But it gives the error
[merit] no target_obj found on Rule#applies?
And I can't access the model and it doesn't grant the badge or log the user. What is wrong? I followed the guide.
https://github.com/merit-gem/merit/wiki/How-to-grant-badges-on-user-using-Devise
It's doing something.
Processing by CustomSessionsController#create as HTML
Parameters: {"utf8"=>"√", "authenticity_token"=>"gqUQjF9hfzJdQqxAAQJxv7bi+kZYwuv1NWtOP0YhkbjHKwnfa5WAb/CkRZ5c+Xi5yVlnJ2v774w3XLhTa1b1sQ==", "user"=>{"email"=>"student#gmail.com", "password"=>"[FILTERED]", "remember_me"=>"0"}, "commit"=>"Log in"}
User Load (6.0ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 ORDER BY "users"."id" ASC LIMIT 1 [["email", "student#gmail.com"]]
(7.0ms) BEGIN
SQL (3.0ms) UPDATE "users" SET "last_sign_in_at" = $1, "current_sign_in_at" = $2, "sign_in_count" = $3, "updated_at" = $4 WHERE "users"."id" = $5 [["last_sign_in_at", "2018-08-09 05:38:58.345271"], ["current_sign_in_at", "2018-08-10 01:40:51.644592"], ["sign_in_count", 15], ["updated_at", "2018-08-10 01:40:51.668609"], ["id", 3]]
(25.0ms) COMMIT
Streak Load (21.0ms) SELECT "streaks".* FROM "streaks" WHERE "streaks"."user_id" = $1 LIMIT 1 [["user_id", 3]]
Redirected to http://localhost:3000/
(1.0ms) BEGIN
SQL (7.0ms) INSERT INTO "merit_actions" ("user_id", "action_method", "target_model", "target_data", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [["user_id", 3], ["action_method", "create"], ["target_model", "custom_sessions"], ["target_data", "--- \n...\n"], ["created_at", "2018-08-10 01:40:53.539847"], ["updated_at", "2018-08-10 01:40:53.539847"]]
(8.0ms) COMMIT
Merit::Action Load (6.0ms) SELECT "merit_actions".* FROM "merit_actions" WHERE "merit_actions"."processed" = $1 [["processed", "f"]]
(3.0ms) BEGIN
SQL (2.0ms) UPDATE "merit_actions" SET "processed" = $1, "updated_at" = $2 WHERE "merit_actions"."id" = $3 [["processed", "t"], ["updated_at", "2018-08-10 01:40:53.581875"], ["id", 17]]
(20.0ms) COMMIT
User Load (2.0ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT 1
[merit] no target_obj found on Rule#applies?
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT 1
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT 1
[merit] no target_obj found on Rule#applies?
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT 1
Completed 302 Found in 2567ms (ActiveRecord: 293.2ms)
Merit 2.4, Rails 4.2.
I tried
grant_on 'custom_sessions#create', badge: :streak, level: 3, temporary: true do
puts current_user.inspect
current_user.streak.count >= 3
end
But it gave
[merit] no target found: uninitialized constant CustomSession. base_target_finder.rb:13:in 'find'
error NameError (undefined local variable or method 'current_user'
I tried
grant_on 'custom_sessions#create', badge: :streak, level: 3, temporary: true, to: :itself do |user|
puts user.inspect
user.streak.count >= 3
end
def create
#custom_session = resource # needed for Merit
def after_sign_in_path_for(resource)
#custom_session = resource # needed for Merit
But it gave
[merit] no target found: uninitialized constant CustomSession. C:/ruby23/lib/ruby/gems/2.3.0/gems/merit-2.4.0/lib/merit/base_target_finder.rb:13:in `find'
true
Completed 500 Internal Server Error in 2181ms (ActiveRecord: 177.1ms)
NoMethodError (undefined method `streak' for true:TrueClass):
app/models/merit/badge_rules.rb:43:in `block in initialize'
I got it working with
grant_on 'custom_sessions#create', badge: :streak, level: 3, temporary: true, model_name: 'User', to: :itself do |user|
def create
super
#custom_session = resource # needed for Merit
But I don't know why because the /users/sign_in path does not have an :id parameter.
https://github.com/merit-gem/merit#how-merit-finds-the-target-object
Merit would fetch the Article object from the database, found by the :id param sent in that update action.
Here is my problem:
I'm using Devise's guest_user, that contains a logging_in method to transfer guest_user parameters to the registered user when he logs in. So in my case, the user has_many periods, dependent: :destroy, so here is the logging_in method:
def logging_in
guest_periods = guest_user.periods.all
guest_periods.each do |p|
p.user_id = current_user.id
p.save!
end
current_user.latest_entry = guest_user.latest_entry
current_user.is_in_zone = guest_user.is_in_zone
current_user.save
end
However, when a guest_user logs in, his periods gets destroyed instead of being transfered. Here is the log:
Started GET "/" for ::1 at 2015-05-11 00:18:03 +0300
Processing by WelcomeController#index as HTML
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT 1 [["id", 24]]
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 23]]
Period Load (0.3ms) SELECT "periods".* FROM "periods" WHERE "periods"."user_id" = $1 [["user_id", 23]]
(0.2ms) BEGIN
CACHE (0.0ms) SELECT "periods".* FROM "periods" WHERE "periods"."user_id" = $1 [["user_id", 23]]
SQL (0.8ms) UPDATE "periods" SET "user_id" = $1, "updated_at" = $2 WHERE "periods"."id" = $3 [["user_id", 24], ["updated_at", "2015-05-10 21:18:03.863162"], ["id", 170]]
(0.9ms) COMMIT
(0.2ms) BEGIN
SQL (2.1ms) UPDATE "users" SET "is_in_zone" = $1, "latest_entry" = $2, "updated_at" = $3 WHERE "users"."id" = $4 [["is_in_zone", "t"], ["latest_entry", "2015-05-04"], ["updated_at", "2015-05-10 21:18:03.875572"], ["id", 24]]
(15.8ms) COMMIT
(0.5ms) BEGIN
SQL (0.3ms) DELETE FROM "periods" WHERE "periods"."id" = $1 [["id", 170]]
SQL (0.7ms) DELETE FROM "users" WHERE "users"."id" = $1 [["id", 23]]
(1.2ms) COMMIT
So we can see that the transfer is done, but then in the end, the periods are destroyed anyway. They should not be, as they are not belonging to the user to be destroyed any more.
Why is it happening?
Even though Period#user_id has changed, guest_user.periods is still loaded in memory and is what gets destroyed when you destroy the guest user. If you guest_user.reload, its associations will clear out and it becomes safe to destroy. You could also guest_user.periods(true) to force reload of just the periods.
Another option is:
guest_user.periods.update_all(user_id: current_user.id)
This executes a single query to perform the update, which will be nice if there are a lot of periods, and also doesn't load the guest_user.periods association, so it will load fresh during the destroy and find the correct empty set.
This one is driving me absolutely crazy. I tested updating user's email address on dev and staging environments, and it all worked fine. It updates unconfirmed_email field and sends out a confirmation email address.
However, only in production environment, it fails!
In dev/staging environment, I see the following statements when a user updates his/her email address.
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
Profile Load (0.3ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."user_id" = 2 LIMIT 1
(0.1ms) BEGIN
User Exists (0.6ms) SELECT 1 FROM "users" WHERE ("users"."email" = 'aaaa#yahoo.com' AND "users"."id" != 2) LIMIT 1
(0.3ms) UPDATE "users" SET "unconfirmed_email" = 'aaaa#yahoo.com', "updated_at" = '2012-08-27 04:22:10.470329' WHERE "users"."id" = 2
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."confirmation_token" = 'jUcfXqMqDjeEsJ3TEKws' LIMIT 1
(0.3ms) UPDATE "users" SET "unconfirmed_email" = 'aaaa#yahoo.com', "updated_at" = '2012-08-27 04:22:10.470329', "confirmation_token" = 'jUcfXqMqDjeEsJ3TEKws', "confirmation_sent_at" = '2012-08-27 04:22:10.473264' WHERE "users"."id" = 2
However, in production, I see the following.
User Load (6.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = 5 LIMIT 1
Profile Load (2.1ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."user_id" = 5 LIMIT 1
(1.2ms) BEGIN
User Exists (1.3ms) SELECT 1 FROM "users" WHERE ("users"."email" = 'aaaa#yahoo.com' AND "users"."id" != 5) LIMIT 1
(1.2ms) ROLLBACK
One more odd thing. I could update the email address manually in rails console.....
1.9.3p194 :005 > u.update_attributes(:email => "aaaa#yahoo.com")
(1.3ms) BEGIN
User Exists (1.4ms) SELECT 1 FROM "users" WHERE ("users"."email" = 'aaaa#yahoo.com' AND "users"."id" != 1) LIMIT 1
(1.4ms) UPDATE "users" SET "unconfirmed_email" = 'aaaa#yahoo.com', "updated_at" = '2012-08-27 05:05:26.337961' WHERE "users"."id" = 1
User Load (1.4ms) SELECT "users".* FROM "users" WHERE "users"."confirmation_token" = 'Yyg98zno81adJt4mp7pG' LIMIT 1
(1.4ms) UPDATE "users" SET "unconfirmed_email" = 'aaaa#yahoo.com', "updated_at" = '2012-08-27 05:05:26.337961', "confirmation_token" = 'Yyg98zno81adJt4mp7pG', "confirmation_sent_at" = '2012-08-27 05:05:26.342299' WHERE "users"."id" = 1
Profile Load (1.4ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."user_id" = 1 LIMIT 1
(1.7ms) COMMIT
=> true
Any help would be much appreciated!
Thanks,
I was trying to figure out what the differences are between the two environments - dev/staging vs. production. Since staging and production have pretty much identical settings, the only I could think of was cacheing. So, I changed the update_attributes line to the following and it worked.
if #user.reload.update_attributes(:email => params[:email])
I have an ajax request that is causing problems in my Rails 3.0.9 app. I can see the problem in the logs, but I don't have any idea what is triggering it between the ajax call and the render. Here's the log, and the event I don't want with ** beside it:
Started DELETE "/notifications/13" for 127.0.0.1 at 2011-06-21 22:08:39 -0500
Processing by NotificationsController#destroy as JS
Parameters: {"id"=>"13"}
SQL (0.4ms) SELECT name
FROM sqlite_master
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
SQL (0.3ms) SELECT name
FROM sqlite_master
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Slug Load (0.4ms) SELECT "slugs".* FROM "slugs" WHERE ("slugs".sluggable_id = 1 AND "slugs".sluggable_type = 'User') ORDER BY id DESC LIMIT 1
****AREL (0.3ms) UPDATE "users" SET "remember_token" = NULL, "remember_created_at" = NULL, "updated_at" = '2011-06-22 03:08:40.084049', "preferences" = '---
:email_notifications: ''true''
' WHERE "users"."id" = 1
Notification Load (0.2ms) SELECT "notifications".* FROM "notifications" WHERE "notifications"."id" = 13 LIMIT 1
User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
AREL (0.3ms) UPDATE "users" SET "notifications_count" = COALESCE("notifications_count", 0) - 1 WHERE "users"."id" = 1
AREL (0.1ms) DELETE FROM "notifications" WHERE "notifications"."id" = 13
Completed 200 OK in 1334ms
I'd like to somehow step by step debug this request, sort of like the way you can step through a function in javascript using firebug.
Is there a way to debug like this so I can see how that specific AREL command is getting called??
Have you looked at ruby on rails guides - debugging?? you can debug just like in gdb
This railscast is also quite useful.