FixedRecord

FixedRecord provides ActiveRecord-like read-only access to a set of records described in a YAML file.

Why is this useful? Occasionally you have tabular data which hardly ever changes and can easily be edited by hand. Although this data could be placed in a database, it may not be worth the overhead involved (loading a database, maintaining database code, etc.).

It may be quicker and simpler to implement this as an array or hash of objects in a YAML file, and use this gem to provide access to the data.

See the Usage section below.

Installation

Add this line to your application's Gemfile:

gem 'fixed_record'

And then execute:

$ bundle

Or install it yourself as:

$ gem install fixed_record

Usage

Array of Records

Create a YAML file defining an array of records like this:

-
    name: Risky Thinking
    url: https://www.riskythinking.com/
-
    name: Krebs on Security
    url: https://krebsonsecurity.com/

Then to load these, create a class

require 'fixed_record'

class MyFavoriteWebsite < FixedRecord
    data "#{Rails.root}/data/my_favorite_websites.yml", required: [:name, :url], optional: [:title]

  # Return hostname of url for company
  def hostname
    URI.parse(url).host
  end

end

The collection can then be enumerated as required:

MyFavoriteWebsite.each do |w| 
    puts w.name
    puts w.hostname
end

Or can be accessed as an array:

MyFavoriteWebsite.all.is_a?(Array)  # true

A count of the number of records is available:

puts MyFavoriteWebsite.count

As is the filename and the set of valid field names:

puts MyFavoriteWebsite.filename # .../my_favorite_websites.yml
puts MyFavoriteWebsite.valid_keys.include?( :name )  # true

Value presence

It's possible to check whether a value is supplied for an optional field for an item:

puts MyFavoriteWebsite.all.first.present?(:title) # true

This makes it possible to distinguish between an optional value being omitted and explicitly being given the value nil.

The declared class will also include all the methods from the Enumerable module.

Hash of Records

Create a YAML file my_web_pages.yml defining a hash of records like this:

StaticPage#first:
  title: First Page
  description: Welcome to the First Page

StaticPage#last:
  title: Last Page
  short_title: LastP 
  description: Welcome to the Last Page

Then to load these, create a class

require 'fixed_record'

class MyWebPages < FixedRecord
    data "#{Rails.root}/data/my_web_pages.yml", required: [:title,:description], optional: [:short_title]

end

The collection can be accessed by index:

MyWebPages['StaticPage#first'].title # First Page
MyWebPages['StaticPage#last'].description # Welcome to he Last page
MyWebPages['StaticPage#first'].key # StaticPage#fifst

The collection can then be enumerated as required:

MyWebPages.each do |k,v| 
    puts k
    puts v.title
end

Or can be accessed as an hash:

MyWebPages.all.is_a?(Hash)  # true

A count of the number of records is available:

puts MyWebPages.count

The declared class will also include all the methods from the Enumerable module.

Singleton Record

Create a YAML file site_settings.yml defining a single records like this:


sessionExpiry: 3600
phone: +1.613.555.1212
address: 500 Main Street, Anytown, Antartica

Then to load these, create a class

require 'fixed_record'

class SiteSettings < FixedRecord
    data "#{Rails.root}/data/site_settings.yml", singleton: true

end

The values can be accessed by index:

SiteSettings['phone'] # '+1.613.555.1212'
SiteSettings['address'] # '500 Main Street, Anytown, Antartica'
SiteSettings[:phone] # This works too

The required and optional arguments can be used to check for inadvertent errors being introduced into the YAML file. An ArgumentError will be raised if the name is not defined in the file and is not declared as optional.

Error Checking

Some basic sanity checks are performed on the YAML file to catch common errors:

  • It must define a non-empty array or hash of records
  • If the optional required: or optional: arguments are given, then each record must have all the required fields and may also have any of the optional fields.
  • If niether the required: or optional: arguments are given, then each record must have the same set of fields.

An ArgumentError exception will be thrown if any validation errors are detected. A system-dependent error (probably Errno::ENOENT) will be thrown if the file cannot be read.

Additional validations can be performed by defining a validate method. e.g.

require 'fixed_record'
require 'uri'

class MyFavoriteWebsite < FixedRecord
    data "#{Rails.root}/data/my_favorite_websites.yml", required: [:name, :url]

  # Check that the url can be parsed
  def validate( values, index )
    begin
      URI.parse(url)
    rescue URI::InvalidURIError -> e
      raise e.class, "#{filename} index #{index} has invalid url: #{e.message}"
    end
  end

end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rspec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in fixed_record.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/m-z-b/fixed_record.