从has_many:through关系中检索记录,包括联接模型列并赋予约束?

时间:2019-02-09 11:15:44

标签: ruby-on-rails

我已经建立了三个模型:User,List和UserList,后者是具有has_many_through关系的User和List之间的联接模型。

UserList的“加入模型”也有一个名为“评级”的列。

我想检索给定用户的所有“列表”记录,以及用户列表中每个列表记录的等级。但是,我做错了事,已经转了一些圈。希望对我不了解的内容提供指导。

技术细节

这是三个模型:

class User < ApplicationRecord
  has_many :user_lists
  has_many :lists, through: :user_lists, dependent: :destroy
End

class List < ApplicationRecord
  has_many :user_lists
  has_many :users, through: :user_lists, dependent: :destroy

  # no duplicate titles in the List table
  validates :title, uniqueness: true
End

class UserList < ApplicationRecord
  belongs_to :list
  belongs_to :user

  # a given user can only have one copy of a list item
  validates :list_id, uniqueness: { scope: :user_id }
end

所需结果

检索属于当前用户的所有(且仅)列表项,以及UserList模型中每个列表项的等级。

到目前为止我的代码

# list_controller.rb

def index
  @lists = current_user
    .lists
    .joins(:user_lists)
    .select('lists.*, user_lists.id as user_lists_id, user_lists.rating as rating')
    .order(:id)
  render json: @lists
end

我从各种Stack Overflow文章中共同总结了上述内容。但这不是很正确,我不确定为什么。

这是运行控制器索引代码时的rails控制台输出:

List Load (1.2ms)
  SELECT DISTINCT
    lists.*,
    user_lists.id as user_lists_id,
    user_lists.rating as rating
  FROM "lists"
  INNER JOIN "user_lists" "user_lists_lists"
    ON "user_lists_lists"."list_id" = "lists"."id"
  INNER JOIN "user_lists"
    ON "lists"."id" = "user_lists"."list_id"
  WHERE "user_lists"."user_id" = $1
  ORDER BY "lists"."id" ASC  [["user_id", 1]]

[#<List:0x00007fef741da338
  id: 2,
  title: "useronly",
  created_at: Sat, 09 Feb 2019 09:22:44 UTC +00:00,
  updated_at: Sat, 09 Feb 2019 09:22:44 UTC +00:00>,
 #<List:0x00007fef741d9f78
  id: 3,
  title: "The Art of Gathering",
  created_at: Sat, 09 Feb 2019 09:23:05 UTC +00:00,
  updated_at: Sat, 09 Feb 2019 09:23:05 UTC +00:00>]

正在正确检索列表项-即ID,标题,created_at和updated_at。并且,仅适用于current_user(即,不是其他用户的项目)。 但是,user_lists中的评级列未正确检索。

rails控制台输出的这一部分看起来根本不正确:

  INNER JOIN "user_lists" "user_lists_lists"
    ON "user_lists_lists"."list_id" = "lists"."id"

但是坦率地说,我不确定如何解决它。我已经尝试过基于Googling + Stack-Overflowing的各种方法,但是没有任何效果。

有什么想法吗?非常感谢。

根据Obermillerk的建议进行的编辑:

基于Obermillerk的delegate指南,我认为为了将List模型中的“ title”添加到每个“ user_list”项目,我需要执行以下操作:

# lists_controller.rb (lines 9 + 11 indicated below)
    def index
      lists = current_user.user_lists
9:       lists.each do |list|
        # retrieve title to each record in user_list. Title is delegated to List
11:         list.title = list.title
      end
      render json: lists
    end

但是,这还不太有效-视图为空;服务器控制台显示:

00:01:22 api.1  |   UserList Load (2.2ms)  SELECT "user_lists".* FROM "user_lists" WHERE "user_lists"."user_id" = $1  [["user_id", 1]]
00:01:22 api.1  |   ↳ app/controllers/api/v1/lists_controller.rb:9
00:01:22 api.1  |   User Update (1.5ms)  UPDATE "users" SET "last_login" = $1, "updated_at" = $2 WHERE "users"."id" = $3  [["last_login", "2019-02-12 08:01:22.664620"], ["updated_at", "2019-02-12 08:01:22.676667"], ["id", 1]]
00:01:22 api.1  |   ↳ app/controllers/api/v1/users_controller.rb:44
00:01:22 api.1  |   List Load (0.5ms)  SELECT  "lists".* FROM "lists" WHERE "lists"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
00:01:22 api.1  |   ↳ app/models/user_list.rb:6
00:01:22 api.1  | Completed 500 Internal Server Error in 55ms (ActiveRecord: 11.1ms)
00:01:22 api.1  | 
00:01:22 api.1  | 
00:01:22 api.1  |   
00:01:22 api.1  | NoMethodError (undefined method `title=' for #<UserList:0x00007f9220002b48>
00:01:22 api.1  | Did you mean?  title):
00:01:22 api.1  |   
00:01:22 api.1  | app/controllers/api/v1/lists_controller.rb:11:in `block in index'
00:01:22 api.1  | app/controllers/api/v1/lists_controller.rb:9:in `index'

2 个答案:

答案 0 :(得分:0)

我检查了您的脚本,一切正常。我没有找到您在问题中提到的问题。当我运行此索引操作时,结果如下。您可以在末尾看到评分。

[{"id":3,"title":"This is list","created_at":"2019-02-09T16:19:18.552Z","updated_at":"2019-02-09T16:19:18.552Z","user_lists_id":2,"rating":"5"}]

我认为您没有提供足够的数据。您可以尝试放入以下数据吗

u = User.create(:name => "another", :email => "ok@gmail.com")
l = List.create("This is list") // creating first
UserList.create(:user_id => 1, :list_id => 3,:rating => 5)

请确保您检查ID,并将正确的ID放在UserList中。请注意,最佳实践推荐的ListUser名称应为连接表的字母顺序。

答案 1 :(得分:0)

让我看看是否能再次帮助您,我现在觉得很投入;)

我相信您在这里的问题是,您误会了活跃记录领域。对于活动记录查询,最终将检索特定模型结构的数据,在这种情况下,该数据就是列表结构。 List结构永远不能包含来自另一个模型的数据字段,只能包含它自己的数据字段。

因此,联接的目的不是在模型中添加数据字段,而是使您可以基于其他表中关联记录的数据来优化结果。也就是说,如果您只想包含来自特定用户的评分大于3的列表项,则可以使用join来包含UserList表,然后使用WHERE子句将您的结果过滤为仅包含评分大于3的那些大于3。但是,所得的List对象将仅包含其自身的数据,而UserList数据中则不包含任何数据。我不相信有任何方法可以注入一个字段。

我认为您实际上应该做的是使用UserList模型而不是List模型。 UserList模型具有您要查找的等级,您始终可以直接从UserList访问列表,因为它具有直接的一对一关联。从列表到用户列表需要您正在寻找用户列表的用户的额外输入,这会使事情变得复杂。

在活动记录中可以做的一件很酷的事情,可能就是您正在寻找的是对关系的委托方法处理。也就是说,您可以告诉UserLists,而不是自己调用title而不提出任何内容,而是将其发送到正确的List对象并返回结果。这样,您可以在UserList对象中委派List项目的所有数据字段,例如title和您拥有的内容,然后UserList对象基本上可以充当其List对象的包装器。为此,您可以在模型顶部附近添加这样的一条线,最好将其放置在关联之后:

class UserList < ApplicationRecord
  belongs_to :list
  belongs_to :user

  # delegate the :title method to the :list relation above
  delegate :title, to: :list

  # a given user can only have one copy of a list item
  validates :list_id, uniqueness: { scope: :user_id }
end

您可以在一个呼叫中添加任意多个要委派的字段,所以说还有yeardirector字段,您可以执行delegate :title, :year, :director, to: :list

如果您确实希望将UserList变成List对象的包装器,则可能会更好,那就是说delegate_missing_to :list,然后在UserList上未定义的任何方法都会自动尝试通过清单。这样,您可以将新方法和字段添加到List模型中,并将它们自动委派到UserList中。

希望这会有所帮助,很高兴根据需要澄清任何内容!