即使外键有效,Rails仍返回nil 更新我尝试过的另一件事另一个更新

问题描述

摘要

我遇到了一个问题,我的belongs_to关联在我认为不应该返回nil的情况下。

要做什么

ChannelUser应该能够从关联的channel返回消息,其中created_at大于其user最后一次从{{1 }}。类定义如下:

channel

我正在尝试从用户已加入视图的所有渠道中呈现未读消息的总数:

class ChannelUser < ApplicationRecord
  belongs_to :channel
  belongs_to :user

  scope :with_channel,-> { includes(:channel) }

  def unread_messages
    channel.messages.where('created_at > ?',last_read_at)
  end
end

class User < ApplicationRecord
  has_many :channel_users,-> { includes(:channel) },dependent: :destroy
  has_many :channels,through: :channel_users
  has_many :messages
end

class Channel < ApplicationRecord
  has_many :channel_users,dependent: :destroy
  has_many :users,through: :channel_users
  has_many :messages,-> { order(created_at: :asc).limit(100) },dependent: :destroy
end

class Message < ApplicationRecord
  belongs_to :channel
  belongs_to :user
  has_rich_text :body
  ...
end

<%= current_user.channel_users.sum(&:unread_messages) %> 类具有Userhas_many :channel_usershas_many :channels,through: :channel_users类具有Channelhas_many :channel_users

问题

当我在Rails控制台中使用任何has_many :users,through: :channel_users实例执行unread_messages方法时,没有收到错误。但是,当我在开发中运行该应用程序时,会得到:

enter image description here

如您在图片底部的网络控制台中所见,即使ChannelUser是有效的外键,channel也会返回nil。实际上,该架构是这样定义的:

channel_id

我还使用class CreateChannelUsers < ActiveRecord::Migration[6.0] def change create_table :channel_users do |t| t.belongs_to :channel,null: false,foreign_key: true t.belongs_to :user,foreign_key: true t.timestamps end end end 在视图中放置了一个断点,并查看了<% debugger %>current_usercurrent_user.channel_users的值。对于current_user.channel_users.first.unread_messages,我获得了current_user的实例与登录帐户的关联。对于User,我得到了代表我加入的聊天频道的记录。对于current_user.channel_users,我没有收到错误,得到了一个空的关联集,可以正确识别出我没有任何未读消息。需要特别注意的是,当我不汇总所有未读邮件的总和时,不会发生此错误

更新

我发现我可以通过单击调用堆栈中的行来更改Web控制台的范围,因此这是Web控制台的更多输出

curretn_user.channel_users.first.unread_messages

最后一个让我很困惑。通过current_user的channel_users关联请求频道,我得到>> current_user.channel_users => #<ActiveRecord::Associations::CollectionProxy [#<ChannelUser id: 7,channel_id: 5,user_id: 1,created_at: "2020-11-09 06:42:52",updated_at: "2020-11-15 23:44:28",last_read_at: "2020-11-15 23:44:28">,#<ChannelUser id: 3,channel_id: 4,created_at: "2020-11-09 06:26:12",updated_at: "2020-11-09 06:27:03",last_read_at: "2020-11-09 06:27:03">,#<ChannelUser id: 4,channel_id: 6,created_at: "2020-11-09 06:30:41",updated_at: "2020-11-15 23:32:26",last_read_at: "2020-11-15 23:32:26">,#<ChannelUser id: 8,channel_id: 7,created_at: "2020-11-10 01:20:44",updated_at: "2020-11-15 23:33:24",last_read_at: "2020-11-15 23:33:24">]> >> current_user.channel_users.map(&:unread_messages) NoMethodError: undefined method `messages' for nil:NilClass from /home/jakehockey10/Code/team-portal/app/models/channel_user.rb:29:in `unread_messages' from /home/jakehockey10/Code/team-portal/app/views/dashboard/show.html.erb:9:in `map' from /home/jakehockey10/Code/team-portal/app/views/dashboard/show.html.erb:9:in `_app_views_dashboard_show_html_erb___259171981150348787_116880' >> current_user.channel_users.first.unread_messages NoMethodError: undefined method `messages' for nil:NilClass from /home/jakehockey10/Code/team-portal/app/models/channel_user.rb:29:in `unread_messages' from /home/jakehockey10/Code/team-portal/app/views/dashboard/show.html.erb:9:in `_app_views_dashboard_show_html_erb___259171981150348787_116880' >> current_user.channel_users.first => #<ChannelUser id: 7,last_read_at: "2020-11-15 23:44:28"> >> current_user.channel_users.first.channel => nil >> Channel.find(5) => #<Channel id: 5,name: "general",account_id: 2,created_at: "2020-11-09 06:30:32",updated_at: "2020-11-09 06:30:32",direct_message: false> ,但是当我看到channel_id为5并通过nil请求ID为5的频道时,我发现它很好。有什么作用?

我尝试过的另一件事

我在Rails控制台中继续前进,并查询与我相同的内容,但没有错误

Channel

一个更新

这是锦上添花:

enter image description here

我不知道该怎么做,我想知道这是某种名称冲突问题还是什么。我该如何诊断?

更新

同一问题,尝试不同的方法

enter image description here

为什么在世界上会在此行上引发错误,但是如果我将同一行粘贴到错误页面上的Web控制台中,却不会引发错误?我想念什么?

解决方法

尝试删除includes,我不确定100%,但这可能是潜在问题。

class User < ApplicationRecord
  has_many :channel_users,dependent: :destroy

您可以将模型中的includes替换为 <%= current_user.channel_users.includes(:channel).sum(&:unread_messages) %>

,

我95%确信问题来自消息模型!

似乎channel.messages不等于channel.user.messages。

首先,您没有在问题中向我们展示 消息的模型 ,请更新问题。

其次,您有两个模型:“频道和用户”,并且两者都具有与消息模型的 关联 ({{1 }}),对我而言,这并不是很好的关联(在看到消息的模型之前,我对解决方案一无所知)。

第三次,在您的查询中:

has_many :messages

其中一行:

User.first.channel_users.sum(&:unread_messages)

示例的第一行是这样的:

Message Load (0.4ms)  SELECT "messages".* FROM "messages" WHERE "messages"."channel_id" = $1 AND (created_at > '2020-11-15 23:44:28.787257') ORDER BY "messages"."created_at" ASC LIMIT $2  [["channel_id",5],["LIMIT",100]]
  Message Load (0.2ms)  SELECT "messages".* FROM "messages" WHERE "messages"."channel_id" = $1 AND (created_at > '2020-11-09 06:27:03.105358') ORDER BY "messages"."created_at" ASC LIMIT $2  [["channel_id",4],100]]
  Message Load (0.2ms)  SELECT "messages".* FROM "messages" WHERE "messages"."channel_id" = $1 AND (created_at > '2020-11-15 23:32:26.070403') ORDER BY "messages"."created_at" ASC LIMIT $2  [["channel_id",6],100]]
  Message Load (0.2ms)  SELECT "messages".* FROM "messages" WHERE "messages"."channel_id" = $1 AND (created_at > '2020-11-15 23:33:24.022821') ORDER BY "messages"."created_at" ASC LIMIT $2  [["channel_id",7],100]]

这意味着:获得100条具有channel_id = 5的已排序消息。这返回null!或 空消息集

所以对我来说这样的问题就来自那里。

,

我终于弄清楚了这个问题。在此应用程序中,为了提供多租户,每个用户都有一个关联的帐户。帐户可以是个人帐户,也可以是非个人帐户。登录后,您也可以将视角从个人帐户更改为与之关联的任何非个人帐户。然后,将具有account_id列的资源过滤到当前帐户是什么。这种过滤发生在中间件中,因此与进行实际查询的代码分开。

当我第一次开始实现此功能时,我先在个人帐户上创建了一些渠道,然后尝试将整个渠道的功能限制为非个人帐户的上下文。因此,我的数据库中有一些与个人帐户相关联的渠道。

在我的屏幕截图中,该应用程序正在使用该中间件将频道限制为具有特定的当前account_id的频道。但是,当我在Web控制台或Rails控制台中模拟相同的查询时,显然没有中间件在运行以过滤当前帐户的内容,因为在这种情况下没有当前帐户。这解释了为什么我在运行应用程序和在控制台中运行这些查询时总是看到不同的结果。

现在,我已经销毁了与个人帐户相关联的所有渠道,该错误消失了。我还添加了一些验证以防止将来出现这种情况:

class Channel < ApplicationRecord

  ...

  validate :account_not_personal

  private

  ...

  def account_not_personal
    errors.add(:account,'Channels are only supported for non-personal accounts') if account&.personal?
  end
end
require 'test_helper'

class ChannelTest < ActiveSupport::TestCase
  test 'name is required' do
    channel = Channel.new(name: nil)
    assert_not channel.valid?
    assert_not_empty channel.errors[:name]
  end

  test 'account is required' do
    channel = Channel.new(account: nil)
    assert_not channel.valid?
    assert_not_empty channel.errors[:account]
  end

  test 'account should be non-personal' do
    channel = Channel.new(name: 'test',account: Account.new(personal: false))
    assert channel.valid?
  end

  test 'account cannot be personal' do
    channel = Channel.new(name: 'test',account: Account.new(personal: true))
    assert_not channel.valid?
    assert_not_empty channel.errors[:account]
  end
end