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:

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 (Devise breaks migrations with scopes if the field isn't present · Issue #357 · heartcombo/devise · GitHub), so I'm switching to that!

—P