23

I am mapping 2 models:

User
Account

class Account 
  has_many :users


class User
  has_one :account

The user table as the account_id in it.

Now on the Account model I want to create a 'primary user' which an account only has 1 off. The user table has a boolean flag :is_primary, how can I create a has_one on the account side for a user who has the is_primary and account_id mapped.

So the SQL would look like:

SELECT * FROM users where account_id=123 and is_primary = 1

So I want:

A user has an account. An account has many users, and has a single primary user also.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Blankman
  • 259,732
  • 324
  • 769
  • 1,199

2 Answers2

49

Approach 1 - Add a new association

Add an has_one association with a where lambda. This allows you to work within your current schema.

class Account 
  has_many :users
  has_one  :primary_user, -> { where(is_primary: true) }, :class_name=> "User"
end

Now:

account.users #returns all users associated with the account
account.primary_user #returns the primary user associated with the account
# creates a user with is_primary set to true
account.build_primary_user(name: 'foo bar', email: 'bar@foo.com')

Approach 2 - Add an association method

class Account 
  has_many :users do
    def primary
      where(:is_primary => true).first
    end
  end
end

Now:

account.users.primary # returns the primary account
Harish Shetty
  • 64,083
  • 21
  • 152
  • 198
  • would appreciated your comments on this, as it is related: http://stackoverflow.com/questions/9365068/rails-model-that-has-both-has-one-and-has-many-but-with-some-contraints – Blankman Feb 20 '12 at 20:12
  • Nice and clean approach. Would you mind explaining how to update the primary_user in a account#update form with a (collection_)select of all users, using approach 1 ? Thank you – Mene Nov 01 '15 at 17:05
  • 1
    @Patient55 Have you tried using `accepts_nested_attributes_for`? – Harish Shetty Nov 02 '15 at 07:54
6

It would probably be simpler to add a primary_user_id field to Account, and add a 'has_one' association for the primary_user:

class Account
  has_many :users
  has_one :primary_user, :class_name => "User"
end

class User
  has_one :account
end

If you must use the existing schema (with the :is_primary boolean flag), you can add a scope like this:

class User
  has_one :account
  scope :primary, where(:is_primary => true)
end

and then chain the scope to the users lookup:

account = Account.find(1)
primary_user = account.users.primary.first
PinnyM
  • 35,165
  • 3
  • 73
  • 81
  • +1 for first approach. Though `account.users.primary` more expressive than `account.users.primary.first` – Amit Patel Sep 24 '12 at 08:40
  • 1
    Very true as the naming convention doesn't flow well. But this is easily rectified by calling the scope `primaries`, and making a class method defined as `def self.primary; primaries.first; end`. Now you can use the general scope `primaries`, or the method `primary` to refer to a single record (where only 1 is expected) – PinnyM Sep 24 '12 at 15:40
  • -1 Account has no business storing user information. The cleanest approach is to introduce a AccountPrimaryUser tie model, and a uniqueness validation on `account_primary_users.account_id` column will make sure there is ever only one primary user. – Epigene Jan 29 '21 at 13:13