CrackedRuby CrackedRuby

Overview

The Open Graph Protocol defines a standard for web pages to represent themselves as rich objects in social graphs. Created by Facebook in 2010, the protocol extends HTML meta tags to provide structured metadata that social media platforms, messaging applications, and web crawlers use to generate previews of shared links.

When a URL is shared on platforms like Facebook, Twitter, LinkedIn, or messaging applications, these services fetch the page's HTML and parse Open Graph tags to extract the title, description, image, and other metadata. This metadata controls how the shared link appears in feeds, messages, and previews, replacing default browser behaviors that might show generic or poorly formatted information.

The protocol addresses a fundamental problem in web sharing: without standardized metadata, social platforms must guess at appropriate preview content, often extracting the first image found on a page, using the HTML title tag, or showing no preview at all. Open Graph tags provide explicit instructions, giving content creators control over their social media presentation.

<meta property="og:title" content="Ruby Performance Optimization Guide" />
<meta property="og:description" content="Advanced techniques for optimizing Ruby application performance" />
<meta property="og:image" content="https://example.com/ruby-guide.jpg" />
<meta property="og:url" content="https://example.com/ruby-guide" />

The protocol has become ubiquitous across the web, implemented by content management systems, web frameworks, and static site generators. Social media platforms beyond Facebook have adopted or extended the protocol, making it a de facto standard for social sharing metadata.

Key Principles

Open Graph operates through meta tags placed in the HTML head section. Each tag uses the property attribute with an og: namespace prefix to distinguish Open Graph tags from standard HTML meta tags. The content attribute contains the actual metadata value.

The protocol defines four required properties that every Open Graph-enabled page should include:

og:title specifies the human-readable title as it should appear in social shares. This differs from the HTML title tag in that it's optimized for social context rather than browser tabs or search results. The title should be concise and descriptive without site names or additional branding.

og:type categorizes the content using a vocabulary of predefined types. The most common type is website for general pages, but the protocol defines specialized types like article, video.movie, music.song, and book that unlock additional type-specific properties.

og:image provides the URL of an image to represent the content. Social platforms download this image and display it in link previews. The image should have specific dimensions and aspect ratios for optimal display across different platforms.

og:url defines the canonical URL for the content. This URL becomes the permanent identifier in the social graph, and social platforms attribute all shares and interactions to this URL regardless of query parameters or tracking codes in the actual shared URL.

<head>
  <meta property="og:title" content="Understanding Database Indexing" />
  <meta property="og:type" content="article" />
  <meta property="og:image" content="https://example.com/images/db-index.png" />
  <meta property="og:url" content="https://example.com/articles/database-indexing" />
</head>

Beyond required properties, the protocol defines numerous optional properties. The og:description property provides a brief summary of the content, typically 2-4 sentences. The og:site_name identifies the larger site or brand, appearing separately from the content title.

Structured properties use dot notation to express relationships. An og:image property can have related properties like og:image:width, og:image:height, and og:image:alt that provide additional details about the image. Array properties allow multiple values by repeating the same property name with different content values.

<meta property="og:image" content="https://example.com/image1.jpg" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image" content="https://example.com/image2.jpg" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />

Type-specific properties extend the base protocol for particular content types. The article type adds properties like article:published_time, article:author, article:section, and article:tag. Video content types include properties for video URLs, dimensions, and MIME types. Music types define properties for duration, album, and artist information.

The protocol specifies how platforms should handle missing or invalid properties. If required properties are missing, platforms fall back to HTML meta tags or heuristic content extraction. Invalid image URLs or dimensions result in platforms attempting to find alternative images on the page.

Ruby Implementation

Ruby web applications implement Open Graph tags through view templates that generate the appropriate meta tags. Rails applications typically use helper methods in layouts or partials to generate these tags dynamically based on controller instance variables or model data.

# app/helpers/open_graph_helper.rb
module OpenGraphHelper
  def og_tags(title:, description:, image:, url:, type: 'website')
    content_tag(:meta, nil, property: 'og:title', content: title) +
    content_tag(:meta, nil, property: 'og:description', content: description) +
    content_tag(:meta, nil, property: 'og:image', content: image) +
    content_tag(:meta, nil, property: 'og:url', content: url) +
    content_tag(:meta, nil, property: 'og:type', content: type)
  end
end

The helper method generates the required Open Graph meta tags. The view template calls this helper with content-specific data, typically derived from an Active Record model or presenter object.

<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
  <title><%= content_for?(:title) ? yield(:title) : 'Default Title' %></title>
  <%= yield(:og_tags) if content_for?(:og_tags) %>
</head>
<body>
  <%= yield %>
</body>
</html>
<!-- app/views/articles/show.html.erb -->
<% content_for :og_tags do %>
  <%= og_tags(
    title: @article.title,
    description: @article.summary,
    image: url_for(@article.cover_image),
    url: article_url(@article),
    type: 'article'
  ) %>
<% end %>

Rails provides the content_for mechanism to inject content into layout sections. This pattern keeps Open Graph logic in views where it belongs while maintaining layout flexibility.

For more sophisticated implementations, a dedicated decorator or presenter object encapsulates Open Graph metadata generation:

# app/presenters/article_og_presenter.rb
class ArticleOgPresenter
  def initialize(article, view_context)
    @article = article
    @view = view_context
  end

  def tags
    {
      'og:title' => @article.title,
      'og:description' => truncated_description,
      'og:image' => image_url,
      'og:image:width' => '1200',
      'og:image:height' => '630',
      'og:url' => canonical_url,
      'og:type' => 'article',
      'og:site_name' => 'TechBlog',
      'article:published_time' => @article.published_at.iso8601,
      'article:author' => @article.author.name,
      'article:section' => @article.category.name
    }
  end

  private

  def truncated_description
    @article.summary.truncate(200, separator: ' ')
  end

  def image_url
    @view.url_for(@article.cover_image.variant(resize_to_limit: [1200, 630]))
  end

  def canonical_url
    @view.article_url(@article, host: @view.request.host)
  end
end

The presenter centralizes all Open Graph logic for articles, handling text truncation, image transformation, and URL generation. The controller instantiates the presenter and makes it available to the view:

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def show
    @article = Article.find(params[:id])
    @og_presenter = ArticleOgPresenter.new(@article, view_context)
  end
end

Several Ruby gems provide Open Graph functionality. The meta-tags gem offers a DSL for managing all types of meta tags including Open Graph:

# Gemfile
gem 'meta-tags'

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def show
    @article = Article.find(params[:id])
    
    set_meta_tags(
      title: @article.title,
      description: @article.summary,
      og: {
        title: @article.title,
        description: @article.summary,
        type: 'article',
        url: article_url(@article),
        image: url_for(@article.cover_image)
      }
    )
  end
end

The gem handles tag rendering, duplicate prevention, and proper escaping. The layout includes a single call to render all configured meta tags:

<!-- app/views/layouts/application.html.erb -->
<head>
  <%= display_meta_tags site: 'TechBlog' %>
</head>

For API-based applications or single-page applications, server-side rendering or pre-rendering ensures social crawlers receive Open Graph tags. Ruby frameworks like Sinatra serve pre-rendered HTML for crawler user agents:

# app.rb
require 'sinatra'
require 'nokogiri'

get '/articles/:id' do
  article = Article.find(params[:id])
  
  if crawler_request?
    render_with_og_tags(article)
  else
    send_file 'public/index.html'
  end
end

def crawler_request?
  user_agent = request.user_agent.to_s.downcase
  user_agent.include?('facebookexternalhit') ||
    user_agent.include?('twitterbot') ||
    user_agent.include?('linkedinbot')
end

def render_with_og_tags(article)
  template = File.read('public/index.html')
  doc = Nokogiri::HTML(template)
  head = doc.at_css('head')
  
  og_tags = [
    ['og:title', article.title],
    ['og:description', article.summary],
    ['og:image', "https://example.com#{article.image_url}"],
    ['og:url', "https://example.com/articles/#{article.id}"]
  ]
  
  og_tags.each do |property, content|
    meta = Nokogiri::XML::Node.new('meta', doc)
    meta['property'] = property
    meta['content'] = content
    head.add_child(meta)
  end
  
  doc.to_html
end

Practical Examples

Article pages require comprehensive Open Graph implementation to control how content appears across social platforms. The implementation includes all article-specific properties:

# app/models/article.rb
class Article < ApplicationRecord
  belongs_to :author
  belongs_to :category
  has_one_attached :cover_image

  def og_metadata
    {
      type: 'article',
      title: title,
      description: sanitized_summary,
      url: canonical_url,
      image: cover_image_url,
      image_width: 1200,
      image_height: 630,
      site_name: 'TechInsights',
      article: {
        published_time: published_at.iso8601,
        modified_time: updated_at.iso8601,
        author: author.profile_url,
        section: category.name,
        tag: tags.pluck(:name)
      }
    }
  end

  private

  def sanitized_summary
    ActionView::Base.full_sanitizer.sanitize(summary).truncate(200)
  end

  def canonical_url
    Rails.application.routes.url_helpers.article_url(
      self,
      host: ENV['CANONICAL_HOST']
    )
  end

  def cover_image_url
    return default_image_url unless cover_image.attached?
    
    Rails.application.routes.url_helpers.url_for(
      cover_image.variant(resize_to_fill: [1200, 630])
    )
  end

  def default_image_url
    "#{ENV['CANONICAL_HOST']}/images/default-og.jpg"
  end
end

E-commerce product pages need product-specific Open Graph properties to display pricing, availability, and multiple product images:

# app/models/product.rb
class Product < ApplicationRecord
  has_many_attached :images

  def og_metadata
    base_tags = {
      type: 'product',
      title: "#{name} - #{brand}",
      description: short_description,
      url: product_url,
      image: primary_image_urls,
      site_name: 'TechStore'
    }

    product_tags = {
      'product:price:amount' => price.to_f,
      'product:price:currency' => 'USD',
      'product:availability' => availability_status,
      'product:condition' => 'new',
      'product:retailer_item_id' => sku
    }

    base_tags.merge(product_tags)
  end

  private

  def primary_image_urls
    images.first(4).map do |image|
      Rails.application.routes.url_helpers.url_for(
        image.variant(resize_to_fill: [1200, 630])
      )
    end
  end

  def availability_status
    return 'out of stock' if stock_quantity.zero?
    return 'preorder' if available_at > Time.current
    'in stock'
  end

  def short_description
    ActionView::Base.full_sanitizer.sanitize(description).truncate(150)
  end

  def product_url
    Rails.application.routes.url_helpers.product_url(
      self,
      host: ENV['CANONICAL_HOST']
    )
  end
end

Video content requires video-specific properties including video file URLs, dimensions, and thumbnails:

# app/models/video.rb
class Video < ApplicationRecord
  has_one_attached :video_file
  has_one_attached :thumbnail

  def og_metadata
    {
      type: 'video.other',
      title: title,
      description: description.truncate(200),
      url: video_page_url,
      image: thumbnail_url,
      video: video_metadata,
      site_name: 'VideoHub'
    }
  end

  private

  def video_metadata
    {
      url: video_file_url,
      secure_url: video_file_url,
      type: video_file.content_type,
      width: video_width,
      height: video_height,
      duration: duration_seconds
    }
  end

  def video_file_url
    Rails.application.routes.url_helpers.url_for(video_file)
  end

  def thumbnail_url
    return default_thumbnail unless thumbnail.attached?
    
    Rails.application.routes.url_helpers.url_for(
      thumbnail.variant(resize_to_fill: [1200, 630])
    )
  end

  def video_page_url
    Rails.application.routes.url_helpers.video_url(
      self,
      host: ENV['CANONICAL_HOST']
    )
  end

  def default_thumbnail
    "#{ENV['CANONICAL_HOST']}/images/video-default.jpg"
  end
end

Dynamic locale-based content requires alternate locale tags to help platforms serve content in the appropriate language:

# app/helpers/localized_og_helper.rb
module LocalizedOgHelper
  def localized_og_tags(localizable)
    base_tags = og_tags_for_current_locale(localizable)
    alternate_tags = alternate_locale_tags(localizable)
    
    base_tags + alternate_tags
  end

  private

  def og_tags_for_current_locale(localizable)
    [
      tag.meta(property: 'og:locale', content: current_locale_code),
      tag.meta(property: 'og:title', content: localizable.title),
      tag.meta(property: 'og:description', content: localizable.description)
    ]
  end

  def alternate_locale_tags(localizable)
    available_locales.map do |locale|
      next if locale == I18n.locale
      
      tag.meta(
        property: 'og:locale:alternate',
        content: locale_code(locale)
      )
    end.compact
  end

  def current_locale_code
    locale_code(I18n.locale)
  end

  def locale_code(locale)
    {
      en: 'en_US',
      es: 'es_ES',
      fr: 'fr_FR',
      de: 'de_DE'
    }[locale]
  end

  def available_locales
    I18n.available_locales
  end
end

Common Patterns

The default fallback pattern ensures pages always have valid Open Graph tags even when specific content metadata is unavailable. This pattern defines site-wide defaults that apply unless overridden:

# app/helpers/application_helper.rb
module ApplicationHelper
  def default_og_tags
    {
      'og:title' => content_for?(:title) ? content_for(:title) : 'TechBlog',
      'og:description' => 'In-depth technical articles and tutorials',
      'og:image' => "#{root_url}images/default-og-image.jpg",
      'og:url' => request.original_url,
      'og:type' => 'website',
      'og:site_name' => 'TechBlog'
    }
  end

  def render_og_tags(custom_tags = {})
    tags = default_og_tags.merge(custom_tags)
    
    tags.map do |property, content|
      tag.meta(property: property, content: content)
    end.join.html_safe
  end
end

The content-type dispatch pattern routes different content types to specialized Open Graph generators:

# app/services/og_tag_generator.rb
class OgTagGenerator
  def self.for(object)
    case object
    when Article
      ArticleOgGenerator.new(object)
    when Product
      ProductOgGenerator.new(object)
    when Video
      VideoOgGenerator.new(object)
    when User
      ProfileOgGenerator.new(object)
    else
      DefaultOgGenerator.new(object)
    end
  end
end

class ArticleOgGenerator
  def initialize(article)
    @article = article
  end

  def generate
    {
      'og:type' => 'article',
      'og:title' => @article.title,
      'og:description' => @article.summary,
      'og:image' => @article.cover_image_url,
      'og:url' => @article.canonical_url,
      'article:published_time' => @article.published_at.iso8601,
      'article:author' => @article.author.name
    }
  end
end

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  helper_method :og_tags_for

  def og_tags_for(object)
    OgTagGenerator.for(object).generate
  end
end

The image optimization pattern ensures images meet platform requirements for dimensions, file size, and format:

# app/models/concerns/og_image_processor.rb
module OgImageProcessor
  extend ActiveSupport::Concern

  OG_IMAGE_WIDTH = 1200
  OG_IMAGE_HEIGHT = 630
  OG_IMAGE_ASPECT_RATIO = OG_IMAGE_WIDTH.to_f / OG_IMAGE_HEIGHT

  def og_image_url
    return default_og_image unless main_image.attached?

    if main_image.metadata[:width] && main_image.metadata[:height]
      process_existing_image
    else
      analyze_and_process_image
    end
  end

  private

  def process_existing_image
    width = main_image.metadata[:width]
    height = main_image.metadata[:height]
    aspect_ratio = width.to_f / height

    if aspect_ratio > OG_IMAGE_ASPECT_RATIO
      # Image is wider than target, crop width
      url_for(main_image.variant(
        resize_to_fill: [OG_IMAGE_WIDTH, OG_IMAGE_HEIGHT, crop: :centre]
      ))
    else
      # Image is taller than target, crop height
      url_for(main_image.variant(
        resize_to_fit: [OG_IMAGE_WIDTH, OG_IMAGE_HEIGHT]
      ))
    end
  end

  def analyze_and_process_image
    main_image.analyze unless main_image.analyzed?
    process_existing_image
  end

  def default_og_image
    "#{ENV['CANONICAL_HOST']}/images/og-default.jpg"
  end
end

The validation pattern checks Open Graph tags before page rendering to catch missing or malformed metadata:

# app/validators/og_metadata_validator.rb
class OgMetadataValidator
  REQUIRED_PROPERTIES = %w[og:title og:type og:image og:url].freeze
  
  def initialize(metadata)
    @metadata = metadata
    @errors = []
  end

  def valid?
    validate_required_properties
    validate_image_url
    validate_url_format
    validate_description_length
    
    @errors.empty?
  end

  def errors
    @errors
  end

  private

  def validate_required_properties
    REQUIRED_PROPERTIES.each do |property|
      unless @metadata[property].present?
        @errors << "Missing required property: #{property}"
      end
    end
  end

  def validate_image_url
    image_url = @metadata['og:image']
    return unless image_url.present?

    unless image_url.start_with?('http://', 'https://')
      @errors << "Image URL must be absolute: #{image_url}"
    end
  end

  def validate_url_format
    url = @metadata['og:url']
    return unless url.present?

    uri = URI.parse(url)
    unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
      @errors << "URL must use HTTP or HTTPS protocol: #{url}"
    end
  rescue URI::InvalidURIError
    @errors << "Invalid URL format: #{url}"
  end

  def validate_description_length
    description = @metadata['og:description']
    return unless description.present?

    if description.length > 300
      @errors << "Description exceeds recommended length of 300 characters"
    end
  end
end

# Usage in controller or service
metadata = article.og_metadata
validator = OgMetadataValidator.new(metadata)

unless validator.valid?
  Rails.logger.warn "OG metadata validation failed: #{validator.errors.join(', ')}"
end

The caching pattern stores generated Open Graph HTML fragments to reduce processing overhead:

# app/models/article.rb
class Article < ApplicationRecord
  after_save :clear_og_cache

  def cached_og_tags
    Rails.cache.fetch(['og_tags', cache_key_with_version]) do
      generate_og_tags
    end
  end

  private

  def generate_og_tags
    metadata = og_metadata
    
    metadata.map do |property, content|
      case content
      when Array
        content.map { |c| meta_tag(property, c) }.join
      when Hash
        content.map { |key, value| meta_tag("#{property}:#{key}", value) }.join
      else
        meta_tag(property, content)
      end
    end.join
  end

  def meta_tag(property, content)
    "<meta property=\"#{property}\" content=\"#{ERB::Util.html_escape(content)}\" />\n"
  end

  def clear_og_cache
    Rails.cache.delete(['og_tags', cache_key_with_version])
  end
end

Security Implications

Open Graph tags can expose applications to cross-site scripting attacks when user-generated content appears in tag values without proper sanitization. An attacker might inject malicious JavaScript through article titles, descriptions, or other fields that populate Open Graph tags.

# Vulnerable implementation
def og_tags
  "<meta property='og:title' content='#{@article.title}' />"
end

# If @article.title contains: Hello</title><script>alert('XSS')</script>
# The generated HTML becomes:
# <meta property='og:title' content='Hello</title><script>alert('XSS')</script>' />

Rails automatically escapes content in ERB templates, but manual string concatenation bypasses this protection. Always use Rails helpers or explicit escaping:

# Secure implementation
def og_tags
  tag.meta(property: 'og:title', content: @article.title)
end

# Or with explicit escaping
def og_tags
  "<meta property='og:title' content='#{ERB::Util.html_escape(@article.title)}' />".html_safe
end

Content injection in Open Graph descriptions poses particular risk because descriptions often display user-generated content. Attackers can inject HTML entities, script tags, or event handlers:

# app/models/article.rb
class Article < ApplicationRecord
  def safe_og_description
    # Remove all HTML tags
    sanitized = ActionView::Base.full_sanitizer.sanitize(description)
    
    # Truncate to reasonable length
    truncated = sanitized.truncate(200, separator: ' ')
    
    # Encode HTML entities
    ERB::Util.html_escape(truncated)
  end
end

Image URLs in Open Graph tags require validation to prevent attackers from specifying malicious URLs or exposing internal resources:

# app/validators/og_image_validator.rb
class OgImageValidator
  ALLOWED_SCHEMES = %w[http https].freeze
  BLOCKED_HOSTS = %w[localhost 127.0.0.1 169.254.169.254].freeze

  def self.valid?(url)
    return false if url.blank?

    uri = URI.parse(url)
    
    # Check protocol
    return false unless ALLOWED_SCHEMES.include?(uri.scheme)
    
    # Block internal/private IPs
    return false if BLOCKED_HOSTS.include?(uri.host)
    return false if uri.host =~ /^10\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^192\.168\./
    
    true
  rescue URI::InvalidURIError
    false
  end
end

# Usage
def og_image_url
  url = @article.cover_image_url
  OgImageValidator.valid?(url) ? url : default_image_url
end

Server-side request forgery vulnerabilities can occur when applications fetch remote images to validate or process them for Open Graph tags:

# Vulnerable: Fetches any URL provided
def validate_image_dimensions(url)
  response = Net::HTTP.get_response(URI(url))
  # Process image...
end

# Secure: Validates URL before fetching
def validate_image_dimensions(url)
  return false unless OgImageValidator.valid?(url)
  
  uri = URI(url)
  
  # Use explicit timeout
  response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
                             open_timeout: 5, read_timeout: 5) do |http|
    http.get(uri.path)
  end
  
  return false unless response.is_a?(Net::HTTPSuccess)
  
  # Validate content type
  content_type = response['content-type']
  return false unless content_type.start_with?('image/')
  
  # Process image
  process_image(response.body)
rescue StandardError => e
  Rails.logger.error "Image validation failed: #{e.message}"
  false
end

Information disclosure through Open Graph tags can reveal sensitive data when tags expose unpublished content, private information, or internal identifiers:

# app/models/article.rb
class Article < ApplicationRecord
  def og_metadata
    return nil unless published?
    return nil if private? && !accessible_to?(current_user)
    
    {
      'og:title' => title,
      'og:description' => public_summary, # Not full content
      'og:url' => public_url,              # Not admin URL
      'og:image' => public_image_url       # Not internal path
    }
  end

  private

  def public_summary
    # Return sanitized, truncated summary
    # Never return full content or private notes
    safe_og_description
  end

  def public_url
    # Return canonical public URL
    # Never return URLs with tokens or session identifiers
    Rails.application.routes.url_helpers.article_url(
      self,
      host: ENV['PUBLIC_HOST'],
      protocol: 'https'
    )
  end

  def public_image_url
    # Return CDN URL, not application URL
    # Prevents exposure of file storage structure
    cover_image.cdn_url
  end
end

Content Security Policy headers can conflict with Open Graph meta tags when policies restrict image sources or scripts. Configure CSP to allow Open Graph functionality:

# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
  policy.default_src :self
  
  # Allow Open Graph images from CDN
  policy.img_src :self, 'https://cdn.example.com', 'data:'
  
  # Allow social media platform scrapers (they don't execute scripts)
  # But maintain strict CSP for browsers
  policy.script_src :self
end

# Don't apply CSP to social crawler requests
Rails.application.config.content_security_policy_nonce_generator = 
  ->(request) { request.user_agent&.match?(/bot|crawler/i) ? nil : SecureRandom.base64(16) }

Tools & Ecosystem

The meta-tags gem provides comprehensive meta tag management including Open Graph, Twitter Cards, and standard HTML meta tags:

# Gemfile
gem 'meta-tags'

# config/initializers/meta_tags.rb
MetaTags.configure do |config|
  config.title_limit = 70
  config.description_limit = 300
  config.keywords_limit = 255
  config.keywords_separator = ', '
end

# app/controllers/articles_controller.rb
def show
  @article = Article.find(params[:id])
  
  set_meta_tags(
    title: @article.title,
    description: @article.summary,
    keywords: @article.tags.pluck(:name),
    og: {
      title: @article.title,
      type: 'article',
      url: article_url(@article),
      image: @article.og_image_url,
      description: @article.summary,
      site_name: 'TechBlog'
    },
    article: {
      published_time: @article.published_at.iso8601,
      author: @article.author.name
    }
  )
end

Facebook's Sharing Debugger validates Open Graph implementation and shows how URLs appear when shared. The tool clears Facebook's cache and refetches metadata:

# app/services/fb_debug_service.rb
class FbDebugService
  GRAPH_API_URL = 'https://graph.facebook.com/v18.0/'
  
  def initialize(access_token)
    @access_token = access_token
  end

  def debug_url(url)
    response = HTTParty.post(
      "#{GRAPH_API_URL}",
      body: {
        id: url,
        scrape: true,
        access_token: @access_token
      }
    )
    
    JSON.parse(response.body)
  end

  def clear_cache(url)
    response = HTTParty.post(
      "#{GRAPH_API_URL}",
      body: {
        id: url,
        scrape: true,
        access_token: @access_token
      }
    )
    
    response.success?
  end
end

The LinkedIn Post Inspector examines how content appears on LinkedIn and validates Open Graph tags:

# Rake task to validate OG tags before deployment
# lib/tasks/og_validation.rake
namespace :og do
  desc 'Validate Open Graph tags for published articles'
  task validate: :environment do
    articles = Article.published.limit(10)
    
    articles.each do |article|
      url = Rails.application.routes.url_helpers.article_url(
        article,
        host: ENV['PRODUCTION_HOST']
      )
      
      puts "Validating: #{url}"
      
      validator = OgMetadataValidator.new(article.og_metadata)
      if validator.valid?
        puts "  ✓ Valid"
      else
        puts "  ✗ Errors:"
        validator.errors.each { |error| puts "    - #{error}" }
      end
    end
  end
end

The rack-og-preview middleware serves preview pages showing how Open Graph tags will appear:

# lib/middleware/og_preview.rb
class OgPreview
  def initialize(app)
    @app = app
  end

  def call(env)
    request = Rack::Request.new(env)
    
    if request.path == '/og-preview'
      return preview_response(request.params['url'])
    end
    
    @app.call(env)
  end

  private

  def preview_response(url)
    return [400, {}, ['Missing url parameter']] unless url
    
    html = fetch_og_tags(url)
    
    [
      200,
      { 'Content-Type' => 'text/html' },
      [preview_html(html)]
    ]
  end

  def fetch_og_tags(url)
    # Fetch and parse Open Graph tags from URL
    # Return formatted preview
  end

  def preview_html(og_html)
    # Generate preview HTML showing how tags appear
  end
end

# config/application.rb
config.middleware.use OgPreview

Automated testing tools validate Open Graph implementation across the site:

# spec/support/og_helpers.rb
module OgHelpers
  def og_tag(property)
    page.find("meta[property='#{property}']", visible: false)['content']
  rescue Capybara::ElementNotFound
    nil
  end

  def expect_valid_og_tags
    expect(og_tag('og:title')).to be_present
    expect(og_tag('og:type')).to be_present
    expect(og_tag('og:image')).to be_present
    expect(og_tag('og:url')).to be_present
  end
end

# spec/features/article_og_spec.rb
require 'rails_helper'

RSpec.describe 'Article Open Graph tags', type: :feature do
  let(:article) { create(:article, :published) }

  before do
    visit article_path(article)
  end

  it 'includes required Open Graph tags' do
    expect_valid_og_tags
  end

  it 'sets correct article type' do
    expect(og_tag('og:type')).to eq('article')
  end

  it 'includes article-specific properties' do
    expect(og_tag('article:published_time')).to be_present
    expect(og_tag('article:author')).to eq(article.author.name)
  end

  it 'uses optimized image dimensions' do
    image_url = og_tag('og:image')
    expect(image_url).to include('1200x630')
  end
end

Reference

Required Properties

Property Description Example
og:title Title of content as it should appear in social shares Understanding Ruby Metaprogramming
og:type Content type from OG vocabulary article
og:image URL of image representing the content https://example.com/image.jpg
og:url Canonical URL for the content https://example.com/article/123

Common Optional Properties

Property Description Recommended For
og:description Brief summary of content (2-4 sentences) All content types
og:site_name Name of the overall site All pages
og:locale Language and territory of content Internationalized sites
og:locale:alternate Available alternate locales Multi-language content

Image Properties

Property Description Recommended Value
og:image:url Same as og:image Required
og:image:secure_url HTTPS version of image URL HTTPS sites
og:image:type MIME type of image image/jpeg, image/png
og:image:width Width in pixels 1200
og:image:height Height in pixels 630
og:image:alt Accessible description of image Descriptive text

Content Types

Type Usage Additional Properties
website General pages and homepages None required
article Blog posts and articles article:published_time, article:author, article:section
profile Personal profiles profile:first_name, profile:last_name, profile:username
video.movie Movies and video content video:actor, video:director, video:duration
music.song Audio content music:duration, music:album, music:musician
book Books and publications book:author, book:isbn, book:release_date
product E-commerce products product:price:amount, product:availability

Article Properties

Property Description Format
article:published_time Publication timestamp ISO 8601
article:modified_time Last modification timestamp ISO 8601
article:expiration_time Content expiration timestamp ISO 8601
article:author Author profile URLs Array of URLs
article:section Content category or section String
article:tag Content tags Array of strings

Product Properties

Property Description Example
product:price:amount Product price 29.99
product:price:currency Currency code USD
product:availability Stock status in stock
product:condition Product condition new
product:retailer_item_id Internal product identifier SKU-12345

Image Dimension Guidelines

Platform Minimum Size Recommended Size Aspect Ratio
Facebook 200x200 1200x630 1.91:1
Twitter 144x144 1200x675 16:9 or 1:1
LinkedIn 180x110 1200x627 1.91:1
Pinterest 600x900 1000x1500 2:3

Rails Helper Method Reference

Method Purpose Returns
set_meta_tags Configure meta tags in controller Hash
display_meta_tags Render meta tags in view HTML string
content_for Define content sections Nil
yield Retrieve content sections String
tag.meta Generate meta tag HTML string
url_for Generate asset URLs String

Validation Checklist

Check Requirement Tools
Required tags present og:title, og:type, og:image, og:url Facebook Debugger
Image accessible Returns 200 status code curl, wget
Image dimensions Minimum 200x200, recommended 1200x630 Image inspector
URL format Absolute URLs with protocol URL validator
Content length Title under 70 chars, description under 300 Character counter
HTML escaping All content properly escaped View source
HTTPS protocol All URLs use HTTPS Protocol checker

Ruby Gem Comparison

Gem Features Maintenance Status
meta-tags Comprehensive meta tag DSL, SEO helpers Active
social-share-button Social sharing with OG support Active
twitter-cards Twitter Card implementation Deprecated
rails-seo SEO and social meta tags Active

Common User Agent Strings

Platform User Agent Pattern Purpose
Facebook facebookexternalhit Crawling shared links
Twitter Twitterbot Card validation
LinkedIn LinkedInBot Preview generation
Slack Slackbot-LinkExpanding Link unfurling
WhatsApp WhatsApp Link previews