Skip to content

An ORM inspired by activerecord which uses the filesystem and TMSU tagging system

Notifications You must be signed in to change notification settings

MaxPleaner/tmsu-rubY

Repository files navigation

This is an ORM similar to ActiveRecord, but uses the filesystem instead.

It uses TMSU which is a filesystem tagging system.

Usage:

Install the gem

gem install tmsu_file_db

Define a model

require 'tmsu_file_db'

class User < TmsuModel

  # this configure block is optional
  # it defaults to a randomly named dir in ./db
  configure root_path: "./db/users"

  # Validations must return an array
  validate do |record|
    record.name.nil? ? ["name can't be blank"] : []
  end

  # Specific attributes can be validated as well
  validate(:email) do |email, record|
    email&.include?("@") ? ["email isn't valid"] : []
  end

end

Create instances

# create, update, and delete
u = User.new name: "max"
u.valid? # => false
u.errors # => ["email isn't valid"]
u.save # => false
u.[:email] = "maxpleaner@gmail.com"
u.valid? # => true
u.save # => true
u.update(email: "max.pleaner@gmail.com") # => true
u.update(email: "") # => false

# There are getter methods for convenience
# Setters need to use []=
u.name # => "max"
u["name"] # => "max"
u[:name] # => "max"
u[:name] # => "max p."

# All these getter/setters are working on 'attributes' under the hood.
u.attributes[:name] # => "max p."

# each record is assigned a filesystem path
u.path

# creating a new record will create a new file in the root_path
# but will not add anything to the file unless .write is called
u.write "hello"

# The content of the file is not part of the "attributes" i.e. name and email
# Those are stored using TMSU tags
u.tags # => { email: "max.pleaner@gmail.com", name: "max" }
u.tags == u.attributes # true

# Attributes can be deleted
u.delete :name
u.tags # => { email: "max.pleaner@gmail.com" }

# Records can be deleted (this will destroy the file)
u.destroy

Use class-level query methods

Note that this does not use Arel or any of that jazz. So chaining queries or using joins will not work.

Note also that there is no id on models, only path, which is an absolute path.

User.where(name: "max p.")[0].name == "max p." # => true
User.find_by(name: "max p.").name == "max p." # => true
User.update_all(name: "max") # => true

# You can make arbitrary queries using TMSU syntax
# e.g. select all users with email set that are not named melvin
User.query("name != 'melvin' and email")[0].name == "max" # => true

Although there's an index method (all), there's no typical auto-incrementing ids stored in TMSU. So to load a single, arbitrary record without tags, its file path is used:

  u.path # => "./db/users/23uj8d9j328dj"
  User.from_file(u.path).name == "max p."

Use TmsuRuby.file

An alternative to TmsuModel is to use TmsuRuby.file instead. This does not handle creation / deletion of files. It should only be used with files that already exist.

Note that these methods are technically available on TmsuModel instances as well. But this shouldn't be done, because it will cause the in-memory attributes to be out of sync. Also, some operations like tag will error if called on unsaved records.

file_path = './my_file.mp3' # this should already exist

tmsu_file = TmsuRuby.file file_path
tmsu_file.tags # => {}

tmsu_file.tag "foo" # .tag can be passed a string
tmsu_file.tags # => { foo: nil }

tmsu_file.untag "foo"
tmsu_file.tags # => { }

tmsu_file.tag ["foo", "bar"] # .tag can also be passed an array
tmsu_file.tags # => { foo: nil, bar: nil }

tmsu_file.tag(a: 1, b: 2) # .tag can also be passed a hash
tmsu_file.tags # => { foo: nil, bar: nil, a: 1, b: 2 }

It's also possible to use TmsuRuby to work on multiple files instead of just one:

glob_selector = "./**/*.jpg"

tmsu_file = TmsuRuby.file glob_selector

# there is a special method used to add tags in this case
tmsu_file.tag_selector "foo"
tmsu_file.tag_selector ["a", "b"]
tmsu_file.tag_selector c: 1, d: 2

# Simiarly to untag
tmsu_file.untag_selector "c"

# check that the tags were added to files
TmsuRuby.file("./my_pic.jpg").tags
# => { foo: nil, a: nil, b: nil, d: 2 }

Using TmsuRuby.file you can search by tag as well. All these methods return an array of absolute paths

query_glob = "./**/*.jpg"

# To perform a scoped search (the same used by .where, .find_by, and .query):
# This is a simple query, but the whole TMSU syntax is available
TmsuRuby.file(query_glob).paths_query("foo")

# Search the whole filesystem for files with tag
TmsuRuby.file.files("foo")

Test && Examples

See automated_test.rb, which can double as usage examples.

About

An ORM inspired by activerecord which uses the filesystem and TMSU tagging system

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages