在Rails上创建多对多记录

时间:2019-07-13 03:54:53

标签: ruby-on-rails ruby many-to-many favorites

我有一个简单的任务列表应用程序,上面有用户和列表,这些用户由Devise管理,可以创建任务列表以及其他用户或他们自己创建的收藏夹列表。用户和列表之间的所有权关系很容易建立,但是我很难建立喜欢列表的用户关系。我设想它毕竟是一个多对多关系,一个用户可以收藏很多列表,而一个列表可以被许多用户收藏,这种关系发生在另一个已经存在的列表所有权的一对多关系之上。使用者让我暂停了一下这是否是个好习惯,但无论如何我还是继续尝试。

目前,我有两种模型,一种用于用户,一种用于列表,并且我尝试通过运行rails g migration CreateJoinTableFavorites users lists为收藏夹创建迁移,这导致了以下迁移

class CreateJoinTableFavorites < ActiveRecord::Migration[5.2]
  def change
    create_join_table :users, :lists do |t|
      t.index [:user_id, :list_id] <-- I uncommented this line
      # t.index [:list_id, :user_id]
      t.timestamps <-- I added this line
    end
  end
end

我认为这将创建一个名为“收藏夹”的表,该表将自动链接用户和列表,但相反,它创建了一个名为“ lists_users”的表。现在我被困在下一步该怎么做。我读过我需要为此联接表创建一个模型,但是我不知道该怎么做。我运行什么命令? rails g model Favoritesrails g model ListsUsers?我是否还会通知我要添加的字段,例如rails g model Favorites user_id:integer list_id:integer,还是还有另一种更好的方法,例如rails g model Favorites user:references list:references?这里的最佳做法是什么

除此之外,我还在list#show视图内添加了一个按钮,供用户单击以将该列表添加到他们的收藏夹中,并且在路由时遇到了一些麻烦。我所做的就是创建一个像这样的按钮:

<%= button_to 'Add to favorites', add_favorites_path({list_id: @list.id}), method: :post %>

以及一条新路线:

post 'add_favorites', to: 'lists#add_favorites'

尽管我在该操作中可以访问列表ID和用户ID,但是现在我不知道如何继续在lists_users表中创建“收藏夹”数据库条目。为了说明,我将在此处粘贴“ add_favorite”操作

def add_favorites
    user_id = current_user.id
    list_id = params[:list_id]

    #TODO: create the relation in lists_items table
end

我知道,如果没有联接表的模型,我将无法使用它,但是即使我拥有该模型,也没有多少运气来寻找在控制器中创建该模型的方法关系。无论如何,我的模型如下:

class List < ApplicationRecord
    belongs_to :user
    has_many :users, through: :lists_users
end
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  has_many :lists
  has_many :lists, through: :lists_users
end

总而言之,我知道我缺少联接表的模型,并且想逐步介绍如何创建它,给它命名什么名称以及如何进行创建。在我的控制器中继续执行操作,以创建新的收藏夹条目

2 个答案:

答案 0 :(得分:1)

您可以尝试以下操作:

  <?php echo $post['text']; ?>
  <div class="post-info">
    <!-- if user likes post, style button differently -->
    <i <?php if (userLiked($post['id'])): ?>
          class="fa fa-thumbs-up like-btn"
      <?php else: ?>
          class="fa fa-thumbs-o-up like-btn"
      <?php endif ?>
      data-id="<?php echo $post['id'] ?>"><img src="catlogo.ico"></i>
    <span class="likes"><?php echo getLikes($post['id']); ?></span>

答案 1 :(得分:1)

有两种方法可以在Rails中创建多对多关系。您正在做的事情似乎将两者混为一谈,我怀疑这是问题的根源。

简而言之,这两种方法是:

1)has_many :other_models, through: :relation

2)has_and_belongs_to_many :other_models

主要区别在于,“ has_many through”方法期望联接表是一个单独的模型,如果需要,可以独立于此关系进行处理,而“ has_and_belongs_to_many”方法不需要联接表具有相应的模型。在后一种情况下,您将不能独立处理联接表。 (顺便说一下,这使联接表上的时间戳无效。)

应使用哪种方法取决于您的用例。 docs很好地总结了条件:

  

最简单的经验法则是,如果需要将关系模型作为独立实体使用,则应建立has_many:through关系。如果不需要使用在关系模型中,建立has_and_belongs_to_many关系可能更简单(尽管您需要记住要在数据库中创建联接表)。 (添加了重点)


现在要提出的问题是:使用create_join_table时,您就像在为has_and_belongs_to_many关系设置事物一样对待它。 create_join_table将创建一个名为"#{table1}_#{table2}"的表,其ID指向这些表。它也按字母顺序排列,这就是为什么您获得“ lists_users”而不是“ users_lists”的原因。实际上,如果您打算使用has_and_belongs_to_many,这实际上是Rails连接表的标准命名约定,通常不应重命名。

如果您确实要使用has_and_belongs_to_many,请继续使用create_join_table进行迁移,并在模型中进行以下操作:

# user.rb
class User
  has_and_belongs_to_many :lists
end

# list.rb
class List
  has_and_belongs_to_many :users
end

瞧。不需要Favorite模型,并且rails足够聪明,可以自行处理通过表的关系。如上所述,虽然有点容易,但缺点是您将无法将连接表作为独立模型来处理。 (同样,在这种情况下,联接表上的时间戳是无用的,因为Rails不会设置它们。)

编辑:由于您不能直接触摸lists_users,因此可以通过在用户上设置列表关系或在列表上设置用户关系来创建关系,例如:

def add_favorites
  list = List.find(params[:list_id])
  current_user.lists << list # creates the corresponding entry in lists_users

  # Don't forget to test how this works when the current_user has already favorited a list!
  # If you want to prevent that from happening, try
  # current_user.lists << list unless current_user.lists.include?(list)

  # Alternatively you can do the assignment in reverse:
  # list.users << current_user

  # Again, because the join table is not an independent model, Rails won't be able to do much to other columns on lists_users out of the box.
  # This includes timestamps
end

另一方面,如果要使用“ has_many through”,请不要使用create_join_table。如果您一直使用has_many,则联接表应被视为几乎是一个完全独立的模型,该表恰好具有两个外键并将其他两个模型以多对多关系关联在一起。在这种情况下,您可以执行以下操作:

# migration
class CreateFavorites < ActiveRecord::Migration[5.2]
  def change
    create_table :favorites do |t|
      t.references :list
      t.references :user
      t.timestamps
    end
  end
end

# user.rb
class User
  has_many :favorites
  has_many :lists, through: :favorites
end

# list.rb
class List
  has_many :favorites
  has_many :users, through: :favorites
end

# favorite.rb
class Favorite
  belongs_to :list
  belongs_to :user
end

# controller
def add_favorites
  # You actually have a Favorite model in this case, while you don't in the other. The Favorite model can be more or less independent of the List and User, and can be given other attributes like timestamps.
  # It's the rails methods like `save`, `create`, and `update` that set timestamps, so this will track those for you as any other model.
  Favorite.create(list_id: params[:list_id], user: current_user)
end

您可能需要考虑使用哪种方法。同样,这实际上取决于您的用例和上述条件。就我个人而言,不确定时,我更喜欢“ has_many through”方法,因为它为您提供了更多可使用的工具,并且通常更灵活。