Reputation: 43123
I changed my User model to accept_nested_attributes_for Profile, and I'm trying to create the User and Profile at the same time. I'm using Devise for authentication.
This seems to be working -- except for one giant gotcha...
Every time I create a new user it crashes the app with "Illegal Instruction", and when I check the log it looks like this...
Started POST "/users" for 127.0.0.1 at 2011-04-18 21:01:54 -0500
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"‚úì", "authenticity_token"=>"Rua6PUxnE4a4TvaFcVMfmycw8Y9AFRjEsXVrqwWC2EM=", "user"=>{"email"=>"[email protected]", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "profile_attributes"=>{"first_name"=>"Name", "last_name"=>"Tester"}, "student_claimed"=>"false", "school"=>"", "invite_code"=>"Texas!", "terms_of_service"=>"1"}, "commit"=>"Create Account!"}
[1m[35mSQL (0.3ms)[0m SELECT name
FROM sqlite_master
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
[1m[36mSQL (0.3ms)[0m [1m SELECT name
FROM sqlite_master
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
[0m
[1m[35mUser Load (0.2ms)[0m SELECT "users"."id" FROM "users" WHERE (LOWER("users"."email") = LOWER('[email protected]')) LIMIT 1
[1m[36mInvitation Load (0.1ms)[0m [1mSELECT "invitations".* FROM "invitations" WHERE "invitations"."code" = 'Texas!' LIMIT 1[0m
[1m[35mUser Load (0.1ms)[0m SELECT "users".* FROM "users" WHERE "users"."confirmation_token" = 'duALIT6yCL5ShpMvbw79' LIMIT 1
[1m[36mRole Load (0.3ms)[0m [1mSELECT "roles".* FROM "roles" WHERE "roles"."name" = 'member' LIMIT 1[0m
[1m[35mAREL (0.3ms)[0m UPDATE "invitations" SET "remaining_uses" = 9993, "updated_at" = '2011-04-19 02:01:54.506243' WHERE "invitations"."id" = 1
[1m[36mAREL (0.2ms)[0m [1mINSERT INTO "users" ("email", "encrypted_password", "reset_password_token", "remember_token", "remember_created_at", "sign_in_count", "current_sign_in_at", "last_sign_in_at", "current_sign_in_ip", "last_sign_in_ip", "created_at", "updated_at", "plan_code", "confirmation_token", "confirmed_at", "confirmation_sent_at", "student_claimed", "student_confirmed", "school", "invitation_id") VALUES ('[email protected]', '$2a$10$7qzC7T6b1kLiXvPSkMRkduCFClBznDWnnOu7I1ssU8blB9NMJznn2', NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, '2011-04-19 02:01:54.509656', '2011-04-19 02:01:54.509656', NULL, 'duALIT6yCL5ShpMvbw79', NULL, '2011-04-19 02:01:54.437796', 'f', 'f', '', 1)[0m
[1m[35mSQL (0.1ms)[0m INSERT INTO "roles_users" ("role_id", "user_id") VALUES (3, 6)
Rendered devise/mailer/confirmation_instructions.html.erb (0.9ms)
Sent mail to [email protected] (1966ms)
Date: Mon, 18 Apr 2011 21:01:55 -0500
From: __________
Reply-To: ___________
To: _____________
Message-ID: <[email protected]>
Subject: Please confirm your email address
Mime-Version: 1.0
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit
<p>Name,</p>
<p>You registered with the email address: [email protected]. You can confirm your account through the link below:</p>
<p><a href="http://localhost:3000/users/confirmation?confirmation_token=duALIT6yCL5ShpMvbw79">Confirm my account</a></p>
<p>Thanks for signing up!</p>
[1m[36mAREL (0.2ms)[0m [1mINSERT INTO "profiles" ("first_name", "last_name", "created_at", "updated_at", "user_id", "avatar_file_name", "avatar_content_type", "avatar_file_size", "avatar_updated_at", "address1", "city", "state", "country", "zip") VALUES ('Name', 'Tester', '2011-04-19 02:01:57.266502', '2011-04-19 02:01:57.266502', 6, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL)[0m
[paperclip] Saving attachments.
[1m[35mUser Load (0.1ms)[0m SELECT "users"."id" FROM "users" WHERE (LOWER("users"."email") = LOWER('[email protected]')) AND ("users".id <> 6) LIMIT 1
[1m[36mUser Load (1.6ms)[0m [1mSELECT "users".* FROM "users" WHERE "users"."id" = 6 LIMIT 1[0m
[1m[35mProfile Load (1.6ms)[0m SELECT "profiles".* FROM "profiles" WHERE ("profiles".user_id = 6)
[1m[36mCACHE (0.0ms)[0m [1mSELECT "users"."id" FROM "users" WHERE (LOWER("users"."email") = LOWER('[email protected]')) AND ("users".id <> 6) LIMIT 1[0m
[1m[35mCACHE (0.0ms)[0m SELECT "users".* FROM "users" WHERE "users"."id" = 6 LIMIT 1
[1m[36mCACHE (0.0ms)[0m [1mSELECT "profiles".* FROM "profiles" WHERE ("profiles".user_id = 6)[0m
[1m[35mCACHE (0.0ms)[0m SELECT "users"."id" FROM "users" WHERE (LOWER("users"."email") = LOWER('[email protected]')) AND ("users".id <> 6) LIMIT 1
[1m[36mCACHE (0.0ms)[0m [1mSELECT "users".* FROM "users" WHERE "users"."id" = 6 LIMIT 1[0m
[1m[35mCACHE (0.0ms)[0m SELECT "profiles".* FROM "profiles" WHERE ("profiles".user_id = 6)
[1m[36mCACHE (0.0ms)[0m [1mSELECT "users"."id" FROM "users" WHERE (LOWER("users"."email") = LOWER('[email protected]')) AND ("users".id <> 6) LIMIT 1[0m
[1m[35mCACHE (0.0ms)[0m SELECT "users".* FROM "users" WHERE "users"."id" = 6 LIMIT 1
[1m[36mCACHE (0.0ms)[0m [1mSELECT "profiles".* FROM "profiles" WHERE ("profiles".user_id = 6)[0m
[1m[35mCACHE (0.0ms)[0m SELECT "users"."id" FROM "users" WHERE (LOWER("users"."email") = LOWER('[email protected]')) AND ("users".id <> 6) LIMIT 1
[1m[36mCACHE (0.0ms)[0m [1mSELECT "users".* FROM "users" WHERE "users"."id" = 6 LIMIT 1[0m
[1m[35mCACHE (0.0ms)[0m SELECT "profiles".* FROM "profiles" WHERE ("profiles".user_id = 6)
... and so on for about 100 more lines ...
[1m[35mCACHE (0.0ms)[0m SELECT "users".* FROM "users" WHERE "users"."id" = 6 LIMIT 1
[1m[36mCACHE (0.0ms)[0m [1mSELECT "profiles".* FROM "profiles" WHERE ("profiles".user_id = 6)[0m
[1m[36mSQL (0.3ms)[0m [1m SELECT name
So, this wasn't happening before I started accepting nested attributes... and I'm pretty confused as to why it's happening now. Does anyone have any insight into how to debug this and fix the problem?
Thanks!
--EDIT--
User Model:
class User < ActiveRecord::Base
# RELATIONSHIPS
has_one :profile, :dependent => :destroy
has_many :photos
has_many :votes
has_many :voted_photos, :through => :votes, :source => :photo
has_many :ratings
has_many :rated_photos, :through => :ratings, :source => :photo
has_many :comments
has_and_belongs_to_many :roles
has_many :assignments
has_many :collections, :through => :assignments
belongs_to :invitation
accepts_nested_attributes_for :profile
# VIRTUAL ATTRIBUTES
attr_accessor :invite_code
# AUTHENTICATION
devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable, :confirmable
# SECURITY
attr_accessible :email, :password, :password_confirmation, :remember_me, :confirmed_at, :invite_code, :student_claimed, :school, :terms_of_service, :profile_attributes
# FILTERS
before_create :set_role_to_member, :set_invitation
after_save :update_recurly_account, :unless => Proc.new { Rails.env.test? }
# VALIDATIONS
validates_acceptance_of :terms_of_service, :message => "You must agree to the terms of service in order to create an account."
validate :invitation_status, :on => :create
validates_presence_of :profile
validates_associated :profile
# DELEGATES
delegate :first_name, :last_name, :full_name,
:to => :profile,
:allow_nil => true
# ROLES
def set_role_to_member
self.roles << Role.find_by_name('member')
end
def has_role?( r )
!roles.find_by_name( r ).nil?
end
def list_roles
list = []
roles.all.each do |r|
list << r.name
end
list.join(', ')
end
# DEVISE RELATED
# Hook up recurly account after confirmation
def confirm!
self.setup_recurly_account unless Rails.env.test?
if student_claimed && validate_student_email
self.student_confirmed = true
self.save
end
super
end
protected
# Don't require password on update
def password_required?
!persisted? || password.present? || password_confirmation.present?
end
public
# RECURLY RELATED
def setup_recurly_account
...
end
private
def update_recurly_account
...
end
def validate_student_email
self.email =~ /\.edu$/ ? true : false
end
def invitation_status
...
end
def set_invitation
...
end
end
Profile Model
class Profile < ActiveRecord::Base
include Helpers::AssetStorage
# RELATIONSHIPS
belongs_to :user
stores_file_as :avatar,
:styles => { :tenth => "87x87#", :eighth => "106x106#" },
:filename_interpolation => "avatars/:user_id/:id_:style.:extension",
:default_url => '/images/no_avatar_:style.png'
# VALIDATIONS
validates_presence_of :first_name, :last_name
# CALLBACKS
after_update :save_user
def full_name
[first_name,last_name].join(" ")
end
private
def save_user
self.user.save!
end
end
Upvotes: 1
Views: 507
Reputation: 16011
You don't need the save_user
callback for Profile
model.
When doing user.save
, it automatically save user.profile
. Due to the callback, the user.profile
saved, and it calls it's user to save again. And the user save, it also save his profile......
That's the loop.
So the simplest modification would be remove the after_update callback in Profile model.
If you want to save the profile only, use profile.save
. If the user object has updates too, use user.save
or profile.user.save
.
Upvotes: 1