J. Haselden J. Haselden - 6 months ago 15
Ruby Question

Ruby on Rails: How to "check-out" a model value

I'm new at this so please excuse the lack of terminology as well as experience.

If you don't want to read the whole explanation below here's the short of what I'm trying to do:


  • Users on my site give feedback on each other's songs

  • Two users should not be able to give feedback on the same song at the same time



So, I want to put in place some sort of "check-out" system where a user checks out a song to give it feedback. So if one user checks out a song, another user won't be able to access it. Also, if the user who checks out the song leaves feedback - the song will return to it's non checked out state. If the user doesn't leave feedback and leaves the page, the song will return to its unchecked out state.

That's the short version, here's the long version:

I have a website where I want people to trade feedback on each others songs, however they can only receive as much feedback as they give. To do this I have a page I call "feed".

When someone visits this page, they are showed a song and are given a form to leave feedback. If they leave feedback, they earn one feedback for their own song to receive.

I do this in the controller with:

@track = Track.where.not(user_id: current_user.id).
where.not(id: current_user.feeds.select(:track_id)).
where("tracks.feed_avaliable > '0'").
where("pref_genre = #{current_user.genre_id} OR genre_wait <= ?", ((Time.now.to_i - Track.last.last_feed.to_i) / 3600)).
reorder(feed_level: :asc, last_feed: :desc, created_at: :asc).
first()


Which hopefully works out to, find the first track/song where the user of the track is not the current user, where the current user has not previously left a feed(back), where the feed available is > 0, where there tracks preferred genre is the current users genre or the genre wait time is greater than the time since the last feed. Sort the tracks in order of feed level smallest to largest, last fed the longest ago to the soonest, created at the longest ago to the soonest.

Anyways, now I'm stuck on the issue of what if two people end up giving feedback on the same track at the same time.

This is why I want some sort of "check-out" method for the tracks. So that if a user is currently giving feedback on a track, another user won't get served that track from the formula above.

Can anyone help?

Answer

You could create a boolean field on the Song model called something like 'checked_out'. Create a controller method to check out the song or include code to set the checked_out field in whatever controller method is checking it out. After creating the boolean field Rails will generate a method checked_out? which you can use to determine if a song is checked out, and whether to allow another user to check it out.

...
@song = Song.find(params[:id])
unless @song.checked_out?
  @song.update_attribute :checked_out, true
end
...

You will want to watch out that you don't allow users to check out songs forever - if a user checks out a song and then walks away and never comes back, you will want to have a process that checks it back in at some point.

If, as you progress, you find yourself adding a lot of boolean fields around the song's checked-out status, that might indicate that a state machine is an appropriate solution.

It would also be helpful to create a scope in the model file for Song to get songs that are not checked out:

scope :not_checked_out, -> { where checked_out: false }

Use this if you only want to show songs that are not checked out - of course you still want to check in the controller as well, in the event that a song is checked out by another user after the page is rendered.

Comments