Programmatic content
Generate content programmatically from data sources instead of creating files manually. Define a template once and Perron creates resources for every combination of the data. Perfect for to pull data from an API or for programmatic SEO where similar pages with different data are needed.
Basic Usage
First create data resources:
// app/content/data/countries.json
[
{"id": "de", "name": "Germany"},
{"id": "nl", "name": "The Netherlands"}
]
# app/content/data/products.csv
id,name,price
1,iPhone,999
2,iPad,799
Then configure in the content resource class:
# app/models/content/product.rb
class Content::Product < Perron::Resource
sources :countries, :products
def self.source_template(source)
<<~TEMPLATE
---
title: #{source.products.name} in #{source.countries.name}
country_id: #{source.countries.id}
product_id: #{source.products.id}
---
# #{source.products.name}
Available in #{source.countries.name} for $#{source.products.price}.
TEMPLATE
end
end
Use the generator
bin/rails generate content Product --data countries.json products.csv
Generate resources:
bin/rails perron:sync_sources
This creates four files in app/content/products/:
de-1.erbnl-1.erbde-2.erbnl-2.erb
Each file is processed like any regular resource with full access to layouts, helpers and routing.
Custom primary keys
By default, Perron uses id to identify records. Use the primary_key option to specify a different column:
class Content::Product < Perron::Resource
sources :countries, products: { primary_key: :code }
def self.source_template(source)
<<~TEMPLATE
---
title: #{source.products.name}
country_id: #{source.countries.id}
product_code: #{source.products.code}
---
TEMPLATE
end
end
Filenames use the specified primary keys: us-iphone-15.erb
Lambda filtering
Filter data sources using lambda expressions:
class Content::Product < Perron::Resource
sources, :countries,
products: -> (products) { products.select(&:featured?) }
def self.source_template(source)
<<~TEMPLATE
---
title: #{source.products.name} in #{source.countries.name}
product_id: #{source.products.id}
---
TEMPLATE
end
end
Single source
Use source (singular) for a single data source:
class Content::City < Perron::Resource
source :cities
def self.source_template(source)
<<~TEMPLATE
---
title: #{source.cities.name}
city_id: #{source.cities.id}
---
TEMPLATE
end
end
Syncing
Sync all source-backed resources:
bin/rails perron:sync_sources
Sync a specific resource:
bin/rails perron:sync_sources[products]
In zsh, quote the task name:
bin/rails "perron:sync_sources[products]"
Run the sync task whenever data changes to regenerate affected resources.
API integration with custom classes
Use the class option to pull data from external APIs:
# app/models/content/project.rb
class Content::Project < Perron::Resource
source repos: {
class: GitHubRepo,
primary_key: :name,
scope: -> (repos) { repos.select { it.language == "Ruby" } }
}
def self.source_template(source)
<<~TEMPLATE
---
title: #{source.repos.name}
description: #{source.repos.description}
language: #{source.repos.language}
stars: #{source.repos.stargazers_count}
repo_name: #{source.repos.name}
---
# #{source.repos.name}
#{source.repos.description}
**Language:** #{source.repos.language}
**Stars:** #{source.repos.stargazers_count}
**URL:** #{source.repos.html_url}
TEMPLATE
end
end
# app/models/git_hub_repo.rb
class GitHubRepo < ActiveResource::Base
self.site = "https://api.github.com/"
def self.all
find(:all, from: "/users/Rails-Designer/repos")
end
end
This will generate individual project pages for each repository (tagged “Ruby”) with live GitHub data.
Custom class requirements
Classes used with the class option must:
- implement an
.allmethod that returns an enumerable collection - return objects that respond to the specified primary_key method
Use cases for API integration
Ideas for pulling from APIs are infinite. Here are some ideas:
Content Generation:
- product catalogs; pull from Shopify, WooCommerce APIs
- real estate listings; generate property pages from MLS data
- job boards; create job posting pages from recruitment APIs
- event listings; pull from Eventbrite, Meetup APIs
Programmatic SEO:
- location pages; “Service in [City]”
- comparison pages; “[Product] vs [Competitor]”
- industry pages; “[Tool] for [Industry]”
On this page