Asked 1 month ago by InterstellarSatellite998
How Do I Configure Fabricator to Use a Custom Foreign Key for Rails STI Associations?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by InterstellarSatellite998
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I have the following models in Rails 6.1.7:
RUBYclass AdminUser < ApplicationRecord belongs_to :organization, foreign_key: 'type_id' end class OrganizationUser < AdminUser end
The fabricators are defined in spec/fabricators/admin_user_fabricator.rb as follows:
RUBYFabricator(:admin_user) do email { Forgery(:internet).email_address } first_name { Forgery(:name).first_name } last_name { Forgery(:name).last_name } end Fabricator(:organization_user, from: :admin_user, class_name: :organization_user) do organization end
When I call the fabrication in my RSpec test:
RUBYFabricate.attributes_for(:organization_user)
I get the following output:
JSON{"email":"wwright@buzzster.edu", "first_name":"Carolyn", "last_name":"Gutierrez", "organization_id":6919}
This causes an error:
ActiveModel::UnknownAttributeError: unknown attribute 'organization_id' for OrganizationUser.
The attribute "organization_id" shouldn’t be there. Instead, I expect to see "type" with the value "OrganizationUser" and a "type_id" attribute (e.g. 6939).
I suspect the issue is that Fabrication automatically generates an attribute based on the association name, ignoring the custom foreign key 'type_id'. I've tried several adjustments, but I’m not sure what’s wrong. How can I modify my fabricators to work correctly with this custom foreign key and STI setup?
Comments 0
•Answers 3
•Views 98
Answer by TychoAI Agent • 1 month ago
Based on your description, the problem is that Fabrication is automatically creating an attribute key called "organization_id" from your association declaration:
RUBYbelongs_to :organization, foreign_key: 'type_id'
Fabrication isn’t aware of the custom foreign key (type_id) and so produces "organization_id". When Rails tries to assign organization_id to your OrganizationUser (which inherits from AdminUser), it raises an ActiveModel::UnknownAttributeError because there is no organization_id column.
To fix this, you have a couple of options:
Instead of fabricating the association using the name “organization”, you can explicitly assign the organization using the correct attribute key. For example, if you have another fabricator for Organization (or the appropriate model), you can do:
RUBYFabricator(:admin_user) do email { Forgery(:internet).email_address } first_name { Forgery(:name).first_name } last_name { Forgery(:name).last_name } end Fabricator(:organization_user, from: :admin_user, class_name: :organization_user) do type_id { Fabricate(:organization).id } end
Here, instead of simply writing organization
, you assign the organization’s id to type_id
, which matches your association's foreign key.
If you want to continue using the association helper (i.e. organization
), you need to let Fabrication know about the custom key. Fabrication doesn’t automatically look up the association’s foreign_key
option. One workaround is to overwrite the attribute in your fabricator block after the association is set:
RUBYFabricator(:organization_user, from: :admin_user, class_name: :organization_user) do organization do Fabricate(:organization) end # Override the automatically generated organization_id attribute with type_id after_build do |user| user.type_id = user.organization.id if user.organization # Remove the incorrect attribute if needed (depending on how Fabrication handles it) user.attributes.delete('organization_id') end end
This way, once the organization is built and assigned to the user, you can manually copy its id into type_id
and remove the incorrect organization_id
attribute.
Since your OrganizationUser inherits from AdminUser, Rails is using single table inheritance (STI), which means a “type” column is usually maintained automatically. Make sure your database is set up accordingly so that Rails sets the type
column to "OrganizationUser"
when you instantiate a record of that class.
The core of the issue is that when using a custom foreign key (type_id
), Fabrication defaults to generating association_name + _id
(i.e. organization_id). By updating your fabricators to assign the organization id to the correct attribute (type_id
) or by overriding the generated attribute, you can resolve the error.
If you have further questions about Rails plugins, factories, or associations, feel free to ask!
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by SolarCommander191 • 1 month ago
There seems to be some confusion between your database tables structure, your models and the way Fabrication gem works.
type
fieldIt looks like you're working with Single Table Inheritance, so it makes sense to expect your database table to contain the type
column. And you can verify that by running OrganizationUser.column_names
or AdminUser.column_names
. If STI was generated correctly, you should see the type
column listed there.
This column will be automatically set whenever you instantiate an object for the child class.
RUBY> OrganizationUser.new.type => "OrganizationUser"
And a similar result is expected if you build an OrganizationUser
object with fabrication:
RUBY> organization_user = Fabricate.build(:organization_user) > organization_user.type => "OrganizationUser"
However, when you call Fabricate.attributes_for
, you're not instantiating a model object. attributes_for
only loads what's present in the definitions for your fabrications. That's why you don't see the type
field listed there. Your fabricators have no type
field. If you want it to be returned, you must explicitly add type "OrganizationUser"
to your organization_user
fabricator.
organization_id
attribute and the type_id
columnThe organization_id
attribute is returned when you call for Fabricate.attributes_for(:organization_user)
because you specified the association
relation in your fabricator. Similar to the topic above, note that attributes_for
will return a list according to your fabricator definitions. If an association is passed, it will try to generate an ID based on it (regardless of which foreign key you defined in your model).
I don't see much about Organization
in your question.
I'm assuming you also have an organizations
table and an Organization
model and that you added a type_id
column to the admin_users
table. You might want to go through those steps if any of these assumptions are incorrect. I'd suggest not using the type_id
name for that FK unless you have a strong reason for that choice. If you could stick to the conventional FK organization_id
, that can make your code easier to follow.
Assuming you're keeping type_id
as the FK, you could have something like the following:
RUBYFabricator(:organization_user, from: :admin_user, class_name: :organization_user) do type "OrganizationUser" type_id { Fabricate(:organization).id } end
And then you call it on your specs without the need for extra params:
RUBYattributes = Fabricate.attributes_for(:organization_user)
No comments yet.
Answer by AstralCosmonaut923 • 1 month ago
As Lidiane suggested, I fixed the issue with:
RUBYlet(:orguser_attrs) { Fabricate.attributes_for(:organization_user, type: 'OrganizationUser', type_id: Fabricate(:organization).id).except(:organization_id) }
Yeah, ugly as heck, I know. Bad designed models.
No comments yet.
No comments yet.