简体   繁体   中英

Preventing duplicates via a custom foreign key in has_many :through

I'm trying to implement a two-way has_many :through association between a User model and a Location model using a UserLocations join table. This will enable setting user locations with built in ActiveRecord methods, ie. @user.locations = [<Location 1>, <Location 2>, ...] . My goal is to not associate locations to users individually, but rather for users to add and remove them, one or more at a time, via another field: :zip_code . This means that when a user adds a zip code, ActiveRecord should insert a single record into UserLocations (something like INSERT INTO user_locations (user_id, zip_code) VALUES (1, 12345) ). Then, when @user.locations is called, ActiveRecord should join by :zip_code and get the matching location(s). My current implementation works, except that one INSERT into UserLocations is generated for each location associated with a zip code.

class User < ActiveRecord::Base
  has_many :user_locations
  has_many :locations, through: :user_locations
end

class UserLocation < ActiveRecord::Base
  belongs_to :user
  belongs_to :location, primary_key: :zip_code, foreign_key: :zip_code
end

class Location < ActiveRecord::Base
  has_many :user_locations, primary_key: :zip_code, foreign_key: :zip_code
  has_many :users, through: :user_locations
end

Things I've tried:

  • validates_uniqueness_of :zip_code, scope: :user_id - just throws a validation error and prevents all record creation
  • has_many unique: true - doesn't prevent duplicate DB queries
  • add_index unique: true for (user_id, zip_code) - would at least prevent duplicate entries from being created, but I'm trying to prevent unnecessary queries entirely

Using questions like this one for guidance hasn't gotten me any closer. Is what I'm trying to do possible without using my own methods to get/set user locations?

First of all, I'm not very experienced in rails yet, but I'll still try to help :)

What I would do is not using zipcodes as a key. When a user inputs zip codes you look up the code in the Location:

@zip_code = Location.where(zipcode: user_input).first
@zip_code.user_locations.create!(user_id #some other stuff you want)

This way you store the id of the location into the user location and no duplicates are made. You can then generate user locations by joining the UserLocation and Location.

But as I said, there may be a better way of doing this as I'm beginner.

Stop me if I'm wrong :)

You have zipcodes in your locations table (ie: 111, 222, 333) When a user selects a zipcode of '111' for him self, his record is associated with the existing locations record; but when a user selects a zipcode of '444' a new locations record is created and link to that user. Next use that selects '444' will be linked to this same record.

If my assumption if correct, you should have:

  • validates_uniqueness_of :zip_code (without scope) in your Location model
  • in your User model while creating/updating you could use Location.find_or_create_by(params[:zipcode])

This is pseudo-code (don't copy-paste it), I don't exactly know how your code is writen, but my point is for you to have a look at find_or_create , I believe it could be your solution

It looks like you have the association setup correctly.

When you have a has_many association in rails and want to do something like this:

@user.locations = [<Location 1>, <Location 2>, ...]

Rails will create individual INSERT statements for each location in the array, although it will do a bulk DELETE for you. If you want it to do bulk INSERT statements, you'll need to roll your own code or look into the activerecord-import gem to do this.

As for the duplicates, if you are only doing the above code, there shouldn't be duplicate record errors unless there are duplicates in that location array, in which case you should call uniq on it first.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM