saving and updating on serialized column - ruby-on-rails

Apologies in advance for the ugly code (in addition to it not working).
I have a Rails app that has many users who play different quiz games, which were created by various users. There is no rails-style association between user and quizes except that each quiz has been made by a particular user. There's actually not even a 'quiz' model in the application (quizes are made of question and answer models).
For obvious reasons, users are only allowed to try each quiz once; therefore, I have to record which users' quizers a particular user has tried. Therefore, I created a 'quiz' attribute on the user model, and serialized it, so that I would hopefully have a database column like this ['2','4','10'] if I had played the quizes that were made by users with ids 2,4 and 10.
In the user model, after creating :quiz as a 'string', I did this to turn the string into an array in the db
attr_accessible :quiz
serialize :quiz
However, I'm having a lot of trouble writing code that does all of the following
a) checks if that column's not empty (maybe the player hasn't played a quiz yet): I did player.quiz? to test that.
b) checking whether the array includes a particular user.id (i.e. if the player has already played that quiz) I do player.quiz.include?(user.id.to_s) to test that
c) creating the first entry if a user hasn't played any quizzes yet. I did quiz = user.id.to_s
d) adding to the array of game's once a user's played a second user's game (i.e. player's quiz column is ['2'] and now they play quiz by user.id 4.
With various combinations of the code below, I keep erasing/resetting the quiz column when I try to update, or I'm creating strings like '68' when I want ['6','8'] instead.
I'd be grateful if you could give me some tips to fix this up.
code not working
if player && player.quiz? && player.quiz.include?(user.id.to_s) #checks if signed in (if player), if the player.quiz column isn't empty, and if it includes a user id
alreadyplayedthisquiz = true
else
if player.quiz? #it might be that no quizzes are registered on db
quiz = player.quiz #this is a string
quiz.push(user.id.to_s) #can't push onto a string
quiz += user.id.to_s #this is wrong, because if I've played quiz 6 and 8, it'll be '68' in the db
else
quiz = user.id.to_s #if no quiz on player's db, create first one
end
alreadyplayedthisquiz = false
end
player.update_attributes({quiz: quiz})

First, I would store the values in the database as "2","4","6" rather than as ["2","4","6"]. The [ and ] are not necessary.
I would use the String#split to convert the string in the database to an array that can be used in the code. I would use Array#join to convert the array used in the code back into a string for storage in the database.
quiz = player.quiz? ? player.quiz.split(",") : []
alreadyplayedthisquiz = quiz.include? user.id.to_s
if (!alreadyplayedthisquiz)
quiz.push user.id.to_s
player.update_attributes({:quiz => quiz.join(",")})
end

Related

How do I calculate values for the current user in ruby on rails?

I have an application with a series of tests (FirstTest, SecondTest etc.)
Each test has a calculation set out its relevant model that gets calculated before being saved to the database set out like this:
#first_test.rb
before_save :calculate_total
private
def calculate_total
...
end
I then have an index page for each user (welcome/index) which displays the current user's results for each test. This all works fine, however I want to work out various other things such as each users average score overall etc.
Is it possible to access the current user from the welcome model?
Currently my welcome.rb is accessing the data follows:
#welcome.rb
def self.total
FirstTest.last.total
end
This obviously access the last overall test NOT the last test from the current user.
I feel like I may have just laid the whole application out in a fairly unintelligent manner...
Thanks in advance x
Well you need to save user_id in a column for each record in FirstTest. Then you can find the total for current user
FirstTest.where(:user_id => current_user.id).last.total

Rails cross model validation

I have two tables one for members and the other for employees, both have an attribute called id_number this attribute is not required and can be null.
Is it possible to run a validation to ensure the uniqueness of the id_number, so that if an employee is added with the same id_number as an member or vice versa that it will give an error.
I am thinking of writing my own validation but hitting the db for each instance will be very slow as some companies upload 10's of thousands of employees at a time.
Yes that's possible with your own validation. I think you have to hit the database, otherwise you never could check if it exists already.
def your_validation
employee_ids = Employee.all.map(&:id_number)
member_ids = Member.all.map(&:id_number)
id = self.id_number
if employee_ids.include?(id) || member_ids.include?(id)
errors.add(:id_number, "is already taken")
end
end
I think adding an index to your id_number will be good.
UPDATE: The above method could be changed to following to improve the performance:
def your_validation
employee_ids = Employee.all.map(&:id_number)
if employee_ids.include?(self.id_number)
errors.add(:id_number, "is already taken")
else
member_ids = Member.all.map(&:id_number)
if member_ids.include?(self.id_number)
errors.add(:id_number, "is already taken")
end
end
end
The first one is cleaner, the second one should be faster. But check this out with a lot of db entries and a benchmark tool.
I think you'll want something like this:
def your_validation
if self.id_number.present?
if Employee.exists?(:id_number=>self.id_number) || Member.exists(:id_number=>self.id_number)
errors.add(:id_number, "is already taken")
end
end
end
if you have indices on the id_number columns this check should run very quickly and is the same check that validates_uniqueness_of would use within a single table. Solutions that involves fetching all ids into rails will start running into problems when the tables get large.
Another thing to note is that if your app runs multiple web server instances at a time these kinds of rails side checks can't 100% guarantee uniqueness as they are subject to races between threads. The only way to ensure uniqueness in such situations would be to use facilities built into your database or generate the id_numbers yourself from a source that precludes duplicates (such as a database sequence).

Cache instance variable after refresh

I've 3 models: Game, Player, Card.
In Game model i've function:
def cards
#cards ||= Card.all.shuffle
end
When i'm dealing cards to players i do:
player.cards << cards.pop
i'm trying to save cards variable, that after refresh i can deal to another player from the remain cards.
Basically i'm trying to avoid remain cards calculation in that way:
def remain_cards
all_cards = Card.all
table_cards = players.map(&:cards).flatten
all_cards - table_cards
end
Is there any way to do that?
It's a BlackJack game, The Game Cycle:
human player enter into the game and click on start game button and ajax request sent to server
the Game model deals two cards to each player, first bots players and then human players.
after the starting dealing, i'm sending back the all data which includes players and their cards.
then i want thatת if player clicks on the 'hit me' button, that will add him random card from the remain cards
Can you give a little more detail on what's going on? What do you mean by refresh?
Update: In that case I believe the simple answer is no. "Each request is treated as an independent event and no state information is carried over apart from what is stored in the user session and any external databases, caches, or file stores. It is best that you design your application with this in mind and not expect things to persist just because you've set them." (source: What data (if any) persists across web-requests in Ruby on Rails?). The only other thing I can think of to allow the "cards" data to persist across requests is by using a cookie. But then you'd have to keep updating the cookie info anyway I believe. So it may be better or not depending on what your situation is.
Update: I've been thinking...One possible way to have a "card deck" persist is to try something like the following:
#config/initializers/card.rb - store an instance to an app-wide Card object using an initializer.
THE_CARD_DECK = Card.new
#Now we change the before filter which sets the #cards variable to use that constant:
class ApplicationController < ActionController::Base
before_filter :init_cards
def init_cards
#cards = THE_CARD_DECK
end
end
Now #cards should be the sole card deck and you can change its value throughout the game.

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.

how to store facebook friends to DB (newbie)

i'm creating a facebook-app for university project and i'm trying to store all my friends in the DB.
By using the API-syntax "me/friends" i get a facebook-respond looking like this:
{"data"=>[{"name"=>"Albert Einstein", "id"=>"11111111"}, {"name"=>"Max Mustermann", "id"=>"222222222"}, {"name"=>"Just Another Name", "id"=>"333333333"}]}
I believe its a json-object, but i'm not sure.
Question: How can i save the data, i need a DB with all the User-IDs of my friends.
Thx!
Edit:
Hey, this is what i have searched for. But i still get an error and don't know why.
My code:
def insert_1
fb_friends = rest_graph.get('me/friends')
fb_friends[:data].each do |f|
#friend = Model.new(:name => f["name"] )
#friend.save
end
end
I get an Heroku error (We're sorry, but something went wrong.)
You have two options -
Option 1-
You can create a friends table which will belong to users table. If a user has 200 friends, it will create 200 entries in friends table all belonging to the user via has_many-belongs_to relationship. For storing data, you just have to iterate over facebook friends hash and then save each of them separately
Pros : You can search for any friend separately.
Cons : There will be so many of friend entries. Saving them will take time, if somebody has many friends(say 500-700). Repeating entries will be created for mutual friends.
Options 2
You can add a friends column in your users table and declare this in your user.rb
serialize :friends
This way, you just have to pass a hash object to friends attribute of user table, and rails will save that in yaml format for you. When you will do #user.friends, rails will again convert that yaml formatted data to hash object and return it.
Pros : There will be only one query to save all users. You can iterate through this hash to show list of all friends.
Cons : You can't update them separately, you will update all together. Not good if you want to store some other information in relation to user's friends.
Update
as per your code example above
fb_friends = #your logic to get data as shown above.
fb_friends[:data].each do |f|
#friend = Friend.new(:name => f["name"],:fb_user_id => f["id"] )#creating Friend model obj.
#friend.save
end

Resources