{"id":22119991,"url":"https://github.com/maxpleaner/tmsu-ruby","last_synced_at":"2025-03-24T06:25:45.246Z","repository":{"id":62559120,"uuid":"51343482","full_name":"MaxPleaner/tmsu-rubY","owner":"MaxPleaner","description":"An ORM inspired by activerecord which uses the filesystem and TMSU tagging system","archived":false,"fork":false,"pushed_at":"2017-03-31T22:47:50.000Z","size":140,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-01T16:04:58.009Z","etag":null,"topics":["activerecord","orm","ruby","tmsu"],"latest_commit_sha":null,"homepage":"http://rubydoc.info/gems/tmsu_file_db","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MaxPleaner.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-02-09T03:00:22.000Z","updated_at":"2017-03-31T22:47:17.000Z","dependencies_parsed_at":"2022-11-03T11:15:25.433Z","dependency_job_id":null,"html_url":"https://github.com/MaxPleaner/tmsu-rubY","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MaxPleaner%2Ftmsu-rubY","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MaxPleaner%2Ftmsu-rubY/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MaxPleaner%2Ftmsu-rubY/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MaxPleaner%2Ftmsu-rubY/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MaxPleaner","download_url":"https://codeload.github.com/MaxPleaner/tmsu-rubY/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245219772,"owners_count":20579647,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","orm","ruby","tmsu"],"created_at":"2024-12-01T14:19:44.164Z","updated_at":"2025-03-24T06:25:45.227Z","avatar_url":"https://github.com/MaxPleaner.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"This is an ORM similar to ActiveRecord, but uses the filesystem instead.\n\nIt uses TMSU which is a filesystem tagging system.\n\nUsage:\n\n**Install the gem**\n\n```sh\ngem install tmsu_file_db\n```\n\n**Define a model**\n\n```rb\nrequire 'tmsu_file_db'\n\nclass User \u003c TmsuModel\n\n  # this configure block is optional\n  # it defaults to a randomly named dir in ./db\n  configure root_path: \"./db/users\"\n\n  # Validations must return an array\n  validate do |record|\n    record.name.nil? ? [\"name can't be blank\"] : []\n  end\n\n  # Specific attributes can be validated as well\n  validate(:email) do |email, record|\n    email\u0026.include?(\"@\") ? [\"email isn't valid\"] : []\n  end\n\nend\n```\n\n**Create instances**\n\n```rb\n# create, update, and delete\nu = User.new name: \"max\"\nu.valid? # =\u003e false\nu.errors # =\u003e [\"email isn't valid\"]\nu.save # =\u003e false\nu.[:email] = \"maxpleaner@gmail.com\"\nu.valid? # =\u003e true\nu.save # =\u003e true\nu.update(email: \"max.pleaner@gmail.com\") # =\u003e true\nu.update(email: \"\") # =\u003e false\n\n# There are getter methods for convenience\n# Setters need to use []=\nu.name # =\u003e \"max\"\nu[\"name\"] # =\u003e \"max\"\nu[:name] # =\u003e \"max\"\nu[:name] # =\u003e \"max p.\"\n\n# All these getter/setters are working on 'attributes' under the hood.\nu.attributes[:name] # =\u003e \"max p.\"\n\n# each record is assigned a filesystem path\nu.path\n\n# creating a new record will create a new file in the root_path\n# but will not add anything to the file unless .write is called\nu.write \"hello\"\n\n# The content of the file is not part of the \"attributes\" i.e. name and email\n# Those are stored using TMSU tags\nu.tags # =\u003e { email: \"max.pleaner@gmail.com\", name: \"max\" }\nu.tags == u.attributes # true\n\n# Attributes can be deleted\nu.delete :name\nu.tags # =\u003e { email: \"max.pleaner@gmail.com\" }\n\n# Records can be deleted (this will destroy the file)\nu.destroy\n\n```\n\n\n**Use class-level query methods**\n\n_Note that this does not use Arel or any of that jazz. So chaining queries or using joins will not work._\n\n_Note also that there is no `id` on models, only `path`, which is an absolute path._\n\n```rb\nUser.where(name: \"max p.\")[0].name == \"max p.\" # =\u003e true\nUser.find_by(name: \"max p.\").name == \"max p.\" # =\u003e true\nUser.update_all(name: \"max\") # =\u003e true\n\n# You can make arbitrary queries using TMSU syntax\n# e.g. select all users with email set that are not named melvin\nUser.query(\"name != 'melvin' and email\")[0].name == \"max\" # =\u003e true\n```\n\nAlthough 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:\n\n```rb\n  u.path # =\u003e \"./db/users/23uj8d9j328dj\"\n  User.from_file(u.path).name == \"max p.\"\n```\n\n**Use TmsuRuby.file**\n\nAn 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.\n\nNote 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.\n\n```rb\nfile_path = './my_file.mp3' # this should already exist\n\ntmsu_file = TmsuRuby.file file_path\ntmsu_file.tags # =\u003e {}\n\ntmsu_file.tag \"foo\" # .tag can be passed a string\ntmsu_file.tags # =\u003e { foo: nil }\n\ntmsu_file.untag \"foo\"\ntmsu_file.tags # =\u003e { }\n\ntmsu_file.tag [\"foo\", \"bar\"] # .tag can also be passed an array\ntmsu_file.tags # =\u003e { foo: nil, bar: nil }\n\ntmsu_file.tag(a: 1, b: 2) # .tag can also be passed a hash\ntmsu_file.tags # =\u003e { foo: nil, bar: nil, a: 1, b: 2 }\n```\n\nIt's also possible to use `TmsuRuby` to work on multiple files instead of just one:\n\n```rb\nglob_selector = \"./**/*.jpg\"\n\ntmsu_file = TmsuRuby.file glob_selector\n\n# there is a special method used to add tags in this case\ntmsu_file.tag_selector \"foo\"\ntmsu_file.tag_selector [\"a\", \"b\"]\ntmsu_file.tag_selector c: 1, d: 2\n\n# Simiarly to untag\ntmsu_file.untag_selector \"c\"\n\n# check that the tags were added to files\nTmsuRuby.file(\"./my_pic.jpg\").tags\n# =\u003e { foo: nil, a: nil, b: nil, d: 2 }\n```\n\nUsing `TmsuRuby.file` you can search by tag as well. All these methods return\nan array of absolute paths\n\n```rb\nquery_glob = \"./**/*.jpg\"\n\n# To perform a scoped search (the same used by .where, .find_by, and .query):\n# This is a simple query, but the whole TMSU syntax is available\nTmsuRuby.file(query_glob).paths_query(\"foo\")\n\n# Search the whole filesystem for files with tag\nTmsuRuby.file.files(\"foo\")\n```\n\n**Test \u0026\u0026 Examples**\n\nSee [automated_test.rb](./automated_test.rb), which can double as usage examples. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxpleaner%2Ftmsu-ruby","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxpleaner%2Ftmsu-ruby","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxpleaner%2Ftmsu-ruby/lists"}