Greetings all,
I’m having a weird problem with a habtm relationship and honestly
I’m beginning to think I may have stumbled upon some weird bug in
rails 3. Surely I’m crazy though. I’ve been beating my head against
the wall on this for 3 days, have googled everything under the sun I
can think of and still can’t come up with an answer.
Ok, the situation:
I’m creating a Rails app to replace both a Java app and a PHP app
(java application and php front-end). This is going to be a phased
operation with the first phase being the Rails application takes over
registration and billing. In order to do this, the Rails application
must create data in the databases for the Java and PHP apps. The
Rails application itself is using Devise for authentication.
In database.yml I have my standard 3 databases defined and also a
connection defined for the Java apps database.
Here are pieces of the model definitions for the external object (I’m
just creating regular rails models to talk to the external databases):
class Pushbroom::UserAccount < ActiveRecord::Base
require ‘digest/md5’
require ‘base64’
establish_connection :pushbroom
set_table_name :user_account
set_primary_key :id
has_and_belongs_to_many :user_roles, :join_table =>
‘pb_prod.users_roles’, :class_name =>
‘Pushbroom::UserRole’, :foreign_key =>
‘user_account_id’, :association_foreign_key => ‘user_role_id’
belongs_to :user, :dependent => :destroy
attr_accessible :user_roles, :admin_notes, :enabled, :username,
:password_hash, :prefStore, :accepted_tos, :do_not_contact
end
class Pushbroom::UserRole < ActiveRecord::Base
establish_connection :pushbroom
set_table_name :user_role
set_primary_key :id
has_and_belongs_to_many :user_accounts, :join_table =>
‘pb_prod.users_roles’, :class_name =>
‘Pushbroom::UserAccount’, :foreign_key =>
‘user_role_id’, :association_foreign_key => ‘user_account_id’
end
And finally my Rails application user object:
class User < ActiveRecord::Base
before_validation :capture_plaintext_password, :on => :create
before_save :create_pushbroom_user_data
after_save :send_welcome_email
Include default devise modules. Others available are:
:token_authenticatable, :encryptable, :confirmable, :lockable,
:timeoutable
and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :pb_user_account, :class_name =>
“Pushbroom::UserAccount”, :foreign_key =>
“pb_user_account_id”, :dependent => :destroy, :autosave => true
Setup accessible (or protected) attributes for your model
attr_accessible :first_name, :last_name, :username, :dob, :email,
:password, :password_confirmation, :remember_me
validates_presence_of :first_name, :last_name, :username, :dob
validates_date :dob, :on_or_after => lambda
{ 100.years.ago }, :on_or_after_message => “must be on or after
#{100.years.ago.strftime(’%m-%d-%Y’)}”
validates_date :dob, :on_or_before => lambda
{ 13.years.ago }, :on_or_before_message => “must be on or before
#{13.years.ago.strftime(’%m-%d-%Y’)}”
def capture_plaintext_password
@plaintext_password = self.password
end
def create_pushbroom_user_data
pb_user = create_pushbroom_user
add_trial_subscription_to_pb_user(pb_user)
pb_user_account = create_pushbroom_user_account(pb_user)
add_subscription_plan_roles_to_pb_user_account(pb_user_account)
self.pb_user_account = pb_user_account
end
def create_pushbroom_user
pb_user = Pushbroom::User.new
pb_user.attributes = self.attributes.slice(
“email”,
“first_name”,
“last_name”,
“dob”)
pb_user
end
def add_trial_subscription_to_pb_user(pb_user)
subscription = Pushbroom::Subscription.new
subscription.populate_from_subscription_plan(Pushbroom::SubscriptionPlan.find_by_name(“TRIAL”))
pb_user.subscriptions << subscription
end
def add_subscription_plan_roles_to_pb_user_account(pb_user_account)
roles_granted =
pb_user_account.user.subscriptions.first.subscription_plan.roles_granted
pb_user_account.user_roles = roles_granted
end
def create_pushbroom_user_account(pb_user)
pb_user_account = Pushbroom::UserAccount.new
pb_user_account.enabled = true
pb_user_account.password_hash =
Pushbroom::UserAccount.create_password_digest(@plaintext_password,
self.username)
pb_user_account.username = self.username
pb_user_account.user = pb_user
pb_user_account
end
def send_welcome_email
AccountMailer.welcome(self).deliver
end
end
Seems like it should be pretty vanilla. The ONLY weirdness here is
that they aren’t in the native rails database and one of the fields is
named funny in the relations table.
So here’s a rails console session where I create a rails user, call
the method to create the external objects, then try to save:
ruby-1.9.2-p180 :001 > def user_fred
ruby-1.9.2-p180 :002?> {
ruby-1.9.2-p180 :003 > :first_name => “Fred”,
ruby-1.9.2-p180 :004 > :last_name => “Flinstone”,
ruby-1.9.2-p180 :005 > :username => “fflint”,
ruby-1.9.2-p180 :006 > :dob => “1986-06-01”,
ruby-1.9.2-p180 :007 > :email => “[email protected]”,
ruby-1.9.2-p180 :008 > :password => “badpass”
ruby-1.9.2-p180 :009?> }
ruby-1.9.2-p180 :010?> end
=> nil
ruby-1.9.2-p180 :011 > user = User.new(user_fred)
=> #<User id: nil, email: “[email protected]”, encrypted_password:
“$2a$10$IiEOEoSnXIrP7VJAQYckfOVXuzm7Y5ZGo20ayLpSkHhz…”,
reset_password_token: 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: nil, updated_at: nil,
first_name: “Fred”, last_name: “Flinstone”, username: “fflint”, dob:
“1986-06-01”, pb_user_account_id: nil>
ruby-1.9.2-p180 :012 > user.create_pushbroom_user_data
=> #<Pushbroom::UserAccount id: nil, created_by: nil, created_at:
nil, updated_by: nil, updated_at: nil, admin_notes: nil, enabled:
true, username: “fflint”, password_hash: “blah blah”, user_id: nil,
prefStore: nil, accepted_tos: nil, do_not_contact: nil>
ruby-1.9.2-p180 :013 > user.pb_user_account.user_roles
=> [#<Pushbroom::UserRole id: 1, created_by: “script”, created_at:
“2008-11-10 12:10:44”, updated_by: “script”, updated_at: “2008-11-10
12:10:44”, admin_notes: “”, name: “user”, description: “Generic User
Role”, conditional: false>]
ruby-1.9.2-p180 :014 > user.save!
NoMethodError: undefined method relation' for nil:NilClass from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/ activesupport-3.0.5/lib/active_support/whiny_nil.rb:48:in
method_missing’
from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/arel-2.0.9/
lib/arel/insert_manager.rb:22:in insert' from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/arel-2.0.9/ lib/arel/crud.rb:26:in
insert’
from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/
activerecord-3.0.5/lib/active_record/associations/
has_and_belongs_to_many_association.rb:76:in insert_record' from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/ activerecord-3.0.5/lib/active_record/associations/association_proxy.rb: 151:in
send’
from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/
activerecord-3.0.5/lib/active_record/autosave_association.rb:306:in
block in save_collection_association' from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/ activerecord-3.0.5/lib/active_record/associations/ association_collection.rb:431:in
block in method_missing’
from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/
activerecord-3.0.5/lib/active_record/associations/association_proxy.rb:
216:in block in method_missing' from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/ activerecord-3.0.5/lib/active_record/associations/association_proxy.rb: 216:in
each’
from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/
activerecord-3.0.5/lib/active_record/associations/association_proxy.rb:
216:in method_missing' from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/ activerecord-3.0.5/lib/active_record/associations/ association_collection.rb:431:in
method_missing’
from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/
activerecord-3.0.5/lib/active_record/autosave_association.rb:297:in
save_collection_association' from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/ activerecord-3.0.5/lib/active_record/autosave_association.rb:163:in
block in add_autosave_association_callbacks’
from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/
activesupport-3.0.5/lib/active_support/callbacks.rb:415:in
_run_create_callbacks' from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/ activerecord-3.0.5/lib/active_record/callbacks.rb:281:in
create’
from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/
activerecord-3.0.5/lib/active_record/persistence.rb:246:in
create_or_update' ... 18 levels... from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/ activerecord-3.0.5/lib/active_record/callbacks.rb:277:in
create_or_update’
from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/
activerecord-3.0.5/lib/active_record/persistence.rb:56:in save!' from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/ activerecord-3.0.5/lib/active_record/validations.rb:49:in
save!’
from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/
activerecord-3.0.5/lib/active_record/attribute_methods/dirty.rb:30:in
save!' from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/ activerecord-3.0.5/lib/active_record/transactions.rb:245:in
block in
save!’
from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/
activerecord-3.0.5/lib/active_record/transactions.rb:292:in block in with_transaction_returning_status' from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/ activerecord-3.0.5/lib/active_record/connection_adapters/abstract/ database_statements.rb:139:in
transaction’
from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/
activerecord-3.0.5/lib/active_record/transactions.rb:207:in
transaction' from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/ activerecord-3.0.5/lib/active_record/transactions.rb:290:in
with_transaction_returning_status’
from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/
activerecord-3.0.5/lib/active_record/transactions.rb:245:in save!' from (irb):14 from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/ railties-3.0.5/lib/rails/commands/console.rb:44:in
start’
from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/
railties-3.0.5/lib/rails/commands/console.rb:8:in start' from /Users/gander/.rvm/gems/ruby-1.9.2-p180@sms2/gems/ railties-3.0.5/lib/rails/commands.rb:23:in
<top (required)>’
from script/rails:6:in require' from script/rails:6:in
'ruby-1.9.2-p180 :015 >
If I remove the role assignment, everything is just peachy (finds,
saves, destroys, etc), but the second I try to save roles everything
blows sky-high with this message that, frankly, I don’t get. It knows
its got the roles, there is no nil object that I can tell. . .and
basically if I wasn’t already bald I’d be pulling my hair out ; )
Any insight into this is EXTREMELY appreciated!
Gerald