Ruby on Ruby records redundant rows with same ids - ruby-on-rails

I have been facing one serious issue in Ruby on Rails code for quite a long time. The active record records the transactions redundantly sometimes upto 3 times but this doesn't happen to all users. Let's say out of 100 upto 3 are affected. Surprisingly since i am using timestamp to save transaction ids all redundant rows often are recorded with the same id not even a difference of single second between all of them.
skip_before_action :verify_authenticity_token
before_action :restrict_access, except: [:authenticate, :go_success, :go_failed]
def creategoal
goal = #user_info.goal.create(days: 365)
if goal.save
goal.goals_mandates.create(reference: "000-000-GG-#{goal.id}",
price_dd: params[:amount],
mandate_attempt_date: Date.today,
mandate_active: false)
self.initTrans(goal)
end
end
def initTrans(goal)
userType = UserType.where(name: "partner").first
if userType.user_info_types.where(user_info_id: #user.id).first.nil?
#user.user_info_types.create(user_type_id: userType.id)
end
transaction_id = "GG-"+ Time.now.to_i.to_s
transaction = goal.goal_transaction.create(batch_id: transaction_id,
response_batch: transaction_id,
amount_deducted: session[:amount_to_save],
transaction_type: "direct_debit",
merchant: #accessor.name)
if transaction.save
redirect_to "/success?trans_id="+transaction_id
else
redirect_to "/failed"
end
end
I will be grateful if anyone genius out there can help me out.

Related

How to join 2 queries in Arel, one being an aggregation of the other (Rails 5.2.4)?

My application monitors ProductionJobs, derived from BusinessProcesses in successive versions. Thus the unique key of ProductionJob class is composed of business_process_id and version fields.
Initially, the ProductionJob index would display the list of objects (including all versions) using an Arel structured query (#production_jobs).
But it is more convinient to only show the last version of each ProductionJob. So I created a query (#recent_jobs) to retrieve the last version of the ProductionJob for a given BusinessProces.
Joining the 2 queries should return only the last version of each ProductionJob. This is what I can't achieve with my knowledge of Arel, and I would be grateful if you could show me how to do!
Here is the code in production_jobs_controller:
a) Arel objects setup
private
def jobs
ProductionJob.arel_table
end
def processes # jobs are built on the processes
BusinessProcess.arel_table
end
def flows # flows provide a classifiaction to processes
BusinessFlow.arel_table
end
def owners # owner of the jobs
User.arel_table.alias('owners')
end
def production_jobs # job index
jobs.
join(owners).on(jobs[:owner_id].eq(owners[:id])).
join(processes).on(jobs[:business_process_id].eq(processes[:id])).
join(flows).on(processes[:business_flow_id].eq(flows[:id])).
join_sources
end
def job_index_fields
[jobs[:id],
jobs[:code].as("job_code"),
jobs[:status_id],
jobs[:created_at],
jobs[:updated_by],
jobs[:updated_at],
jobs[:business_process_id],
jobs[:version],
processes[:code].as("process_code"),
flows[:code].as("statistical_activity_code"),
owners[:name].as("owner_name")]
end
def order_by
[jobs[:code], jobs[:updated_at].desc]
end
# Latest jobs
def recent_jobs
jobs.
join(owners).on(jobs[:owner_id].eq(owners[:id])).
join_sources
end
def recent_jobs_fields
[ jobs[:code],
jobs[:business_process_id].as('bp_id'),
jobs[:version].maximum.as('max_version')
]
end
b) The index method
# GET /production_jobs or /production_jobs.json
def index
#production_jobs = ProductionJob.joins(production_jobs).
pgnd(current_playground).
where("business_flows.code in (?)", current_user.preferred_activities).
order(order_by).
select(job_index_fields).
paginate(page: params[:page], :per_page => params[:per_page])
#recent_jobs = ProductionJob.joins(recent_jobs).select(recent_jobs_fields).group(:business_process_id, :code)
#selected_jobs = #production_jobs.joins(#recent_jobs).where(business_process_id: :bp_id, version: :max_version)
Unfortunately, #selected_jobs returns a nil object, even though #production_jobs and #recent_jobs show linkable results. how should I build the #selected_jobs statement to reach the expected result?
Thanks a lot!
After several trials, I finally included the sub-request in a 'where ... in()' clause. This may not be optimal, and I am open to other proposals.
The result can be understood as the following:
#recent_jobs provide the list ProductionJobs'last versions, based on their code and version
#production_jobs provide the list of all ProductionJobs
#selected_jobs adds the where clause to #production_jobs, based on the #recent_jobs:
The last request is updated to:
#selected_jobs = #production_jobs
.where("(production_jobs.code,
production_jobs.business_process_id,
production_jobs.version)
in (?)",
#recent_jobs
)
It works this way, but I'd be glad to receive suggestions to enhance this query. Thanks!

ActiveRecord pessimistic locking a record for reservation system

I have a reservation system where I need to lock a record so that two users cannot book it at the same time.
The model Seat has three statuses: available, reserved and booked.
While being reserved the user has 5 minutes to complete the booking.
I can do this with a pessimistic locking. I add a lock_version column to the table and do
def reservation
#seat = Seat.available.order(created_at: :desc).first
if #seat
#seat.reserved!
redirect_to 'continue to confirm booking'
else
redirect_back with message "no seats available"
end
rescue ActiveRecord::StaleObjectError
retry
end
end
Since in this system the possibility of conflicts is very likely, I'd like to use a pessimistic locking intead of an optimistic one, but I didn't succeed.
This is the code I tried (note the addition of lock):
def reservation
#seat = Seat.available.order(created_at: :desc).lock.first # executes a SELECT FOR UPDATE
if #seat
#seat.reserved!
redirect_to 'continue to confirm booking'
else
redirect_back with message "no seats available"
end
end
The problem is that two records are still being selected at the same time even though the query is
SELECT * from seats where status = 'available' order by created_at desc limit 1 for update.
I am not sure how to implement an optimistic locking for such case.
After some hours of research I found out what was the issue.
Looking again at the optimistic code, the issue is that there's no transaction defined.
Adding a transaction fixes the problem. Here is the corrected code:
def reservation
Seat.transaction do
#seat = Seat.available.order(created_at: :desc).lock.first
if #seat
#seat.reserved!
redirect_to 'continue to confirm booking'
else
redirect_back with message "no seats available"
end
end
end
now the select for update and update of the record itself are within the same transaction.

Setting limit to record entries

I have the following:
after_action :prevent_order_create, :only => [:update, :create]
...
private
def prevent_order_create
#order = Order.find(params[:id]) #line 270
#listing = Order.find_by(params[:listing_id])
#order_seller = #order.where(order_status: [1]).where(:seller)
if #order_seller.count >= 0
#listing.update_column(:listing_status, 3)
end
end
The goal is to limit the amount of orders that can be open for any 1 seller.
When I use this code, I get the following error:
ActiveRecord::RecordNotFound (Couldn't find Order without an ID):
app/controllers/orders_controller.rb:270:in `prevent_order_create'
The order gets created but for whatever reason this issue is happening. Shouldn't the method be getting checked AFTER the order is made without issues?
btw using 0 for testing purposes.
EDIT:
I did:
def prevent_order_create
#order_seller = Order.where(order_status: [1]).where(listing_id: Listing.ids)
if #order_seller.count >= 10
#listing.update_column(:listing_status, 3)
end
end
Seems to work for now. Will update.
Everything you describe is as expected.
An after action is run after the action is already executed, so the record has already been persisted to your DB by the time the exception happens.
The exception is caused because params[:id] is nil, which makes sense for a create action.
Update after clarification in comment
OP said:
I want after create and update... to find any orders with matching :listing_id's and with order_status's of [1] to then count those records. If the count is >= 1000, to then change the listing_status of the Listing
I think the way I would do this is in an after_save in the Order model. Based on your latest edit something like:
scope :status_one, -> { where order_status: 1 } # I'm guessing this should be 1 not [1] as you keep putting
after_save :update_listing
def update_listing
if listing.orders.status_one.count >= 10
listing.update! listing_status: 3
end
end
So that's basically saying that if the associated listing has 10 or more associated orders with order_status=1 then set its status to 3.

Automatically Scheduling Conference

I'm trying to create a Ruby on Rails site that manages conferences. It should fill in time slots without any gaps in between. I've got it to the point where it fill in the the slots. But in most instances it leaves some time slots empty. I'm not able to find the flow in my logic.
app/services/conference_service.rb
class ConferenceService
def initialize(conference, temp_file)
self.first_track = conference.tracks.first
self.second_track = conference.tracks.last
self.file = temp_file
self.talks = []
end
def call
create_talks
set_track(1, 'Lunch')
set_track(2, 'Lunch')
set_track(1, 'Networking Event')
# set_track(2, 'Networking Event')
set_second_track_evening
end
private
def create_talks
file.read.split(/\n/).each do |line|
next if line.blank?
title = line.split(/\d|lightning/).first
length = line.scan(/\d+/).first
length = length.nil? ? 5 : length.to_i
talks << Talk.create(title: title, length: length)
end
end
attr_accessor :first_track, :second_track, :file, :talks
def set_track(track_number, track_portion)
track = track_number == 1 ? first_track : second_track
time = track_portion == 'Lunch' ? Time.zone.now.change(hour: 9) : Time.zone.now.change(hour: 13)
minutes = track_portion == 'Lunch' ? 180 : 240
talks.shuffle!
local_talks = []
n = 0
while local_talks.map(&:length).inject(0, &:+) < minutes
local_talks << talks[n]
n += 1
end
if local_talks.map(&:length).inject(0, &:+) == minutes
local_talks.each do |talk|
talk.start_time = time
track.talks << talk
time = time.advance(minutes: talk.length)
end
track.talks << Talk.create(title: track_portion, start_time: time, length: 60)
track.save
(0..local_talks.count - 1).each do |i|
talks.delete_at(i)
end
else
set_track(track_number, track_portion)
end
end
def set_second_track_evening
time = Time.zone.now.change(hour: 13)
talks.each do |talk|
talk.start_time = time
time = time.advance(minutes: talk.length)
end
second_track.talks << talks
second_track.talks << Talk.create(title: 'Networking Event', start_time: time.change(hour: 17), length: 60)
end
end
app/controllers/conference_controller.rb
def create
#conference = Conference.new(conference_params)
build_tracks
conference_service = ConferenceService.new(#conference, input_file)
conference_service.call
respond_to do |format|
if #conference.save
format.html { redirect_to #conference, notice: 'Conference was successfully created.' }
format.json { render :show, status: :created, location: #conference }
else
format.html { render :new }
format.json { render json: #conference.errors, status: :unprocessable_entity }
end
end
end
def input_file
params['conference']['input_file']
end
input file
Writing Fast Tests Against Enterprise Rails 60min
Overdoing it in Python 45min
Lua for the Masses 30min
Ruby Errors from Mismatched Gem Versions 45min
Common Ruby Errors 45min
Rails for Python Developers lightning
Communicating Over Distance 60min
Accounting-Driven Development 45min
Woah 30min
Sit Down and Write 30min
Pair Programming vs Noise 45min
Rails Magic 60min
Ruby on Rails: Why We Should Move On 60min
Clojure Ate Scala (on my project) 45min
Programming in the Boondocks of Seattle 30min
Ruby vs. Clojure for Back-End Development 30min
Ruby on Rails Legacy App Maintenance 60min
A World Without HackerNews 30min
User Interface CSS in Rails Apps 30min
error when calling set_track(2, 'Networking Event')
undefined method `length' for nil:NilClass #line 42
Recommend you do a few things before worrying about the algorithm:
Separate concerns / Single Responsibility. The code that parses the file should be independent from the code that runs the business logic, which should be independent from the code that saves to your database. Separating these things may seem unnecessary for simple logic (and may be), but is necessary as your app complexity grows.
Write tests. As you refactor your code, you're going to want to ensure it still works. Bonus: Writing code that you can test forces you to create interfaces that you can understand, which can make the code easier to understand!
Come up with a design first. Reading this code I have no idea what the intention of the sections are. One of my favorite ways to do this is to use Class, Responsibilities, Collaborators post cards (see https://en.wikipedia.org/wiki/Class-responsibility-collaboration_card and http://agilemodeling.com/artifacts/crcModel.htm).
It seems like you could break this code down into:
Parse input file into generic 'Talk' objects that have a length (in minutes) and a name. I would not have these be DB backed. If it's the same concept as an ActiveRecord model, we often name this a TalkDouble (or similar). I'd also recommend just using CSV here rather than your own custom (and hard to parse) format.
Schedule talk objects into tracks. It seems like you're trying to randomize the talks across two tracks, with some built-in lunch breaks (?). Whatever the desired behavior, this also doesn't need to use anything but plain old ruby objects. I've found it best to have the logic be stateless/idempotent and return a new object each time it's run as the result.
For example:
class TalkScheduler
def schedule(talks, number_of_tracks: 2)
# Logic goes here, returns an array of `Tracks`
# each with a set of talks.
tracks = build_tracks(number_of_tracks)
talks.each do |talk|
tracks.sample.add_talk(talk)
end
tracks
end
def build_tracks(number)
(0..number).times.map do { Track.new }
end
end
However, if you're looking for an algorithm that chooses "best fit" of available talks into open spaces, you're essentially trying to solve the Knapsack problem (https://en.wikipedia.org/wiki/Knapsack_problem). It may not become combinatorially hard due to the limited number of talk lengths (e.g. only 30, 45 and 60) but realize that you're slipping into challenging territory.
I'd also question the value to anyone of the ability to create conference with a random order of talks vs. just being able to organize them by hand.
In any case, you could handle solving the problem of determining a (random?) selection of talks in a given time-space with something like the following:
class Schedule
SLOT_LENGTH = 15
attr_accessor :start, :length, :talks
def initialize(start:, length:)
#start = start
#length = length
#slots = length / SLOT_LENGTH
#talks = []
end
def add_talk(talk)
talks.push(talk)
end
def slots_remaining
slots - talks.map(&:length).sum / SLOT_LENGTH
end
def can_fit?(talk)
talk.length / SLOT_LENGTH <= slots_remaining
end
end
class TalkScheduler
def schedule(talks, schedules)
unscheduled_talks = talks.dup.shuffle # Always dup, even if you don't shuffle
schedules.each do |schedule|
while(talks.any?)
index = unscheduled_talks.index{|t| schedule.can_fit?(t) }
break unless index
talk = unscheduled_talks.delete_at(index)
schedule.add_talk(talk)
end
end
end
end
I'd think a bit more about to model lunches, networking breaks, etc. before deciding to model them as talks or as something else, but using this type of pattern (simple ruby objects that store data being manipulated by NounVerber classes that contain the complex business logic) has been very helpful to me for simplifying handling complex workflows like what you're doing here.
Good luck!

Iterating through every record in a database - Ruby on Rails / ActiveRecord

n00b question. I'm trying to loop through every User record in my database. The pseudo code might look a little something like this:
def send_notifications
render :nothing => true
# Randomly select Message record from DB
#message = Message.offset(rand(Message.count)).first
random_message = #message.content
#user = User.all.entries.each do
#user = User.find(:id)
number_to_text = ""
#user.number = number_to_text #number is a User's phone number
puts #user.number
end
end
Can someone fill me in on the best approach for doing this? A little help with the syntax would be great too :)
Here is the correct syntax to iterate over all User :
User.all.each do |user|
#the code here is called once for each user
# user is accessible by 'user' variable
# WARNING: User.all performs poorly with large datasets
end
To improve performance and decrease load, use User.find_each (see doc) instead of User.all. Note that using find_each loses the ability to sort.
Also a possible one-liner for same purpose:
User.all.map { |u| u.number = ""; puts u.number }

Resources