Why i can't create object with params? - ruby-on-rails

I’ve been suffering from a problem for several days.
I can not create an object using the parameters from the form.
My form:
<%= form_with(model: #battle, local: true) do |form| %>
<div class="field">
<%= form.collection_select(:team_id, #teams, :id, :name) %>
</div>
<%= form.submit 'Submit'%>
<% end %>
In this form i want to choose only 1 team.
controller:
def create
#battle = Battle.new
#battle.user_id = current_user.id
#battle.team_ids = params[:team_id]
if #battle.save
redirect_to root_path, notice: "Battle has been created"
else
render :new
end
end
def battle_params
params.require(:battle).permit(:team_id)
end
And when used, this form creates an object without reference to the team.
logs:
Processing by BattlesController#create as HTML
Parameters: {"authenticity_token"=>"0wWoFrQXYkEsXsMRgGyKi5Mde7WndhI6zYWY4KvhQlgdcCAaCZqH1z1z9dK0x91iqOPw/Jsb2T6Q+EVtGz4VsA==", "battle"=>{"team_id"=>"14"}, "commit"=>"Submit"}
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/controllers/battles_controller.rb:14:in `create'
Team Load (0.4ms) SELECT "teams".* FROM "teams" WHERE 1=0
↳ app/controllers/battles_controller.rb:15:in `create'
(0.4ms) BEGIN
↳ app/controllers/battles_controller.rb:16:in `create'
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/controllers/battles_controller.rb:16:in `create'
Battle Create (3.8ms) INSERT INTO "battles" ("user_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["user_id", 1], ["created_at", "2020-06-19 10:28:28.036147"], ["updated_at", "2020-06-19 10:28:28.036147"]]
If I try to create a battle with two teams, but it will be created with only one (without using parameters):
#battle.team_ids = [params[:team_id], 14]
Although, a battle is created without problems in the console:
battle = Battle.new
battle.team_ids = [13, 14]
I don’t understand what the problem may be.

Instead of params[:team_id] try using battle_params[:team_id] instead:
#battle.team_ids = battle_params[:team_id]
otherwise you'd need to call params[:battle][:team_id] which is not the Rails way as it isn't as secure, but it would still work.

Related

Rails turbo stream destroy action handling inexistent parameter

The following execution upon hitting a button <%= button_to fa_icon('bomb', class: 'fa-lg'), importattribute, class: 'button alert small', method: :delete %> in Rails 7
Started DELETE "/importattributes/1" for ...
Processing by ImportattributesController#destroy as TURBO_STREAM
Parameters: {"authenticity_token"=>"[FILTERED]", "id"=>"1"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 11], ["LIMIT", 1]]
Importattribute Load (0.3ms) SELECT "importattributes".* FROM "importattributes" WHERE "importattributes"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/controllers/importattributes_controller.rb:53:in `set_importattribute'
TRANSACTION (0.2ms) BEGIN
↳ app/controllers/importattributes_controller.rb:47:in `destroy'
TRANSACTION (0.2ms) ROLLBACK
is hitting the error
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column importattributes.importattribute_id does not exist
LINE 1: ...importattributes".* FROM "importattributes" WHERE "importatt...
upon
def destroy
authorize #importattribute
#supplierimport = Supplierimport.find(#importattribute.supplierimport_id)
#importattribute.destroy
redirect_to supplierimport_path(#supplierimport), notice: "Import attribute was successfully destroyed."
end
with the standard private Rails method
def set_importattribute
#importattribute = Importattribute.find(params[:id])
end
Why is Rails looking up in PG importattribute_id instead of id ?
This is also occuring in the test suite & I am quite puzzled by it.

Rails - Devise's registration controller create action seems to trigger twice

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]

Rails. Debugging a Uniqueness validation with scope that fails when object is updated

I have a model Prediction for which its instances have references to other models Participant and Match through the attributes :participant_id :match_id.
The idea is that a #participant can have only one #prediction per #match (every #participant is allowed to give maximum one prediction per #match).
So I've included the following validation in my Prediction model
validates :match_id, uniqueness: { scope: [:participant_id] }
The validation works OK to disallow #prediction objects to be created when there's already a #prediciton for the same #match and #participant. The problem is that it does, as well, prevent updating an object even though I'm not changing those assigned ids of the original object. Plus, it's letting me update only one instance of Prediction, all other's through an error.
What's puzzling is that it was working fine but for some reason, I've been unable to track, it stopped working and I have run out of ideas on how to debug. Could you provide any ideas? Below what I've checked/tried:
The relevant params of a failed request:
"prediction"=><ActionController::Parameters {"participant_id"=>"1", "match_id"=>"2", "local_score"=>"2", "visitant_score"=>"0"}
The error I'm getting:
#details={:match_id=>[{:error=>:taken, :value=>2}]},
#messages={:match_id=>["has already been taken"]}>
There are plenty predictions in the database with match_id: 2. However, this is happening despite having no other #prediction in the database with both match_id: 2 and participant_id: 1 except, of course, for the instance that throws this error while updating.
Funnily enough, if I try the same operation in the console (as below) it successfully updates.
#prediction = Prediction.find_by(participant_id: 1, match_id: 2)
#prediction.update_attributes(local_score: 8, visitant_score: 8)
Suggesting the problem is in the controller action:
app/controllers/predicitons_controller.rb
def update
respond_to do |format|
if #prediction.update(prediction_params)
format.json { render :show, status: :ok, location: #prediction }
format.js
else
#errorMessages = []
#prediction.errors.full_messages.each do |message|
#errorMessages << message
end
format.js {render template: 'predictions/update_error'}
end
end
end
I see no problem there. Additionally, it seems that the only request sent to the controller that updates successfully is:
"prediction"=><ActionController::Parameters {"participant_id"=>"1", "match_id"=>"1", "local_score"=>"1", "visitant_score"=>"1"}
And if I do these others, for example, it doesn't:
"prediction"=><ActionController::Parameters {"participant_id"=>"2", "match_id"=>"1", "local_score"=>"0", "visitant_score"=>"0"}
"prediction"=><ActionController::Parameters {"participant_id"=>"2", "match_id"=>"2", "local_score"=>"9", "visitant_score"=>"9"}
"prediction"=><ActionController::Parameters {"participant_id"=>"4", "match_id"=>"1", "local_score"=>"1", "visitant_score"=>"1"
All of these failed requests throw the "match_id already taken" error and update just fine in the console.
The controller request is touching the correct action with the method: :put. I've tried to change the syntax of the validation switching match_id for pool_id (which of course shouldn't make a difference) with no success.
Any, help would be appreciated.
app/models/prediction.rb
class MatchPrediction < ApplicationRecord
belongs_to :participant
belongs_to :match
validates :match_id, uniqueness: { scope: [:pool_participant_id] }
def correct_score?
if match.official_outcome
local_score == match.local_score && visitant_score == match.visitant_score ? true : false
end
end
def correct_outcome?
if match.official_outcome
predicted_outcome == match.official_outcome ? true : false
end
end
end
and server output pre rollback:
Started PUT "/pools/1/predictions/19" for 127.0.0.1 at 2018-03-29 18:17:11 +1100
Processing by PredictionsController#update as JS
Parameters: {"utf8"=>"✓", "authenticity_token"=>"SagJX+a7m0eCAdH7AdA0eYz6BVL1cesXYXOUkoe2FRynta6wyiWdskrC1007V1vyrIApPtdEQnVHWlzhSeJs5Q==", "prediction"=>{"participant_id"=>"4", "match_id"=>"2", "local_score"=>"7", "visitant_score"=>"0"}, "pool_id"=>"1", "id"=>"19"}
User Load (1.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 4], ["LIMIT", 1]]
↳ /Users/andres/.rvm/gems/ruby-2.5.0#global/gems/rack-2.0.4/lib/rack/tempfile_reaper.rb:15
Prediction Load (0.4ms) SELECT "predictions".* FROM "predictions" WHERE "predictions"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/controllers/predictions_controller.rb:86
(0.2ms) BEGIN
↳ app/controllers/predictions_controller.rb:55
Participant Load (0.2ms) SELECT "participants".* FROM "participants" WHERE "participants"."id" = $1 LIMIT $2 [["id", 4], ["LIMIT", 1]]
↳ app/controllers/predictions_controller.rb:55
Match Load (0.3ms) SELECT "matches".* FROM "matches" WHERE "matches"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
↳ app/controllers/predictions_controller.rb:55
Prediction Exists (1.8ms) SELECT 1 AS one FROM "predictions" WHERE "predictions"."match_id" = $1 AND "predictions"."id" != $2 AND "predictions"."participant_id" = $3 LIMIT $4 [["match_id", 2], ["id", 1], ["participant_id", 4], ["LIMIT", 1]]
↳ app/controllers/predictions_controller.rb:55
Pool Load (0.5ms) SELECT "pools".* FROM "pools" WHERE "pools"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/models/prediction.rb:35
Round Load (0.6ms) SELECT "rounds".* FROM "rounds" INNER JOIN "groups" ON "rounds"."id" = "groups"."round_id" WHERE "groups"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/models/prediction.rb:21
(0.2ms) ROLLBACK
↳ app/controllers/predictions_controller.rb:55
Embarrassing mistake really. I missed checking how the Predictions Controller was setting #prediction. I had entred the params to find the object incorrectly as #GorillaApe suggested:
def set_match_prediction
#prediction = Prediction.find(params[:pool_id])
end
instead of:
def set_match_prediction
#prediction = Prediction.find(params[:id])
end
this in turn was always setting the same object thus raising the validation error in all but one of the instances which, by chance, its :id coincided with :pool_id.

Actioncable: Counter does not increase live, even though user has sucessfully suscribed to channel

I believe I have set up Actioncable properly in my rails app on cloud9, but i can't seem to understand why the counter does not increase live and the message doesn't appear in the notification list automatically, if a user creates a comment on an article.
I am able to subscribe to the Notifications channel successfully.
Counter updates and notification gets rendered only after i reload the page, but I would like it to update automatically with Actioncable.
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 2], ["LIMIT", 1]]
Registered connection (Z2lkOi8vd2ViLWFwcC9Vc2VyLzI)
NotificationsChannel is transmitting the subscription confirmation
NotificationsChannel is streaming from notifications_channel
The broadcast job is enqueued, and message says the partials are rendered with the correct notification count and id.
Notification Load (1.1ms) SELECT "notifications".* FROM "notifications" WHERE "notifications"."id" = $1 LIMIT $2 [["id", 23], ["LIMIT", 1]]
[ActiveJob] [NotificationBroadcastJob] [7496ec3f-891b-47e2-b7e6-37d954aae3f5] Performing NotificationBroadcastJob (Job ID: 7496ec3f-891b-47e2-b7e6-37d954aae3f5) from Async(default) with arguments: 22, #<GlobalID:0x007fa20330ec40 #uri=#<URI::GID gid://web-app/Notification/23>>
[ActiveJob] [NotificationBroadcastJob] [7496ec3f-891b-47e2-b7e6-37d954aae3f5] Rendered notifications/_counter.html.erb (0.6ms)
[ActiveJob] [NotificationBroadcastJob] [7496ec3f-891b-47e2-b7e6-37d954aae3f5] Comment Load (0.4ms) SELECT "comments".* FROM "comments" WHERE "comments"."id" = $1 LIMIT $2 [["id", 15], ["LIMIT", 1]]
[ActiveJob] [NotificationBroadcastJob] [7496ec3f-891b-47e2-b7e6-37d954aae3f5] Article Load (0.3ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" = $1 LIMIT $2 [["id", 19], ["LIMIT", 1]]
[ActiveJob] [NotificationBroadcastJob] [7496ec3f-891b-47e2-b7e6-37d954aae3f5] User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
[ActiveJob] [NotificationBroadcastJob] [7496ec3f-891b-47e2-b7e6-37d954aae3f5] Rendered notifications/_notification.html.erb (7.0ms)
[ActiveJob] [NotificationBroadcastJob] [7496ec3f-891b-47e2-b7e6-37d954aae3f5] [ActionCable] Broadcasting to notification_channel: {:counter=>"<span class = \"glyphicon glyphicon-bell\"></span>\n<span id=\"notification-counter\">22</span>\n", :notification=>" <div class = \"row\" data-notification-id= \"23\">\n <div class= \"col-xs-10 col-md-10\">\n \n <li>Test1 posted a Comment on Si ergo lumen, quod </li> \n </div>\n <div class= \"col-xs-2 col-md-2\"> \n <a class=\"delete_notification\" data-remote=\"true\" rel=\"nofollow\" data-method=\"delete\" href=\"/notifications/23\">\n <span class=\"glyphicon glyphicon-remove\"></span>\n</a> </div>\n </div>\n <hr>\n"}
channels/notifications_channel.rb
class NotificationsChannel < ApplicationCable::Channel
def subscribed
stream_from "notifications_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
javascripts/channels/notifications.coffee
App.notifications = App.cable.subscriptions.create "NotificationsChannel",
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
# Called when there's incoming data on the websocket for this channel
$('#notificationList').prepend "#{data.notification}"
this.update_counter(data.counter)
update_counter: (counter) ->
$counter = $('#notification-counter')
val = parseInt $counter.text()
val++
$counter
.css({opacity: 0})
.text(val)
.css({top: '-10px'})
.transition({top: '-2px', opacity: 1})
Notification broadcast job
class NotificationBroadcastJob < ApplicationJob
queue_as :default
def perform(counter,notification)
ActionCable.server.broadcast 'notification_channel', counter: render_counter(counter), notification: render_notification(notification)
end
private
def render_counter(counter)
ApplicationController.renderer.render(partial: 'notifications/counter', locals: { counter: counter })
end
def render_notification(notification)
ApplicationController.renderer.render(partial: 'notifications/notification', locals: { notification: notification })
end
Notification model
class Notification < ApplicationRecord
after_create_commit { NotificationBroadcastJob.perform_later(Notification.count, self)}
end
views:
navigation bar:
<%= render "notifications/notification_center", notifications: #notifications %>
_notification_center.html.erb:
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<%= render 'notifications/counter', counter: notifications.count %>
</a>
<ul class="dropdown-menu" id="notificationContainer">
<div id="notificationTitle">Notifications</div>
<div id="notificationsBody">
<ul id="notificationList">
<%= render notifications %>
</ul>
</div>
<div id="notificationFooter">
<%= link_to notifications_path do %>
Show More
<% end %>
</div>
</ul>
_counter.html.erb
<span id="notification-counter"><%= counter %></span>
_notification.html.erb
<li>Comment posted on <%= notification.notifiable.article.title %></li>
UPDATE:
1) I do have to different channels, wondering if this is causing any problems with the subscription? I noticed that the 2 channels stop streaming when i click submit and before the comment gets committed to the database.
Started GET "/cable/" [WebSocket] for 103.252.202.198 at 2017-11-02 19:04:35 +0000
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
Registered connection (Z2lkOi8vd2ViLWFwcC9Vc2VyLzE)
NotificationsChannel is transmitting the subscription confirmation
NotificationsChannel is streaming from notifications_channel
ConversationChannel is transmitting the subscription confirmation
ConversationChannel is streaming from conversations-1
Finished "/cable/" [WebSocket] for 103.252.202.198 at 2017-11-02 19:04:46 +0000
NotificationsChannel stopped streaming from notifications_channel
ConversationChannel stopped streaming from conversations-1
Started POST "/articles/quia-receperunt-merc/comments" for 103.252.202.198 at 2017-11-02 19:04:47 +0000
Cannot render console from 103.252.202.198! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by CommentsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"G1ygwktPQhsScBjGyr9bCz8aZMx8wC39aVG3NKmXtYkjSsbHnbygO1cmltLi/PtH4qVdbJaYorDkoV/xFPcPRA==", "comment"=>{"content"=>"why is it not updating live"}, "commit"=>"Add Comment", "article_id"=>"quia-receperunt-merc"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
Article Load (0.4ms) SELECT "articles".* FROM "articles" WHERE "articles"."slug" = $1 LIMIT $2 [["slug", "quia-receperunt-merc"], ["LIMIT", 1]]
(0.8ms) BEGIN
SQL (0.6ms) INSERT INTO "comments" ("content", "article_id", "user_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["content", "why is it not updating live"], ["article_id", 14], ["user_id", 1], ["created_at", "2017-11-02 19:04:47.088094"], ["updated_at", "2017-11-02 19:04:47.088094"]]
(2.4ms) COMMIT
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
(0.1ms) BEGIN
SQL (1.1ms) INSERT INTO "notifications" ("recipient_id", "actor_id", "action", "notifiable_id", "notifiable_type", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id" [["recipient_id", 2], ["actor_id", 1], ["action", "posted"], ["notifiable_id", 14], ["notifiable_type", "Comment"], ["created_at", "2017-11-02 19:04:47.094991"], ["updated_at", "2017-11-02 19:04:47.094991"]]
(1.9ms) COMMIT
(0.5ms) SELECT COUNT(*) FROM "notifications"
[ActiveJob] Enqueued NotificationBroadcastJob (Job ID: ac5a005f-84d9-4ac8-be89-9f579732b167) to Async(default) with arguments: 13, #<GlobalID:0x007f10f0c6bc88 #uri=#<URI::GID gid://web-app/Notification/13>>
Redirected to https://test-site-dianaow.c9users.io/articles/quia-receperunt-merc
Completed 302 Found in 56ms (ActiveRecord: 8.7ms)
2) Upon checking in Chrome developer tool and following the steps here, I realized that there is no WebSocket message content after i submit the comment. Instead i get this constantly generating message: "type":"ping","message":1509650054}
3) I changed the adapter for development to Redis as i read that Redis is needed for multiple channels. However after running redis-server and restarting the rails server, I get a failed websocket connection message: WebSocket is closed before the connection is established. Am i setting up Redis wrongly?
cable.yml
production:
adapter: redis
url: <%= ENV["REDISCLOUD_URL"] %>
local: &local
adapter: redis
url: redis://127.0.0.1:6379/1
development: *local
test: *local
UPDATE 2:
Comments controller
def create
#comment = #article.comments.new(comment_params)
#comment.user = current_user
if #comment.save
flash[:success] = "Your comment was created successfully"
unless #comment.article.user == current_user
Notification.create!(recipient: #article.user, actor: current_user, action: "posted", notifiable: #comment)
end
redirect_to #article
else
flash[:error] = "Unable to submit comment."
redirect_to #article
end
end

Ruby on Rails: dependent object destroyed when transfered from guest user to registered user

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.

Resources