Reputation: 12933
I have a model that subclasses another model, this particular model does not use the table that I specified, instead it defaults back to the parent class trying to find that table.
The parent class is in a gem, the child class is in an engine who's name space is isolated.
Parent (in the gem):
require_relative 'concerns/user_concerns'
require 'bcrypt'
module CoreModels
module Models
class User < ActiveRecord::Base
self.abstract_class = true
extend FriendlyId
friendly_id :first_name, use: [:slugged, :finders, :history]
before_save :encrypt_password
has_many :group_memberships, :dependent => :delete_all
has_many :groups, :through => :group_memberships, :dependent => :delete_all
has_many :roles, :through => :group_memberships, :dependent => :delete_all
has_many :api_keys
validates :first_name, presence: true
validates :user_name, uniqueness: true, presence: true, length: {minimum: 5}
validates :email, presence: true, confirmation: true, uniqueness: true
validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
validates :password, presence: true, confirmation: true, length: { minimum: 10 }, if: :new_record?
include CoreModels::Models::Concerns::UserConcerns
before_create{ generate_token(:auth_token) }
def self.authenticate_user(user_name, password)
user = Xaaron::User.find_by_user_name(user_name)
if(user && (user.password == BCrypt::Engine.hash_secret(password, user.salt)))
user
else
nil
end
end
def encrypt_password
if password.present?
self.salt = BCrypt::Engine.generate_salt
self.password = BCrypt::Engine.hash_secret(password, salt)
end
end
def send_password_reset
generate_token(:password_reset_token)
self.password_reset_timestamp = Time.zone.now
save!
UserMailer.password_reset(self).deliver
end
protected
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
end
end
end
Child (in the engine):
require 'core_models/models/user'
module Xaaron
class User < CoreModels::Models::User
self.table_name = 'xaaron_users'
end
end
When I run my tests, 135 of them fail giving me the same error over and over again:
Failure/Error: setup_group_role_permissions_relations_for_administrator
ActiveRecord::StatementInvalid:
PG::UndefinedTable: ERROR: relation "users" does not exist
LINE 5: WHERE a.attrelid = '"users"'::regclass
^
: SELECT a.attname, format_type(a.atttypid, a.atttypmod),
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = '"users"'::regclass
AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum
This particular method is:
def setup_group_role_permissions_relations_for_administrator
@user = FactoryGirl.create(:user)
@role = FactoryGirl.create(:admin_role)
@group = FactoryGirl.create(:administrator_group)
@permission = FactoryGirl.create(:can_read)
@role.add_permission = @permission.permission_name
@group.add_role = @role.role_name
@user.add_group_membership(@group, @role)
end
So as you can see its not listening to me when I say use table x. Instead its trying to use a table that doesn't exist./ All of my engines tables are name spaced with xaaron_
The core issue, after some investigation is FactoryGirl, if we look at the first thing that happens in: setup_group_role_permissions_relations_for_administrator
I am doing @user = FactoryGirl.create(:user)
, When I used pry to figure out what wa going on there - thats the issue, so lets look at this factory:
FactoryGirl.define do
sequence :user_email do |n|
"user#{n}@example.com"
end
# Allows for multiple user names
sequence :user_name do |n|
"user#{n}"
end
sequence :permission_name do |n|
"can_read#{n}"
end
sequence :role_name do |n|
"Member#{n}"
end
factory :user, :class => Xaaron::User do
first_name 'Adam'
last_name 'Something'
user_name {generate :user_name}
email {generate :user_email}
password 'somePasswordThat_Is$ecure10!'
end
factory :admin, :class => Xaaron::User do
first_name "Sample Admin"
email "[email protected]"
user_name "Admin_User_Name"
password "admin_Password10985"
end
end
There is something wrong with the way factory girl is creating the user, because I can boot up a rails console and do Xaaron::User.all
and it knows to look in xaaron_users
and not users
So the self.table_name=""
works, but isn't working with factory girl for some reason.
I have filed a possible bug report at: Their Github Repo
Upvotes: 3
Views: 1068
Reputation: 66343
In general, Active Record is designed to use single table inheritance (same table used for records of all subclasses) rather than multiple table inheritance (different tables for subclasses.)
That said, instead of setting the table name in your subclass using self.table_name =
you could try overriding the table_name
method e.g.
def table_name
'xaaron_users'
end
and see if that works in your scenario. This is mentioned as an alternative in the docs.
Upvotes: 1