我需要为我的设计用户模型进行一些更改跟踪。我正在尝试使用paper_trail,但它在更改电子邮件和还原时会导致问题。我可以在下面的rails控制台的几行中演示这个问题:
1.9.3p385 :048 > u = User.create email: 'aaa@bob.com', password: 'password', username: 'bob'
(0.2ms) BEGIN
User Exists (0.3ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'aaa@bob.com' LIMIT 1
User Exists (0.1ms) SELECT 1 AS one FROM `users` WHERE `users`.`username` = BINARY 'bob' LIMIT 1
User Exists (0.1ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'aaa@bob.com' LIMIT 1
User Load (0.2ms) SELECT `users`.* FROM `users` WHERE `users`.`confirmation_token` = 'zNfBSVLJJ9nF4y5sanVU' LIMIT 1
SQL (0.2ms) INSERT INTO `users` (`confirmation_sent_at`, `confirmation_token`, `confirmed_at`, `created_at`, `current_sign_in_at`, `current_sign_in_ip`, `email`, `encrypted_password`, `last_sign_in_at`, `last_sign_in_ip`, `remember_created_at`, `reset_password_sent_at`, `reset_password_token`, `roles_mask`, `sign_in_count`, `unconfirmed_email`, `updated_at`, `username`) VALUES ('2013-03-05 13:15:53', 'zNfBSVLJJ9nF4y5sanVU', NULL, '2013-03-05 13:15:53', NULL, NULL, 'aaa@bob.com', '$2a$10$1BrAH1yqgF3qbgkmpMPl/uE0UY792aB8fvYKLksMnyQsn7sM/dY4O', NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, '2013-03-05 13:15:53', 'bob')
SQL (0.2ms) INSERT INTO `versions` (`created_at`, `event`, `item_id`, `item_type`, `object`, `whodunnit`) VALUES ('2013-03-05 13:15:53', 'create', 9, 'User', NULL, NULL)
(1.3ms) COMMIT
=> #<User id: 9, username: "bob", email: "aaa@bob.com", encrypted_password: "$2a$10$1BrAH1yqgF3qbgkmpMPl/uE0UY792aB8fvYKLksMnyQs...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, created_at: "2013-03-05 13:15:53", updated_at: "2013-03-05 13:15:53", roles_mask: nil, confirmation_token: "zNfBSVLJJ9nF4y5sanVU", confirmed_at: nil, confirmation_sent_at: "2013-03-05 13:15:53", unconfirmed_email: nil>
1.9.3p385 :049 > u.confirm!
(0.1ms) BEGIN
(0.6ms) UPDATE `users` SET `confirmation_token` = NULL, `confirmed_at` = '2013-03-05 13:16:23', `updated_at` = '2013-03-05 13:16:23' WHERE `users`.`id` = 9
SQL (0.4ms) INSERT INTO `versions` (`created_at`, `event`, `item_id`, `item_type`, `object`, `whodunnit`) VALUES ('2013-03-05 13:16:23', 'update', 9, 'User', '---\nusername: bob\nemail: aaa@bob.com\nencrypted_password: !binary |-\n JDJhJDEwJDFCckFIMXlxZ0YzcWJna21wTVBsL3VFMFVZNzkyYUI4ZnZZS0xr\n c01ueVFzbjdzTS9kWTRP\nreset_password_token: \nreset_password_sent_at: \nremember_created_at: \nsign_in_count: 0\ncurrent_sign_in_at: \nlast_sign_in_at: \ncurrent_sign_in_ip: \nlast_sign_in_ip: \ncreated_at: 2013-03-05 13:15:53.205031000 Z\nupdated_at: 2013-03-05 13:15:53.205031000 Z\nroles_mask: \nconfirmation_token: zNfBSVLJJ9nF4y5sanVU\nconfirmed_at: \nconfirmation_sent_at: 2013-03-05 13:15:53.204780000 Z\nunconfirmed_email: \nid: 9\n', NULL)
(25.4ms) COMMIT
=> true
1.9.3p385 :050 > u.versions.length
Version Load (0.5ms) SELECT `versions`.* FROM `versions` WHERE `versions`.`item_id` = 9 AND `versions`.`item_type` = 'User' ORDER BY created_at ASC, id ASC
=> 2
1.9.3p385 :051 > u.email = 'bbb@bob.com'
=> "bbb@bob.com"
1.9.3p385 :052 > u.save
(0.1ms) BEGIN
User Exists (0.3ms) SELECT 1 AS one FROM `users` WHERE (`users`.`email` = BINARY 'bbb@bob.com' AND `users`.`id` != 9) LIMIT 1
User Exists (0.2ms) SELECT 1 AS one FROM `users` WHERE (`users`.`username` = BINARY 'bob' AND `users`.`id` != 9) LIMIT 1
User Exists (0.2ms) SELECT 1 AS one FROM `users` WHERE (`users`.`email` = BINARY 'bbb@bob.com' AND `users`.`id` != 9) LIMIT 1
(0.2ms) UPDATE `users` SET `unconfirmed_email` = 'bbb@bob.com', `updated_at` = '2013-03-05 13:17:07' WHERE `users`.`id` = 9
SQL (0.2ms) INSERT INTO `versions` (`created_at`, `event`, `item_id`, `item_type`, `object`, `whodunnit`) VALUES ('2013-03-05 13:17:07', 'update', 9, 'User', '---\nusername: bob\nemail: aaa@bob.com\nencrypted_password: !binary |-\n JDJhJDEwJDFCckFIMXlxZ0YzcWJna21wTVBsL3VFMFVZNzkyYUI4ZnZZS0xr\n c01ueVFzbjdzTS9kWTRP\nreset_password_token: \nreset_password_sent_at: \nremember_created_at: \nsign_in_count: 0\ncurrent_sign_in_at: \nlast_sign_in_at: \ncurrent_sign_in_ip: \nlast_sign_in_ip: \ncreated_at: 2013-03-05 13:15:53.205031000 Z\nupdated_at: 2013-03-05 13:16:23.677037000 Z\nroles_mask: \nconfirmation_token: \nconfirmed_at: 2013-03-05 13:16:23.674113000 Z\nconfirmation_sent_at: 2013-03-05 13:15:53.204780000 Z\nunconfirmed_email: \nid: 9\n', NULL)
User Load (0.2ms) SELECT `users`.* FROM `users` WHERE `users`.`confirmation_token` = 'KJHQCtu74MqUSAdczu3Q' LIMIT 1
(11.2ms) UPDATE `users` SET `unconfirmed_email` = 'bbb@bob.com', `updated_at` = '2013-03-05 13:17:07', `confirmation_token` = 'KJHQCtu74MqUSAdczu3Q', `confirmation_sent_at` = '2013-03-05 13:17:07' WHERE `users`.`id` = 9
SQL (0.2ms) INSERT INTO `versions` (`created_at`, `event`, `item_id`, `item_type`, `object`, `whodunnit`) VALUES ('2013-03-05 13:17:07', 'update', 9, 'User', '---\nusername: bob\nemail: aaa@bob.com\nencrypted_password: !binary |-\n JDJhJDEwJDFCckFIMXlxZ0YzcWJna21wTVBsL3VFMFVZNzkyYUI4ZnZZS0xr\n c01ueVFzbjdzTS9kWTRP\nreset_password_token: \nreset_password_sent_at: \nremember_created_at: \nsign_in_count: 0\ncurrent_sign_in_at: \nlast_sign_in_at: \ncurrent_sign_in_ip: \nlast_sign_in_ip: \ncreated_at: 2013-03-05 13:15:53.205031000 Z\nupdated_at: 2013-03-05 13:16:23.677037000 Z\nroles_mask: \nconfirmation_token: \nconfirmed_at: 2013-03-05 13:16:23.674113000 Z\nconfirmation_sent_at: 2013-03-05 13:15:53.204780000 Z\nunconfirmed_email: \nid: 9\n', NULL)
(0.5ms) COMMIT
=> true
1.9.3p385 :053 > u.email
=> "aaa@bob.com"
1.9.3p385 :054 > u.unconfirmed_email
=> "bbb@bob.com"
1.9.3p385 :055 > u.confirm!
(0.1ms) BEGIN
User Exists (0.3ms) SELECT 1 AS one FROM `users` WHERE (`users`.`email` = BINARY 'bbb@bob.com' AND `users`.`id` != 9) LIMIT 1
User Exists (0.2ms) SELECT 1 AS one FROM `users` WHERE (`users`.`username` = BINARY 'bob' AND `users`.`id` != 9) LIMIT 1
User Exists (0.2ms) SELECT 1 AS one FROM `users` WHERE (`users`.`email` = BINARY 'bbb@bob.com' AND `users`.`id` != 9) LIMIT 1
(0.3ms) UPDATE `users` SET `confirmation_token` = NULL, `confirmed_at` = '2013-03-05 13:18:08', `email` = 'bbb@bob.com', `unconfirmed_email` = NULL, `updated_at` = '2013-03-05 13:18:08' WHERE `users`.`id` = 9
SQL (0.2ms) INSERT INTO `versions` (`created_at`, `event`, `item_id`, `item_type`, `object`, `whodunnit`) VALUES ('2013-03-05 13:18:08', 'update', 9, 'User', '---\nusername: bob\nemail: aaa@bob.com\nencrypted_password: !binary |-\n JDJhJDEwJDFCckFIMXlxZ0YzcWJna21wTVBsL3VFMFVZNzkyYUI4ZnZZS0xr\n c01ueVFzbjdzTS9kWTRP\nreset_password_token: \nreset_password_sent_at: \nremember_created_at: \nsign_in_count: 0\ncurrent_sign_in_at: \nlast_sign_in_at: \ncurrent_sign_in_ip: \nlast_sign_in_ip: \ncreated_at: 2013-03-05 13:15:53.205031000 Z\nupdated_at: 2013-03-05 13:17:07.788127000 Z\nroles_mask: \nconfirmation_token: KJHQCtu74MqUSAdczu3Q\nconfirmed_at: 2013-03-05 13:16:23.674113000 Z\nconfirmation_sent_at: 2013-03-05 13:17:07.790567000 Z\nunconfirmed_email: bbb@bob.com\nid: 9\n', NULL)
(15.4ms) COMMIT
=> true
1.9.3p385 :056 > u.email
=> "bbb@bob.com"
1.9.3p385 :057 > u.versions.last.reify.save # revert to before the email confirm
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 9 LIMIT 1
(0.1ms) BEGIN
User Exists (0.2ms) SELECT 1 AS one FROM `users` WHERE (`users`.`email` = BINARY 'aaa@bob.com' AND `users`.`id` != 9) LIMIT 1
User Exists (0.2ms) SELECT 1 AS one FROM `users` WHERE (`users`.`username` = BINARY 'bob' AND `users`.`id` != 9) LIMIT 1
User Exists (0.2ms) SELECT 1 AS one FROM `users` WHERE (`users`.`email` = BINARY 'aaa@bob.com' AND `users`.`id` != 9) LIMIT 1
(0.3ms) UPDATE `users` SET `created_at` = '2013-03-05 13:15:53', `updated_at` = '2013-03-05 13:17:07', `confirmation_token` = 'KJHQCtu74MqUSAdczu3Q', `confirmed_at` = '2013-03-05 13:16:23', `confirmation_sent_at` = '2013-03-05 13:17:07', `unconfirmed_email` = 'aaa@bob.com' WHERE `users`.`id` = 9
SQL (0.2ms) INSERT INTO `versions` (`created_at`, `event`, `item_id`, `item_type`, `object`, `whodunnit`) VALUES ('2013-03-05 13:20:13', 'update', 9, 'User', '---\nusername: bob\nemail: bbb@bob.com\nencrypted_password: !binary |-\n JDJhJDEwJDFCckFIMXlxZ0YzcWJna21wTVBsL3VFMFVZNzkyYUI4ZnZZS0xr\n c01ueVFzbjdzTS9kWTRP\nreset_password_token: \nreset_password_sent_at: \nremember_created_at: \nsign_in_count: 0\ncurrent_sign_in_at: \nlast_sign_in_at: \ncurrent_sign_in_ip: \nlast_sign_in_ip: \ncreated_at: 2013-03-05 13:15:53.000000000 Z\nupdated_at: 2013-03-05 13:18:08.000000000 Z\nroles_mask: \nconfirmation_token: \nconfirmed_at: 2013-03-05 13:18:08.000000000 Z\nconfirmation_sent_at: 2013-03-05 13:17:07.000000000 Z\nunconfirmed_email: \nid: 9\n', NULL)
User Load (0.2ms) SELECT `users`.* FROM `users` WHERE `users`.`confirmation_token` = 'q9Y5ovWigLySxvz7PsaB' LIMIT 1
(0.2ms) UPDATE `users` SET `created_at` = '2013-03-05 13:15:53', `updated_at` = '2013-03-05 13:17:07', `confirmed_at` = '2013-03-05 13:16:23', `confirmation_sent_at` = '2013-03-05 13:20:13', `unconfirmed_email` = 'aaa@bob.com', `confirmation_token` = 'q9Y5ovWigLySxvz7PsaB' WHERE `users`.`id` = 9
SQL (17.4ms) INSERT INTO `versions` (`created_at`, `event`, `item_id`, `item_type`, `object`, `whodunnit`) VALUES ('2013-03-05 13:20:13', 'update', 9, 'User', '---\nusername: bob\nemail: bbb@bob.com\nencrypted_password: !binary |-\n JDJhJDEwJDFCckFIMXlxZ0YzcWJna21wTVBsL3VFMFVZNzkyYUI4ZnZZS0xr\n c01ueVFzbjdzTS9kWTRP\nreset_password_token: \nreset_password_sent_at: \nremember_created_at: \nsign_in_count: 0\ncurrent_sign_in_at: \nlast_sign_in_at: \ncurrent_sign_in_ip: \nlast_sign_in_ip: \ncreated_at: 2013-03-05 13:15:53.000000000 Z\nupdated_at: 2013-03-05 13:18:08.000000000 Z\nroles_mask: \nconfirmation_token: \nconfirmed_at: 2013-03-05 13:18:08.000000000 Z\nconfirmation_sent_at: 2013-03-05 13:17:07.000000000 Z\nunconfirmed_email: \nid: 9\n', NULL)
(7.0ms) COMMIT
=> true
现在,我们已将用户恢复到新电子邮件确认之前的状态:
1.9.3p385 :059 > u = User.where(username: 'bob').first
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`username` = 'bob' LIMIT 1
=> #<User id: 9, username: "bob", email: "bbb@bob.com", encrypted_password: "$2a$10$1BrAH1yqgF3qbgkmpMPl/uE0UY792aB8fvYKLksMnyQs...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, created_at: "2013-03-05 13:15:53", updated_at: "2013-03-05 13:17:07", roles_mask: nil, confirmation_token: "q9Y5ovWigLySxvz7PsaB", confirmed_at: "2013-03-05 13:16:23", confirmation_sent_at: "2013-03-05 13:20:13", unconfirmed_email: "aaa@bob.com">
1.9.3p385 :060 > u.email
=> "bbb@bob.com"
1.9.3p385 :061 > u.unconfirmed_email
=> "aaa@bob.com"
正如我们所看到的,这不是用户之前的状态。我想这是因为Devise检测到电子邮件更改以及设置为新电子邮件设置为未确认电子邮件的内容。无论如何我还是可以在恢复到这个对象的先前版本时绕过这个吗?
另外,我注意到Devise为单个电子邮件更新执行2 USER users
个语句(第1个是第2个的子集)。这有什么理由并且有修复吗?它为用户添加了额外的,不必要的版本记录。
答案 0 :(得分:0)
我花了更多的时间来使用devise和paper_trail,并相信我已经想出如何手动更改电子邮件并跳过(重新)确认选项。
# skipping the initial confirmation
user = User.new email: 'foo@bar.com', password: 'secret', username: 'foo'
user.skip_confirmation!
user.save
user.versions.length # => 1
# skip the reconfirmation
user.email = 'bar@baz.com'
user.skip_reconfirmation!
user.save
user.versions.length # => 2
# revert
reverted_user = user.versions.last.reify
reverted_user.live? # => false
reverted_user.skip_reconfirmation!
reverted_user.save
reverted_user.live? # => true
reverted_user.email # => "foo@bar.com"
skip_reconfirmation!
直接将保存设置为email
字段,而不是将其设置为unconfirmed_email
。
编辑:经过更多工作后,我又对paper_trail正在做什么感到困惑:
let(:user) { User.new(username: 'bob', email: 'bob@default.com', password: 'secret101') }
it "can revert to a previous version" do
user.save # version 1 (unconfirmed)
user.confirm! # version 2 (confirmed)
user.email.should eq 'bob@default.com'
user.unconfirmed_email.should be_nil
user.update_attributes email: 'bob@builder.com' # version 3, 4 (un-reconfirmed)
user.versions.should have(4).items
user.email.should eq 'bob@default.com'
user.unconfirmed_email.should eq 'bob@builder.com'
user.confirm! # version 5 (reconfirmed)
user.versions.should have(5).items
user.email.should eq 'bob@builder.com'
user.unconfirmed_email.should be_nil
# revert to the first confirmed user
reverted_user = user.versions[2].reify
reverted_user.email.should eq 'bob@default.com'
reverted_user.unconfirmed_email.should be_nil
reverted_user.should be_confirmed
reverted_user.should_not be_live
reverted_user.skip_reconfirmation!
reverted_user.save!.should be_true
reverted_user.should be_live # FAILS
live_user = User.where(username: 'bob').first
live_user.should be_live
live_user.should be_confirmed
live_user.email.should eq 'bob@default.com'
live_user.unconfirmed_email.should be_nil
user.email.should eq 'bob@builder.com' # => passes
user.should_not be_live # => FAILS
end
我不明白为什么用“FAILS”注释的2个项目都没有通过规范