Keeping random numbers the same after refresh, Rails - ruby-on-rails

I am creating a multiple choice generator that can generate multiple choice answers based on input data. The random number generators are in the page's controller. The controller gets called during a refresh and as such, the random numbers change.
I thought of storing the random numbers in sessions. But this would cause all the random numbers to stay the same even when I do want to change my input data (such as loading a new page).
Is there a way to block the controller action during page refreshes? Or another easy way to have the random numbers stay the same on page refresh, but change when loading new page?

From what I understood, you want:
IF page reloaded, the random number would stay the same on the page
ELSE if changed / new page, the random number would be regenerated
Then you could do something like:
your_controller.rb:
before_action :set_random_number, only: [:index]
def set_random_number
message_encryptor = ActiveSupport::MessageEncryptor.new(Rails.application.secrets.secret_key_base)
# if no random number yet for this page, then generate, then redirect to same page but now with random_number param
if params[:random_number].blank?
# we need to encrypt the random_number so that user won't be able to manipulate the value in the query params
encrypted_random_number = Base64.urlsafe_encode64(
message_encryptor.encrypt_and_sign(
some_code_of_yours_that_generates_the_random_number
)
)
redirect_to request.query_parameters.merge(random_number: encrypted_random_number)
else
#random_number = message_encryptor.decrypt_and_verify(
Base64.urlsafe_decode64(params[:random_number])
)
end
end
index.html.erb:
<%= #random_number %>

I think cookies are good idea for your problem, try this as an example, in your controller:
def index
cookies[:number] ||= SecureRandom.hex(4)
end
Then in your view:
<%= cookies[:number] %>
Does that do what you want?

Related

How to avoid model's data being changed by redirect

My home_controller.rb looks likes like this:
class HomeController < ApplicationController
def index
#post_a = Post.where(title: 'a').sample
#post_b = Post.where(title: 'b').sample
end
And index.html.erb is like this:
<div>
<%= #post_a.title %>
<%= #post_b.title %>
</div>
When the page is refreshed, #post_a.title and #post_b.title are changed.
Is there any way to prevent data from being changed by refresh or redirect?
It is a basic property of RESTful, hence state-less, systems like those created by Rails that a response to a request cannot directly access what is done in another response. Everything that is to be accessed beyond responses have to be saved somewhere, usually databases.
In order to access from the second response the values of the variables you have set in the first response, the standard way is to save information in session. You can perhaps read this.
Given that you are using redirect, another way is to pass the values as parameters to the path helper method when you call the redirect. Instead of doing:
redirect_to foo_bar_path
you can do:
redirect_to foo_bar_path(post_a_id: #post_a.id, post_b_id: #post_b.id)
and then in the controller action at the destination of redirect, you can access the ids of #post_a and post_b as params[:post_a_id] and params[:post_b_id].
The variables hold the value you assign to them. As you are taking a random record (using sample), the result of the query is a random record fetched from the database, therefore the value of those 2 variables is sort of random.
If you want a predictable record to be assigned to #post_a and #post_b, then you need to update the query to fetch the record you want.
For example, fetch by ID:
#post_a = Post.find(1)

How to cache unlimited number of static pages in Rails

I have a Rails app which allows users to create unlimited number of static pages (I store these pages in postgres database), because these pages are statics I would like to use page caching, but I am not sure what's the limit number/size of caching pages in Rails, can I cache unlimited number of pages ?
we do this on our main company blog at https://reinteractive.net.
What you probably want to do is do fragment caching on the show action of the page you want to cache. There are some pitfalls to this, the biggest of which is expiring the cache if the underlying page changes.
It works like this:
In the routes.rb file:
AppName::Application.routes.draw do
get "page/:id" => "pages#show"
end
In the controller (app/controllers/pages_controller.rb):
class PagesController < ApplicationController::Base
def show
#page = Page.find(params[:id])
end
end
Note here in the controller you are hitting the database on every request but this should be a really fast request with the correct index in place.
Then in the view (app/views/pages/show.html.erb):
<% cache("pages/show/#{#page.id}-#{#page.updated_at.to_i}") do %>
<%# Render your complex page here %>
<% end %>
What you gain from caching a fragment like this is that the output of the rendering gets stored in the cache and if this page takes a while to render, you can get significant time savings, especially if it takes 100ms or more to render the page and all it's parts, you pay for it the first time, and then after that it loads in a few milliseconds.
Note we are checking the last update time of the page? This is so that if you want to expire this cache (and render again) all you need to do is call page.touch on the page object to update is updated_at time.
If you have any other objects that could also update (such as say a page.header_image or something, then you can also put the updated_at on these related objects into the cache key as well, or expire them manually when you update the page.
An alternative to adding updated_at into the cache key is expiring them in the model like this:
class Page < ActiveRecord::Base
after_save :expire_cache
def expire_cache
Rails.cache.delete("CACHE_KEY")
end
end
But this has it's own challenges.
Good luck!
#MikelLindsaar 's answer is good but kinda too much work for something that could be handled easier, ActiveRecord objects have a method called cache_key that auto generates a unique key for each record, if the record gets touched or updated, the generated key will be different, this key is used for generating the whole view caching key, which is a function that has a lot of parameters, like the object id, updated_at (from the cache_key), and the view's hash ( the cache gets invalidated if the view file is updated, not just the db), so there's no worry about any stale data, all you need to do would be:
The controller
class PagesController < ApplicationController::Base
def show
#page = Page.find(params[:id])
end
end
The view
<% cache #page do %>
<%# Render your page here %>
<% end %>
if you have multiple pages that use the same page object you could use a compound cache key, something like this for example
<% cache(prefix: 'page/show', page: #page) %>
and rails will handle the generation of the caching key.
More info about this over here http://guides.rubyonrails.org/caching_with_rails.html#fragment-caching

Redirecting to a random page while maintaining browser history

I'm trying to implement a "random page" link, which will render to the user a random article from the database. I've tried two separate (but similar) approaches, in both of which I route the URL "/random" to the "random" method in the ArticleController. Here's the first:
def random
offset = rand(Article.published.size)
#article = Article.published.offset(offset).first
render :action => 'show'
end
This works for serving random articles, but there are two issues: First, the URL doesn't update to the correct article, so users can't copy the link or bookmark the article; second, the previously viewed random articles don't show up in the browser's back button's history (i.e. pressing "Back" brings the user back to the page they were on before clicking "random" for the first time).
The second approach substituted render with redirect_to:
def random
offset = rand(Article.published.size)
#article = Article.published.offset(offset).first
redirect_to #article
end
This fixes the first issue - it's a redirect, so the browser is actually redirected to the appropriate URL for the randomly selected article (so it's available for copying/bookmarking). However, the problem with the Back button still remains. Moreover, it feels a bit wrong to co-opt an HTTP Redirect for something like this.
What would be the best way to go about serving random articles, while displaying the correct URL for the article and also maintaining a browser history chain?
Why don't you make actually a LINK for random article?
Helper:
def random_article_link
random_article = Article.find_by_sql("SELECT 1 FROM articles ORDER BY RANDOM() LIMIT 1") # for MySql RAND()
link_to "Random Article", random_article
end
In your approach you can not change URL string on a fly on a controller level. Only on routing level using Constraints.
Umm what about
#article = Article.find(rand(Article.count))
redirect_to #article
I wanted to do something similar and was also having trouble. Here is the solution I came up with:
In your controller you don't need a new page articles/random. I'm assuming you want the link on your article page which would be the show action in your controller.
def show
#article = Article.find(params[:id]
#random_article = Article.order('random()').first
end
Then in your view show.html.erb file
<%= link_to "Random Article", #random_article %>
Expanding on Chris's comment a bit - it's possible create a link within the view which will generate a random id for the next link (if you have some reason to do this...that's up to you).
<%= link_to 'Show me a random thing', thing_path(rand(1..4)) %>
You could drop this wherever and when clicked it'll simply route to a random page. Of course you'll want to change the number to reflect actual :id 's

How to go 'back' 2 levels?

From the list view of my app, I can view a list of records or drill down and edit/update a record. After updating, I want to go directly back to the list view, bypassing a couple of intermediate pages - but I don't simply want to link_to(:action => list) - there's pagination involved. I want to go back to the exact 'list' page I came from. What's the best way? Pass a hidden arg somewhere with the page number? Is there an elegant way to accomplish this?
I'm just going to throw this one out there with the disclaimer that there may be security considerations or existing gems.
On your edit action, you could store the previous page in a session. Then in your update action, redirect to it.
class MyController < ApplicationController
def edit
session[:prev_url] = request.referer
end
def update
redirect_to session[:prev_url]
end
end
As an alternative to use the session, you could carry the referer through the actions using a hidden form field.
class MyController < ApplicationController
def edit
#prev_url = request.referer
end
def update
redirect_to params[:prev_url]
end
end
Form using hidden_field:
f.hidden_field :prev_url, :value => #prev_url
If you do not want to carry along the whole referer url you could also do the same with the page parameter instead and append the parameter to the url in the update action. I would also expect Rails' url helpers to accept parameters.

Question about a/bingo basics (a/b testing)

from:
http://www.bingocardcreator.com/abingo/usage
#A view example with a block passed to ab_test:
<% ab_test("call_to_action", %w{button1.jpg button2.jpg}) do |button| >
<%= image_tag(button, :alt => "Call to action!" %>
<% end %>
Does whatever "choice" that gets passed in the block have to be some sort of link? How does a/bingo know when different choices have been converted?
The way Abingo works is to issue different options to different "identities" in a consistent manner so that the results can later be aggregated together again. There are several ways to do this, such as by IP address, by session_id, or by registered account, all of which are valid and can be used in conjunction. In effect, a particular identity will always get the same random selection of options.
An example from the documentation on assigning the identity is as a handler in ApplicationController:
before_filter :set_abingo_identity
def set_abingo_identity
if #user
# Assign identity based on user
Abingo.identity = #user.abingo_identity
else
# Assign identity for anonymous user
session[:abingo_identity] ||= rand(10 ** 10).to_i.to_s
Abingo.identity = session[:abingo_identity]
end
end
When you want to track action based on which A/B option was used, you need to inject calls in your controllers. Another example:
def show
# Track conversion for active Abingo identity
bingo!("show_info_page")
end
The mechanism by which the user navigates to that particular page is entirely arbitrary and can be by link, by form submission, by JavaScript redirect, or by clicking on an email. The only thing that matters is that the display of the A/B option and the later controller action that tracks the activity both have the same Abingo identity assigned.

Resources