Calling create or build on relations
When you call create (or build) on a relation, the newly created record inherits values from the relation’s scoped attributes:
# User(id: integer, name: string)
user = User.where(name: "hello").create
user.name # => "hello"
This only works if the scope condition is a hash with scalar values, that materialize as equality comparisons. If your scope uses an SQL snippet, or any operator other than equality, the created record will not inherit any attribute value:
User.where("age >= 18").build.age # => nil
User.where("name ILIKE '%foo%'").build.name => nil
User.where(name: ['Bob', 'Alice']).build.name => nil
default_scope
This behavior is especially relevant when using default_scope. Despite what the name might suggest, default_scope has no special mechanism for assigning default values. It simply applies given scopes by default, which can indirectly provide values for newly created records.
class User < ApplicationRecord
default_scope { where(name: "hello") }
end
user = User.create
user.name # => "hello"
create_with
Sometimes you may want to apply a condition by default without using the same value when creating new records.
For example, suppose a record has a boolean visible flag. We want queries to return only visible posts by default, while newly created posts should remain hidden unless explicitly made visible:
# Post(id: integer, visible: boolean)
class Post < ApplicationRecord
default_scope { where(visible: true).create_with(visible: false) }
end
post = Post.create
post.visible # => false
Post.all
# SELECT * FROM posts WHERE visible IS TRUE