Adding scopes breaks migrations (Rails 3 beta 4)

I came across an interesting issue this morning using Rails 3 beta 4.

in my user.rb I have a scope:

scope :public, where(:private => false)

This was absolutely fine when I added the scope, since the "private"
field was in the database.

I wanted to modify the migration, so I rolled back the database, but
by doing so, I nuked the private field.

Now when I try to rollback or run any migrations, I get:

No attribute named `private` exists for table `users`

Full Trace:
http://gist.github.com/461216

This is happening (I think) because Rails is building the predicate
for the scope at runtime ... when the field doesn't exist.

Whenever something like this happened in Rails 2, I'd normally put the
class inside the migration, eg:

class AddUsernameToUser < ActiveRecord::Migration

class User < ActiveRecord::Base
end

def self.up
   add_column :users, :username, :string
   add_column :users, :private, :boolean, :default => false
end

def self.down
   remove_column :users, :private
   remove_column :users, :username
end
end

This doesn't work in this case, because it all happens before _any_
migration is run.

So basically, I have to remove the scope to run any migrations.

This is fine for now, as I can move on with development, but the
implication is that if I start off with a clean database, and run rake
db:migrate, it won't work.

Now, I know that best practice is to use rake db:schema:load, but I
think that this is still a brittle case.

Basically, by adding a scope that depends on a field existing, all
prior migrations break.

—Paul

I think the article entitled "Named Scopes Are Dead" had some good
insights along those lines.

http://www.railway.at/2010/03/09/named-scopes-are-dead/

In the Rails 3 app I've been working on, my scopes are just class
methods like shown there.

I swear I had fixed this. Will have another look.

I swear I had fixed this. Will have another look.

It may be fixed in edge, I'm working off beta4. Will switch to edge
and double check.

—P

Pratik, seems like you’re right. A quick glance at the scoped method looks like it should be deferring any attempt to actually call a condition until the scope is used, not when it’s defined. From Paul’s trace, it looks like he is running into an issue caused by the devise_for stuff in his routes file. (Sorry José!) It may also be related to the questionable choice of defining an attribute based on a Ruby keyword (private).

/Users/paul/.rvm/gems/ree-1.8.7-2010.02@roadio/gems/devise-1.1.rc2/lib/devise/mapping.rb:88:in `to'

/Users/paul/.rvm/gems/ree-1.8.7-2010.02@roadio/gems/devise-1.1.rc2/lib/devise/rails/routes.rb:95:in `devise_for'

/Users/paul/.rvm/gems/ree-1.8.7-2010.02@roadio/gems/devise-1.1.rc2/lib/devise/rails/routes.rb:91:in `each'

/Users/paul/.rvm/gems/ree-1.8.7-2010.02@roadio/gems/devise-1.1.rc2/lib/devise/rails/routes.rb:91:in `devise_for'

/Users/paul/Dropbox/Sites/roadio/roadio/config/routes.rb:6

On a related note, time for me to go back and change my class method definitions back to scopes. :)

I swear I had fixed this. Will have another look.

It may be fixed in edge, I'm working off beta4. Will switch to edge
and double check.

—P

If you are using 1.9.2, you may also want to check which revision of Ruby you're using. (Yes, revision as it the SVN trunk number.) There was a constant lookup bug for a little while which may have contributed to this problem, too.

-Rob

Ernie, Pratik,

Pratik, seems like you're right. A quick glance at the scoped method looks
like it should be deferring any attempt to actually call a condition until
the scope is used, not when it's defined. From Paul's trace, it looks like
he is running into an issue caused by the devise_for stuff in his routes
file. (Sorry José!) It may also be related to the questionable choice of

Sorry about the false alarm ... removing "devise_for" in the routes
"fixes" this, so it looks like it is a devise issue.

Definitely something I can live with in the short term.

defining an attribute based on a Ruby keyword (private).

I'm pretty sure this isn't the case, since there's no "private" method
name. Interestingly, it's very difficult to come up for a name in this
that isn't a potential collision.

"private" and "public" being the most obvious choices, even
"anonymous" doesn't really work.

Anyway, thanks for your help.

—P

I haven’t taken a look at the devise code, just wondered if perhaps a bareword public was being interpreted somewhere as a call to your scope instead in the context of a singleton_class eval.

I misspoke re "private" vs "public". I'm now on my second cup of coffee so fully qualified to respond. :slight_smile:

I think this test lends credence to my suspicion, though:

ruby-1.9.2-head > class Tester
ruby-1.9.2-head ?> def self.public
ruby-1.9.2-head ?> puts "Hey, I'm not a keyword, I'm a class method!"
ruby-1.9.2-head ?> end
ruby-1.9.2-head ?> end
=> nil
ruby-1.9.2-head > Tester.instance_eval do
ruby-1.9.2-head > public
ruby-1.9.2-head ?> def blah
ruby-1.9.2-head ?> puts "blah"
ruby-1.9.2-head ?> end
ruby-1.9.2-head ?> end
Hey, I'm not a keyword, I'm a class method!
=> nil

Try renaming your scope and see if the issue persists.

ruby-1.9.2-head > Tester.instance_eval do
ruby-1.9.2-head > public
ruby-1.9.2-head ?> def blah
ruby-1.9.2-head ?> puts "blah"
ruby-1.9.2-head ?> end
ruby-1.9.2-head ?> end
Hey, I'm not a keyword, I'm a class method!
=> nil

Try renaming your scope and see if the issue persists.

I had tried that, it did persist : José assures me he's fixed the
issue in Rails edge
(http://github.com/plataformatec/devise/issues/issue/357/#comment_296820),
so I'm switching to that!

—P