I'm building an invitation system where a user can either invite a friend, setting their user.id to the sender field in the database, or request an invitation, which should set the sender to '0' (an integer field).
User model
...
has_many :invites, :class_name => 'Invitation', :foreign_key => 'sender' #sent_invitations
belongs_to :invitation
...
Invitation model
belongs_to :sender, :class_name => 'User'
has_one :recipient, :class_name => 'User'
Invitations controller
def create
#invitation = Invitation.new(invitation_params)
if current_user?
#invitation.sender = current_user
if #invitation.save
redirect_to invitations_url, notice: 'Thank you. Your invitation has been sent.'
else
render action: "new"
end
else
#invitation.sender = 0
if #invitation.save
redirect_to invitations_url, notice: 'Thank you. You request is being processed..'
else
render action: "new"
end
end
end
The invitation is being created (I can see it in the database), but the sender isn't being set. This is the output form the dev_log:
Started POST "/invitations" for 127.0.0.1 at 2016-05-14 17:49:54 -0600
Processing by InvitationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"aCasBmkfw0m1T/EwuBUlXTA/z+REEWo3Hpv2HpB9w6s=", "invitation"=>{"name"=>"john", "surname"=>"public", "recipient_email"=>"jp#sasdf.com"}, "commit"=>"Create Invitation"}
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."auth_token" = 'i__MG0iqyoIND68k6qJmvw' LIMIT 1
DEPRECATION WARNING: You're trying to create an attribute `sender_id'. Writing arbitrary attributes on a model is deprecated. Please just use `attr_writer` etc. (called from create at C:/Sites/template/app/controllers/invitations_controller.rb:38)
(0.0ms) begin transaction
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."email" = 'jp#sasdf.com' LIMIT 1
SQL (1.0ms) INSERT INTO "invitations" ("created_at", "invite_token", "name", "recipient_email", "sender", "sent_at", "surname", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?) [["created_at", Sat, 14 May 2016 23:49:55 UTC +00:00], ["invite_token", "m_1zd0UxW3W1JqoDdp1EMA"], ["name", "john"], ["recipient_email", "jp#sasdf.com"], ["sender", nil], ["sent_at", nil], ["surname", "public"], ["updated_at", Sat, 14 May 2016 23:49:55 UTC +00:00]]
(0.0ms) UPDATE "users" SET "invites_avail" = 4, "updated_at" = '2016-05-14 23:49:55.161966' WHERE "users"."id" = 2
(70.0ms) commit transaction
Redirected to http://localhost:3000/invitations
Completed 302 Found in 193.0ms (ActiveRecord: 76.0ms)
I'm stumped because #1, I'm not creating an arbitrary attribute "sender_id". The applicable column in the database is "sender", and #2, the POST isn't setting the sender id, which should either be "0" or the id of the current_user.
What am I doing wrong?
I couldn't find an answer to my question, so I monkeyed it instead.
Since I'm able to get the invitation.sender to save when the invitation is sent from a current_user, and the rest were just setting nothing (NULL) in the database, I changed the query parameters in the invitations_controller.rb to look for #invitation.sender = nil for all corporate-sent invitations.
I'll leave this up until/unless someone posts a better response. Cheers!
Related
I am trying to set up a messaging system on my app and I had it working, but now ActiveRecord is saving the wrong id for the recipient_id.
Here's where the user.id is loaded as 3 but when the values are inserted the messages's recipient.id is 5:
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3 ORDER BY "users"."id"
ASC LIMIT 1
Message Load (0.4ms) SELECT "messages".* FROM "messages" LIMIT 1
User Load (0.6ms) SELECT "users".* FROM "users" LIMIT 1
(0.5ms) BEGIN
SQL (1.3ms) INSERT INTO "messages" ("content", "created_at", "recepient_id", "sender_id",
"subject", "updated_at") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [["content", "erferfe"],
["created_at", Wed, 17 Dec 2014 19:15:08 UTC +00:00], ["recepient_id", 5], ["sender_id", 3],
["subject", "erfer"], ["updated_at", Wed, 17 Dec 2014 19:15:08 UTC +00:00]]
(5.4ms) COMMIT
Redirected to http://0.0.0.0:3000/messages
Completed 302 Found in 23ms (ActiveRecord: 9.6ms)
Here's MessagesController#create:
def create
#message = current_user.sent_messages.new(message_params)
#message.recepient_id = User.find_by(params[:id]).id
if #message.save
flash[:notice] = 'Message has been sent.'
redirect_to messages_path
else
render :action => :new
end
end
Here's messages.rb:
class Message < ActiveRecord::Base
belongs_to :sender, class_name: "User", primary_key: "sender_id"
belongs_to :recepient, class_name: "User", primary_key: "recepient_id"
belongs_to :user
This is the routing for the messages:
resources :users do
resources :messages
end
This is the view:
<div class="ui button"><i class="mail icon"></i><%= link_to 'Message', new_user_message_path(#user) %></div>
Message_params:
def message_params
params.require(:message).permit(:subject, :user_id, :content, :recepient_id)
end
User.rb
class User < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :omniauthable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauth_providers => [:facebook, :twitter]
TEMP_EMAIL_PREFIX = 'change#me'
TEMP_EMAIL_REGEX = /\Achange#me/
attr_accessor :login
has_many :projects, class_name: "Project", foreign_key: "creator_id"
has_many :sent_messages, class_name: "Message", foreign_key: "sender_id"
has_many :recieved_messages, class_name: "Message", foreign_key: 'recepient_id'
has_many :messages
has_many :projects, dependent: :destroy
has_many :authentications, :dependent => :destroy
validates :email, presence: true,
uniqueness: true,
format: {
with: /\A[A-Za-z0-9._%+-]+#[A-Za-z0-9\.-]+\.[A-Za-z]+\Z/
}
validates_format_of :email, :without => TEMP_EMAIL_REGEX, on: :update
def downcase_email
self.email = email.downcase
end
def generate_password_reset_token
update_attribute(:password_reset_token, SecureRandom.urlsafe_base64(48))
end
def self.find_for_facebook_oauth(auth)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
unless user
user = User.create(:first_name => auth.extra.raw_info.first_name,
:last_name => auth.extra.raw_info.last_name,
:avatar => auth.info.image,
:provider => auth.provider,
:uid => auth.uid,
:email => auth.info.email,
:password => Devise.friendly_token[0,20]
)
user.confirm!
end
user.save
end
def self.find_for_twitter_oauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.avatar = auth.info.image
user.save
end
end
After code change suggested:
User Load (1.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 ORDER BY "users"."id" ASC
LIMIT 1
Message Load (0.3ms) SELECT "messages".* FROM "messages" LIMIT 1
(0.1ms) BEGIN
SQL (0.4ms) INSERT INTO "messages" ("content", "created_at", "sender_id", "subject", "updated_at")
VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["content", "efvefvf"], ["created_at", Wed, 17 Dec 2014
21:02:48 UTC +00:00], ["sender_id", 2], ["subject", "erfvef"], ["updated_at", Wed, 17 Dec 2014
21:02:48 UTC +00:00]]
(7.2ms) COMMIT
Also when I go /users/:user_id/messages my css and javascript files don't work
I am assuming that this feature is initialized from a User "Profile" page which allows the current session user (sender) to send a message to the profile user (recipient).
First off, in your Message model, you do not need belongs_to :user on the Message model nor do you need has_many :messages on the User model.
Next, you have way over complicated the #create action:
def create
#message = current_user.sent_messages.new(message_params)
#message.recipient_id = params[:user_id]
if #message.save
flash[:notice] = 'Message has been sent.'
redirect_to messages_path
else
render :action => :new
end
end
Now, adjust your message_params. You do not need the user_id (will be provided by current_user) nor do you need the recipient_id (will be provided by the url param).
def message_params
params.require(:message).permit(:subject, :content)
end
Give that a try.
I am calling this js from a link:
function createNewTopLevelEntry(){
var user_id = $("#user").val();
var header = prompt("Enter the name");
$.ajax( '/users/' + user_id + '/entries', {
data: {
entry: { header: header,
user: user_id } },
type: 'POST',
cache: false,
dataType: 'json',
success: displayTopLevelEntries
});
}
It hits this controller:
def create
#entry = Entry.new(params[:entry])
respond_to do |format|
if #entry.save
format.html { redirect_to #entry, notice: 'Entry was successfully created.' }
format.json { render json: #entry, status: :created, location: #entry }
else
format.html { render action: "new" }
format.json { render json: #entry.errors, status: :unprocessable_entity }
end
end
end
This is the response on the server:
Started POST "/users/1/entries" for 127.0.0.1 at 2013-03-25 21:50:36 -0700
Processing by EntriesController#create as JSON
Parameters: {"entry"=>{"header"=>"Hi", "user"=>"1"}, "user_id"=>"1"}
(0.1ms) begin transaction
SQL (0.5ms) INSERT INTO "entries" ("completed", "created_at", "endtime", "header", "parent", "starttime", "starttimeset", "text", "totaltime", "updated_at", "user") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["completed", nil], ["created_at", Tue, 26 Mar 2013 04:50:36 UTC +00:00], ["endtime", nil], ["header", "Hi"], ["parent", nil], ["starttime", nil], ["starttimeset", nil], ["text", nil], ["totaltime", nil], ["updated_at", Tue, 26 Mar 2013 04:50:36 UTC +00:00], ["user", "1"]]
(2.5ms) commit transaction
Completed 500 Internal Server Error in 10ms
NoMethodError - undefined method `entry_url' for #<EntriesController:0x007fb22b9f7fd8>:
(gem) actionpack-3.2.11/lib/action_dispatch/routing/polymorphic_routes.rb:129:in `polymorphic_url'
(gem) actionpack-3.2.11/lib/action_dispatch/routing/url_for.rb:150:in `url_for'
(gem) actionpack-3.2.11/lib/action_controller/metal/rendering.rb:60:in `_process_options'
(gem) actionpack-3.2.11/lib/action_controller/metal/streaming.rb:208:in `_process_options'
(gem) actionpack-3.2.11/lib/action_controller/metal/renderers.rb:34:in `block in _handle_render_options'
What is the entry_url? Why is it looking for it? Do i need to include something in the model. Its just has attr_accessors for the vars.
class Entry < ActiveRecord::Base
attr_accessible :completed, :endtime, :header, :starttime, :starttimeset, :totaltime, :user, :text, :parent
end
Heres is my routes file:
Tasks::Application.routes.draw do
match '/users/:id/projects' => 'users#show_projects_for_user'
authenticated :user do
root :to => 'home#index'
end
root :to => "home#index"
devise_for :users
resources :users do
resources :entries
end
end
Thanks for the help.
The entry_url is what it's asking you to redirect to when you say redirect_to #entry
You don't have an entries resource in the routes file. You do have one nested within user, but then you need to pass as well as the entry.
redirect_to [ #user, #entry ]
just saw your comment - if it's doing this on the JSON path similarly you need to have
location: [#user, #entry]
Basically anywhere you're asking rails to build a url for an entry you need to pass the entry's user in because you have entry nested within user in the routes and not as a standalone resource routing.
Adding an edit to respond to the comment because there's no formatting in comments:
Yes, this it will work to delete the location as it will no longer call the helper to build that location in the json, but I am presuming you want that. So try this to make the location work:
format.json { render json => { :entry => #entry, :status => created, :location => [#user, #entry] }}
from your comment... if that's not working then let's try calling the url helper directly
format.json { render json => { :entry => #entry, :status => created, :location => user_entry_url(#user, #entry) }}
If you are using Rails3, this might case because with rails3, the url has become path
Ex:
#rails2
entry_url
#rails3
entry_path
So try entry_path instead of entry_url
I'm trying to create a few associated factories, but the Event is NOT working:
factories.rb
factory :user, class: User do
first_name 'John'
last_name 'Doe'
email { "#{first_name}.#{last_name}#example.com".downcase }
username 'johndoe'
password 'johndoe'
password_confirmation 'johndoe'
association :account_id, factory: :account
end
factory :account, class: Account do
#id is the only field
end
factory :event, class: Event do
name 'Go to the Dentist'
start_date "#{Time.now.next_month}"
end_date "#{Time.now+1.hour.next_month}"
copyright "#{Time.now.year}"
association :account_id, factory: :account
end
controller_spec.rb
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
#user = FactoryGirl.create(:user_profile, :username => 'johndoe' )
sign_in #user
#acct = FactoryGirl.create(:account, :id => #user.account_id)
#event = FactoryGirl.create(:event, :account_id => #acct.id)
end
but this event line is where it all goes awry. Even if I use #user.account_id to set :account_id for event, it fails with this error:
Failure/Error: #event = FactoryGirl.create(:event, :account_id => #acct.id)
ActiveRecord::StatementInvalid:
SQLite3::ConstraintException: constraint failed: INSERT INTO "event" ("account_id", "copyright", "created_at", "deleted", "end_date", "info", "name", "start_date", "type", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
# ./spec/controllers/controller_spec.rb:13:in `block (3 levels) in <top (required)>'
Thank you very much for any advice you can offer on this!
I think the reason you are getting the constraint exception is that you have already have some data in your tables that conflicts the information you are trying to add.
You probably want to delete all data and then try the rspec test case.
I have testing code as follow, why "test "should get create" do" always failed????
# called before every single test
def setup
logger.debug '2'
#new_user = User.create(:email => 'shrimpy#email.com',
:password => 'secret',
:password_confirmation => 'secret')
logger.debug "==========> #{#new_user.id}"
end
# called after every single test
def teardown
#new_user.delete
end
test "should get create" do
logger.debug '1'
get :create, :email => "shrimpy#email.com", :password => "secret"
assert_response :success # <------why it always failed here?
assert_redirected_to user_path(#new_user)
end
Console output:
Finished in 3.834383 seconds.
1) Failure:
test_should_get_create(SessionsControllerTest) [test/functional/sessions_controller_test.rb:24]:
Expected block to return true value.
1 tests, 1 assertions, 1 failures, 0 errors, 0 skips
test.log:
2
SQL (2.0ms) SELECT 1 FROM "users" WHERE ("users"."email" = 'shrimpy#email.com') LIMIT 1
SQL (0.0ms) SELECT name
FROM sqlite_master
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
AREL (6.0ms) INSERT INTO "users" ("email", "hashed_password", "salt", "created_at", "updated_at") VALUES ('shrimpy#email.com', 'b4e991c44d9738effa3
98e97d7ed1e6ccad19c90ce2e911344a21bf9c82f915f', '258003960.04544115923816805', '2011-04-21 07:04:00.891929', '2011-04-21 07:04:00.891929')
==========> 980190963
1
Processing by SessionsController#create as HTML
Parameters: {"email"=>"shrimpy#email.com", "password"=>"[FILTERED]"}
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."email" = 'shrimpy#email.com' LIMIT 1
Redirected to http://test.host/users/980190963
Completed 302 Found in 166ms
AREL (0.0ms) DELETE FROM "users" WHERE "users"."id" = 980190963
This is the function i am testing:
def create
if user = User.authenticate(params[:email], params[:password])
session[:user_id] = user.id
redirect_to user_path(user), :notice => 'You have successfully login'
else
redirect_to login_url, :alert => "Invalid email/password combination"
end
end
Because the response isn't success, the response is a redirect.
Understanding this requires a little understanding of HTTP, and a little understanding of what assert_response :success and redirect_to do. All that assert_response :success is testing is the HTTP response code, it's testing to see whether it's a success code. You get a success code when you render a page back to the browser. When you redirect you do not get a success code, you get a redirect code instead.
So if you want to check the response you could use assert_response :redirect - although this is redundant when you already have the assert_redirected_to
Read more here in the Rails guide for testing
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"=>"_______________________#gmail.com", "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('_______________________#gmail.com')) 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 ('_______________________#gmail.com', '$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 _________#gmail.com (1966ms)
Date: Mon, 18 Apr 2011 21:01:55 -0500
From: __________
Reply-To: ___________
To: _____________
Message-ID: <4daced1352c84_1ff5817d6b04978cd#Titan.local.mail>
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: _________#gmail.com. You can confirm your account through the link below:</p>
<p>Confirm my account</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('_______________________#gmail.com')) 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('_______________________#gmail.com')) 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('_______________________#gmail.com')) 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('_______________________#gmail.com')) 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('_______________________#gmail.com')) 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
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.