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.

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)