Database not updating correctly in Rails - ruby-on-rails

I was hoping you could help me with a problem I've been stuck on for quite a while now. I have a database with tickets. These tickets contain information, like a status. My application uses the Zendesk API to get info from support tickets and store them into my database.
What I want to do is store the previous and current status of a ticket into my database. I am trying to accomplish this by storing the old values before updating my database. At first this seemed to work great. Whenever I change the status in Zendesk, my app changes the previous_state to the old state value and the actual state to the one it gathers from Zendesk.
However, it goes wrong whenever I refresh my page. When that happens (and the method gets called again), for some reason it puts both the previous_state and state on the same value. I must be doing something wrong in one of my update or store lines but I can't figure out what. I hope someone of you can help me out.
Ticket is the Ticket database, client is the zendesk connection. The last loop checks if the status and previous_status are the same and if so, tries to put the previous state back to the previous state before the big update with zendesk. The idea is that the previous state remains unchanged until the actual state changes.
previousTickets = Ticket.all
Ticket.all.each do |old|
old.update(:previous_status => old.status)
end
client.tickets.each do |zt|
Ticket.find_by(:ticket_id => zt.id).update(
subject: zt.subject,
description: zt.description,
ticket_type: zt.type,
status: zt.status,
created: zt.created_at,
organization_id: zt.organization_id,
)
end
Ticket.all.each do |newTicket|
if(newTicket.status == newTicket.previous_status)
b = previousTickets.find_by(:ticket_id => newTicket.ticket_id)
c = b.previous_status
newTicket.update(:previous_status => c)
end
end

Your last loop isn't working because previousTickets does not contain previous tickets, but current ones. This is due to the fact that Ticket.all returns only an ActiveRecord relation. Such is relation loads data in a lazy way : unless you use the content of the relation, it won't be loaded from the database.
You could explicitly load all tickets by converting the relation to an array:
previousTickets = Ticket.all.to_a
But I think you could achieve everything in one single loop: instead of populating all previous_status in the first loop and reverting it in the last, you should simply change the previous_status when you change the current one:
client.tickets.each do |zt|
ticket = Ticket.find_by(:ticket_id => zt.id)
previous_status = ticket.previous_status
previous_status = ticket.status if zt.status != ticket.status
ticket.update(
subject: zt.subject,
description: zt.description,
ticket_type: zt.type,
previous_status: previous_status,
status: zt.status,
created: zt.created_at,
organization_id: zt.organization_id,
)
end

Related

how to lock record in RoR

I'm developing a reservation system for stuff that you could rent.
I would like to restrict multiple users from reserving the same item.
I display a list, which user can click on the item to check the details.
If any user has already opened the detail view then other user can not open it at the same time.
I am maintaining a flag call is_lock to check if the record is already locked but I was facing issue when multiple users clicked on the same item at the same time.
So I implementing pessimistic lock, which reduced the rate of occurrence of this issue but multiple users opening the same item but it did not completely fixed the issue. I am still facing the same thing.
begin
Item.transaction do
item = Item.lock.where(id: item_id, is_lock: false)
item.is_lock = true;
item.save!
end
rescue Exception => e
# Something went wrong.
end
Above is the code that I have implemented.
Please let me know if I am doing anything wrong.
EDIT:
I've tried the solution provided by #rmlockerd in following way:
Run rails in 2 separate consoles.
Fetch record with lock that has id:100 from console-1.
Fetch to fetch the same record from console-2.
But the above test failed as I was able to fetch the same record from both console even though the record was locked from console-1.
Run rails in 2 separate consoles.
It might be misleading to just look at the snippet you provided, but there does seem like a possible race condition due to your .where predicate.
If User2 attempts to get a lock on the same item after User1 but before first the transaction commits, the .where will still return the original record with is_lock false. The default behaviour for .lock is to simply wait its turn for a lock. So User2 would block until the original transaction commits, then get a lock and proceed to set is_lock to true as well.
The good news is that when you get a lock, Rails reloads the record so you are getting the latest data. Checking is_lock after obtaining the lock should eliminate that race condition, like so:
Item.transaction do
item = Item.lock.find_by(id: item_id, is_lock: false) # only 1, so where is unnecessary
return if item.blank? || !item.is_lock
item.update!(is_lock: true)
end
# I have the lock stuff...
The .lock method also takes an optional 'locking clause' -- which varies based on the database you use -- that can be used to configure the locking behaviour. For example, if you use Postgres, you could do:
Item.transaction do
item = Item.lock('FOR UPDATE SKIP LOCKED').find_by(id: item_id, is_lock: false)
return if item.blank?
item.update!(is_lock: true)
end
The SKIP LOCKED clause directs Postgres to automatically skip any record that is already locked. In the race condition described above, the second call to .lock would bail immediately and return nil, so a simple check of item presence would suffice. Check out the Postgres or MySQL documentation if you're interested in database-specific locking clauses.

Rails database changes aren't persisting through tests

I'm writing tests for my current rails project and I'm running into an issue whereby changes made to the test database through a post call aren't persisting long enough to test the changes.
Basically, I have objects which have a barcode assigned to them. I have put in a form whereby a user can scan in several barcodes to change multiple objects at a time. This is the code.
objecta_controller.rb:
def change_many
#barcodes = params[:barcodes].split()
#objects = ObjectA.order("barcode").where(barcode: #barcodes)
#objects.each do |b|
if can? :change, b
b.state_b()
end
end
end
(Note: #barcodes is a string of barcodes seperated by whitespace)
objecta_controller_test.rb:
test "change object" do
sign_in_as(:admin_staff)
b = ObjectA.new(
barcode: "PL123456",
current_status: "state_a")
post :change_many, { barcodes: b.barcode }
assert_equal("state_b", b.current_status, "Current status incorrect: #{b.to_s}")
end
Using byebug, I've ascertained that the objects do change state in the change_many method, but once it gets back to the test the object's state reverts back to its old one and the test fails.
First off, you are holding an in memory object not yet saved to the database, so first:
Add b.save before your post
Second, your in memory object will not automatically reflect changes in the database. You have to tell it to refresh its state, so:
Add b.reload before your assert

How can I update table when a user is locked after maximum login attempts?

In my rails application, I managed to lock users after a maximum failed login treshold using Devise lockable, but how can I update the table so that I can add an entry to user denoting this user is locked also with timestamp !
I just don't know where to put that code in !
I tried to create a file called "lockable.rb" in Initializer with following code,
def lock_access!(opts = { })
#user.is_lock = "Yes"
#user.reason_of_deactivation = "Failed login attempt"
#user.deactivated_date = DateTime.now
#user.save
end
That didn't worked out !
One potential solution I see here is that you could have a condition inside a callback on after_save where, you check if the user is locked.
If the User is locked, update the timestamp with the current time or updated_at .
This solution might have problems as the callback would get executed every time a save is called on the user object, thus updating the timestamp. Please take care to add enough conditions to prevent this from happening.
Also, please write tests around this, so that at some later point of time, when you revisit that part of code, it will provide you with some context about the conditions.
After 1 hour of research and testing I myself found solution, I overrided lockable.rb in Devise gem and added code.
Created file lockable.rb in lib/devise/models/lockable.rb
def lock_access!(opts = { })
super
self.is_lock = "Yes"
self.reason_of_deactivation = "Exceeded max login threshold"
self.deactivated_date = DateTime.now
end
Closed.

How to store the last two records in an array

I have a method that gets a random banner, but I do not want it to show the same banner twice in a row.
def random_banner
#koder = #side.reklamers.all(:select => :id).collect(&:id)
#koder.sample gets a random ID
#reklame = Reklamer.find(#koder.sample)
render :text => #reklame.kode
end
What is the best solution?
TDGS solution:
When I visit the action, it works well, but I have a blog that makes an ajax call to get the banner code, and there, the same banner appear twice in a row.
Store the last used banner id in the session somewhere, say session[:banner_id]
Then, do the following:
#koder = #side.reklamers.pluck(:id).reject{|id| id == session[:banner_id}
#reklame = Reklamer.find(#koder.sample)
session[:banner_id] = #reklame.id
Things like this should be stored in the session and not in the database. Modifying the session is near zero cost, whereas modifying the database incurs at least a round-trip to the database engine and back, plus the overhead of creating a query and decoding the result.
Example:
loop do
random_id = #koder.sample
break if (random_id != session[:last_random_id]))
end
session[:last_random_id] = random_id
As James Mason points out, be sure to have at least two things that can be selected or this loop will run forever. Sometimes, as a failsafe, it's better to have either a loop of fixed length, like 10.times do, or a method that reliably emits random numbers by doing this internally, as #koder.sample(session) could test and update.
You could pass the previous ID to random_banner and reject the random ID if it matches. You'll need to make sure you don't get stuck in an infinite loop if there's only one banner to choose from, too.

Is this a race condition issue in Rails 3?

Basically I have this User model which has certain attributes say 'health' and another Battle model which records all the fight between Users. Users can fight with one another and some probability will determine who wins. Both will lose health after a fight.
So in the Battle controller, 'CREATE' action I did,
#battle = Battle.attempt current_user.id, opponent.id
In the Battle model,
def self.attempt current_user.id, opponent_id
battle = Battle.new({:user_id => current_user.id, :opponent_id => opponent_id})
# all the math calculation here
...
# Update Health
...
battle.User.health = new_health
battle.User.save
battle.save
return battle
end
Back to the Battle controller, I did ...
new_user_health = current_user.health
to get the new health value after the Battle. However the value I got is the old health value (the health value before the Battle).
Has anyone face this kind of problem before ???
UPDATE
I just add
current_user.reload
before the line
new_user_health = current_user.health
and that works. Problem solved. Thanks!
It appears that you are getting current_user, then updating battle.user and then expecting current_user to automatically have the updated values. This type of thing is possible using Rails' Identity Map but there are some caveats that you'll want to read up on first.
The problem is that even though the two objects are backed by the same data in the database, you have two objects in memory. To refresh the information, you can call current_user.reload.
As a side note, this wouldn't be classified a race condition because you aren't using more than one process to modify/read the data. In this example, you are reading the data, then updating the data on a different object in memory. A race condition could happen if you were using two threads to access the same information at the same time.
Also, you should use battle.user, not battle.User like Wayne mentioned in the comments.

Resources