How to allow at most one record of a rails model to have a bool property true?

rails custom validation
rails validates inclusion
rails validation message
rails validate if
rails validate on: :create
rails conditional validation
rails validate email
active model validations

In the typical rails (4.2.x) blog app, I have a Post model. The post has a boolean column called primary. I want to enforce a model level constraint that at most one post has primary=true. If user sets a new post to be primary=true, all other posts must be marked primary=false before saving this post.

I could do this in the controller, when a post is created or updated to be primary=true, by changing all other posts to primary=false. Something like:

# in posts_controller#create and #update
...
if @post.primary
  [Post.all - self].select(&:primary).each do {|p|p.primary = false; p.save}
end
@post.save!
...

However, I want this to be a model level constraint, so I can add validations, unit tests, etc. that there is only one post with primary=true. If I use a callback like before_commit, then I may run into an infinite loop since updating the older posts in a new post's before_commit will trigger the older posts' before_commit, etc.

How do I enforce this behavior at the model level?

ActiveRecord has some update attributes methods that don't trigger callbacks like post.update_column, Post.update_all, etc. So you can use these in a callback like

before_save :set_primary

private
def set_primary
  Post.where.not(id: id).update_all(primary: false)
end

[Help] Best way to ensure only one boolean is true on has_many , [Help] Best way to ensure only one boolean is true on has_many association The only suggestions I have seen on Google is to configure a before_save if: and then validates :primary, uniqueness: true, if: :primary? in the Email model but I then Rails properly and trying to land a job that would allow me to use them. 7 Validates greater than existing column in rails model Dec 7 '15 7 Ruby/Rails sample unique object from collection each time Dec 22 '15 6 How to allow at most one record of a rails model to have a bool property true?

Active Record Validations — Ruby on Rails Guides, It's the opinion of the Rails team that model-level validations are the most To verify whether or not a particular attribute of an object is valid, you can use errors [:attribute] . Let's take a look at each one of the available helpers. Since false. blank? is true, if you want to validate the presence of a boolean field you should� Active Record BasicsThis guide is an introduction to Active Record.After reading this guide, you will know: What Object Relational Mapping and Active Record are and how they are used in Rails. How Active Record fits into the Model-View-Controller paradigm. How to use Active Record models to manipulate data stored in a relational database. Active Record schema naming conventions. The concepts

One approach you could use is to implement a custom validator on the model that prevents other primary Posts from being saved to the DB if one already exists.

You could then define a class method on Post to reset the primary Post to a normal Post and then set a different Post as the primary one.

The Custom Validator (app/validators/primary_post_validator.rb)

class PrimaryPostValidator < ActiveModel::Validator
  def validate(record)
    if record.primary
      record.errors[:primary] << "A Primary Post already exists!" if
        Post.where(primary: true).any?
    end
  end
end

Post Model

class Post < ApplicationRecord
  validates_with PrimaryPostValidator

  def self.reset_primary!
    self.update_all(primary: false)
  end
end

schema.rb

create_table "posts", force: :cascade do |t|
  # any other columns you need go here.
  t.boolean  "primary",        default: false, null: false
  t.datetime "created_at",                      null: false
  t.datetime "updated_at",                      null: false
end

This set up will allow you to control which Post gets assigned as the primary Post from a controller, and handle occasions where you need to swap the primary Post. I think it's a bad idea allowing the saving of a model to affect other records in the DB as you originally requested.

One way of handling this logic in the controller:

def make_primary
  @post = Post.find(params[:id])
  Post.reset_primary!
  @post.update_attributes(primary: true)
end

While this appears contrived compared to the accepted answer, I believe it gives you a much greater level of control over which Post gets set to primary and when. This solution will also work with validations, not skipping them like in the answer above.

Active Record Basics — Ruby on Rails Guides, How to use Active Record models to manipulate data stored in a relational database. Using ORM, the properties and relationships of the objects in an application can be (or this one) or study them by other means if you would like to learn more. So, for a class Book , you should have a database table called books. Active Record AssociationsThis guide covers the association features of Active Record.After reading this guide, you will know: How to declare associations between Active Record models. How to understand the various types of Active Record associations. How to use the methods added to your models by creating associations.

Please checkout this simple gem set_as_primary which does the same thing. It supports other features too.

The simplest way to handle the primary or default flag to your Rails models.

How to Use The Ruby Select Method (With Examples), Inside the block, you have to return something that evaluates to true or false , and select You can learn about boolean values in Ruby by reading this article. I don't want you to become confused when you work with Rails models & learn You have learned about select, one of Ruby's most helpful methods to work with � config.active_record.record_timestamps is a boolean value which controls whether or not timestamping of create and update operations on a model occur. The default value is true . config.active_record.partial_writes is a boolean value and controls whether or not partial writes are used (i.e. whether updates only set attributes that are dirty).

Working with Databases: Active Record, yii\db\ActiveRecord::findOne(): returns a single Active Record instance populated with the Both methods can take one of the following parameter formats: If you are interested in the attribute values prior to their most recent modification, This allows data retrieved from table column declared as integer to be populated in� Caching with Rails: An OverviewThis guide is an introduction to speeding up your Rails application with caching.Caching means to store content generated during the request-response cycle and to reuse it when responding to similar requests.Caching is often the most effective way to boost an application's performance. Through caching, web sites running on a single server with a single database

makandra/active_type: Make any Ruby object quack like , This means that if you use ActiveType, ActiveRecord::Base.attribute will be overriden. :date_of_birth, :date attribute :accepted_terms, :boolean attribute : account_type end class Holiday < ActiveRecord::Base validates :date, presence: true end class Allow to destroy records if the attributes contain _destroy => '1'. If we want a raw insert method for a single record, it should have it's on method. Could just be #insert. I'd rather break out the on_conflict options into a few different things. First, we can do skip_duplicates: true for that part. Second, we can have an explicit method called #upsert/upsert_all to handle upserts.

Query Parameters - Routing, Query params allow for additional application state to be serialized into the URL that See finding records to see how query parameters are applied to API requests in Note that you can't make queryParams be a dynamically generated property Route { queryParams = { category: { refreshModel: true } }; model( params)� The ActionCable.startDebugging() and ActionCable.stopDebugging() methods have been removed and replaced with the property ActionCable.logger.enabled.If you are currently using these methods you