Topic: habtm grief - Rails bug? (very desperate ; )

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

  after_create :send_welcome_email
  before_save :create_pushbroom_user_data

  # 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 create_pushbroom_user_data
    pb_user = create_pushbroom_user
    pb_user_account = create_pushbroom_user_account(pb_user)
    pb_user_account.user_roles << Pushbroom::UserRole.find_by_name('user')
    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

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

P.S. As a side-note I've been recently working on my 'style'.  If anybody has any comments on things I could do better, I'm open. Method comments have been withheld for brevity.

Last edited by hardaur (2011-04-12 13:45:38)

Re: habtm grief - Rails bug? (very desperate ; )

Doesn't seem to be a lot going on here, but I'll post the answer here so others may find it:

After beating myself silly for 4 days on this I finally found the problem: Rails (habtm) doesn't have the ability to determine the database to use for external relation tables.  I also found the answer, and it doesn't even smell bad!  There's a whole thread on the process here:  http://groups.google.com/group/rubyonra … 0442039ccd


The answer?  has_many :through - something I'd never taken much of a look at, but it is actually a pretty nice feature (even in other circumstances).

Basically this just allows me to create a model class which represents the relationship.  And since I have a model class for it I can explicitly specify the database to connect to.

For posterity sake, here's the code:

    class Pushbroom::UsersRolesRelationship < ActiveRecord::Base
  
      establish_connection :pushbroom
      set_table_name :users_roles

      belongs_to :user_account
      belongs_to :user_role
    end
    class Pushbroom::UserAccount < ActiveRecord::Base

      establish_connection :pushbroom
      set_table_name :user_account
      set_primary_key :id

      has_many :users_roles_relationships
      has_many :user_roles, :through => :users_roles_relationships, :source => :user_role
    end
    class Pushbroom::UserRole < ActiveRecord::Base

      establish_connection :pushbroom
      set_table_name :user_role
      set_primary_key :id

      has_many :users_roles_relationships
      has_many :user_accounts, :through => :users_roles_relationships, :source => :user_account
    end

And is used thusly:

    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

Thanks a ton folks for helping me get this train moving again!  All my tests are passing and it seems to be working, but if you see something wrong, please do still let me know.

Thanks!
Gerald

Last edited by hardaur (2011-04-14 08:10:07)