2

My Goal:

I'm trying to create two different types of users, with different profiles.

Barber, Client, BarberProfile, and ClientProfile

I have my base User object that contains information like email, password, and all the other fields that Devise keeps track of.

I'd like the base User model to have one Profile that keeps track of all basic information that I want all my users to have. For instance, first_name, last_name, avatar, etc.

I'm using single table inheritance to create two different types of users: Client and Barber.

I want each of these types of users to have a base Profile associated with it, and then have additional fields that belong to a BarberProfile and a ClientProfile, respectively.

The BarberProfile will have things that the Barber needs, but the Client doesn't. For instance, a bio. The ClientProfile will have things the Client needs, but the Barber doesn't. For instance, hair_type.

What I currently have, and my problem:

As stated above, I've created a table for User and Profile. So I'm able to call user.profile.first_name. I created a BarberProfile and ClientProfile table in order to add the extra fields.

I'd like to just be able to reference user.profile.bio if the user type is Barber. But bio isn't a part of the base profile. So in this case I'd have to create an associated Profile and and associated BarberProfile to get everything I need. I could just do user.profile.first_name and user.barber_profile.bio, but it feels messy, and I'm making two different associations from essentially the same type of model. I feel like it should be a simple thing to just have the BarberProfile inherit all fields from Profile and add its own specific Barber fields on top.

How does one go about doing this in Rails?

Edit: One of the main reasons I want to do this is so I can update things like first_name and bio within the same form for a Barber. And similarly, a first_name and hair_type within the same form for a Client.

Doug
  • 1,517
  • 3
  • 18
  • 40

2 Answers2

1

If you want to avoid using two associations on the users for Profile and Client/BarberProfile, I think you should make ClientProfile and BarberProfile extend Profile (single table inheritance) and each of them "has one :barber_profile_data" (I'm not sure how to call it). To fix the long method calls, you can use delegated methods.

class Barber > User
  has_one :barber_profile
  delegate :bio, to: :barber_profile

class Client < User
  has_one :client_profile
  delegate :first_name, to: :client_profile

class BarberProfile < Profile
  has_one :barber_profile_data
  delegate :bio, to: :barber_profile_data

class ClientProfile < Profile
  has_one :client_profile_data
  delegate :first_name, to: :client_profile_data

Then, when you do "@barber.bio", it should call, internally, "@barber.barber_profile.barber_profile_data.bio".

arieljuod
  • 15,460
  • 2
  • 25
  • 36
  • Thanks for the input. I like the idea, as extending profile was my first instinct and seems pretty intuitive. So in your suggestion, barber_profile_data exists as a separate table? Is it possible to update bio from one form with this setup? – Doug Apr 16 '17 at 05:21
  • Yes, BarberProfileData should have its own table. About the update, it should be possible, check rails form_for method fields_for and play with that. Anyway, you can always customize the update action and do wahatever you need to set the right fields_for. – arieljuod Apr 17 '17 at 09:41
1

This sounds like a good use case for Multiple Table Inheritance. In MTI you use additional tables to decorate the base model.

The main advantage to MTI is that the clients and barbers tables can contain the specific columns for that type vs STI which requires you to cram everything into users by design (thats why they call it single table).

create_table "barbers", force: :cascade do |t|
  # only the specific attributes
  t.text     "bio"
end

create_table "clients", force: :cascade do |t|
  # only the specific attributes
  t.string   "hair_type"
end

create_table "users", force: :cascade do |t|
  t.string   "email"
  t.string   "first_name"
  t.string   "last_name"
  # ... all the other common attributes
  t.integer  "actable_id"
  t.string   "actable_type"
  t.datetime "created_at",   null: false
  t.datetime "updated_at",   null: false
end

This is an example with the ActiveRecord::ActsAs gem.

class User < ApplicationRecord
  actable
end

class Barber < ApplicationRecord
  acts_as :user
end

class Client < ApplicationRecord
  acts_as :user
end

Note that Barber and Client should not be true subclasses. ActiveRecord::ActsAs instead delegates to the "actable" class.

You can then do Barber.all or Client.all to fetch specific types or use User.all.map(:specific) to get decorated users of all types.

Pere Joan Martorell
  • 2,608
  • 30
  • 29
max
  • 96,212
  • 14
  • 104
  • 165