Rails: ActiveRecord's default_scope and class inheritance
Asked Answered
T

2

6

My app has not only Users but also Admins and SuperAdmins. Since all three share the same attributes i'd like to use only one Table with an additional attribute "role" that can either be "user", "admin" or "super_admin":

class User < ActiveRecord::Base
 # with nickname and password
end


class Admin < User

  def power
    puts "admin rights"
  end
end


class SuperAdmin < Admin

  def superpower
    puts "I've got the #{power}!"
  end
end

Now I want to do something like SuperAdmin.all to get only SuperAdmins. Using a default_scope seems to get me there:

class SuperAdmin < Admin
  default_scope where(["role = ?", "super_admin"])

  # ...
end

Now I'm adding a default_scope to Admin too:

class Admin < User
  default_scope where(["role = ?", "admin"])

  # ...
end

Aaaand... SuperAdmin.all returns nothing anymore. Why is that?

Toreador answered 7/9, 2012 at 13:38 Comment(0)
T
9

If there is more than one default_scope, ActiveRecord chains them. So SuperAdmin.all looks for Users having role "Admin" AND "SuperAdmin" – which can never happen.

To get around that you can override the default_scope of the inherited Model, so just define a self.default_scope by yourself:

 class SuperAdmin < Admin
    def self.default_scope
        where(["role = ?", "super_admin"])
    end
    #...
 end

SuperAdmin.all should work as expected now.

Toreador answered 7/9, 2012 at 13:38 Comment(1)
For Rails 5 this override does not work (it will still use parent default_scope). Use Edmund Lee answer: first clear scopes and than define newBrasserie
M
8

Anyone who stumples upon this like me, there is another option (better imo).

You can simply remove all the default_scopes, which is defined as an array.

class Admin < User
  # This line will clear all the default_scopes defined in parent classes
  self.default_scopes = []
  default_scope { ... }
end

Refer to source code here https://apidock.com/rails/ActiveRecord/Base/default_scope/class. You can see that all it does is adding subsequent default scope to an array.

self.default_scopes = default_scopes + [scope]
Marchant answered 5/4, 2017 at 0:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.