问题描述
摘要
我遇到了一个问题,我的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) %>
类具有User
和has_many :channel_users
。 has_many :channels,through: :channel_users
类具有Channel
和has_many :channel_users
。
问题
当我在Rails控制台中使用任何has_many :users,through: :channel_users
实例执行unread_messages
方法时,没有收到错误。但是,当我在开发中运行该应用程序时,会得到:
如您在图片底部的网络控制台中所见,即使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_user
和current_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
另一个更新
这是锦上添花:
我不知道该怎么做,我想知道这是某种名称冲突问题还是什么。我该如何诊断?
更新
同一问题,尝试不同的方法:
为什么在世界上会在此行上引发错误,但是如果我将同一行粘贴到错误页面上的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