Limiting has_one relationships in Rails - ruby-on-rails

I have two ActiveRecord models like so:
class User < ActiveRecord::Base
  has_one :contact_information
end
class ContactInformation < ActiveRecord::Base
  belongs_to :user
end
This is my setup for a One-to-One relationship between the user and contact information table.
The issue I am encountering is when I create a new contact_information entry that points to a user that already has a existing contact_information, the new record is created including the relationship, meaning I have two contact_information records pointing to the same user, even though it is a one-to-one relationship.
Rails seems to pick the first record and returns it.
For example in irb:
> User.create
> ContactInformation.create(user: User.last)
> ContactInformation.create(user: User.last)
> ContactInformation.all
=> #<ActiveRecord::Relation [#<ContactInformation id: 3, created_at: "2016-01-04 22:28:11", updated_at: "2016-01-04 22:28:11", user_id: 2>, #<ContactInformation id: 4, created_at: "2016-01-04 22:28:18", updated_at: "2016-01-04 22:28:18", user_id: 2>]>
Both with user_id set to 2.
The ideal scenario would be a validation failure from contact_information when a record is created for a user that already has a contact_information.
I came up with this initial custom validation method for the Contact Information model that kind of works for creation (Need to make one for update)
validate :check_parent, on: :create
def check_parent
if user.contact_information.id.nil?
errors.add(:user, "already has contact information")
end
end
I am wondering if there is a more rails-y way of solving this problem? Or will I need to create a custom validator like this for all has_one relationships?
Thanks

A few options are available to you.
You could use validates_uniqueness_of on your ContactInformation model:
class ContactInformation < ActiveRecord::Base
validates_uniqueness_of :user_id
end
However, this will query the database to ensure that no duplicates exist on every save and could be very slow.
Another option is to have your database handle this for you. Depending on what you're using ActiveRecord with, adding a uniqueness constraint on the user_id column of your contact_informations table could be valuable.
I would suggest not using validates_uniqueness_of if your dataset is large, otherwise it might be perfect.
Make sure you at least add an index to the user_id column on that table:
class AddIndexOnContactInformationToUserId < ActiveRecord::Migration
add_index :contact_information, :user_id
end

You will need to have a validation but you don't need a custom one:
class ContactInformation < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :user_id
end
You may also want to add a unique index in the database for that column. It would not only make the query performant but will also protect your app against race conditions in case you are running multiple instances of it in parallel.

Related

How rails ActiveRecord or Nosql ORM like mongoid handle has_many, has_one side of the relationship?

Lets Say I have User and a Team model.
class User
#some fields
has_one :team
end
class Team
#some fields
belongs_to :user
end
Now when i do User.last.update(team: Team.last), it stores user_id: foreign key in team document. Prety common stuff.
I want to know what ORMs do/store on the has_one/has_many models(User) just like they store foreign_key on belongs_to(Team) model so that User.last.team query actually gives me a team. Does it create instance methods on User model like
def team
Team.where(user_id: self.id).first
end
Am I wrong?
Okay I can definitely explain what's going on here. Given the following models:
class User
include Mongoid::Document
#some fields
has_one :team
end
class Team
include Mongoid::Document
#some fields
belongs_to :user
end
We can create the documents as follows:
Team.create!(user: User.create!)
This is equivalent to what you did in the OP. Now, this yields the following documents:
> db.users.find()
{ "_id" : ObjectId("62d9ad683282a400a799c7e7") }
> db.teams.find()
{ "_id" : ObjectId("62d9ad683282a400a799c7e8"), "user_id" : ObjectId("62d9ad683282a400a799c7e7") }
As you can see, the foreign key is stored on the belongs_to side of the relationship. Now, when trying to retrieve the team from the user:
User.last
# => #<User _id: 62d9adc63282a400c637b234, >
User.last.team
The take method (as of Mongoid 8, before that, the first method was used) is used on the following criteria:
#<Mongoid::Criteria
selector: {"user_id"=>BSON::ObjectId('62d9adc63282a400c637b234')}
options: {}
class: Team
embedded: false>
To put this into layman's terms, we are searching the Team collection for a document with the same value for user_id as our user's _id.
So yes, your idea is almost correct, it's more like:
Team.where(user_id: user._id).take
The difference between take and first is that take doesn't put a sort on _id, while first does. Using take is a little more efficient.

User belongs_to another User in Rails

I'm trying to determine the appropriate relationship to use in Rails. I'm going to have Users who can belong to one other User (their spouse). They will both be of the same model type (User).
I want to be able to call User.spouse on either user and get the user that is associated with them, and also take advantage of being able to build nested attributes for each other. Can I add a tag in the model specify a call to .spouse should return this user? Or would it just be user.user?
In your user.rb model you can make an association like this:
class AdminUser < ApplicationRecord
has_one :spouse, through: :user, foriegn_key: :spouse_id
This is assuming you have a field in your users table called spouse_id which is a foreign_key to users. (see below)
More information about this can be found here: Rails has_one :through association
When you add your spouse_id (or spouse_user_id might be a better name) via a migration don't forget to add a foreign key to strongly enforce legitimate data at the DB level.
add_column :users, :spouse_user_id, :integer
add_foreign_key :users, :users, column: 'spouse_user_id'
Example usage:
User id: 1, name: 'Fred', spouse_user_id: 2
User id: 2, name: 'Wilma', spouse_user_id: 1
User.find(1).spouse
=> Wilma
User.find(2).spouse
=> Fred

Creating two different objects (from different models) at the same time - Rails

I have two models: Product and Category, which I want to tie together.
Say each product has a unique category, how do I set Rails to create (or generate) a new Category object each time a user creates a new Product object?
class Product < ActiveRecord::Base
after_create do
Category.create product: self
end
has_one :category
end
class Category < ActiveRecord::Base
belongs_to :product
end
and in console
> a= Product.create
> a.category
=> #<Category id: 1, product_id: 5, created_at: "2015-11-04 12:53:22", updated_at: "2015-11-04 12:53:22">
First of all, if a category belongs so much to a product, why don't you add it to the product model? But still if you insist on the way you would like to have it coded, you can take advantage of callbacks like after_create, and write code for creating a category over there. This way, whenever a product is created, an associated category will be created along the way.
class Product < ActiveRecord::Base
has_one :category
after_create :create_category
private
def create_category
# code for creating an associated category
end
end
Note: Most of the time, I need to save mobile number for a user in the database, and each user has a unique mobile number - so instead of defining a new model for mobile number, I tend to have it inside users table. But if the information for mobile number expands like its operator name, country code - I'd definitely split it into a separate table.

Create if record does not exist

I have 3 models in my rails app
class Contact < ActiveRecord::Base
belongs_to :survey, counter_cache: :contact_count
belongs_to :voter
has_many :contact_attempts
end
class Survey < ActiveRecord::Base
has_many :questions
has_many :contacts
end
class Voter < ActiveRecord::Base
has_many :contacts
end
the Contact consists of the voter_id and a survey_id. The Logic of my app is that a there can only be one contact for a voter in any given survey.
right now I am using the following code to enforce this logic. I query the contacts table for records matching the given voter_id and survey_id. if does not exist then it is created. otherwise it does nothing.
if !Contact.exists?(:survey_id => survey, :voter_id => voter)
c = Contact.new
c.survey_id = survey
c.voter_id = voter
c.save
end
Obviously this requires a select and a insert query to create 1 potential contact. When I am adding potentially thousands of contacts at once.
Right now I'm using Resque to allow this run in the background and away from the ui thread. What can I do to speed this up, and make it more efficient?
You can do the following:
Contact.where(survey_id: survey,voter_id: voter).first_or_create
You should add first a database index to force this condition at the lowest level as possible:
add_index :contacts, [:voter_id, :survey_id], unique: true
Then you should add an uniqueness validation at an ActiveRecord level:
validates_uniqueness_of :voter_id, scope: [:survey_id]
Then contact.save will return false if a contact exists for a specified voter and survey.
UPDATE: If you create the index, then the uniqueness validation will run pretty fast.
See if those links can help you.
Those links are for rails 4.0.2, but you can change in the api docks
From the apidock: first_or_create, find_or_create_by
From the Rails Guide: find-or-create-by
It would be better if you let MySQL to handle it.
Create a migration and add a composite unique key to survey_id, voter_id
add_index :contact, [:survey_id, :voter_id], :unique=> true
Now
Contact.create(:survey_id=>survey, :voter_id=>voter_id)
Will create new record only if there is no duplicates.

Ruby on Rails build makes id nil

In my project, a user has a hotel, and each hotel has room types associated with it. Briefly, the models are
class User < ActiveRecord::Base
has_many :hotels
class Hotel < ActiveRecord::Base
belongs_to :user
has_many :roomtypes
class Roomtype < ActiveRecord::Base
attr_accessible :maxguests, :name
belongs_to :hotel
If I do the commands:
#user = User.find(1)
#user.hotels.find(1).roomtypes.build(name: "something", maxguests: "2")
The console returns:
#<Roomtype id: nil, name: "something", maxguests: 2, created_at: nil, updated_at: nil, hotel_id: 1>
For some reason, the Roomtype id and the timestamps are nil. Any ideas why?
You have built the roomtype (which creates a new instance of the object), but you not saved it yet. You have to explicitly save roomtype for it to go into the db (and thus get an id).
Instead of build use create or create! to accomplish this with a single method call
Replace build with create and you'll have your persisted object with id and timestamp.
Would not make sense to have this before.

Resources