Ruby on Rails: new vs. create vs. build difference

27
Oct
5

Difference by examples. Let’s say that a User has_many Blogs and a Blog belongs_to a User.

$ ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-linux]
$ rails -v
Rails 2.3.4

Last updated: 27.10.2009

new

New objects can be instantiated as either empty (pass no construction parameter) or pre-set with attributes but not yet saved (pass a hash with key names matching the associated table column names). In both instances, valid attribute keys are determined by the column names of the associated table — hence you can‘t have attributes that aren‘t part of the table columns.

Model.new

Requires additional save to save to database.

>> b = Blog.new({:title => "Test"})
+-------+---------+------------+------------+
| title | user_id | created_at | updated_at |
+-------+---------+------------+------------+
| Test  | 1       |            |            |
+-------+---------+------------+------------+
1 row in set
>> b.save
  SQL (0.1ms)   BEGIN
  Blog Create (0.3ms)   INSERT INTO `blogs` (`created_at`, `title`, `updated_at`, `user_id`) VALUES('2009-10-27 04:35:20', 'Test', '2009-10-27 04:35:20', 1)
  SQL (1.9ms)   COMMIT
=> true

New and associating through <<

The object is saved to the database after adding the association using <<.

>> u = User.first
>> b = Blog.new({:title => "Test"})
+-------+---------+------------+------------+
| title | user_id | created_at | updated_at |
+-------+---------+------------+------------+
| Test  | 1       |            |            |
+-------+---------+------------+------------+
1 row in set
>> u.blogs << b
  SQL (0.1ms)   BEGIN
  Blog Create (0.4ms)   INSERT INTO `blogs` (`created_at`, `title`, `updated_at`, `user_id`) VALUES('2009-10-27 04:41:15', 'Test', '2009-10-27 04:41:15', 1)
  SQL (11.6ms)   COMMIT
  Blog Load (0.4ms)   SELECT * FROM `blogs` WHERE (`blogs`.user_id = 1)
+----+---------+---------+---------+---------+
| id | title   | user_id | crea... | upda... |
+----+---------+---------+---------+---------+
| 6  | Test    | 1       | 2009... | 2009... |
+----+---------+---------+---------+---------+
1 row in set

Calling new on an association

When calling new on an association, it will create the object with the foregin key correctly set, but will not save it to the database (not even when parent is saved). The created object must be saved manually with save.

>> u = User.first
>> b = u.blogs.new({:title => "Test"})
+-------+---------+------------+------------+
| title | user_id | created_at | updated_at |
+-------+---------+------------+------------+
| Test  | 1       |            |            |
+-------+---------+------------+------------+
1 row in set
>> u.save
  SQL (0.1ms)   BEGIN
  SQL (0.2ms)   COMMIT
=> true
>> u.blogs.all
  Blog Load (0.1ms)   SELECT * FROM `blogs` WHERE (`blogs`.user_id = 1)
0 rows in set
>> b.save
  SQL (0.1ms)   BEGIN
  Blog Create (0.3ms)   INSERT INTO `blogs` (`created_at`, `title`, `updated_at`, `user_id`) VALUES('2009-10-27 04:54:06', 'Test', '2009-10-27 04:54:06', 1)
  SQL (3.9ms)   COMMIT
=> true
>> u.blogs.all
  Blog Load (0.3ms)   SELECT * FROM `blogs` WHERE (`blogs`.user_id = 1)
+----+---------+---------+---------+---------+
| id | title   | user_id | crea... | upda... |
+----+---------+---------+---------+---------+
| 7  | Test    | 1       | 2009... | 2009... |
+----+---------+---------+---------+---------+
1 row in set

Build

build(associations, parent = nil)

Model.build

Build cannot be called directly on the model. It can only be called on associations.

>> Blog.build({:title => "Test"})
NoMethodError:   SQL (9.0ms)   SHOW TABLES
undefined method `build' for #

Calling build on an association

When calling build on an association, the new object will be saved when the parent is saved.

>> u = User.first
>> u.blogs.build({:title => "Test"})
+-------+---------+------------+------------+
| title | user_id | created_at | updated_at |
+-------+---------+------------+------------+
| Test  | 1       |            |            |
+-------+---------+------------+------------+
1 row in set
>> u.save
  SQL (0.1ms)   BEGIN
  Blog Create (0.3ms)   INSERT INTO `blogs` (`created_at`, `title`, `updated_at`, `user_id`) VALUES('2009-10-27 04:56:10', 'Test', '2009-10-27 04:56:10', 1)
  SQL (2.6ms)   COMMIT
=> true
>> u.blogs.all
  Blog Load (0.3ms)   SELECT * FROM `blogs` WHERE (`blogs`.user_id = 1)
+----+---------+---------+---------+---------+
| id | title   | user_id | crea... | upda... |
+----+---------+---------+---------+---------+
| 7  | Test    | 1       | 2009... | 2009... |
+----+---------+---------+---------+---------+
1 row in set

Create

Creates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not.

Model.create

As the description of the method says, the object will be saved to the database if it passes validations.

>> u = User.first
>> Blog.create({:title => "Test"})
  SQL (0.1ms)   BEGIN
  Blog Create (0.3ms)   INSERT INTO `blogs` (`created_at`, `title`, `updated_at`, `user_id`) VALUES('2009-10-27 05:06:55', 'Test', '2009-10-27 05:06:55', NULL)
  SQL (10.7ms)   COMMIT
+----+-------+---------+-----------+-----------+
| id | title | user_id | create... | update... |
+----+-------+---------+-----------+-----------+
| 9  | Test  |         | 2009-1... | 2009-1... |
+----+-------+---------+-----------+-----------+
1 row in set
>> Blog.all
  Blog Load (0.3ms)   SELECT * FROM `blogs`
+----+-------+---------+-----------+-----------+
| id | title | user_id | create... | update... |
+----+-------+---------+-----------+-----------+
| 9  | Test  |         | 2009-1... | 2009-1... |
+----+-------+---------+-----------+-----------+
1 row in set

Note: In this case, there is no validation that the user_id field must be present, so such an object passes validation. If validation was added for user_id, the row would not be created in this case, because the object would not have passed validation. In that case, :user_id would have to be explicitly passed in the attrbiutes hash.

Calling create on an association

Works similar to build in this case (automatically adds the foregin_key - user_id in this case), but the new row is saved to the database already, and not only after the parent model is saved (as is the case with build).

>> u = User.first
>> u.blogs.create({:title => "Test"})
  SQL (0.1ms)   BEGIN
  Blog Create (0.3ms)   INSERT INTO `blogs` (`created_at`, `title`, `updated_at`, `user_id`) VALUES('2009-10-27 05:11:22', 'Test', '2009-10-27 05:11:22', 1)
  SQL (0.7ms)   COMMIT
+----+-------+---------+-----------+-----------+
| id | title | user_id | create... | update... |
+----+-------+---------+-----------+-----------+
| 10 | Test  |         | 2009-1... | 2009-1... |
+----+-------+---------+-----------+-----------+
1 row in set
>> u.blogs.all
  Blog Load (0.3ms)   SELECT * FROM `blogs` WHERE (`blogs`.user_id = 1)
+----+-------+---------+-----------+-----------+
| id | title | user_id | create... | update... |
+----+-------+---------+-----------+-----------+
| 10 | Test  |         | 2009-1... | 2009-1... |
+----+-------+---------+-----------+-----------+
1 row in set
>> u.save
  SQL (0.1ms)   BEGIN
  SQL (0.2ms)   COMMIT
=> true

Cheat cheet

Steps required for the object to be saved to database.

New

  • Blog.new(…).save
  • user.blogs << Blog.new(…)
  • user.blogs.new(…).save – do not use, no practical use case

Build

  • Blog.build – not possible
  • user.blogs.build(…), user.save – both are required to save to DB

Create

  • Blog.create(…)
  • user.blogs.create(…)