Allow an action once per session - ruby-on-rails

I have a site that tracks video views... every time the controller is called. One could flood their own video views with some heavy F5ing.
How to make it so a view counts or a method runs only once per session?
def show
new_views = #video.views + 1
#video.update_attributes(:views => new_views)
end

You can create a session variable, probably upon login, like :
session[:is_logged] = 1
Then, every time you are about to increment the counter, just check this variable.

Single session variable doesn't work because you have many videos and you would like to separate counts for different videos. I think that a better way is to store view events in db.
pros: you can even allow action once
per user avoiding login/logout
cons: huge size of users_videos table

Well the simplest way would be to track it in the session itself:
def show
if session[:has_counted_view] == nil
new_views = #video.views + 1
#video.update_attributes(:views => new_views)
session[:has_counted_view] = true
end
end

Related

Need to store query result instead of storing query in ActiveRecord

I am having following function to find some of the selected records from public pages.
def find_public_page(title)
#footer_public_pages ||= PublicPage.where(title: %w(welcome_to_toylist terms_of_services buying_a_toy selling_a_toy requesting_a_toy ad_guildelines))
#footer_public_pages.find_by(title: title)
end
what I need is that #footer_public_pages should store the result set in first time and then on next time it will directly hit the find_by query instead of firing two queries.
Any help would be appreciated.
You can try this. I hope this will help You.
def find_public_page(title)
#footer_public_pages ||= PublicPage.where(title: %w(welcome_to_toylist terms_of_services buying_a_toy selling_a_toy requesting_a_toy ad_guildelines)).group_by(&:title)
#footer_public_pages[title].first unless #footer_public_pages[title].blank?
end
The instance variable #footer_public_pages belongs to a class, in this case probably the controller. Because of the way the controller works, every action (i.e. redirect) is going to be a new instance, and will need to set that value again on calling find_public_page. The way it's currently coded would help you if you were calling the method multiple times in the same action (and/or its views), but I doubt that is the case. Instead, if you're trying to keep this variable across multiple pages, you'll want to use a session variable. So you could say:
def find_public_page(title)
session[:footer_public_pages] ||= PublicPage.where(title: %w(welcome_to_toylist terms_of_services buying_a_toy selling_a_toy requesting_a_toy ad_guildelines))
session[:footer_public_pages].find_by(title: title)
end
If you follow this approach, just be warned that these session variables will only be dumped when the user closes the browser (NOT when they navigate away from your site), so you have to be careful in managing them.

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.

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.

cycle() an image_tag

I'm want to display different image version:
first article: big banner
second: small banner that float to right/left
so, first thing: use cycle() but dont work:
= cycle(image_tag(banner_big), image_tag(banner_small)
or
= image_tag(cycle(banner_big_path, banner_small_path))
Only first image is displayed
There's a proper way to make one like that ?
Your problem is that rails is expecting you to call cycle with the same set of strings each time. At the moment you're passing a different pair of strings to each call to cycle, so rails resets the cycle each time. New cycles always start with their first value, hence the result you describe.
Assuming your articles had methods called small_path, big_path, something like
article.send(cycle("big_path","small_path"))
Should return alternate image paths.
You can make use of the session facility to store indexes there and use those. For instance:
# application_helper.rb
def session_banner_index
session[:banner_index] || 0
end
def session_banner(*list)
list[session_banner_index % list.length]
end
# application_controller.rb
def increment_session_banner_index!
session[:banner_index] = (session[:banner_index] || 0) + 1
end
These helper methods approximate the interface you were asking for:
= image_tag(session_banner(banner_big, banner_small))

How do I change a variable every so minutes in rails?

I want to display a random record from the database for a certain amount of time, after that time it gets refreshed to another random record.
How would I go about that in rails?
Right now I'm looking in the directions of cronjobs, also the whenever gem, .. but I'm not 100% sure I really need all that for what seems to be a pretty simple action?
Use the Rails.cache mechanism.
In your controller:
#record = Rails.cache("cached_record", :expires_in => 5.minutes) do
Model.first( :offset =>rand(Model.count))
end
During the first execution, result gets cached in the Rails cache. A new random record is retrieved after 5 minutes.
I would have an expiry_date in my model and then present the user with a javascript timer. After the time has elapsed, i would send a request back to the server(ajax probably, or maybe refreshing the page) and check whether the time has indeed expired. If so, i would present the new record.
You could simply check the current time in your controller, something like:
def show
#last_refresh ||= DateTime.now
#current ||= MyModel.get_random
#current = MyModel.get_random if (DateTime.now - #last_refresh) > 5.minutes
end
This kind of code wouldn't scale to more servers (as it relies on class variables for data storage), so in reality you would wan't to store the two class variables in something like Redis (or Memcache even) - that is for high performance. Depends really on how accurately you need this and how much performance you need. You could as well use your normal database to store expiry times and then load the record whose time is current.
My first though was to cache the record in a global, but you could end up with different records being served by different servers. How about adding a :chosen_at datetime column to your record...
class Model < AR::Base
def self.random
##random = first(:conditions => 'chosen_at NOT NULL')
return ##random unless ##random.nil? or ##random.chosen_at < 5.minutes.ago
##random.update_attribute(:chosen_at,nil) if ##random
ids = connection.select_all("SELECT id FROM things")
##random = find(ids[rand(ids.length)]["id"].to_i)
end
end

Resources