Belongs_to causing NoMethodError exceptions ...?

I’ve been pulling my hair our over this same problem for the last few
hours… Did you ever figure out the resolution? I found this thread
here:
http://lists.rubyonrails.org/pipermail/rails/2005-December/007657.html
but
see at the time you were not able to get an answer from the list.

Any suggestion greatly appreciated!

Chris

I’ve got a really strange problem using belongs_to. I apologize in
advance for the length… this is going to take a while to explain.

Basic idea: Creating a User requires that the user enter an email
address and activation key that matches an existing PendingUser.
After creating the user successfully, that pending user should be
marked as “used”.

The problem:
When I add a belongs_to :pending_user declaration to the User class,
attempting to save a User yields this exception (taken here from the
output of a simple test case – code below.) You’ll note some lines
in the User class with two hashes ‘##’ preceding them – those are
alternate statements that avoid using the .pending_user attribute. If
you comment out the belongs_to :pending_user and use the alternate
statements (which just work with the pending_user_id field),
everything seems to work fine.

  1. Error:
    test_create_with_valid_pending_user(UserTest):
    NoMethodError: undefined method updated?' for #<PendingUser:0x8d09310> /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/ active_record/base.rb:1498:in method_missing’
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/
    active_record/callbacks.rb:341:in callback' /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/ active_record/callbacks.rb:335:in callback’
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/
    active_record/callbacks.rb:330:in each' /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/ active_record/callbacks.rb:330:in callback’
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/
    active_record/callbacks.rb:248:in create_or_update' /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/ active_record/base.rb:1226:in save_without_validation’
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/
    active_record/validations.rb:698:in save_without_transactions' /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/ active_record/transactions.rb:126:in save’
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/
    active_record/transactions.rb:126:in transaction' /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/ active_record/transactions.rb:91:in transaction’
    /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/
    active_record/transactions.rb:118:in transaction' /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.13.2/lib/ active_record/transactions.rb:126:in save’
    test/unit/user_test.rb:62:in `test_create_with_valid_pending_user’

I’m not calling the ‘updated?’ method anywhere in my code, but
looking in ${PREFIX}/lib/ruby/gems/1.8/doc/activerecord-1.13.2/rdoc/
classes/ActiveRecord/Associations/ClassMethods.src, it seems that
belongs_to calls the .updated? method.

Any light you can shed would be greatly appreciated – I’ve been
banging my head against this for a few days, and no one on
#rubyonrails on freenode has been able to figure out what’s going on.

-Marshall Pierce

Code follows…

Test case:

require File.dirname(FILE) + ‘/…/test_helper’

class UserTest < Test::Unit::TestCase
fixtures :accounts, :pending_users

we don’t need the users fixture – all we’re testing here is

creating a new user

and the corresponding validation of the associated pending user

and account
def setup
@new = User.new
# give it a valid email address
@new.email_address = pending_users(:ok_req).email_address
@new.password = @new.password_confirmation = ‘doesntmatter’
@new.activation_key = ‘activationkey’
@new.account_id = pending_users(:ok_req).account_id
end

def test_create_with_valid_pending_user
assert @new.save
end
end

SQL definitions for the tables for the classes involved (postgresql):

DROP TABLE generic_users CASCADE;
CREATE TABLE generic_users (
password_hash text NOT NULL
, email_address text UNIQUE NOT NULL
, created_at timestamp with time zone NOT NULL
, updated_at timestamp with time zone NOT NULL
);

DROP TABLE users CASCADE;
CREATE TABLE users (
LIKE generic_users INCLUDING DEFAULTS
, id SERIAL PRIMARY KEY
, account_id int NOT NULL REFERENCES accounts(id) ON
UPDATE CASCADE ON DELETE CASCADE
, is_account_admin bool NOT NULL DEFAULT false
, email_validation_key text NOT NULL
, email_validated bool NOT NULL DEFAULT false
, pending_user_id int NOT NULL REFERENCES pending_users(id)
ON UPDATE CASCADE ON DELETE CASCADE
);

DROP TABLE pending_users CASCADE;
CREATE TABLE pending_users (
id SERIAL PRIMARY KEY
, account_id int NOT NULL REFERENCES accounts(id) ON
UPDATE CASCADE ON DELETE CASCADE
, email_address text UNIQUE NOT NULL
, activation_key_hash text NOT NULL
, created_at timestamp with time zone NOT NULL
, updated_at timestamp with time zone NOT NULL
, used bool NOT NULL DEFAULT false
);

The class definitions: (yeah, I know – re-implementing login logic
yet again is dumb, but this isn’t near completion yet, so don’t give
me too much grief about it. :slight_smile:

class GenericUser < ActiveRecord::Base

validates_presence_of :password, :password_confirmation, :email_address
validates_uniqueness_of :email_address
validates_confirmation_of :password

password must be at least 8 characters long

validates_format_of :password, :with => %r{^.{8,}$}, :message =>
“must be at least 8 characters”

validates_format_of :email_address, :with => MiscUtils::EMAIL_REGEX

user can only supply these params

attr_accessible :password, :password_confirmation, :email_address

class variable – see koz’s post http://www.koziarski.net/

archives/2005/07/16/environment
cattr_accessor :current_user

non-db variables

attr_accessor :password

def before_save
#breakpoint(“generic user before_save”)
# save the new hash if the password is set
# XXX - only set password_hash during methods that can change
password
self.password_hash = MiscUtils.hexdigest(@password) unless
@password.nil?
end

def after_save
@password = nil
@password_confirmation = nil
end

def try_to_login
self.class.lookup(self.email_address, @password)
end

Class methods

def self.lookup(email_address, password, additional_conds = ‘’)
hashed_password = MiscUtils.hexdigest(password)
begin
find( :first,
:conditions => [“email_address = ? and password_hash
= ?” + additional_conds,
email_address, hashed_password])
rescue
return nil
end
end

end


class User < GenericUser
set_table_name “users”

belongs_to :account
belongs_to :pending_user

validates_presence_of :activation_key

attr_accessible :activation_key

non-db variable

attr_accessor :activation_key

XXX: need to make sure that hypothetical user-editing tools

don’t allow malicious queries

to change their account_id or anything like that

def validate_on_create
# we use an artificial, non db-backed parameter to hold the
activation key,
# then perform lookup here so we have access to errors.add, etc.
@pending_user = PendingUser.lookup(self.email_address,
@activation_key)
if @pending_user.nil?
errors.add_to_base(“Invalid email address or activation key”)
return false;
elsif @pending_user.used == true
# XXX : only for debug. TMI.
errors.add_to_base(“User request already used”)
end

 begin
   self.account = Account.find(@pending_user.account_id)
 rescue
   errors.add(:account_id, "is invalid")
 end

end

def before_create
self.email_address = @pending_user.email_address
self.account = @pending_user.account

 ##self.pending_user = @pending_user
 self.pending_user_id = @pending_user.id

 # XXX: send an email with the activation key here...
 self.email_validation_key = MiscUtils.random_string(80)

 # XXX: since we're not sending email, manually set the

validation flag
self.email_validated = true
#breakpoint(“user before_create finish”)
end

def after_create
# mark the pending user as used
# XXX - handle failure better

 ##self.pending_user.used = true
 @pending_user.used = true

 ##unless self.pending_user.save
 unless @pending_user.save
   ##raise "couldn't save pending user #{self.pending_user.id}"
   raise "couldn't save pending user #{@pending_user.id}"
 end

end

def self.lookup(email_address, password)
super(email_address, password, “and email_validated = true”)
end

end


class PendingUser < ActiveRecord::Base
validates_presence_of :email_address, :account_id
validates_uniqueness_of :email_address
validates_format_of :email_address, :with => MiscUtils::EMAIL_REGEX

attr_accessible :email_address, :account_id

attr :activation_key

belongs_to :account
has_one :user

XXX: eventually we’ll want to programmatically set which account

the pending user will

be tied to, and remove the accessible, etc symbols. (that is,

pull it from the current

user’s info.)

def validate
begin
self.account = Account.find(self.account_id)
rescue
errors.add(:account_id)
end
end

need to create an activation key and store its hash

def before_create
#randkey = MiscUtils.random_string(20)
# XXX: right now we’re not sending emails, so we won’t know what
the un-hashed key was…
randkey = “foo”
self.activation_key_hash = MiscUtils.hexdigest(randkey)
end

simple wrapper to do the digest’ing of the key

def self.lookup(email_address, key)
hashed_key = MiscUtils.hexdigest(key)
begin
find( :first,
:conditions => [“email_address = ? and
activation_key_hash = ? and used = false”,
email_address, hashed_key])
rescue
return nil
end
end
end
-------------- next part --------------
A non-text attachment was scrubbed…
Name: smime.p7s
Type: application/pkcs7-signature
Size: 2357 bytes
Desc: not available
Url :
http://wrath.rubyonrails.org/pipermail/rails/attachments/20051229/946e583c/smime.bin

Chris Eagan wrote:

I’ve been pulling my hair our over this same problem for the last few
hours… Did you ever figure out the resolution? I found this thread
here:
http://lists.rubyonrails.org/pipermail/rails/2005-December/007657.html
but see at the time you were not able to get an answer from the list.

Any suggestion greatly appreciated!

See this thread:
http://lists.rubyonrails.org/pipermail/rails/2006-March/025508.html


We develop, watch us RoR, in numbers too big to ignore.