3

If we have a small table which contains relatively static data, is it possible to have Active Record load this in on startup of the app and never have to hit the database for this data?

Note, that ideally I would like this data to be join-able from other Models which have relationships to it.

An example might be a list of countries with their telephone number prefix - this list is unlikely to change, and if it did it would be changed by an admin. Other tables might have relationships with this (eg. given a User who has a reference to the country, we might want to lookup the country telephone prefix).

I saw a similar question here, but it's 6 years old and refers to Rails 2, while I am using Rails 5 and maybe something has been introduced since then.

Preferred solutions would be:

  1. Built-in Rails / ActiveRecord functionality to load a table once on startup and if other records are subsequently loaded in which have relationships with the cached table, then link to the cached objects automatically (ie. manually caching MyModel.all somewhere is not sufficient, as relationships would still be loaded by querying the database).
  2. Maintained library which does the above.
  3. If neither are available, I suppose an alternative method would be to define the static dataset as an in-memory enum/hash or similar, and persist the hash key on records which have a relationship to this data, and define methods on those Models to lookup using the object in the hash using the key persisted in the database. This seems quite manual though...

[EDIT] One other thing to consider with potential solutions - the manual solution (3) would also require custom controllers and routes for such data to be accessible over an API. Ideally it would be nice to have a solution where such data could be offered up via a RESTful API (read only - just GET) if desired using standard rails mechanisms like Scaffolding without too much manual intervention.

Community
  • 1
  • 1
asibs
  • 243
  • 6
  • 14
  • You can use `enum` – Rajdeep Singh Sep 16 '16 at 20:59
  • @RSB - I may be misunderstanding, but isn't `enum` effectively just acting as a string identifier for a number? Meaning the enum can only be one value, not a complex object? This might be suitable in some circumstances, but I'm not sure if it would be suitable here. eg. If we take the country -> telephone prefix again as an example, we need both the country part and the telephone prefix part. Also, if we have `User` and `Company`, both might need to access the same country -> phone mapping, so would the enum need to be duplicated in both Model classes (or in some parent class)? Thanks – asibs Sep 16 '16 at 21:07
  • I think you are going to have to use option 3. 1, is not really possible, that just isn't how sql works. you have to query databases, they are separate pieces of technology. 2 depends on what library you are looking for. Carmen for example works great for countries/states, and keeps that data in `yml` files https://github.com/jim/carmen, but if that is not the data you are looking for, then you'll have to settle for using their approach (yml files) that are manually updated. – Sean Sep 16 '16 at 21:33
  • Maybe this answer comes a little bit late but I was looking around for a similar solution and the best I found was this: https://github.com/zilkey/active_hash, I've been using it in production for a while and it works great. The downside is that without those records on the database, I can't use FK to guarantee integrity – pesta Jun 06 '19 at 23:36

2 Answers2

2

I think you may be discounting the "easy" / "manual" approach too quickly.

Writing the data to a ruby hash / array isn't that bad an idea.

And if you want to use a CRUD scaffold, why not just use the standard Rails model / controller generator? Is it really so bad to store some static data in the database?

A third option would be to store your data to a file in some serialized format and then when your app loads read this and construct ActiveRecord objects. Let me show an example:

data.yml

---
- a: "1"
  b: "1"
- a: "2"
  b: "2"

This is a YAML file containing an array of hashes; you can construct such a file with:

require 'yaml'
File.open("path.yml", "w") do |f|
  data = [
    { "a" => "1", "b" => 1 },
    { "a" => "2", "b" => 2 }
  ]
  f.write(YAML.dump(data))
end

Then to load the data, you might create a file in config/initializers/ (everything here will be autoloaded by rails):

config/initializers/static_data.rb

require 'yaml'

# define a constant that can be used by the rest of the app
StaticData = YAML.load(File.read("data.yml")).map do |object|
  MyObjectClass.new(object)
end

To avoid having to write database migrations for MyObjectClass (when it's not actually being stored in the db) you can use attr_accessor definitions for your attributes:

class MyObjectClass < ActiveRecord::Base
  # say these are your two columns
  attr_accessor :a, :b
end

just make sure not to run stuff like save, delete, or update on this model (unless you monkeypatch these methods).

If you want to have REST / CRUD endpoints, you'd need to write them from scratch because the way to change data is different now. You'd basically need to do any update in a 3 step process:

  1. load the data from YAML into a Ruby object list
  2. change the Ruby object list
  3. serialize everything to YAML and save it.

So you can see you're not really doing incremental updates here. You could use JSON instead of YAML and you'd have the same problem. With Ruby's built in storage system PStore you would be able to update objects on an individual basis, but using SQL for a production web app is a much better idea and will honestly make things more simple.

Moving beyond these "serialized data" options there are key-val storage servers store data in memory. Stuff like Memcached and Redis.

But to go back to my earlier point, unless you have a good reason not to use SQL you're only making things more difficult.

max pleaner
  • 26,189
  • 9
  • 66
  • 118
  • I'm having a similar situation. I'm not willing to store in db because it's too small and the queries for them are rather very large. The update to these data also very rare. So, Some kind of coaching in memory or anything to avoid extra queries would be great. I don't like Yaml or json approach since I think updating will be difficult. – Anwar Feb 21 '17 at 09:27
  • So store it in memory then. I don't really know what your question is. – max pleaner Feb 21 '17 at 10:04
0

It sounds like FrozenRecord would be a good match for what you are looking for.

Active Record-like interface for read only access to static data files of reasonable size.

cdmo
  • 1,239
  • 2
  • 14
  • 31