Search code examples
ruby-on-railsactiverecordrailstutorial.org

Hartl Tutorial Chapter 13.1.3 User/Micropost associations: Why use create instead of create with a bang that raises exceptions?


In Michael Hartl's rails tutorial chapter 13.3.1, we create a twitter-style micropost like this:

Micropost.create

My question is why use Micropost.create and not Micropost.create!?

It seems that you would always want to raise exceptions in active record if there's a problem so that you can address the exceptions.

Why would want to ever use Micropost.create? He explains that both are options with this table: enter image description here

But he doesn't really explain why you would choose one or the other. So, why would you choose one over the other?


Solution

  • Well, it depends where you are using the method.

    When to use create

    If it's a simple create in your controller, generally you will use create to be able to control the flow logic, take this example:

    if Micropost.create(micropost_params)
      # handle success save, e.g.:
      redirect_to :index
    else
      # handle failure in save, e.g.:
      render :new
    end
    

    With the above code you control the flow without using exceptions, and that's what you want when you create a record: Handle errors that you know that will be likely to happen without raising exceptions.

    So, i prefer to ask it the other way around: why use create! and raise an exception (and get the related overhead) if you could just get a false and handle the error? And why do it if i need more code to handle that exception?

    While this is a valid use create, it's not that common, since save and update are more likely to be used (in most cases you will create using new and then saving with save).

    When to use create!

    If you want to update multiple records within the same action (read transactions), then create! will serve you better; consider the following example:

    ActiveRecord::Base.transaction do
      user1.update!(balance: user1.balance + 100)
      user2.update!(balance: user2.balance - 100)
      transfer.create!(amount: 100, receiver: user1, sender: user2)
    end
    

    Here you update 2 records and create 1 within the same transaction, but if one of them fails you need to do a rollback to keep you data integrity intact. So, if you use create you will need to handle the rollback yourself to undo the transaction; on the other hand, using create! will raise an exception that triggers the rollback automatically.

    Another good use for create! is testing (as in Hartl's tutorial) and debugging, i found them more convenient since they help catch errors a lot faster.