The Back Story
I am currently changing my Ruby on Rails app to a multi-database configuration. Main reason for the switch was to put my Member(User) and Profile tables in a separate database, which could be accessed from another RoR app; thus allowing me to have a single sign-on capability aside from using OAuth and Doorkeeper. This has been a many many hours project with many crazy hurdles. Finally tonight it seemed that everything was working, until I ran my spec tests, and one of my work arounds I did today is throwing flags.
Here is the appropriate code for the problem, simplified for brevity sake.
Any help would be greatly appreciated. Thank you.
Models
app/models/members_record.rb
class MembersRecord < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :members, reading: :members }
end
app/models/member.rb
class Member < MembersRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable,
:validatable, :confirmable
has_one :member_map, dependent: :destroy
has_one :profile, dependent: :destroy
has_one :site_profile, dependent: :destroy
has_many :approved_group_members, -> { approved }, class_name: "GroupMember"
has_many :groups, through: :approved_group_members, class_name: "Group", source: :group
app/models/group.rb
class Group < ApplicationRecord
belongs_to :owner, class_name: 'Member'
belongs_to :organization, optional: true
has_many :group_members, dependent: :destroy
# Many of the lines below no longer work with Multiple DBs, as we can not INNER JOIN across DBs.
# They have been commented out and methods built in their place.
has_many :approved_group_members, -> { approved }, class_name: "GroupMember"
# has_many :members, through: :approved_group_members, class_name: "Member", source: :member
def members
Member.where(id: self.approved_group_members.pluck(:member_id))
end
has_many :invited_group_members, -> { invited }, class_name: "GroupMember"
# has_many :invited_members, through: :invited_group_members, class_name: "Member", source: :member
def invited_members
Member.where(id: self.invited_group_members.pluck(:member_id))
end
app/models/group_member.rb
class GroupMember < ApplicationRecord
belongs_to :group
belongs_to :member
enum status: { approved: 0, invited: 1, pending_approval: 2, banned: 3}
end
The Workarounds
Now, looking at the above code, you should see a few of the workarounds in question within the group model. The original code was commented out with the workaround beneath it. Here is just that code:
# Many of the lines below no longer work with Multiple DBs, as we can not INNER JOIN across DBs.
# They have been commented out and methods built in their place.
has_many :approved_group_members, -> { approved }, class_name: "GroupMember"
# has_many :members, through: :approved_group_members, class_name: "Member", source: :member
def members
Member.where(id: self.approved_group_members.pluck(:member_id))
end
has_many :invited_group_members, -> { invited }, class_name: "GroupMember"
# has_many :invited_members, through: :invited_group_members, class_name: "Member", source: :member
def invited_members
Member.where(id: self.invited_group_members.pluck(:member_id))
end
The reason for these workarounds is because the has_many
relationship was not working with the class Member
once that class was referencing data on a different database. The SQL queries being built were failing. So, I made a method instead and called a where
on the Member
model. That gave me the list of members that I was looking for. I had to do this on 9 different occurrences.
Now the problem that I am having
Within my code in over 100 different spots I add a member, or other object, into a given list, say of 'invited members' with this type of code. (This code specifically is within one of my specs now failing)
it 'will set the GroupMember to approved if the group is invite only and member was invited' do
group_invitation
group.invited_members << member
expect { service.call! }.to change{GroupMember.count}.by(0)
expect(service.success?).to be_truthy
expect(service.success_message).to eq("You have successfully joined this group.")
expect(service.error).to eq("")
end
The second line reads group.invited_members << member
and then I get an error that states:
NoMethodError (undefined method `<<' for #<ActiveRecord::Relation []>):
My Question
I completely understand what the error is stating, but am at a loss for how to correct it. Is there a different way that I could do the workaround in order to allow the <<
to remain? I'd very much prefer to only have to change 9 different times I did the workaround instead of rewriting over 100 different instances where <<
is being used to add the member, or whatever the object is, to the list being referenced.
Please let me know if you need any further details. Thank you again!
This is supported natively on Rails 7 upwards. (See https://edgeguides.rubyonrails.org/active_record_multiple_databases.html#handling-associations-with-joins-across-databases, https://github.blog/2021-07-12-adding-support-cross-cluster-associations-rails-7/ and https://github.com/rails/rails/pull/41937)
If you really need this, I'd probably recommend moving to rails master, and use the native way:
has_many :members,
through: :approved_group_members,
class_name: "Member",
source: :member,
disable_joins: true