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 => "fred@mydomain.org", 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: "fred@mydomain.org", 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 `<main>'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