"Class level methods will no longer inherit scoping" deprecation warning

I have run into this deprecation warning in my code base, and I’m not really sure I understand what the purpose of it is, or if it’s a bug in this particular case. I have a model that does a query in a validator to ensure records aren’t created too frequently. When creating a model using find_or_create_by, I get the deprecation warning (I created an example case here: deprecation.rb · GitHub). However if I change find_or_create_by to create, it works and I get no deprecation warning.

I’m not sure that it makes sense for the behaviour to need to be different here, but it seems to be because create is using Persistence#create whereas find_or_create_by uses Relation#create, which calls _deprecated_scope_block if given a hash.

Is this a bug? Having to add default_scoped (which, maybe just me, but makes me think of default scopes, which my class does not have) in one case or not the other feels strange to me.

1 Like

You should be getting a warning about find_or_create_by. The correct syntax to use nowadays is where(foo: 'bar').first_or_create. I’m surprised you’re not seeing that warning more explicitly. I believe this change happened somewhere in Rails 4.

Walter

Thanks @walterdavis I didn’t even notice that! Unfortunately where.first_or_create still does bring up the deprecation warning though.

What version of Rails are you using? I’ve just tried this in a Rails 6 application, and had no error. I’m using first_or_initialize, which does everything except save the record (I’ve also used the first_or_create, just to be sure):

2.6.6 :002 > Page.where(user_id: 4).first_or_initialize
  Page Load (0.5ms)  SELECT `pages`.* FROM `pages` WHERE `pages`.`user_id` = 4 ORDER BY `pages`.`id` ASC LIMIT 1
 => #<Page id: 12, title: "1,000th Volume", teaser: "<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" ...", body: "", user_id: 4, slug: "1-000th-volume", publish: false, parent_id: 2084, position: nil, index: false, image_id: 21853, keywords: "volume library book oll pleased announce collectio...", short_name: "1,000th Volume", slug_can_change: true, search_context: "/pages/search", ancestry: "2084", created_at: "2007-12-02 07:59:06", updated_at: "2020-05-11 01:09:49"> 
2.6.6 :003 > Page.where(user_id: 999).first_or_initialize
  Page Load (0.4ms)  SELECT `pages`.* FROM `pages` WHERE `pages`.`user_id` = 999 ORDER BY `pages`.`id` ASC LIMIT 1
 => #<Page id: nil, title: nil, teaser: nil, body: nil, user_id: 999, slug: nil, publish: nil, parent_id: nil, position: nil, index: nil, image_id: nil, keywords: nil, short_name: nil, slug_can_change: nil, search_context: nil, ancestry: nil, created_at: nil, updated_at: nil> 
2.6.6 :004 > 

Check your model and its relationships.

I updated my test case (deprecation.rb · GitHub) to use where.first_or_create and it still throws the deprecation warning. I’m on Rails 6.0.3.2 and Ruby 2.6.2.

Does it do this when you manually call the method in Rails console, as I did? Can you create an example in console, and just paste the output here?

This isn’t true… I don’t know (or don’t recall) the details behind that API distinction, but the documented API absolutely is find_or_create_by.

No, that sounds like the warning is behaving correctly. Under the historical behaviour, the find-or-create version’s validation method is pre-scoped, meaning that Post.all would be equivalent to Post.where(author: author_id). That’s surprising, which is why it’s being changed.

At least in your gist example, the difference would be masked by the fact your validation is filtering its query using the same condition you used in the find-or-create.

Post.default_scoped is just how you spell “the base relation, including only the default scope [if there is one]”, which is almost certainly what you want/expect, and is what Post / Post.all will return in future versions.

2 Likes

@matthewd why doesn’t it come up from create then? Especially since the warning says that it “will no longer inherit scoping from create”.

I guess my problem with this deprecation is that if I want the 6.1 default behaviour I need to change my code now, and then change it again when 6.1 is out, otherwise I get noise in my tests, etc. Unlike a normal deprecated method, a behaviour deprecation generates noise that I can’t silence if I want the behaviour (the where-nor deprecation warning has the same problem IMO).

2 Likes

@matthewd It seems like this might have been accomplished better with a feature flag - and it isn’t too late to change. Something like this?

config.active_record.create_inherits_scoping

1 Like

Thanks @matthewd for clearing that up for me! I think I was remembering from way-too-long-ago, the change from the “magick” methods like find_by_first_name_and_last_name('Joe', 'Bloggs'), which became find_by(first_name: 'Joe', last_name: 'Bloggs') with a noisy deprecation warning. You are obviously correct, and I’ve got a fair amount of embarrassingly-recent work to clean up. Not sure, though, why I’m not getting the same deprecation warning in my own use.

Walter