#ruby-on-rails-3 #has-and-belongs-to-many
#ruby-on-rails-3 #имеет и принадлежит многим
Вопрос:
Приветствую всех,
У меня странная проблема с отношениями habtm, и, честно говоря, я начинаю думать, что, возможно, наткнулся на какую-то странную ошибку в rails 3. Конечно, я сумасшедший. Я бился головой об стену по этому поводу в течение 3 дней, погуглил все, что только можно придумать, и до сих пор не могу придумать ответ.
Хорошо, ситуация:
Я создаю приложение Rails, чтобы заменить как Java-приложение, так и PHP-приложение (Java-приложение и php front-end). Это будет поэтапная операция, на первом этапе приложение Rails берет на себя регистрацию и выставление счетов. Чтобы сделать это, приложение Rails должно создать данные в базах данных для приложений Java и PHP. Само приложение Rails использует Devise для аутентификации.
В database.yml у меня определены мои стандартные 3 базы данных, а также определено соединение для базы данных Java apps.
Вот фрагменты определений модели для внешнего объекта (я просто создаю обычные модели rails для взаимодействия с внешними базами данных):
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
И, наконец, мой пользовательский объект приложения Rails:
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
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
Кажется, что это должно быть довольно ванильным. ЕДИНСТВЕННАЯ странность здесь в том, что их нет в собственной базе данных rails, и одно из полей названо funny в таблице отношений.
Итак, вот сеанс консоли rails, в котором я создаю пользователя rails, вызываю метод для создания внешних объектов, затем пытаюсь сохранить:
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 >
Если я удалю назначение ролей, все будет просто замечательно (находит, сохраняет, уничтожает и т.д.), Но как только я пытаюсь сохранить роли, все взлетает до небес с этим сообщением, которое, честно говоря, я не получаю. Он знает, что у него есть роли, нет нулевого объекта, о котором я могу сказать . . . и в принципе, если бы я уже не был лысым, я бы рвал на себе волосы ; )
Любое понимание этого ЧРЕЗВЫЧАЙНО ценится!
Джеральд
PS Также спрашивали здесь http://railsforum.com/viewtopic.php?id=43647 Продублирует ответ, если найден.
Комментарии:
1. Пожалуйста, добавьте свое
create_pushbroom_user_account
определение метода2. Упс, где-то потерялся в cut amp; paste. Добавлена резервная копия в коде. Спасибо, что уловили это.
Ответ №1:
После того, как я 4 дня мучил себя этим, я, наконец, обнаружил проблему: Rails (habtm) не имеет возможности определять базу данных для использования во внешних таблицах отношений. Я также нашел ответ, и он даже не пахнет плохо! Здесь есть целый поток о процессе:http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/c5655d0442039ccd
Ответ? has_many : through — то, на что я никогда особо не обращал внимания, но на самом деле это довольно приятная функция (даже при других обстоятельствах).
По сути, это просто позволяет мне создать класс модели, который представляет взаимосвязь. И поскольку у меня есть класс модели для этого, я могу явно указать базу данных для подключения.
Ради потомков, вот код:
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
И используется таким образом:
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
Огромное спасибо, ребята, за то, что помогли мне снова запустить этот поезд! Все мои тесты проходят, и, похоже, это работает, но если вы видите что-то не так, пожалуйста, все равно дайте мне знать.
Спасибо! Джеральд
Ответ №2:
Попробуйте выполнить несколько ручных сохранений объектов, созданных в разных местах, таких как create_pushbroom_user_account(pb_user)
метод. В прошлом у меня были некоторые проблемы, когда я полагался на систему «автосохранения».
Комментарии:
1. Я пробовал сохранять каждый шаг на этом пути, это на 100% успешно, пока я не доберусь до user_roles. Независимо от того, как я их назначаю, даже при выполнении прямых вставок SQL (connection.execute / insert / etc) Выдается одна и та же ошибка. Это почти должно быть так, как настроено отношение, но я повторил это 100 раз, и это не ракетостроение : / Спасибо за комментарий!