Best way to create a belongs_to object from a has_many
Sorry for the vague title but I cant think of a better explanation!
Let's say I have a list of Users and an admin could click a button/link that would create a site for them from the existing user's data. What would be the best way to set this up?
This is what I currently have but I feel like I'm on the wrong track:
Let's say I have a list of Users and an admin could click a button/link that would create a site for them from the existing user's data. What would be the best way to set this up?
This is what I currently have but I feel like I'm on the wrong track:
class Site < ApplicationRecord has_one :user end
class User < ApplicationRecord belongs_to :site end
I then have a link on the list of users:
<%= link_to "Create site", sites_path(user_id: user.id), method: :post %>
And then the Sites controller:
class SitesController < ActionController::Base def create # What now? end def site_params params.fetch(:site, {}).permit! end end
Hey Morgan,
Your associations are backwards for what you're wanting I think. Try:
class Site < ApplicationRecord belongs_to :user end
class User < ApplicationRecord has_one :site end
Now you can build the association like so:
user.build_site
Hi Jacob!
I started out with that association but the Site holds :subdomain, & :main_domain which is then used to load realted layouts, pages and other relations so I'm not sure how I would do this if a Site belongs_to a User?
I started out with that association but the Site holds :subdomain, & :main_domain which is then used to load realted layouts, pages and other relations so I'm not sure how I would do this if a Site belongs_to a User?
Hmm, I'm not sure I follow what the problem is here..
Can you provide a specific use case that prohibits this setup from working for your needs? How are you querying for your :subdomain and :main_domain that would keep you from getting the desired result?
You could be completely correct for your use case, I'm just not tracking yet is all :)
Can you provide a specific use case that prohibits this setup from working for your needs? How are you querying for your :subdomain and :main_domain that would keep you from getting the desired result?
You could be completely correct for your use case, I'm just not tracking yet is all :)
I think what you suggested is correct and I have updated my models but I'm still stuck on the controllers.
So I have a list of users who each have a link that looks like this:
So I have a list of users who each have a link that looks like this:
<%= link_to "Create site", sites_path(user_id: user.id), method: :post %>
But I'm not sure how to handle the params?
class SitesController < ActionController::Base def create @user = User.find(params[:user_id]) @site = @user.build_site(subdomain: @user.subdomain) @site.save end def listing_params params.fetch(:listing, {}).permit! end end
This is what my Sites schema looks like:
create_table "sites", force: :cascade do |t| t.string "subdomain" t.string "main_domain" t.string "type" t.string "label" t.bigint "user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["user_id"], name: "index_sites_on_user_id" end
As you can see, I have a subdomain which is created from the users first_name & last_name by friendly_id
Ok cool - so it looks like you just want the button to create the association and then later you'll provide a page to enter the additional info such as the subdomain and main_domain, correct?
If so, then just this would work:
If so, then just this would work:
class SitesController < ActionController::Base def create @user = User.find(params[:user_id]) @site = @user.build_site.save end end
There's no need to mess with site_params in this case since you're not passing any of that information to the new record yet. So just pass the user_id, find that user, then use the build_site method to create the association and then call save to commit it all to the DB.
I forgot to specifically address the friendly_id part...
Can you post your method you use for friendly_id to create the subdomain? As long as it runs the `slugginator` after save when it will have the user_id then you should be good
Can you post your method you use for friendly_id to create the subdomain? As long as it runs the `slugginator` after save when it will have the user_id then you should be good
Ahh perfect thanks, Jacob!
I was originally generating the :subdomains in the user model, but now that I have the Sites model I think I should move it there but this obviously won't work now as it doesn't know about the users :first_name & :last_name
I was originally generating the :subdomains in the user model, but now that I have the Sites model I think I should move it there but this obviously won't work now as it doesn't know about the users :first_name & :last_name
class Site < ApplicationRecord
extend FriendlyId
friendly_id :slug_candidates, use: :slugged, slug_column: :subdomain
belongs_to :userdef slug_candidates
[
:first_name,
[:first_name, :last_name],
[:first_name, :last_name, :id],
]
end
end
No problem at all!
You can still set it thanks to the has_one:
You can still set it thanks to the has_one:
class Site < ApplicationRecord
def slug_candidates
[
user.first_name,
[user.first_name, user.last_name],
[user.first_name, user.last_name, user.id],
]
end
end
Man, I love Ruby!
I just changed the above to below and it works perfectly!
I just changed the above to below and it works perfectly!
def slug_candidates
[
user.first_name,
[user.first_name, user.last_name,],
[user.first_name, user.last_name, :id]
]
end
Hi Jacob, I had all this working beautifully until I added a third model and I have been going around in circles since!
This is not valid, but I'm basically trying to achieve this:
A Site is a very small model which stores things like domains, subdomains etc can be used to create differnt type of sites. i.e User site, Listing site, Company site.
A Listing could have its own Site (but doesn't have to) and must belong to at least one User but possibly more.
A User can have one Site and can have many Listings.
So something like:
User has_one Site
User has_many Listings
Listing has_one Site
Listinghas_many Users
A Site has_many Users
Is had a look at has many through and polymorphic associations but kept getting stuck with all the two way relationships so I suspect I'm going about this the wrong way.
This is not valid, but I'm basically trying to achieve this:
A Site is a very small model which stores things like domains, subdomains etc can be used to create differnt type of sites. i.e User site, Listing site, Company site.
A Listing could have its own Site (but doesn't have to) and must belong to at least one User but possibly more.
A User can have one Site and can have many Listings.
So something like:
User has_one Site
User has_many Listings
Listing has_one Site
Listinghas_many Users
A Site has_many Users
Is had a look at has many through and polymorphic associations but kept getting stuck with all the two way relationships so I suspect I'm going about this the wrong way.
This may not be *correct* but I believe it does what you want. There could very well be a more railsy way or a cleaner / slicker way... but here goes:
class User < ApplicationRecord has_one :site has_one :listing has_many :association_groups has_many :listings, through: :association_groups end #columns: user_id class Listing < ApplicationRecord belongs_to :user has_one :site has_many :association_groups has_many :users, through: :association_groups end #columns: user_id, listing_id class Site < ApplicationRecord has_one :user has_one :listing has_many :association_groups has_many :users, through: :association_groups end #columns: listing_id, user_id, site_id class AssociationGroup < ApplicationRecord belongs_to :listing, optional: true belongs_to :user, optional: true belongs_to :site, optional: true end user1 = User.create user1_site = user1.build_site.save user1_listing = user1.build_listing.save user1_association_group = user1.association_groups.build(listing_id: user1.listing.id, site_id: user1.site.id).save user2 = User.create user2_site = user2.build_site.save user2_listing = user2.build_listing.save user2_association_group = user2.association_groups.build(listing_id: user2.listing.id, site_id: user2.site.id).save # assign user1 to the listing user2 created user2.listing.association_groups.build(user_id: user1.id).save user2.listing.users # assign user2 to the site user1 created user1.site.association_groups.build(user_id: user2.id).save user1.site.users
I had to create a new table - AssociationGroup - to handle the additional associations. You may want to come up with a more descriptive name... I'm bad at naming :)
If you're using Rails 5 - you'll have to use optional: true on the AssociationGroup table since it's now required by default
Probably the trickiest thing to remember is that when you're creating a users site or listing, you can use user.build_listing or user.build_site - but if you're assigning a user to another site (that's not initially theirs), then you have to create the association through association_groups.
Be interesting to see if anyone has any other ideas!
Thanks, Jacob, this makes a lot of sense, but how would you handle the freindlyId subdomains on the Site table with this setup?
Before I was just doing @listing.assign_attributes(listing_body) which would pass the required attributes to build the subdomain.
It looks like I need to somehow pass the listing to user1_site = user1.build_site.save
Before I was just doing @listing.assign_attributes(listing_body) which would pass the required attributes to build the subdomain.
It looks like I need to somehow pass the listing to user1_site = user1.build_site.save
Well, considering a site now can be created for a user or a listing, I believe you're going to have to set the slug manually or potentially put a "type" like field on the site table so your slug generator can check which type of site it is then set the slug based on that.
So for instance:
So for instance:
def slug_candidates if self.user_site? [ user.first_name, [user.first_name, user.last_name,], [user.first_name, user.last_name, :id] ] else # listing site [ listing.title ] end end
This way you can kind of control how the slug gets created based on whatever criteria you want
Yes, I think that would have been the next issue I would have ran into, but I think my problem now is that I am trying to build up the associations in the same order as your example with a User first but I'm creating the Listing first.
I'll need to play with it some more!
Do you have a patreon account or similar setup? I really appreciate all your help!
I'll need to play with it some more!
Do you have a patreon account or similar setup? I really appreciate all your help!
Can a listing exist without a user? If so then you'll want to add the optional: true to the has_one :user on the listing model then you should be able to create it without any problems.
#columns: user_id class Listing < ApplicationRecord belongs_to :user, optional: true has_one :site has_many :association_groups has_many :users, through: :association_groups end listing = Listing.create listing_site = listing.build_site.save
And thanks for the offer, but no need! Answering questions helps me learn more and I enjoy the challenge! :)
Yes, a listing can exist without a user in the model you've outlined, thanks to the optional: true clause within the belongs_to :user association. This clause specifically informs Rails that the user_id field in the listings table is not mandatory, allowing for listings to be created without an associated user.