I have an Audit
class which is backed by ActiveRecord.
class Audit < ActiveRecord::Base
belongs_to :user, polymorphic: true
end
I have a User
class which is a plain ruby object with some ActiveModel functionality included. It's not a database model because my users are actually stored in a different database and served over an API.
class User
include ActiveModel::Conversion
include ActiveModel::Validations
extend ActiveModel::Naming
def self.primary_key
'id'
end
def id
42
end
def persisted?
false
end
end
I'm trying to assign a user to an audit like this:
audit = Audit.new
audit.user = User.new
audit.save!
From a data perspective, this should work ok. To define a polymorphic association, we need to put values into two columns on the audits table. We can set audits.user_id
to the value 42
and audits.user_type
to the string "User".
However, I hit an exception:
undefined method `_read_attribute' for #<User:0x007f85faa49520 @attributes={"id"=>42}> (NoMethodError)
active_record/associations/belongs_to_polymorphic_association.rb:13:in `replace_keys'
I traced that back to the ActiveRecord
source and it seems to be defined here. Unfortunately, the fact that it's ActiveRecord
rather than ActiveModel
means that I can't include that mixin into my class.
I tried defining my own _read_attribute
method but I go down a rabbit hole of having to redefine more and more ActiveRecord functionality like AttributeSet and so on.
I also realise that I can workaround the problem by assigning Audit#user_type
and Audit#user_id
. That is unsatisfactory however because, in reality, I would have to fork a gem and edit it to do that.
How can I modify my User class so that I can cleanly assign it to an audit.
P.S. Here's a sample app so you can try this yourself.
Instead of hacking deeper and deeper to replicate ActiveRecord
functionality, you may want to consider actually inheriting from ActiveRecord::Base
instead of including ActiveModel
. Your only constraint is that you don't have a table. There's a gem for that:
This class works with your sample app:
require 'active_record'
require 'activerecord-tableless'
class User < ActiveRecord::Base
has_no_table
# required so ActiveRecord doesn't try to create a new associated
# User record with the audit
def new_record?
false
end
def id
42
end
end