Project

garland

0.0
Repository is archived
No commit activity in last 3 years
No release in over 3 years
Provides GarlandRails::Base class for ActiveRecord, which allows you to save Hashes using snapshots and diffs (in short, it's HashDiff Rails storage).
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Runtime

 Project Readme

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

  • Garland is 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 .env file 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.