TheWebs
TheWebs

Reputation: 12933

Rails not using the table the model specified

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_

Update

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.

Update 2

I have filed a possible bug report at: Their Github Repo

Upvotes: 3

Views: 1068

Answers (1)

mikej
mikej

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

Related Questions