Skip to content


Subversion checkout URL

You can clone with
Download ZIP


first_or_create is applying erroneous scope to model callbacks #7853

uberllama opened this Issue · 12 comments

4 participants


The following code does not result in a raise to the controller.


  before_create :verify_wish_limit


  def verify_wish_limit
    raise OverLimitException # if blah blah


  def create
    @wish = @user.wishes.where(:product_id =>
  rescue Wish::OverLimitException
    # handle error

Changing the creation line to the following does:

@wish = @user.wishes.find_or_create_by_product_id(

I'll investigate.

@senny senny referenced this issue from a commit in senny/rails
@senny senny test case for #7853 493d5a6

I tried to reproduce the error in the test-case mentioned above but as I see it, everything works as expected:

class Topic
  before_create  :default_written_on

  def default_written_on
    self.written_on = unless attribute_present?("written_on")
  def test_first_or_create_with_callbacks
    # make sure, that the attribute is not set without the callback beeing run
    assert_equal nil,
    topic = Topic.where(:title => 'new topic').first_or_create
    assert_not_equal nil, topic.written_on

@uberllama If my test-case represents your situation I need some more inputs how to proceed. could it be related to your if condition? Did you make sure that the callback does not run or did you just check if the exception is not raised? What rails version did you use?


@steveklabnik could you tag this with needs feedback?


Senny, the problem specifically occurs with exceptions. Changing the controller code from a first_or_create to a find_or_create_by results in the exception being caught and handled.


Sorry, Rails 3.2.8.

In further model testing I found that the wish is actually being created with the first_or_create call, despite an exception being raised in before_create.

# creates record when it shouldn't
Wish.where(:product_id => 44444).first_or_create

# raises exception and does not create record

I don't see why it should behave differently when exceptions are involved. I modified my test case to raise an exception in the before_create callback and I could catch that exception in the test case. Also the record was not created.

@uberllama can you test it against master? Feel free to fork my copy of rails and modify the test-case I created to reveal the bug. Also does your before_create callback run or not when you use first_or_create?


This is definitely a confounding issue. I'm looking deeper into my actual conditional that throws the exception and will report back.


I've looked at the log, and it seems like there's a deeper scoping issue at play. Let me try to explain, with clearer code.


belongs_to :user
before_create :verify_wish_limit
 def verify_wish_limit
   raise OverLimitException unless user.wishes.count < 5

Now, assuming the user has reached their limit in this case, look at these logs:

user.wishes.where(:product_id => 12345).first_or_create:

SELECT "wishes".* FROM "wishes" WHERE "wishes"."user_id" = 1 AND "wishes"."product_id" = 12345 LIMIT 1
SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
SELECT COUNT(*) FROM "wishes" WHERE "wishes"."product_id" = 12345 AND "wishes"."user_id" = 5
INSERT INTO "wishes"...

Note the last select. Its erroneously applying the original where condition to the user.wishes.count call in Wish#verify_wish_limit


SELECT "wishes".* FROM "wishes" WHERE "wishes"."user_id" = 1 AND "wishes"."product_id" = 12345 LIMIT 1
SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
SELECT COUNT(*) FROM "wishes" WHERE "wishes"."user_id" = 1

This is correct behaviour.


@uberllama can you adjust the issue title to reflect the actual issue?




I'm not sure but when you call user.wishes.where(:product_id => 12345) it is mutating the wishes relation in the scope of the create. This is why it is using the same where clause in the count.

@jonleighton could you take a look on this one?

@jonleighton jonleighton was assigned


@user.wishes.where(:product_id =>

Expands to:

scope = @user.wishes.where(:product_id =>
scope.first || scope.create

Which expands to:

scope = @user.wishes.where(:product_id =>
scope.first || scope.scoping { Wish.create }

Therefore, you can easily see that the create callbacks will run within the scope.

I think the only solution is to add a find_or_create_by method to Relation:

@user.wishes.find_or_create_by(:product_id =>

This actually reads better to me than first_or_create, tbh. And it's consistent with the new Relation#find_by method.

So I'm gonna do that.

@jonleighton jonleighton closed this issue from a commit
@jonleighton jonleighton Add Relation#find_or_create_by and friends
This is similar to #first_or_create, but slightly different and a nicer
API. See the CHANGELOG/docs in the commit.

Fixes #7853
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.