In development mode not all types are included in the query related to type

class User
end

class Agent < User
end

script/console production
User.find_by_name 'john'

SELECT "people".* FROM "people" WHERE ((("people"."type" = 'User' OR
"people"."type" = 'Manager') OR "people"."type" = 'Agent')) AND
("people"."name" = 'agent') LIMIT 1

In development mode.
script/console
User.find_by_name 'john'
SELECT "people".* FROM "people" WHERE ("people"."type" = 'User') AND
("people"."name" = 'agent') LIMIT 1

I should have mentioned the ticket rather than typing all this much.
https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/4516-correct-type-conditions-arent-added-when-using-multiple-inheritances-wsti#ticket-4516-3

I am sure this issue must be there for a while. And probably the
consensus is to leave it the way it is and no need to try to fix it in
development mode. Just asking for confirmation.

class User
end

class Agent < User
end

script/console production
User.find_by_name 'john'

SELECT "people".* FROM "people" WHERE ((("people"."type" = 'User' OR
"people"."type" = 'Manager') OR "people"."type" = 'Agent')) AND
("people"."name" = 'agent') LIMIT 1

That does not make sense, there is no 'john' in the query.

In development mode.
script/console
User.find_by_name 'john'
SELECT "people".* FROM "people" WHERE ("people"."type" = 'User') AND
("people"."name" = 'agent') LIMIT 1

I am guessing that it is due to the way files are loaded in
development vs production. I think (but may be wrong) that in
production all files are loaded so it knows about the other user
types, whereas in development it only loads files as it needs them so
does not know about the other types. Though why it is testing the
type at all in the query I am not sure. Is User derived from
ActiveRecord base? I wonder whether there is more to the class
declarations than you have shown us.

Colin

The sql that I posted came out after testing the exact case provided
by the author of the ticket. While composing this email I did not
layout the full hierarchy.

@Colin. It is definitely due to class loading. If in development I
load Agent then query will include Agent too.

script/console

Agent
User.find_by_name 'john' # now the query will include agent

My question is this: This is something I am encountering now but it
must have been there since STI was introduced which was years ago. So
should this bug be fixed. For some it might not be a bug given that it
happens only in development mode. Nonetheless it could be confusing if
you are not aware of how it works (as I was initially).

The sql that I posted came out after testing the exact case provided
by the author of the ticket. While composing this email I did not
layout the full hierarchy.

@Colin. It is definitely due to class loading. If in development I
load Agent then query will include Agent too.

script/console

Agent
User.find_by_name 'john' # now the query will include agent

My question is this: This is something I am encountering now but it
must have been there since STI was introduced which was years ago. So
should this bug be fixed. For some it might not be a bug given that it
happens only in development mode. Nonetheless it could be confusing if
you are not aware of how it works (as I was initially).

Since you have deleted all of the original post and my comments the
above now makes no sense. No-one reading this will know what your
problem is without going back to the previous mail, which is something
one should not normally be forced to do.

Colin

Since you have deleted all of the original post and my comments the
above now makes no sense. No-one reading this will know what your
problem is without going back to the previous mail, which is something
one should not normally be forced to do.

Extremely sorry about that. I have created a gist which explains the
problem much more succintly.
http://gist.github.com/398486

What are the names of the rb files containing the class definitions?

Colin

Also what version of rails?

Colin

Isn’t the problem obvious? ActiveRecord cannot list all subclasses under STI until it loads all modes in the app. In development mode it relies on autoloading and never preloads all models.

The local solution: require statements in the end of “user.rb”:

require ‘manager’

require ‘agent’

So when User is preloaded, all subclasses will get loaded and be known to ActiveRecord too.

The global solution: ActiveRecord shouldn’t rely on obtaining a list of all subclasses. Instead, when you query on a parent model, it shouldn’t specify any classes at all. Thus:

User.all

SELECT * FROM users

However, this is not adequate with complex STI schemes where there are grandchildren. If we’re querying on a child that has subclasses, we still have to know what they are:

Manager.all

SELECT * from users WHERE users.type = ‘Manager’

OR users.type = ‘EvilManager’ OR users.type = ‘GoodManager’

Isn't the problem obvious? ActiveRecord cannot list all subclasses under STI
until it loads all modes in the app. In development mode it relies on
autoloading and never preloads all models.
The local solution: require statements in the end of "user.rb":
require 'manager'
require 'agent'
So when User is preloaded, all subclasses will get loaded and be known to
ActiveRecord too.
The global solution: ActiveRecord shouldn't rely on obtaining a list of all
subclasses. Instead, when you query on a parent model, it shouldn't specify
any classes at all. Thus:
User.all
# SELECT * FROM users
However, this is not adequate with complex STI schemes where there are
grandchildren. If we're querying on a child that has subclasses, we still
have to know what they are:
Manager.all
# SELECT * from users WHERE users.type = 'Manager'
# OR users.type = 'EvilManager' OR users.type = 'GoodManager'

Can you explain why when the OP does
Agent.find_by_name 'agent'
it generates
SELECT "users".* FROM "users" WHERE (("users"."type" = 'Agent' OR
"users"."type" = 'SuperAgent')) AND ("users"."name" = 'agent') LIMIT 1

I would have expected only type Agent to be allowed, it appears to be
returning SuperAgents also. If the user of name 'agent' is actually a
SuperAgent I would not expect Agent.find_by_name to find it. Note
that both Agent and SuperAgent are directly derived from User. Unless
the OP is leading us astray.

Colin

User.find_by_name 'agent' generates a sql that does not use Agent or
SuperAgent even if they are loaded.

However the behavior of Agent.find_by_name 'agent' is different if
SuperAgent is loaded.

@Colin: SuperAgent should be a subclass of Agent. I have updated the
gist with this info.

@Mislav: Yes the problem is obvious. That's why I am asking if it is
something that needs to be fixed or it is okay the way it is. If it is
something that needs no fixing then ticket #4516 can be marked as
wontfix.

In general, if you have grandchildren you need to load them to ensure
they are known. I compute them this way:

    http://gist.github.com/274219

The bottom line is that Active Record needs to know the STI hierarchy
that is relevant to any given query. That just does not play nice with
autoloading, it is a trade-off.

So you _were_ leading us astray. It is not surprising that I was
confused by the results you were seeing. I think Mislav's reply
covers it in that case.

Colin

I've run across this before too, but I used something like this:

%w(Admin Manager Candidate).each { |c| c.constantize }

As I can never remember which one of require / require_dependency
sometimes jams up (or used to back in the day) the autoloader.

--Matt Jones

When you’re querying a parent that doesn’t have any non-abstract parent (meaning he’s the top of the STI hierarchy), then theoretically you really don’t have to know the list of descendants. AR could just make a query without conditions. I think it would be a good addition to AR.

Or am I missing some edge case? Are explicit conditions with list of classnames always required for some reason?

Exactly, that is why in the last example in Neeraj's gist there's no
conditions on the type column even if you load the subclasses:

    http://gist.github.com/398486

I added a couple of comments at the bottom.

Neeraj these are just two techniques that do not match
transparently. Do you see what is going on now?

I think the ticket could be closed, but a warn about
this gotcha could be helpful both in the API and guides. Santiago
Pastorino is about to add it.

Thanks everyone for the input. I understand what's going on now.

I will mark ticket #4516 as invalid.

I always use "require_dependency"
I guess it's more explicit that it will be reloaded in the future.
https://gist.github.com/4db76a1dbce03f3e4435

But its really annoying when you forget to require one of these.

I guess a simple approach would be doing the following on load

  CtiModel.all(:select => "type", :distinct => true).each{|model|
model.type.constantize}

thereby ensuring that all the types in the db were loaded
(there may be some other subclass missing... but that wouldnt matter)