aboutsummaryrefslogtreecommitdiffstats
path: root/guides/source
diff options
context:
space:
mode:
authorJon Leighton <j@jonathanleighton.com>2012-10-19 13:18:47 +0100
committerJon Leighton <j@jonathanleighton.com>2012-10-19 13:18:47 +0100
commiteb72e62c3042c0df989d951b1d12291395ebdb8e (patch)
tree258c2dc61b2e146fb2cdef47d2799ea9dbdaa004 /guides/source
parent0d7b0f0183ce9f1bfc524cf6afd5d7de852ebc76 (diff)
downloadrails-eb72e62c3042c0df989d951b1d12291395ebdb8e.tar.gz
rails-eb72e62c3042c0df989d951b1d12291395ebdb8e.tar.bz2
rails-eb72e62c3042c0df989d951b1d12291395ebdb8e.zip
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
Diffstat (limited to 'guides/source')
-rw-r--r--guides/source/active_record_querying.md71
1 files changed, 51 insertions, 20 deletions
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 66e6390f67..5e6799e786 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -1225,17 +1225,17 @@ WARNING: Up to and including Rails 3.1, when the number of arguments passed to a
Find or build a new object
--------------------------
-It's common that you need to find a record or create it if it doesn't exist. You can do that with the `first_or_create` and `first_or_create!` methods.
+It's common that you need to find a record or create it if it doesn't exist. You can do that with the `find_or_create_by` and `find_or_create_by!` methods.
-### `first_or_create`
+### `find_or_create_by` and `first_or_create`
-The `first_or_create` method checks whether `first` returns `nil` or not. If it does return `nil`, then `create` is called. This is very powerful when coupled with the `where` method. Let's see an example.
+The `find_or_create_by` method checks whether a record with the attributes exists. If it doesn't, then `create` is called. Let's see an example.
-Suppose you want to find a client named 'Andy', and if there's none, create one and additionally set his `locked` attribute to false. You can do so by running:
+Suppose you want to find a client named 'Andy', and if there's none, create one. You can do so by running:
```ruby
-Client.where(:first_name => 'Andy').first_or_create(:locked => false)
-# => #<Client id: 1, first_name: "Andy", orders_count: 0, locked: false, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">
+Client.find_or_create_by(first_name: 'Andy')
+# => #<Client id: 1, first_name: "Andy", orders_count: 0, locked: true, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">
```
The SQL generated by this method looks like this:
@@ -1243,27 +1243,50 @@ The SQL generated by this method looks like this:
```sql
SELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 1
BEGIN
-INSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 0, NULL, '2011-08-30 05:22:57')
+INSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 1, NULL, '2011-08-30 05:22:57')
COMMIT
```
-`first_or_create` returns either the record that already exists or the new record. In our case, we didn't already have a client named Andy so the record is created and returned.
+`find_or_create_by` returns either the record that already exists or the new record. In our case, we didn't already have a client named Andy so the record is created and returned.
The new record might not be saved to the database; that depends on whether validations passed or not (just like `create`).
-It's also worth noting that `first_or_create` takes into account the arguments of the `where` method. In the example above we didn't explicitly pass a `:first_name => 'Andy'` argument to `first_or_create`. However, that was used when creating the new record because it was already passed before to the `where` method.
+Suppose we want to set the 'locked' attribute to true, if we're
+creating a new record, but we don't want to include it in the query. So
+we want to find the client named "Andy", or if that client doesn't
+exist, create a client named "Andy" which is not locked.
-You can do the same with the `find_or_create_by` method:
+We can achive this in two ways. The first is passing a block to the
+`find_or_create_by` method:
```ruby
-Client.find_or_create_by_first_name(:first_name => "Andy", :locked => false)
+Client.find_or_create_by(first_name: 'Andy') do |c|
+ c.locked = false
+end
+```
+
+The block will only be executed if the client is being created. The
+second time we run this code, the block will be ignored.
+
+The second way is using the `first_or_create` method:
+
+```ruby
+Client.where(first_name: 'Andy').first_or_create(locked: false)
```
-This method still works, but it's encouraged to use `first_or_create` because it's more explicit on which arguments are used to _find_ the record and which are used to _create_, resulting in less confusion overall.
+In this version, we are building a scope to search for Andy, and getting
+the first record if it existed, or else creating it with `locked:
+false`.
-### `first_or_create!`
+Note that these two are slightly different. In the second version, the
+scope that we build will affect any other queries that may happens while
+creating the record. For example, if we had a callback that ran
+another query, that would execute within the `Client.where(first_name:
+'Andy')` scope.
-You can also use `first_or_create!` to raise an exception if the new record is invalid. Validations are not covered on this guide, but let's assume for a moment that you temporarily add
+### `find_or_create_by!` and `first_or_create!`
+
+You can also use `find_or_create_by!` to raise an exception if the new record is invalid. Validations are not covered on this guide, but let's assume for a moment that you temporarily add
```ruby
validates :orders_count, :presence => true
@@ -1272,19 +1295,24 @@ validates :orders_count, :presence => true
to your `Client` model. If you try to create a new `Client` without passing an `orders_count`, the record will be invalid and an exception will be raised:
```ruby
-Client.where(:first_name => 'Andy').first_or_create!(:locked => false)
+Client.find_or_create_by!(first_name: 'Andy')
# => ActiveRecord::RecordInvalid: Validation failed: Orders count can't be blank
```
-As with `first_or_create` there is a `find_or_create_by!` method but the `first_or_create!` method is preferred for clarity.
+There is also a `first_or_create!` method which does a similar thing for
+`first_or_create`.
-### `first_or_initialize`
+### `find_or_initialize_by` and `first_or_initialize`
-The `first_or_initialize` method will work just like `first_or_create` but it will not call `create` but `new`. This means that a new model instance will be created in memory but won't be saved to the database. Continuing with the `first_or_create` example, we now want the client named 'Nick':
+The `find_or_initialize_by` method will work just like
+`find_or_create_by` but it will call `new` instead of `create`. This
+means that a new model instance will be created in memory but won't be
+saved to the database. Continuing with the `find_or_create_by` example, we
+now want the client named 'Nick':
```ruby
-nick = Client.where(:first_name => 'Nick').first_or_initialize(:locked => false)
-# => <Client id: nil, first_name: "Nick", orders_count: 0, locked: false, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">
+nick = Client.find_or_initialize_by(first_name: 'Nick')
+# => <Client id: nil, first_name: "Nick", orders_count: 0, locked: true, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">
nick.persisted?
# => false
@@ -1306,6 +1334,9 @@ nick.save
# => true
```
+There is also a `first_or_initialize` method which does a similar thing
+for `first_or_create`.
+
Finding by SQL
--------------