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.
In essence, this code structure enables the creation of listings that are independent of users, offering flexibility in your application's data modeling.