Ruby on Rails Wednesday, October 10, 2018



On Wednesday, 10 October 2018 10:52:33 UTC+2, Rob Jonson wrote:
You need to enforce the uniqueness at the database level, then catch errors.

More info on transactions here:

iiuc - transaction enforces that all changes within the transaction are applied (or none). 
it doesn't enforce that changes in transaction B are applied before a lookup in transaction A happens

looks like you could also work with a lock, though the database enforcement seems more natural.

Thank you very much, Rob ! I will add a unique index to Users#username column (I had one but without unique option).
What about wrapping the call into transaction ? Will it be correct to call retry ? If I got it right, retry will take another call to 

User.find_or_create_by(username: some_value)

but this time will fetch the existing record ? Or not ?

Thank you.



On Tuesday, 9 October 2018 22:03:35 UTC+1, belgoros wrote:
I can't figure out the right way to use find_or_create_by method which is not atomic.
In short, I have a before_action filter is used to either to find or create a User by its username. 

def user
    if decoded_auth_token && decoded_auth_token[:sub]
      @user ||= User.find_or_create_by!(username: decoded_auth_token[:sub])
      Rails.logger.silence do
        @user.update_column(:token, http_auth_header)
      end
      @user
    end
  rescue ActiveRecord::RecordInvalid => e
    raise(
      ExceptionHandler::InvalidToken,
      ("#{Message.invalid_token} #{e.message}")
    )
  end

The problem is that the above method is called twice by different threads: 2 requests com from a JS front-end app, and I have the situation when 2 Users are created with the same username.

I tried to apply the suggested solution and wrap the method call in a transaction and use retry:
begin    user = User.transaction(requires_new: true) do      User.find_or_create_by(username: some_value)    end  rescue ActiveRecord::RecordNotUnique    retry  end
call_some_method to update other user attributes in User model:
def update_user_info(options)      identifier = normalize_identifier(options[:sitenumber])      update(        first_name: options[:givenName],        last_name: options[:sn],        shop_identifier: identifier,        shop: user_shop(identifier)      )    end

but it creates nevertheless the duplicate record. What am I missing ? 

Used Rails version: 5.2.0
Ruby: 2.5.0

Thank you.

--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-talk+unsubscribe@googlegroups.com.
To post to this group, send email to rubyonrails-talk@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rubyonrails-talk/117734f0-3d20-40aa-b5d4-d25bc6710e70%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

No comments:

Post a Comment