StaticModel

Read-only ActiveRecord-like interface to query static YAML files.

Installation

Add this line to your application's Gemfile:

gem "staticmodel"

And then execute:

$ bundle

Or install it yourself as:

$ gem install staticmodel

Usage

StaticModel usage is pretty similar to ActiveRecord. Although the gem was designed with Rails in mind, it can be used in any Ruby project.

To start using StaticModel you need to:

  • Inherit from StaticModel::Base.
  • Define the source of your data.
  • Define your attributes and/or your default values.

Inherit from StaticModel::Base

Just like with ActiveRecord, your models need to inherit from StaticModel::Base.

require "staticmodel"

class Country < StaticModel::Base
end

Primary key

StaticModel assumes that each model can be identified by a primary key. A model instance’s primary key is always available as model.id whether you name it the default id or set it to something else.

It's possible to override the attribute that should be used as the model's primary key using the primary_key= method:

class Country < ActiveRecord::Base
  self.primary_key = "code"

  attribute :code, String
  attribute :name, String 
end

Country.all
# => [
#      #<Country code="ES", name="Spain">,
#      #<Country code="NL", name="Netherlands">
#    ]

Country.primary_key # => "code"
country = Country.find("ES") # => #<Country code="ES", name="Spain">
country.id # => "ES"
contry.name # => "Spain"

Or you can also override the primary_key method yourself:

class Country < ActiveRecord::Base
  def self.primary_key
    "code"
  end

  attribute :code, String
  attribute :name, String 
end

Country.primary_key # => "code"

Levels of inheritance

StaticModel also supports several levels of inheritance:

require "staticmodel"

class Country < StaticModel::Base
  self.primary_key = "code"

  attribute :code, String
  attribute :name, String 
end

class MyCountry < Country
end

class MyOtherCountry < Country
  self.primary_key = "my_other_code"
end

MyCountry.primary_key # => "code"
MyOtherCountry.primary_key # => "my_other_code"

Define the source of your data

By default, StaticModel uses a naming convention to find out how the mapping between models and YAML files should be created. It will pluralize your class names to find the respective YAML file. So, for a class Book, you should have a YAML file called books. The StaticModel pluralization mechanisms are based on ActiveSupport which is very powerful, being capable to pluralize (and singularize) both regular and irregular words. When using class names composed of two or more words, the model class name should follow the Ruby conventions, using the CamelCase form, while the file name must contain the words separated by underscores. Examples:

  • YAML file - Plural with underscores separating words (e.g., book_clubs).
  • Model Class - Singular with the first letter of each word capitalized (e.g., BookClub).

It's very common to organize all your YAML files into a single directory. To tell StaticModel where it can find your static files you can specify the load path anywhere in your code with:

StaticModel.configure do |config|
  config.base_data_path = File.join(Rails.root, "config/data")
end

If your YAML files are not located in the same directory or you don't want to follow the naming conventions, you can define per model the specific YAML file's path with the data_path= method:

class Country < StaticModel::Base
  self.data_path = File.join(Rails.root, "config/data/countries.yml")
end

YAML format

The format of the YAML file is very simple, an array of hashes. This could be an example for a countries.yml file:

---
- code: ES
  name: Spain
  population: 46770000
- code: NL
  name: Netherlands
  population: 16800000

Define your attributes and/or your default values

StaticModel depends on the well-written Virtus gem to define its attributes. Here is a basic example of the flexibility that it provides:

class Country < StaticModel::Base
  attribute :id, String
  attribute :name, String
  attribute :population, Integer, default: 0
  attribute :european, Boolean, default: false
  attribute :province_names, Array[String]
end

country = Country.all.first
country.id             # => "ES"
country.name           # => "Spain"
country.population     # => 46770000
country.european       # => true
country.province_names # => ["Madrid", "Barcelona", ...]

Advanced usage of the StaticModel attributes can be found at the Virtus documentation.

Query interface

all

Returns all the records.

Country.all
# => [
#      #<Country id="ES", name="Spain" european=true>,
#      #<Country id="NL", name="Netherlands" european=true>,
#      #<Country id="CO", name="Colombia" european=true>
#    ]

where(criteria)

The where method allows you to specify conditions to limit the records returned, like the WHERE-part of the SQL statement. Conditions must be specified as a hash.

Country.where(european: true)
# => [
#      #<Country id="ES", name="Spain" european=true>,
#      #<Country id="NL", name="Netherlands" european=true>
#    ]

find(id)

Using the find method, you can retrieve the object corresponding to the specified primary key that matches the supplied id.

Country.find("ES") # => #<Country id="ES", name="Spain">
Country.find("KO") # => StaticModel::NotFound

The find method will raise a StaticModel::NotFound exception if no matching record is found.

find_by(criteria)

The find_by method finds the first record matching some conditions.

Country.find_by(name: "Spain") # => #<Country id="ES", name="Spain">
Country.find_by(name: "Invalid") # => nil

find_by!(criteria)

The find_by! method behaves exactly like find_by, except that it will raise StaticModel::NotFound if no matching record is found.

Country.find_by!(name: "Spain") # => #<Country id="ES", name="Spain">
Country.find_by!(name: "Invalid") # => StaticModel::NotFound

exists?(criteria)

If you simply want to check for the existence of the object there's a method called exists?. This method will use the same mechanism as find, but instead of returning an object it will return either true or false.

Country.exists?(name: "Spain") # => true
Country.exists?(name: "Invalid") # => false

Development

After checking out the repo, run script/setup to install dependencies. Then, run rake test to run the tests. You can also run script/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 version.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/fertapric/staticmodel. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Author

Fernando Tapia Rico, @fertapric