Garland gem
Summary
Provides GarlandRails::Base class for ActiveRecord, which allows you to save Hashes using snapshots and diffs (in short, it's HashDiff Rails storage).
Installing
Add this to your Gemfile
gem "garland"
and run
bundle install
rails g garland:install
rake db:migrate
This will install gem and create garlands table in your database (models should be created/adjusted manually).
Usage
Simple model
Setup your model:
# models/event.rb
class Event < GarlandRails::Base
end
Push some hashes:
my_event = { where: "here", when: "now" }
Event.push(my_event)
=> #<Event id: 2, entity: "[[\"+\", \":when\", \"now\"], [\"+\", \":where\", \"here\"]]", ... >
# push() always (if succeed) returns record which is latest diff
Now, let's look, what happened our model:
Event.all
=> #<ActiveRecord::Relation [
#<Event id: 1, entity: "{}", entity_type: false, previous: nil, next: 3, belongs_to_id: nil, belongs_to_type: nil, type: "Event", created_at: "[FILTERED]", updated_at: "[FILTERED]">,
#<Event id: 3, entity: "[[\"+\", \":when\", \"now\"], [\"+\", \":where\", \"here\"]]", entity_type: true, previous: 1, next: 2, belongs_to_id: nil, belongs_to_type: nil, type: "Event", created_at: "[FILTERED]", updated_at: "[FILTERED]">,
#<Event id: 2, entity: "{:where=>\"here\", :when=>\"now\"}", entity_type: false, previous: 3, next: nil, belongs_to_id: nil, belongs_to_type: nil, type: "Event", created_at: "[FILTERED]", updated_at: "[FILTERED]">
]>
As you see, GarlandRails::Base model now consists of tail (always {}, id=1 here), head (always the latest hash you push, aka snapshot, here it's under id=2) and diffs between them (id=3).
OK, let's push something else:
my_second_event = { where: "here", when: "tomorrow" }
Event.push(my_second_event)
=> #<Event id: 4, entity: "[[\"~\", \":when\", \"now\", \"tomorrow\"]]", entity_type: true, previous: 31, next: 30, belongs_to_id: nil, belongs_to_type: nil, type: "Event", created_at: "[FILTERED]", updated_at: "[FILTERED]">
Chain of diffs and snapshots now will look like this:
"{}" # "tail"
=>
"[[\"+\", \":when\", \"now\"]
=>
"[[\"~\", \":when\", \"now\", \"tomorrow\"]]"
=>
"{:where=>\"here\", :when=>\"tomorrow\"}" # "head"
Cool! (It would be cooler if the diff wasn't larger than the snapshot though.)
ActiveRecord relations
If you need to Garland records to belong to some other model records, you may use regular Rails belongs_to/has_many relations:
# models/config.rb
class Config < GarlandRails::Base
belongs_to :program
end
# models/program.rb
class Program < ActiveRecord::Base
include GarlandRails::Extend
has_many :configs
end
This will make polymorphic-like relation, storing id and type of relation in belongs_to_id and belongs_to_type fields of Config. Be sure that this won't spoil your other non-garland models' relations.
program1 = Program.new(name: "program1")
program1.save
my_config = { path: "path/to/program", url: "example.com", token: "t0ken" }
Config.push(hash: my_config, belongs_to: program1)
=> #<Config id: 2, entity: "[[\"+\", \":path\", \"path/to/program\"], [\"+\", \":token\"...", entity_type: true, previous: 1, next: 3, belongs_to_id: 2, belongs_to_type: "Program", type: "Config", created_at: "[FILTERED]", updated_at: "[FILTERED]">
Note belongs_to_id and belongs_to_type fields.
Now you can use program1.configs and other relation features like dependent: :destroy!
Caveats
-
Garlandis intended to be long-term data storage, so there are no abstractions that allows you to get n-th snapshot of your model currently implemented -
there are no more snapshots besides head and tail, so make sure you backup your data (well, I think you should back up it anyway)
Testing
- prepare db
sudo -u postgres psql
create role YOUR_DB_USERNANE with CREATEDB SUPERUSER LOGIN PASSWORD 'YOUR_DB_PASSWORD';
\q
rake db:create
rake db:schema:load
-
git clone -
bundle install -
rake db:test:prepare -
create
.envfile in the root and fill it like this
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=YOUR_DB_USERNANE
DB_PASSWORD=YOUR_DB_PASSWORD
rails test
TODO
- auto snapshots from time to time (where to store variables and counters? that's the question!)
- custom table name (where to store it? sooo many questions!)
- savepoints create/rollback/release tests
License
Garland is distributed under the MIT-LICENSE.