I have a rails 4 application whereby I've set up a User model. I want the User to have a has_many association with a user profile model, but here's the catch:
My user profile model needs to be polymorphic - the
User model can have multiple (different) user profiles associated
with it (e.g. ProfileTypeA, ProfileTypeB, ProfileTypeC, etc.)
I want my User model to have one association, say, user_profiles, that
would return the all the user's user profiles associated with it.
I believe I'm on the right track (or am I?), but how would this be accomplished using the rails generator? The part that is most confusing to me, is how to do bullet #2 above.
P.S. I took a look at STI's, but it seems to me that my User model would have to have a hard-association with each user profile type model, which I don't like because it would change the User model with each new user profile type I add to the data model.
You sound on right track,try below
#The polymorphic models
class User
has_many :user_profiles, as: :profileable
end
class UserProfile
belongs_to :profileable, polymorphic: true
end
The Migrations below
#migrations
class CreateUserProfiles < ActiveRecord::Migration
def change
create_table :user_profiles do |t|
t.integer :profileable_id
t.string :profileable_type
t.timestamps null: false
end
add_index :user_profiles, :profileable_id
end
end
Related
I have a parent User model, and each user has_one Contact which hold's the user's contact info.
One of the contact fields is the email. For various legacy reasons and reasons outside the context of this question, I'd love to be able to call #email directly as if it were a property of User
In other words I'd like to do this -
user.email
user.email = "foo#example.com"
Instead of this -
user.contact.email
user.contact.email = "foo#example.com"
I laid out my models as follows, overriding the child's getter/setter from the parent User -
class User < ActiveRecord::Base
has_one :contact
def email
contact.email
end
def email=(value)
contact.email = value
end
end
class Contact < ActiveRecord::Base
belongs_to :user
end
You may have already noticed the problem with the setter - when the User is saved, it doesn't save the child model.
What's the most robust to work around this?
Thanks!
Ok, found the solution for anyone curious - auto-saving
ActiveRecord::AutosaveAssociation is a module that helps with exactly this - saving child associations properly when the parent is saved.
Just need to include an autosave: true. Example -
has_one :contact, autosave: true
It also takes care of destroying records marked for deletion, and a few other fancy things.
I also did a quick check, and it smartly doesn't hit the DB unless it really needs to. That is, if you update the parent only it doesn't bother saving the child association with an other SQL UPDATE statement.
Check out the documentation here.
Short explanation:
I seek architectural advice and help in implementing multiple Devise models in a single app.
More detailed explanation:
My application needs to perform the following behavior:
There are 3 types of users (Partner, Attendee, Speaker) which have some common fields and some unique ones (also, the fields might have different permissions, i.e. Attendee must have a username whereas the Speaker might have it but they don't have to necessarily fill this field in). And moreover, the different user models must have different associations with other tables in the db.
The users need to be able to log in through single log-in form, but the sign-up forms should be different.
So, my first thought was that I should divide the users by roles using Pundit or declarative_authorization or something, but the users don't really have different roles (i.e. permissions) in the app, they rather have different behavior, they might see different content and stuff, so I continued thinking.
My second though was implementing STI and after reading several articles about it, I tried to do that in code.
I generated a Devise User model by doing rails g devise User and after that ran rails g model Attendee and the same for other two users.
Then, I inherited my models from User:
class Attendee < User
end
My User migration looks like this:
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :type
# Devise stuff ...
..................
t.timestamps null: false
end
And other migrations are like this:
create_table :attendees do |t|
t.string :username
t.string :company_name
t.boolean :subscription
t.timestamps null: false
end
Now I realize it was wrong to create separate tables.
I had to put all the possible fields into the User table, is that correct?
Because now when I try to create any new Attendee or Speaker or Partner in rails console, all of these three models have the exact same fields, those the User model has.
But if I add all the possible fields in the User model, how would I perform validations on field presence?
I've read quite a few articles and questions here on SO, but still can't really wrap my head around how to implement all that.
Anyway, is that a correct way to do what I need?
Could anybody explain me in a detailed way how I should implement this kind of behavior and functionality from start to finish, and how should I work with the models after having implemented them?
PS: here's the history of my migrations and the whole github repo
Update
Remembered another issue that stopped my from doing just role separation:
How should I sign up the different users with different sign-up forms? Different routes? I cannot make the user choose their role from the combobox.
You can create conditional validation rules based on the role, but the first place you need to address this is in the new/edit User form, only showing the allowed fields dynamically based on the role:
class User < ActiveRecord::Base
validates :company, presence: true, if: :is_company?
def is_company
# check for the role
self.role == 'company'
end
end
UPDATE: You can pass an extra parameter to the same registration form and use that to differentiate the type of form you display. That's the nicest way. You can also create separate methods in the UserController -> def register_user, def register_company, def register_xxxx
I would like to show only one nested form for the one-to-many relationship between user and appointments. A user can only create one appointment and the form should only show fields for one appointment regardless of how many appointments he has.
I've tried the :limit option
class User
has_many :appointments, dependent: :destroy
accepts_nested_attributes_for :appointments, limit: 1
end
I also tried limiting the number of appointments built in the controller
class UsersController
def edit
#user = current_user
1.times { #appointment = #user.appointments.build }
end
end
I always end up with a nested form for each appointment the user has plus an additional nested form. I know I could change the relationship to one-to-one but this is for legacy code and its likely that the relationship will be one-to-many in the future.
Is there a way that I can only show one nested form regardless of how many appointments a user has?
I'm completely stuck. I've read this http://guides.rubyonrails.org/association_basics.html#the-has-one-association and http://guides.rubyonrails.org/active_record_querying.html and am no closer to solving the problem.
I have a belongs_to, has_one model association. In my case it's a User has_one Booth and a Booth belongs_to a User. What I'm trying to do should be simple, create a link so that users who have created a booth can view the booth they created.
To accomplish this, I'm trying to create a 'user_booth' method. I've tried now about 100 different variations, but something like this should work from everything I've read:
def user_booth
#booth = current_user.booth.find_by(id: params[:id])
end
Well, of course that didn't work. I've also tried many variations similar to this:
def user_booth
Booth.find_by(id: params[:id])
end
When I check my database, everything saves exactly as I intended it to, I just can't seem to call on the correct booth that is associated with correct user. Can anyone lead me in a new direction? I'm entirely stuck.
Here is the show method in the Booths controller:
def show
#booth = Booth.find(params[:id])
end
I should also include my booth migration:
class CreateBooths < ActiveRecord::Migration
def change
create_table :booths do |t|
t.string :name
t.references :user, index: true
t.timestamps null: false
end
end
end
Thanks a million!
I want my user object to be able to be associated with many addresses, and for one of those addresses to be the primary address.
I'm trying to do this without using a Boolean to denote the primary address, instead using both a has-many and and a has-one association - as per the first approach by PinnyM in the following SO: Rails model that has both 'has_one' and 'has_many' but with some contraints
But I can't seem to get it to work.
My migrations:
class User < ActiveRecord::Migration
def change
create_table(:users) do |t|
t.integer :primary_address_id
t.string :name
end
end
end
class Address < ActiveRecord::Migration
def change
create_table(:addresses) do |t|
t.integer :user_id
t.string :address
end
end
end
My models:
class User
has_many :addresses
has_one :primary_address, :class_name => "Address"
end
class Address
belongs_to :user
has_one :user
end
This allows me to use the has_many association by doing user.addresses but I can't seem to access to has one association. I've tried doing:
user.primary_address
user.addresses.primary_address
user.addresses.primary_address.first
I don't really understand how to set these associations up correctly or how to access them. Would appreciate your help!
Just created models and associations that you are using. I don't see why it is not working in your case, because I can access primary_address. This is the code I am using to access it using rails console. Note: I have created a User and couple of Addresses in advance.
# in case if you have user with id = 1
User.find(1).primary_address
# or another example
User.first.primary_address
I don't think your associations will allow this calls though:
user.addresses.primary_address
user.addresses.primary_address.first