[SOLVED] belongs_to is nil for channel attribute on ChannelUser instance
Hey
Chris Oliver
, I wasn't sure if this was the right place to mention this, but I am putting the Group Chat based on your GoRails lessons into my jumpstart-based app and I'm running into this issue: https://stackoverflow.com/questions/64765493/rails-belongs-to-returning-nil-even-though-foreign-key-is-valid
Basically, I implemented an instance method on the ChannelUser class to get the unread messages for that user and that channel. This is the method implementation:
Basically, I implemented an instance method on the ChannelUser class to get the unread messages for that user and that channel. This is the method implementation:
def unread_messages channel.messages.where('created_at > ?', last_read_at) end
I'm attempting to put a total count of unread messages from all joined channels on the user's dashboard page when signed into one of their non-personal accounts:
<%= current_user.channel_users.map(&:unread_messages).sum(&:count) %>
Here is the error I'm getting:
I'm just not sure what could be going wrong here. The channel attribute returns nil, but channel_id is a valid id of a Channel record, and finding it myself works in the web-console. But even MORE strange is that if I change
channel.messages.('created_at > ?', last_read_at)
to Channel.find(channel_id).('created_at > ?', last_read_at)
, I still get the same error!Any ideas?
In case it wasn't obvious, in my second message, I implemented the `unread_messages` method in the `User` model instead to see if that would make a difference. However, I see more weirdness. As in, the same code that errors when running, seems to not error at all any more when I copy and paste the same thing in the console, supposedly with similar scope to when the error was throne.
Here is my current implementation of the
unread_messages
method:This is in the
User
class. When I'm debugging this, I put a breakpoint inside flat_map
so that I could look into each iteration. This is where I learned something multitenancy-related was going on:From what I can tell,
cu.channel
is returning nil
because, and this is the part I don't understand, within this context, Channel.find(4)
isn't generating a sql query that looks for channels where id = 4, but instead looking for channels where account_id = 4. I'm not sure what is causing the find
method to behave differently than what I thought it was supposed to do. But when I go to the rails console, Channel.find(4)
behaves as expected:Since this seems to be related to how multitenancy is implemented in this app, I was wondering if you could help me diagnose why this is behaving this way?
OMG I finally figured it out...
So it was definitely related to multi-tenancy. Once I realized and remembered that there was middleware filtering resources by account_id if the resource type had an account_id attribute, this explained why getting the error while running the app was not happening when running the same queries in the console.
Then the reason came to me: Once I realized that chat Channels don't really make sense for a personal account, I had already created several chat Channels in my development database during initial implementation. So there were Channels floating around in the database that were associated to my User through ChannelUsers, but the account_id on them did not match the current_account.id. Thus, nil was an appropriate return value.
Once I cleaned up the database by removing any Channels that were associated with a personal account, I stopped getting the error. Then I wondered if the unread_messages method should be a little less brittle to potentially handle this better in the future. I could put safe navigation operators inside the block grabbing the unread messages for each channel. But then I thought that I really do only want Channels to be associated with non-personal accounts, so I'd much rather know when the data is in a state where my assumptions don't hold true. Thus, I thought it was better to add more validation to the Channel itself upon creation:
So it was definitely related to multi-tenancy. Once I realized and remembered that there was middleware filtering resources by account_id if the resource type had an account_id attribute, this explained why getting the error while running the app was not happening when running the same queries in the console.
Then the reason came to me: Once I realized that chat Channels don't really make sense for a personal account, I had already created several chat Channels in my development database during initial implementation. So there were Channels floating around in the database that were associated to my User through ChannelUsers, but the account_id on them did not match the current_account.id. Thus, nil was an appropriate return value.
Once I cleaned up the database by removing any Channels that were associated with a personal account, I stopped getting the error. Then I wondered if the unread_messages method should be a little less brittle to potentially handle this better in the future. I could put safe navigation operators inside the block grabbing the unread messages for each channel. But then I thought that I really do only want Channels to be associated with non-personal accounts, so I'd much rather know when the data is in a state where my assumptions don't hold true. Thus, I thought it was better to add more validation to the Channel itself upon creation:
class Channel < ApplicationRecord ... validates :name, presence: true 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
I decided that this will likely be something I need in the future, so I made it a concern:
module OnlyNonPersonalAccounts extend ActiveSupport::Concern included do validate :only_non_personal_account end private def only_non_personal_account return unless account&.personal? errors.add(:account, "#{self.class.name.pluralize} are only supported for non-personal accounts") end end
I also spent WAY too long trying to come up with a good name for the concern. Initially, I got stuck trying to force the name to end in "-able." Can you think of a better name for this concern than
OnlyNonPersonalAccounts
? OnlyNonPersonalAccountAccessible
?Notifications
You’re not receiving notifications from this thread.