Caching Pages in Rails based on attribute value? - ruby-on-rails

I have a PagesController with a show action that currently looks like this:
class PagesController < ApplicationController
caches_page :show
def show
#page = Page.get_page(params)
end
end
My Pages model has a publish_datetime attribute, and the page isn't available to the public until the publish_datetime passes. Is there a way to specify in Ruby that the page should not be cached until the publish_datetime has passed?
Background:
Basically, there are people constantly in and out of the page editing the pages prior to their publication date, so that's one reason why I don't want the page to be cached. However, I figured out that I can expire the page on Update, but I'd prefer not to do that. The second reason is that I have a flag called has_been_published? that changes the way the page looks. If the page is accidentally cached before the publish_datetime, has_been_published? never gets called, and the page won't cache with the proper content.
Thanks for any help!
--Mark

You want to use action caching instead of page caching, so you can use a before_filter (or your auth stuff) to determine whether to render the full page or not.

Related

Ruby on Rails Write a redirect for a dynamic URL to a static URL

Is there a simple way to write a ruby if statement to redirect a dynamic page to a static page?
I’m writing this in my pages controller where the site pages are generated. This is what I’m attempting to do.
If “/index/page1”
redirect_to “page2”
return
end
This redirects all the pages created in the pages controller to “page2”. I know the syntax is incorrect. I need help in writing out the correct way to test for the first condition.
Any help is appreciated. Thank you!
Here is an update / more information to my question.
Here is my show action in the PagesController
def show
#page = Page.find_by_url_path("/#{params[:url_path]}")
layout = "templates/#{#page.pageable.class.name.underscore}"
respond_to do |format|
format.html { render layout: layout }
end
I need to write an if statement that looks for one specific URL that gets generated. This page is created by the show action.
e.g. https://host.com/products/page1
Then redirect it to another specific URL. This is a static page on the site.
e.g. https://host.com/page2
I am having difficulty in writing the if statement to find the first page. This is what I've tried.
if "/products/page1"
redirt_to "/page2"
return
end
Depending where I put the code within the show action, I either get a double render error (as subparry explains below). Or I redirect all the pages generated through the show action to "/page2".
This application was written by a more experienced Ruby developer and I'm doing my best to maintain / update it. If I need to post more detailed information, please let me know. Thank you.
Well, as usual with these kind of requirements, there are many ways to achieve it.
As you know, different pages (views) in controllers are represented by instance methods (or Actions), for example I can imagine your PagesController looking something like this:
class PagesController < ApplicationController
def page1
# do something...
end
def page2
#do something else...
end
end
So, the easiest way would be to trigger a redirect from page1 redirect_to "https://host.com/page2", I don't know the reasons behind the decision to redirect. If it is a temporary redirect, it might be the best solution because of ease of change later, but if it is a more permanent redirection, I would implement it at the web server level (Nginx for example)
It depends on your use case.
PS: Don't forget that if you redirect in your action, it does not imply a return, so if you have more code below and another call to render or redirect, it will fail (double render error) so either you remove further renders/redirects or insert an early return.
EDIT:
Ok, now I understand better your case. You have a model called Pages which has a column called url_path which details the location of each page.
So, if I understand correctly, you'll have to do the conditional statement like this:
def show
if params[:url_path] == 'page1'
redirect_to 'https://host.com/page2'
return
end
# Rest of action code...
end
I don't know for sure how are paths stored in url_path, but you get the idea!
PS2: When you write if 'products/page1' you are basically saying if true and always entering the condition because only nil and false are falsy values, everything else is truthy.

Landing Page for Age check in Rails App

I have built a marketing site for an alcohol brand and I need to check the user's age by adding a landing page before they can enter the main site. What is the best way to tackle the form, submit and validation functionality inside my existing rails app?
Should I just create a raw html form and use javascript?
Add a before_action to ApplicationController that checks if the verification has already taken place (i.e. if it is stored in a cookie, then check for the cookie, etc):
class ApplicationController
before_action :check_age
def check_age
# check if the user has already confirmed their age.
end
...
end
If it doesn't find this, then redirect the user to a controller action that renders a page with the age check form (i.e. AgeVerificationController#new)
Upon submit, set the cookie (or whatever you are doing to store this data), and redirect the user back to the page they were intending to visit (or kick them off the site if they say they are under age!)
You will need to include a skip_before_action on the controller you are using to handle the rendering and submission of the form, i.e.
class AgeVerificationController < ApplicationController
skip_before_action :check_age
...
end`
Using before_action is sometimes a bit of an anti-pattern if you start using it to do a lot of complex stuff, but in this case it is a fairly simple way of doing what you are looking to do.
even if you use javascript,you will need to store the age of the guest to help him out next time for a better user experience.So i will suggest you to save it along with the ip-address to recognise the guest,if you are not storing a unique parameter for login(such as email,mobile number etc).
Once you have the table ready to store this details...you have many options such as:-
first option is to that,before submit get the age of guest using jquery validation and pass it to the controller using form and store it.Use ajax for form submission so that
you can validate other elements as well
second option is to let the user visit the page and show a modal window popup in the middle,after five seconds when page has loaded by using settimeout to call ajax which in turn will call a controller method to render js file which will call a modal such as $(".myModal").show(); or render your own view to get user details such as:
$('#myModal').find('.modal-body').html("<%= escape_javascript(render(:partial => 'users/get_details')) %>");
$('#myModal').modal();

How to profile a rails controller that returns a json response with rack-mini-profiler?

I am using rack-mini-profiler in my rails 3.2 project.
In gemfile:
gem 'rack-mini-profiler'
Everything works great. But my application is mostly a set of json endpoints. So while it is very useful to be to able to inspect the performance of html pages, I would like to also be able to see the performance of controllers that return json.
Example of my controller:
class UsersController < BaseController
def json_method
# you don't see the mini profiler ui for this controller
render json: { users: [:foo, :bar]}
end
end
If I go to localhost:3000/users/json_method, I see my json response but not the profiler ui.
In development, by default, the rack-mini-profiler gem collects the previous JSON call and presents it in the menu accessible from the HTML page. No code change required.
So, make your JSON request, then hit any other HTML page and it will be available in the list. We use this to great effect. If your Rails app is a JSON API service only, make sure you have a 404.html in your public folder at least, then hit something like:
http://localhost/404.html
In the second tab, you can visit this URL /rack-mini-profiler/requests Where you can see the last request log
if rack mini profiler can’t display the results, it will collect them until it can on the next HTML page. So, the solution that I am using is to:
make the JSON request and
then hit an HTML page of my choice.
The results will appear, along with the most recent HTML profile.
http://tech.eshaiju.in/blog/2016/02/25/profile-api-endpoints-using-rack-mini-profiler/
As a first solution, you can just set the format to html, and render inside the html page:
The controller:
class UsersController < BaseController
def json_method
#users_json { users: [:foo, :bar]}
render 'index', formats: ['html']
end
end
And in app/views/users/index.html.erb:
Users:<br/>
<%= #json.inspect %>
I don't care so much about my json result, now I have the profiling ui.
A solution where I have the profiling ui without changing my controller would be much better.
Note that in a new Rails API project initialized using rails new api_name --api, the ApplicationController inherits from ActionController::API, instead of ActionAcontroller::Base. In this case, mini-profiler might not load when your HTML page is shown.
I had to change the base class to ActionController::Base to make it work. If in your app you see no requests to load resources from mini-profiler on your HTML page, you may want to try this change. Took me a long while to figure out.
Also note that you do need to have at least the <body> tag in your template to be rendered, otherwise the mini-profiler divs will not be properly injected.

Caching/Etag for Static Action in Rails 4

Since Rails 4 removed page caching and action caching, I'm wondering what is the Rails 4 way to cache an action that has no variables and has only html in the view? Should I fragment cache the static html in the view? How do I set an etag/fresh_when when there is no model to set it to expire with? I'm struggling to find an example or convention to caching what should be the easiest page to cache.
One note is that while the view is completely static, the page still has a dynamic navbar depending on whether the user is signed in or not. How would you handle a static page like this without resorting to action caching since its been removed and the convention has been set not to use the gem version?
Example:
class HomesController < ApplicationController
def index
end
end
homes/index.html.erb
<div>A bunch of normal html tags with no erb</div>
Edit:
Based on #severin's answer and my own research, here is what I have come up with so far.
class HomesController < ApplicationController
def index
fresh_when(["some-identifier", current_user, flash])
end
end
In addition, I'm using https://github.com/n8/bust_rails_etags to reset all etags after a deploy because the view may have changed between deploys. I think this covers the etag fairly well although I'm still curious whether fresh when will include some identifier about the view automatically and whether "some-idnetifier" is necessary? Is it going to be a problem that sometimes current_user and flash will be nil?
Now on the second point of fragment caching the static content. I'm assuming if I did:
cache "v1" do
all my html
end
I'd have to remember to always change the cache identifier when the page is changed otherwise my app would serve stale content. Any way to automate this as well or is already handled by rails? It would be nice to just cache the last time the view was updated or something clever so I don't have to keep track on when my static content is changed.
You can set an etag/last modified at data without a model, check the documentation: http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-fresh_when
So you could do something like:
def index
fresh_when(:etag => 'some_made_up_etag', :last_modified => a_long_time.ago, :public => true)
render
end
Note: you don't need to provide an etag AND a last modified at timestamp, you could just provide an etag or only a last modified at timestamp.
In addition to this, I would also fragment cache the whole content of the view.
Or you could just continue using action-/page_caching using the official plugin/gem: https://github.com/rails/actionpack-page_caching
Some additions regarding the second part of your question:
Rails adds the content of the RAILS_CACHE_ID environment variable to all its cache keys (the etag and the fragment cache key in your example). The bust_rails_etags gem adds another environment variable that affects only the etags... So in your case you could just remove the bust_rails_etags gem and update the RAILS_CACHE_ID environment variable on all your deploys.
You can even automate the updating of the RAILS_CACHE_ID environment variable by adding something like this in config/environment.rb:
code_revision = # ... some piece of code that gets the current revision.
# I'm using git and I use the following (crude) piece of
# to get the current revision:
# code_revision = `git log --pretty=format:%h -n1`.strip
ENV['RAILS_CACHE_ID'] = code_revision
This way, the current code revision is always added to all cache keys.

How does Twitter get two different home pages depending on login or not? - Rails 3.1

If you go to Twitter.com when you are not logged in, you will see the marketing page with the login & registration fields.
However, if you go there when you are logged in, you will see your activity stream for your Twitter handle.
I know one way to do this in Rails is to have a home/index for the logged in users, and just use public/index.html for the marketing site. But Twitter is not using public/index.html, so I am wondering how they do it?
Is it just a simple case of having one root route, but then an if statement that displays two different pages depending on whether or not the user is logged in?
Or is there some other more fancy routing trick that I can use to do that?
If it is just an if-statement, that seems a bit hacky...no?
Thanks.
This might give a head start. http://collectiveidea.com/blog/archives/2011/05/31/user-centric-routing-in-rails-3/
By default controller methods that handle specific routes render a view that has a corresponding name (home/index route links to home controller and index action which in turn renders the view index.html.erb inside the app/views/home folder).
However, inside the controller method that handles the root request you can simply render a different view based on the fact that a user is logged in or not.
You would probably have something like this:
class HomeController
def index
...
if user_logged_in?
render "logged_in_user"
# else it will render the index view by default
end
end
end
In this case "logged_in_user" would be a different view (template or html.erb file) from the app/views/home folder.
For more information take a look here: Rails rendering guide
it can some kind of redirect for not logged in users to different controller , checking of user session can be done in before_filter

Resources