15 Sep

Dynamic SEO Title and Meta Tags in Rails

This is a simple tutorial for setting up dynamic SEO-friendly title tags and meta description tags in a Rails app. If you want your Rails app to follow SEO best practices and play nicely with web crawlers, it's crucial to have dynamic page titles and meta descriptions that complement the content on each page.

I'm going to use a basic blog app and harness Rails's dynamic templating system to automatically generate title and meta description tags from each blog's title and the first 150 characters of each blog post.

First, I'm going to create two helper methods for the title and meta description tags in app/helpers/application_helper.rb:

def title(blog_title)
  content_for(:title) { blog_title }
end

def meta_description(blog_text)
  content_for(:meta_description) { blog_text }
end

These two methods are pretty straight forward. I can pass them arguments in the form of blog_title and blog_text and call the title and meta_description methods on any view.

For each blog's show page, I want the title tag and meta description tag to properly represent the content on the page. When someone performs a web search for “dynamic seo title tags rails" I want our blog titled “Dynamic Title Tags with Rails" to have the best possible shot of appearing in the search page results. I need to grab each blog's title from the show page and pass it to the helper method as an argument. I have to do the same with the meta description which, if you recall, I am going to generate from the first 150 characters on the blog text itself. The best way to accomplish these two objectives is to call both helper methods on the posts show page template app/views/posts/show.html.erb:

<% title @post.title %>
<% meta_description @post.text.truncate(150, separator: ' ') %>

With these two lines of code I am able to grab each blog's title and the first 150 characters of each post and pass these parameters as arguments in the helper methods. This assumes of course that the Post Controller has @post = Post.find(params[:id]) in its show action.

All I have left to do is to display these dynamic features in the layout file app/views/layouts/application.html.erb. I will set up fallback content for the title and meta description tags through the use of a conditional statement since only blog show pages are covered with this technique. All other pages will default to “Ryan McMahon | Software Development | Ruby on Rails" for the title tag and “Ryan McMahon's blog about Software Engineering, Web Development, and Ruby on Rails" for the meta description:

<title> 
    <% if yield(:title).present? %>
        <%= yield(:title) %>
    <% else %>
        Ryan McMahon | Software Development | Ruby on Rails
    <% end %>
</title>
<% if yield(:meta_description).present? %>
    <meta name="description" content="<%= yield(:meta_description) %>" />
  <% else %>
    <meta name="description" content="Ryan McMahon's blog about Software Engineering, Web Development, and Ruby on Rails" />
  <% end %>

4 Oct

Create a Simple Search Form in Rails

This is an easy tutorial for implementing a keyword search form in a Rails app. Often found in a website's navbar, the search form enhances usability and gives a website or app a more polished feel.

I'm going to implement the search form on a simple recipe-sharing app with a Recipes Controller and Recipe Model. The search form will take the terms entered by a user and look for matches in the recipe titles, ingredients, and cooking instructions. The app will display all of the recipes that contain those keywords or inform the user that there aren't any recipes that contain those terms.

I want to have the search available on every page's navbar so I am going to put the search form in the layout view found in app/views/layouts/application.html.erb.

<%= form_tag(recipes_path, :method => "get", id: "search-form") do %>
    <%= text_field_tag :search, params[:search], placeholder: "Search Recipes" %>
    <%= submit_tag "Search", :name => nil %>
<% end %>

I am using form_tag, a generic form helper, instead of form_for because the latter is intended for situations when you want to edit a model's attributes, but that's not what I am doing here. I just want to search the recipes index and form_tag allows me to do that. After the form_tag I pass it recipes_path, the action I want it to go to along with :method => 'get'. I have to declare the get method because by default the form_tag submits a post request and that's not the functionality I'm looking for.

In the text_field_tag I pass params[:search] as a value and this allows the search query to persist in the search box when the results page loads. This is not critical to the search functionality but it adds a little sugar to the user experience.

Finally, for the submit_tag I want to remove the default name of the search form name="commit" because this appears in the url with every search: www.totaste.org/recipes?search=pancetta&commit=Search. By declaring :name => nil, I end up with prettier urls like www.totaste.org/recipes?search=pancetta

To finish this up I need to put a search method in the model and declare which fields the method should search for matching queries. In /app/models/recipe.rb I put the following code that looks for matches in the name, ingredients, and cooking instructions fields in the database. Note: The LIKE syntax is used for MySQL, but if you are deploying to Heroku or another platform that uses PostgreSQL use the ILIKE syntax instead.

def self.search(search)
  where("name LIKE ? OR ingredients LIKE ? OR cooking_instructions LIKE ?", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%") 
end

And in the Recipes Controller, app/controllers/recipes_controller.rb, I will use the following code to display matching recipes in descending order from the time they were created.

def index
  @recipes = Recipe.all
  if params[:search]
    @recipes = Recipe.search(params[:search]).order("created_at DESC")
  else
    @recipes = Recipe.all.order("created_at DESC")
  end
end

One last thing—if there aren't any recipes that match the search query I want to display a warning on the index page. In /app/views/recipes/index.html.erb I put the following code to alert the user that there aren't any matching recipes. If I skip this step, a user might think something went wrong with the search. Again, it's not a critical function of the search but it makes the user experience a little bit nicer.

<% if @recipes.blank? %>
  <h4>There are no recipes containing the term <%= params[:search] %>.</h4>
<% end %>

18 Dec

Automating Jobs with Rails Rake Tasks and Heroku Scheduler

I recently implemented Heroku Scheduler and Rails Rake tasks to automatically make API calls and update data in a side project I have called Power Pol. Rails and Heroku make it really easy to setup and deploy this functionality in minutes.

Install Heroku Scheduler

The first step is to install Heroku Scheduler by cd-ing into my app and typing this command in terminal: heroku addons:create scheduler:standard.

Create a Custom Rake Task

Next, I need to create the custom Rake tasks and I do this in lib/tasks/scheduler.rake. Originally created to manage software build processes, Rake enables me to define a set of tasks and their dependencies, and it automatically runs custom code when a given task is called. Each task consists of three things:

  • A description
  • A task name, represented by a Ruby symbol, that identifies the task
  • The code the task will execute in a Ruby do...end block
desc "This task calls the NYT API"
task :nyt_api_votes => :environment do
  politicians = Politician.all
  request = Unirest.get("http://api.nytimes.com/svc/politics/v3/us/legislative/congress/114/senate/members/current.json?api-key=#{Figaro.env.nyt_api_key}").body
    request["results"][0]["members"].each do |dw|
        politician = Politician.find_by(:bio_guide => dw["id"])
        if politician
        politician.update(:votes_with_party_pct => dw["votes_with_party_pct"], :missed_votes_pct => dw["missed_votes_pct"])
        end
    end
end

Every time I call the :nyt_api_votes task, the code in the block sends a request to the New York Times Congress API, updates the data for the politicians in my app, and saves it to the database.

Test Locally Before Deployment

I can test my Rake task locally before deploying it to Heroku, and this is as simple as firing up http://localhost:3000 and running heroku run rake nyt_api_votes from the command line. Heroku scheduler uses the same one-off dynos that Heroku run uses to execute jobs, so if my task works with with Heroku run, it will work with the scheduler.

Schedule Jobs in Heroku

Scheduling a job is as easy as going to https://scheduler.heroku.com/dashboard, clicking “Add a Job", and providing the name of my task rake nyt_api_votes. I also need to select a dyno size (for a simple app like mine 1X is fine), choose the frequency at which I want the task to run (daily, hourly, or every 10 minutes), and at what time I want the first task to execute.

With these simple steps, the politicians' data in my app will update automatically, eliminating the hassle of manually calling the API from Rails console.

Additional Info

The code block in the Rake task uses two gems I highly recommend: Unirest for making API calls and Figaro for handling sensitive information (like API keys and passwords) as environment variables.