Rails 3.2上与自定义表名的多对多关系

时间:2013-02-23 21:24:26

标签: ruby-on-rails associations has-many-through rails-migrations

我是Rails的初学者(刚刚完成了Hartl的教程),我遇到了一些类型的关联,这些关联看起来比我到目前为止所遇到的要复杂一些。我一直在谷歌和stackoverflow浏览我的问题的解决方案,但虽然我发现了许多相关的答案,我仍然无法弄清楚如何解决我的问题。

基本上我要做的是实现以下关系:

ER diagram

在研究之后,我想出的是:

模型

# models/user.rb
class User < ActiveRecord::Base
  attr_accessible :first_name, :last_name, :email …

  has_many :speak, :class_name => "Speaks"
  has_many :speaks, :through => :speak, :source => :language

  has_many :want_to_learn, :class_name => "WantsToLearn"
  has_many :wants_to_learn, :through => :want_to_learn, :source => :language
  …

end

# models/language.rb
class Language < ActiveRecord::Base
  attr_accessible :iso_639_1_code, :name_en, :name_fr, :name_pt

  has_many :speak, :class_name => "Speaks"
  has_many :users_who_speak, :through => :speak, :source => :user

  has_many :want_to_learn, :class_name => "WantsToLearn"
  has_many :users_who_want_to_learn, :through => :wants_to_learn, :source => :user
end

# models/speaks.rb
class Speaks < ActiveRecord::Base
  set_table_name 'speak'
  attr_accessible :level, :language
  references :user
  references :language
end

# models/wants_to_learn.rb
class WantsToLearn < ActiveRecord::Base
  set_table_name 'want_to_learn'
  attr_accessible :language
  references :user
  references :language
end

迁移

# db/migrate/20130210153328_create_speak.rb
class CreateSpeak < ActiveRecord::Migration
  def change
    create_table :speak do |t|
      t.references :user
      t.references :language
      t.integer :level

      t.timestamps
    end
  end
  add_index :speak, :user
  add_index :speak, :language
end

# db/migrate/20130210153342_create_want_to_learn.rb
class CreateWantToLearn < ActiveRecord::Migration
  def change
    create_table :want_to_learn do |t|
      t.references :user
      t.references :language

      t.timestamps
    end
  end
  add_index :want_to_learn, :user
  add_index :want_to_learn, :language
end

当我这样做并尝试运行rake:db migrate时,我收到以下错误:

-- add_index(:speak, :user)
rake aborted!
An error has occurred, this and all later migrations canceled:

PG::Error: ERROR:  relation "speak" does not exist
: CREATE  INDEX "index_speak_on_user" ON "speak" ("user")
...

如果我用add_index注释掉行,则迁移成功运行,但是我无法从用户创建“说话”关系。例如,如果我运行:

> john = User.new(first_name: "John", last_name: "Doe", email: "john@doe.com"...)

> john.speaks.create!(level: '6', language: '139')

我明白了:

NoMethodError: undefined method `references' for #<Class:0x007ff14c1771a0>

我试图用“belongs_to”和其他一些东西替换所有“引用”,其中许多我甚至都不记得,但无济于事。这引起了我的一些疑虑,例如:

•“引用”是否与“belongs_to”完全相同?即使在模特的背景下?我已经看到“引用”已用于迁移但从未使用模型。

•我正确使用“class_name”吗?我对它真的没有信心。这是我阅读的所有内容之后最有意义的内容,但我没有在这样的协会中看到任何自定义示例。

•添加这些索引有什么问题?

而且,我不知道这里的礼节是什么,但我的最后一个问题是:

我如何正确地做到这一切?

提前谢谢!

------ 编辑 ------

我将关键字“references”替换为“belongs_to”。有关索引的迁移错误完全相同。当我评论索引创建行时,运行迁移,创建用户,然后尝试命令

> john.speaks.create!(level: 6, language: 139)

错误是:

ActiveModel::MassAssignmentSecurity::Error:
Can't mass-assign protected attributes: level, language

尽管有attr_accessible :level, :language行。

2 个答案:

答案 0 :(得分:1)

我是Rails中的references关键字的新手,但我在Rails指南中的迁移中看到它,但在类中没有,所以不要在类中使用它。

因此,它与belongs_to不同,因为belongs_to在类中使用,并且引用用于迁移。

对我来说,迁移会在创建索引时关心类中的关联。

答案 1 :(得分:1)

行。我终于设法使一切运转起来,我认为社区放弃未回答的问题是不太有建设性的。我的代码现在完美运行,它包括我必须添加的内容以允许嵌套表单。这是它的外观:

<强>模型

# models/user.rb
class User < ActiveRecord::Base
  attr_accessible :first_name, :last_name, :email, ... ,
  :speaks_attributes, :wants_to_learn_attributes
  # These last two attributes are necessary for nested forms

  has_many :speaks, :class_name => "Speaks", :dependent => :destroy
  accepts_nested_attributes_for :speaks, :allow_destroy => true # this one too
  has_many :speaks_languages, :through => :speaks, :source => :language

  has_many :wants_to_learn, :class_name => "WantsToLearn", :dependent => :destroy
  accepts_nested_attributes_for :wants_to_learn, :allow_destroy => true # and this one too
  has_many :wants_to_learn_languages, :through => :wants_to_learn, :source => :language
  .
  .
  .

end

# models/language.rb
class Language < ActiveRecord::Base
  attr_accessible :iso_639_1_code, :name_en, :name_fr, :name_pt

  has_many :speak, :class_name => "Speaks"
  has_many :users_who_speak, :through => :speak, :source => :user

  has_many :want_to_learn, :class_name => "WantsToLearn"
  has_many :users_who_want_to_learn, :through => :want_to_learn, :source => :user
end

# models/speaks.rb
class Speaks < ActiveRecord::Base
  set_table_name 'speak'
  attr_accessible :language, :language_id, :level
  belongs_to :user
  belongs_to :language
end

# models/wants_to_learn.rb
class WantsToLearn < ActiveRecord::Base
  set_table_name 'want_to_learn'
  attr_accessible :language, :language_id
  belongs_to :user
  belongs_to :language
end

<强>迁移

# db/migrate/20130210153328_create_speak.rb
class CreateSpeak < ActiveRecord::Migration
  def change
    create_table :speak, :id => false do |t|
      t.belongs_to :user
      t.belongs_to :language
      t.integer :level

      t.timestamps
    end
    add_index :speak, :user_id
    add_index :speak, :language_id
  end
end


# db/migrate/20130210153342_create_want_to_learn.rb
class CreateWantToLearn < ActiveRecord::Migration
  def change
    create_table :want_to_learn, :id => false do |t|
      t.belongs_to :user
      t.belongs_to :language

      t.timestamps
    end
    add_index :want_to_learn, :user_id
    add_index :want_to_learn, :language_id
  end
end

在迁移过程中,我将“add_index”行放在“def change”块中,因为上次我愚蠢而分心地把它留在了外面,并将“user”和“language”改为“user_id”和“language_id”,分别。修好了。

现在我可以创建一个用户ariel,并通过运行:

让他说葡萄牙语和法语
> ariel.speaks.create!(language_id: 129, level: 6)
> ariel.speaks.create!(language_id: 50, level: 4)

然后我可以通过运行:

获取“speak”表中与ariel相关的条目
> ariel.speaks
=> [#<Speaks id: 1, user_id: 1, language_id: 129, level: 6, created_at: "2013-02-25 19:30:01", updated_at: "2013-02-25 19:30:01">, #<Speaks id: 4, user_id: 1, la
nguage_id: 50, level: 4, created_at: "2013-02-26 12:37:34", updated_at: "2013-02-26 12:37:34">]

通过运行“语言”表中的条目:

> ariel.speaks_languages
 => [#<Language id: 50, iso_639_1_code: "fr", name_en: "French", name_fr: "français\n", name_pt: nil>, #<Language id: 129, iso_639_1_code: "pt", name_en: "Portugu
ese", name_fr: "portugais\n", name_pt: nil>]

该过程类似于wants_to_learn关联。