How to ensure single record being inserted in rails - ruby

In our application, A user can be entered only once or multiple times to a campaign based on the configuration(single time or multiple times).
A user model, campaigns and campaigns_users model are there. If the configuration is single time, the campaigns_users should have only one record for a user. and if the campaign is configured with multiple times, there can be many records for the same user for the same campaign.
Due to concurrent processing, the record is being inserted twice. We have done the application level check to ensure whether the user entered into the campaign or not. In some cases, two processes run at the same time and check for the subscription to the campaign and the user got subscribed twice even if the configuration given as single time.
class User < ApplicationRecord
def already_subscribed?(campaign)
campaign.campaigns_users.find_by(user_id: id).present?
end
end
In job
def perform(user_id, campaign_id)
campaign = Campaign.find_by(id: campaign_id)
user = User.find_by(id: user_id)
return if campaign.config == 'single' && user.already_subscribed?
# Other Logics
end
I have checked for the solution for avoiding the two entries for specific cases and the result I got is to add the UNIQUE constraint. But in my case, the user can be entered multiple times/single time based on the config. What can be the best solution for avoiding the creation of the record?

I ended up using Advisory lock of Postgres(https://vladmihalcea.com/how-do-postgresql-advisory-locks-work/). Please have a look over https://github.com/ClosureTree/with_advisory_lock gem
def perform(user_id, campaign_id)
campaign = Campaign.find_by(id: campaign_id)
user = User.find_by(id: user_id)
# I have used id's as lock name to ensure reducing the time that other threads waiting for accessing this critical section
CampaignUser.with_advisory_lock("#{user.id}-#{campaign.id}")
return if campaign.config == 'single' && user.already_subscribed?
# Other Logics
end
end

Related

how to get value from database when concurrent saving multiple data

class Defect < ApplicationRecord
has_many :work_orders, dependent: :destroy
end
class WorkOrder < ApplicationRecord
belongs_to :defect
before_save :default_values
def default_values
self.running_number = self.defect.work_orders.maximum(:running_number).to_i + 1 if self.new_record?
end
end
ideally the code works like this
Defect A
- Work Order running_number 1
- Work Order running_number 2
- Work Order running_number 3
Defect B
- Work Order running_number 1
- Work Order running_number 2
- Work Order running_number 3
however when multiple users concurrently saving different WorkOrder object that belongs to the same defect, the running_number will go haywire because the maximum_running_number is based on only saved data.
how do i make the running_number save properly?
The issue is that your concurrent saves get the same count of work orders, so you get duplicate running_numbers for the work order.
You can solve it two ways:
Setting a unique constraint on running_number and defect_id
Acquire a lock on the work order table until you've committed the new work order.
To set a unique constraint in a rails migration: add_index :work_orders, [:defect_id, :running_number], unique: true. Then just retry the save if there is an error when you call save.
assuming you're using Postgres
begin
# .. create the work order
work_order.save
rescue PG::UniqueViolation
retry
end
Using retry will retry the block until no unique violation is raised. This could cause a deadlock if there was some other unique violation error on the record, so make sure that the error is caused by the running_number and nothing else.
The other way is to acquire a lock to prevent the race condition. As its a database table that is the shared resource, you acquire a table lock to ensure no other process is using the work order table while you are calculating the number of work orders and saving the record.
assuming your using Postgres explicit-locking docs
ActiveRecord::Base.transaction do
# create order
ActiveRecord::Base.connection.execute('LOCK work_orders IN ACCESS EXCLUSIVE MODE')
work_order.save
end
Acquiring a table lock with this mode will prevent all access to the table from other connections to the database. It will be released when the transaction is committed, but again could cause deadlocks if for whatever reason the ruby process is killed before it has a chance to complete the transaction block.

What is a good way to do an atomic insert or prevent a race condition on create with Mongoid?

class User
include Mongoid::Document
field :email, type: String
validates_uniqueness_of email
end
Although Mongoid supports atomic operations, I do not see one for insert.
Since User.create is not atomic, it seems that 2 Users could be created with the same email address simultaneously.
So, what is a good way to ensure that 2 users do not register the same email address simultaneously?
I can see one solution is to use a unique DB index, but are there any other good ways of doing this?

Using GORM efficiently to retrieve join data - Grails 2.3

I have User, and Follow Domain class that construct the Follower, Following relationship like Twitter. When my User visits another User's page, and click on their Follower list. I pop-up the list of the visited person's follower list, and then show a button in front of it that is labeled "Follow" or "Unfollow" depending on if you already following that person. So I do it the following way, but I'm not sure if this is efficient or maybe there is a better way of doing it. In order to make it efficient, I only retrieve 20 follower at a time and allow pagination.
> example of pop-up:
> ----------------------------------------------
> alex [follow]
> dave [unfollow]
> sarah[follow]
> paul [follow]
> ----------------------------------------------
Follow domain class has:
// The person to follow
User follow
// The person who is following the follow
User follower
// The status of the relationship if removed or still following
boolean status
User domain class is the typical Spring Security User class with a bunch of extra fields like locations and etc.
In my controller when I receive a request for follower list I do the following.
def visitedUser = User.get(visitedUserId)
// get the list of top 20 followers of this user
def followerList = Follow.findAllByFollow(visitedUser, [max:20])
// get the list of all people current user follow who is in the 20 follower list
def mutualFollow = Follow.findAllByFollowerAndFollowInList(currentUser, followerList)
Now I have the list of all the followers in profile of the person I'm visiting, and also the list of those who are mutual. Then I pass both to my GSP and while loop through the followerList in GSP, I compare the id of that follower, if the follower exist in my mutualFollow list I tweak the button to "unfollow" otherwise I keep the button as follow.
One step further to optimize this is to user projection to only retrieve the id of the mutualFollow instead of the whole USER domain object, but since USERs coming back are proxied and they have wrapper around them, I'm not sure that makes a big difference. Because All I retrieve is the id.
I appreciate any suggestion to improve this approach or an alternative. Thanks
I decided to use HQL and only retrieve the necessary info of those USERs within the follow list, and then I did a join to get the mutual people they both follow. And used that in the GSP page. Here is the simplified solution in SQL:
SELECT Ur.follow_id, Urs.follow_id as mutual FROM (SELECT * FROM FOLLOW WHERE FOLLOWER_ID=1)as Urs
RIGHT JOIN (SELECT * FROM FOLLOW WHERE FOLLOWER_ID=3) as Ur
ON Urs.follow_id=Ur.follow_id order by Ur.follow_id;

Seed db with a particular record set with associated records

I am building a feature where when a person signs up for an account we will automatically populate their account with default categories and items to get them started.
Further, they can optionally purchase additional category sets to add/populate their accounts at anytime.
I am thinking my choices are:
1) Somehow use seeds.rb
2) Store these records in a YAML file and load in upon account creation
3) Store these records in the DB as a default set and clone/dup them.
Any help appreciated w/ code examples to get me started.
This is what I would do:
Have a flag in the DB to identify first_sign_in Store the data in a yaml file (e.g db/users.yml) and do:
def populate_user
user.update_attributes(YAML.load(Rails.root + 'db/users.yml')) if first_sign_in == 0
end
Then you can add this to an after_create hook so it is called only when a user is created
I'd do it differently. I'd have a "Registration" form object between the controller and model(s). Have this build some default categories and items at the same time as it first builds a user. How you isolate the attributes of those default items depends on how complex they are. If they're simple a default hash in the form object will suffice, if they're complex you can pull in from YML.
When I've done this in the past - had a signup which requires multiple object creation I've had a RegistrationsController, a RegistrationForm object which takes the params and validates everything, and is also responsible for knowing what to save, and sometimes in intermediate Registrar object (Struct usually) which has all the logic for callbacks. The registrations_controller initializes a registrar which is sent a message register(registration_form).
This leads to isolation of responsibilities and much cleaner code in the long run.
The answers provided are definite options, with which I experimented with. However, I decided to go a different route. I realized that I would need to manage these 'template' records and a DB would be easiest.
1) I setup a column on the table 'is_template' to mark the records that will be used to seed other accounts.
2) Created an after_create call back to seed the accounts using these records.
3) To make matters easier I used the amoeba gem which allows me to copy the records and their associated records which works great since some of them have has_many relationships.
This has been working great so far - and I also have a way for myself and the non-tech staff to update the records.

How to set the first user's data id to 1 by default?

I've 2 model, User and Client with the following relationship.
User has_many :clients
Client belongs_to :user
How can I make all the registered users have their first :client_id => "1", by default?
So, you want all new users to default to the first client. You don't want to set a default id number, what you want to do is set it to the id of the first Client in your clients table.
So, in your users_controller#new action, all you have to do is set the client_id field to the id of the first client, like so:
class UsersController < ApplicationController
...
def new
#user = User.new(:client_id => Client.first.id)
end
end
This makes it so that when the new User record is saved, unless the user has explicitly changed the value themselves, it will always point to the first Client record in the database.
The reason you don't want to default it to 1 is because if you do, and you ever destroy that client from the table, then a client_id of 1 will point to a non-existent record, and your relationships will break for all new users after that happens. Even if you think that situation isn't going to happen, it's better to write your code in such a way that it can handle this situation, than to think it would never happen.

Resources