Link to home
Start Free TrialLog in
Avatar of MFredin
MFredinFlag for United States of America

asked on

Creating a rating system for my users.

I have a small site with a couple thousand users.  The users can currently upload up to 5 pictures of themselves.  I would like to create a rating system to allow users the ability to rate other users pictures and based on the ratings of the possible 5 images, come up with a single rating for that user. This type of thing is a little advanced from anything I've done in the past.  I would appreciate any help.

I will be displaying the images on the home page using some way to display the images fairly and evenly and the images can also be rated directly in the user's profile.

Heres what I'm heading for in a nut shell.

- Users can have up to 5 pictures (they may only have 1, or 2, or 3 etc)
- Each Image will be rated individually, using some way of displaying the images evenly and fair (not sure how yet)
- Based on the ratings from the images, and how many times the users images have been rated the user will have a calculated overall rating
- If the user decides to add or change a picture, this should not effect the past ratings.
- The rating will be on a scale of 1-10.

Basically Im trying to create something similar to hotornot.com or other large ratings systems, on a small scale for my niche site.  

I'm using Coldfusion 8 with a MySql 5 backend. I really appreciate anyones help.

Thanks for your time.
ASKER CERTIFIED SOLUTION
Avatar of Scott Bennett
Scott Bennett
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
dan's approach would be simpler and would keep your database smaller and would work fine for now, but it really depends on what your needs are, and how much control you want to have over your data. In the long term you may want to add more functionality to your image rating application and storing the data as I suggest will give you more options.
  If you track each vote individually you will be able to restrict users from easily placing multiple votes for the same image, preventing them from skewing the numbers in their favor. You can see which users are placing the most votes on your site, and many other things that may be of interest to you.
Avatar of danrosenthal
danrosenthal

to SBennett's point:
I agree that logging all of the data MAY be useful, but if you allow guests to vote, then it wouldn't make a difference for either approach as far as restricting votes unless you wanted to restrict votes at the IP level. You would most likely write a cookie that either directly signified that a vote has been cast or that links to a database and will then link to all votes cast for that guest. In either case clearing one's cookies will allow them to vote as many times as they want.
-Dan
I never said it was bullet proof, all they would have to do is create a bunch of fake accounts to make multiple votes. of course there are ways to make it too much of a pain to create multiple accounts like having to have a uniqe validated email address and using cookies/ip tracking to see if someone is creating excessive amounts of accounts from the same location in a short period of time, using CAPTCHA on your registration screen, etc, etc, but you don't really have to worry about that too much until you get bigger and grow a big target on your back.

My mian point is really that storing the Ratings stats directly in the Image table will work fine if you don't expect to ever make any enhancements to the image rating system and all you will ever need is the data in those three columns. If you expect your site to grow, or you expect to make further enhancments to the image rating system , or you want to enforce rules that restrict users from manipulating the rating process, you probably want to go with the data structure that give you the most options.

Avatar of MFredin

ASKER

Thanks so much for the help guys.  Both are great solutions.  

SBennett I like your solution since it gives me alot more options and it gives me some room to grow.
I like Dan's solution from the simplicity standing.

The only thing is a user doesn't have to be a member to vote on an image.  I guess I could just take out the userID field and change it to userIP and record the user's IP address instead.  How does that work tho?  What if there are alot of users behind 1 IP and I checked to see if that IP has already voted on that image. Then it wouldn't be correct.  What do you do in a situation like that?  It doesn't have to be 100% fool proof but it's not fair if 1 user can keep voting on 1 image.
Agreed.

But also consider that with a more robust site, you wouldn't want to have to calculate ratings in real time. Especially for pulling up the 10 most popular (for instance), so having a field in the image table that gets updated in real time (or on a scheduled basis) might also be a good long term solution. And you can always add in logging down the road.

So either way may lead to a good long term solution. If I were programming the site,  I would probably start with my approach, but also use a table structured as SBennett suggested to log the individual votes.

I would use leave the userid field there and make it so it can be null, and just allow multiple votes for null userids and add an IP address column and a timestamp column in ImageRatings table as well. then you can set your logic so that when a user submits a vote

if the user is logged in:
    Check to see if there are any votes from that userid for this imageid or if there are any votes from
    that IP within a certain time frame (you decide how long). If you find a record don't ad another one.

else if the user is not logged in
   Check to see if there are any votes from that IP within a certain time frame. If you find a record don't
   ad another one.

True, if you get really big you will want to keep a log of each vote but keep running totals of the ratings stats for each image in the image table for performance.

I would suggest, if/when you get to that point, writing a stored procedure that handles all the logic for adding a vote and recalculating the stats for the image all in one transaction.
I would leave the UserID blank if it is a guest vote, but you should put a unique field, maybe a new field called GuestID which is a FK to a guest table that simply has:

GuestID -  Identity Field
DateCreated
IP Address

You would then store this GuestID as a cookie for this guest user and you would then be able to tell which items he has already voted on. This way you are restricting only that one guest user and not the entire IP. The IP storage should really only be used for information and possibly to use in the future to restrict problem IPs.
This is good. I would also make is so if a person logs in, you use that guestid cookie to check to see if they have voted on any images from that computer within a certain timeframe (maybe an hour), and then associate those votes with the userid.
Avatar of MFredin

ASKER

Both solutions have good points.  How about a combo of both?  Something like this...

When a user votes, check against the logged votes on the image to find out if this user has voted on this image before or not.

If the user has voted on the image, update the previous voting log with the new value on the ImageRatings table.
If the user hasn't voted, insert a new log for this image into the ImageRatings table.

After the ImageRatings table is updated/inserted, update the rating field on the Images table for the image just voted on.  

This way I have a log to track who has voted on the image, and the image has a real-time rating associated with it that can be easily queried.

Does this make sense for what I'm trying to do while remaining scalable for future expansion?
I think you are well on your way to an excellent solution.
that sounds fine but if you are going to do this process with two coldfusion queries you should use a named cflock to make sure only one vote process can occur for one image at a time like:

<cflock name="ImageRate#ImageID#">
<cftry>
<!--- put your two update queries here --->
<cfcatch type="Any"></cfcatch>
</cftry>
</cflock>

or better yet use <cftransaction>
Also, I would add (NOLOCK) to all database reads from the image table. This will ensure no deadlocking issues and should also result in faster searches.

SELECT columns
FROM table (NOLOCK)
WHERE condition
Is there anything we left out?  A couple of programmers with too much time on our hands trying to get the last word in can be quite the cornucopia of knowledge =)
by the way don't forget that NOLOCK thing... I run a very high traffic site and when I discoverd (nolock) and placed that in a few long running queries... you wouldn't believe the difference it made in performance some pages loaded 1000 times fatser.
Avatar of MFredin

ASKER

Great!  The table structure and logic makes sense to me.

Here's a map of my tables then to double check I'm on the same page:

USERS
user_id (pk)
various_userinfo...

IMAGES
image_id(pk)
user_id(fk)
rating - updated each time a rating gets logged in the IMAGE_RATINGS table for this image

IMAGE_RATINGS
rating_id(pk)
image_id(fk)
user_id(fk) - allow null
guest_id(fk) - allow null
rating_value
rating_count

GUESTS
guest_id (pk)
insert_timestamp - timestamp of creation
ip_address

I'm a little lost on writing the cookie and using it to check against my timestamp and using it to restrict multiple unique votes without being logged in.  If you guys wouldn't mind helping me out on that I will up the points.  Otherwise if it needs to be asked as a seperate question I can do that too.
Avatar of MFredin

ASKER

Ooops, I don't really need rating_count on the IMAGE_RATINGS table.
basically you check to see if a cookie exists and if not you create a guest record and set a cookie. (using <cfcookie>)

Then when they vote you check to see if they are logged in, if so use userid, if not use guestid to check if they have already voted on this image.

You will want to check the time stamp when you create a guest record to see how many guest records have been generated with that IP within a certain time frame. because peopl can clear their cookies and vote again. if you see a lot of guestids comming from the same ip withing a short period of time you should restrict them or keep assigining them the same ID
Everything looks right to me, but RatingCount doesn't seem to apply to the IMAGE_RATINGS table, I believe you can remove that column.

The guest cookie can be written like this:
<CFCOOKIE name="guestID" value="#GuestIDvaluefromdatabase#" expires="never">

The UserID will be written similarly

Make sure this page is served to the user (no CFLOCATION), otherwise it will not be written correctly.

When looking up the user you first check for the cookies:
<CFIF ISDEFINED("cookie.userID")>
  lookup query using #cookie.userID#
<CFELSEIF ISDEFINED("cookie.guestID")>
  lookup query using #cookie.guestID#
</CFIF>

SBennett, your 4:08 post was 100% right. :)
Avatar of MFredin

ASKER

Awesome! Thanks so much guys! Can't wait to get working on this now that I'm confident it will work!  

Use a named cflock with my rating queries - CHECK
User NOLOCK in the queries  - CHECK


Thanks again!
No problem. Good luck!
Avatar of MFredin

ASKER

I'm finding no way to increase the points on this question.  Do you know where I can do that?
I believe that 500 is the maximum for 1 question. Generally what you would do is open an additional question directed at a particular expert, such as "Points for SBennett" and assign points through these additional questions.
-D
Avatar of MFredin

ASKER

Could you resplit the points on this question evenly?