{"id":13396379,"url":"https://github.com/soveran/ohm","last_synced_at":"2025-05-14T05:00:26.949Z","repository":{"id":578075,"uuid":"210738","full_name":"soveran/ohm","owner":"soveran","description":"Object-Hash Mapping for Redis","archived":false,"fork":false,"pushed_at":"2022-12-20T11:06:01.000Z","size":1156,"stargazers_count":1398,"open_issues_count":14,"forks_count":167,"subscribers_count":39,"default_branch":"master","last_synced_at":"2025-05-03T18:02:51.903Z","etag":null,"topics":["lesscode","ohm","redis","ruby"],"latest_commit_sha":null,"homepage":"http://ohm.keyvalue.org","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/soveran.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2009-05-26T17:33:35.000Z","updated_at":"2025-03-26T21:31:30.000Z","dependencies_parsed_at":"2023-01-13T10:30:18.068Z","dependency_job_id":null,"html_url":"https://github.com/soveran/ohm","commit_stats":null,"previous_names":[],"tags_count":50,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soveran%2Fohm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soveran%2Fohm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soveran%2Fohm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soveran%2Fohm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/soveran","download_url":"https://codeload.github.com/soveran/ohm/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253433682,"owners_count":21907775,"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":["lesscode","ohm","redis","ruby"],"created_at":"2024-07-30T18:00:48.700Z","updated_at":"2025-05-14T05:00:26.860Z","avatar_url":"https://github.com/soveran.png","language":"Ruby","readme":"Ohm ॐ\n=====\n\nObject-hash mapping library for Redis.\n\nDescription\n-----------\n\nOhm is a library for storing objects in [Redis][redis], a persistent key-value\ndatabase. It has very good performance.\n\nCommunity\n---------\n\nJoin the mailing list: [http://groups.google.com/group/ohm-ruby](http://groups.google.com/group/ohm-ruby)\n\nMeet us on IRC: [#ohm](irc://chat.freenode.net/#ohm) on [freenode.net](http://freenode.net/)\n\nRelated projects\n----------------\n\nThese are libraries in other languages that were inspired by Ohm.\n\n* [Ohm](https://github.com/soveran/ohm-crystal) for Crystal, created by soveran\n* [JOhm](https://github.com/xetorthio/johm) for Java, created by xetorthio\n* [Lohm](https://github.com/slact/lua-ohm) for Lua, created by slact\n* [ohm.lua](https://github.com/amakawa/ohm.lua) for Lua, created by amakawa\n* [Nohm](https://github.com/maritz/nohm) for Node.js, created by maritz\n* [Redisco](https://github.com/iamteem/redisco) for Python, created by iamteem\n* [redis3m](https://github.com/luca3m/redis3m) for C++, created by luca3m\n* [Ohmoc](https://github.com/seppo0010/ohmoc) for Objective-C, created by seppo0010\n* [Sohm](https://github.com/xxuejie/sohm.lua) for Lua, compatible with Twemproxy\n\nArticles and Presentations\n--------------------------\n\n* [Simplicity](http://files.soveran.com/simplicity)\n* [How to Redis](http://www.paperplanes.de/2009/10/30/how_to_redis.html)\n* [Redis and Ohm](http://carlopecchia.eu/blog/2010/04/30/redis-and-ohm-part1/)\n* [Ohm (Redis ORM)](http://blog.s21g.com/articles/1717) (Japanese)\n* [Redis and Ohm](http://www.slideshare.net/awksedgreep/redis-and-ohm)\n* [Ruby off Rails](http://www.slideshare.net/cyx.ucron/ruby-off-rails)\n* [Data modeling with Redis and Ohm](http://www.sitepoint.com/semi-relational-data-modeling-redis-ohm/)\n\nGetting started\n---------------\n\nInstall [Redis][redis]. On most platforms it's as easy as grabbing the sources,\nrunning make and then putting the `redis-server` binary in the PATH.\n\nOnce you have it installed, you can execute `redis-server` and it will\nrun on `localhost:6379` by default. Check the `redis.conf` file that comes\nwith the sources if you want to change some settings.\n\nIf you don't have Ohm, try this:\n\n    $ [sudo] gem install ohm\n\nOr you can grab the code from [http://github.com/soveran/ohm][ohm].\n\nNow, in an irb session you can test the Redis adapter directly:\n\n    \u003e\u003e require \"ohm\"\n    =\u003e true\n    \u003e\u003e Ohm.redis.call \"SET\", \"Foo\", \"Bar\"\n    =\u003e \"OK\"\n    \u003e\u003e Ohm.redis.call \"GET\", \"Foo\"\n    =\u003e \"Bar\"\n\n## Connecting to a Redis database\n\nOhm uses a lightweight Redis client called [Redic][redic]. To connect\nto a Redis database, you will need to set an instance of `Redic`, with\nan URL of the form `redis://:\u003cpasswd\u003e@\u003chost\u003e:\u003cport\u003e/\u003cdb\u003e`, through the\n`Ohm.redis=` method, e.g.\n\n```ruby\nrequire \"ohm\"\n\nOhm.redis = Redic.new(\"redis://127.0.0.1:6379\")\n\nOhm.redis.call \"SET\", \"Foo\", \"Bar\"\n\nOhm.redis.call \"GET\", \"Foo\"\n# =\u003e \"Bar\"\n```\n\nOhm defaults to a Redic connection to \"redis://127.0.0.1:6379\". The\nexample above could be rewritten as:\n\n```ruby\nrequire \"ohm\"\n\nOhm.redis.call \"SET\", \"Foo\", \"Bar\"\n\nOhm.redis.call \"GET\", \"Foo\"\n# =\u003e \"Bar\"\n```\n\nAll Ohm models inherit the same connection settings from `Ohm.redis`.\nFor cases where certain models need to connect to different databases,\nthey simple have to override that, i.e.\n\n```ruby\nrequire \"ohm\"\n\nOhm.redis = Redic.new(ENV[\"REDIS_URL1\"])\n\nclass User \u003c Ohm::Model\nend\n\nUser.redis = Redic.new(ENV[\"REDIS_URL2\"])\n```\n\nModels\n------\n\nOhm's purpose in life is to map objects to a key value datastore. It\ndoesn't need migrations or external schema definitions. Take a look at\nthe example below:\n\n### Example\n\n```ruby\nclass Event \u003c Ohm::Model\n  attribute :name\n  reference :venue, :Venue\n  set :participants, :Person\n  counter :votes\n\n  index :name\nend\n\nclass Venue \u003c Ohm::Model\n  attribute :name\n  collection :events, :Event\nend\n\nclass Person \u003c Ohm::Model\n  attribute :name\nend\n```\n\nAll models have the `id` attribute built in, you don't need to declare it.\n\nThis is how you interact with IDs:\n\n```ruby\nevent = Event.create :name =\u003e \"Ohm Worldwide Conference 2031\"\nevent.id\n# =\u003e 1\n\n# Find an event by id\nevent == Event[1]\n# =\u003e true\n\n# Update an event\nevent.update :name =\u003e \"Ohm Worldwide Conference 2032\"\n# =\u003e #\u003cEvent:0x007fb4c35e2458 @attributes={:name=\u003e\"Ohm Worldwide Conference\"}, @_memo={}, @id=\"1\"\u003e\n\n# Trying to find a non existent event\nEvent[2]\n# =\u003e nil\n\n# Finding all the events\nEvent.all.to_a\n# =\u003e [\u003cEvent:1 name='Ohm Worldwide Conference 2032'\u003e]\n```\n\nThis example shows some basic features, like attribute declarations and\nquerying. Keep reading to find out what you can do with models.\n\nAttribute types\n---------------\n\nOhm::Model provides 4 attribute types:\n\n* `Ohm::Model.attribute`,\n* `Ohm::Model.set`\n* `Ohm::Model.list`\n* `Ohm::Model.counter`\n\nand 2 meta types:\n\n* `Ohm::Model.reference`\n* `Ohm::Model.collection`.\n\n### attribute\n\nAn `attribute` is just any value that can be stored as a string. In the\nexample above, we used this field to store the event's `name`. You can\nuse it to store numbers, but be aware that Redis will return a string\nwhen you retrieve the value.\n\n### set\n\nA `set` in Redis is an unordered list, with an external behavior similar\nto that of Ruby arrays, but optimized for faster membership lookups.\nIt's used internally by Ohm to keep track of the instances of each model\nand for generating and maintaining indexes.\n\n### list\n\nA `list` is like an array in Ruby. It's perfectly suited for queues\nand for keeping elements in order.\n\n### counter\n\nA `counter` is like a regular attribute, but the direct manipulation\nof the value is not allowed. You can retrieve, increase or decrease\nthe value, but you can not assign it. In the example above, we used a\ncounter attribute for tracking votes. As the `increment` and `decrement`\noperations are atomic, you can rest assured a vote won't be counted twice.\n\n### reference\n\nIt's a special kind of attribute that references another model.\nInternally, Ohm will keep a pointer to the model (its ID), but you get\naccessors that give you real instances. You can think of it as the model\ncontaining the foreign key to another model.\n\n### collection\n\nProvides an accessor to search for all models that `reference` the current model.\n\nTracked keys\n------------\n\nBesides the provided attribute types, it is possible to instruct\nOhm to track arbitrary keys and tie them to the object's lifecycle.\n\nFor example:\n\n```ruby\nclass Log \u003c Ohm::Model\n  track :text\n  \n  def append(msg)\n    redis.call(\"APPEND\", key[:text], msg)\n  end\n  \n  def tail(n = 100)\n    redis.call(\"GETRANGE\", key[:text], -(n), -1)\n  end\nend\n\nlog = Log.create\nlog.append(\"hello\\n\")\n\nassert_equal \"hello\\n\", log.tail\n\nlog.append(\"world\\n\")\n\nassert_equal \"world\\n\", log.tail(6)\n```\n\nWhen the `log` object is deleted, the `:text` key will be deleted\ntoo. Note that the key is scoped to that particular instance of\n`Log`, so if `log.id` is `42` then the key will be `Log:42:text`.\n\nPersistence strategy\n--------------------\n\nThe attributes declared with `attribute` are only persisted after\ncalling `save`.\n\nOperations on attributes of type `list`, `set` and `counter` are\npossible only after the object is created (when it has an assigned\n`id`). Any operation on these kinds of attributes is performed\nimmediately. This design yields better performance than buffering\nthe operations and waiting for a call to `save`.\n\nFor most use cases, this pattern doesn't represent a problem.\nIf you are saving the object, this will suffice:\n\n```ruby\nif event.save\n  event.comments.add(Comment.create(body: \"Wonderful event!\"))\nend\n```\n\nWorking with Sets\n-----------------\n\nGiven the following model declaration:\n\n```ruby\nclass Event \u003c Ohm::Model\n  attribute :name\n  set :attendees, :Person\nend\n```\n\nYou can add instances of `Person` to the set of attendees with the\n`add` method:\n\n```ruby\nevent.attendees.add(Person.create(name: \"Albert\"))\n\n# And now...\nevent.attendees.each do |person|\n  # ...do what you want with this person.\nend\n```\n\n## Sorting\n\nSince `attendees` is a `Ohm::Model::Set`, it exposes two sorting\nmethods: `Ohm::Model::Collection#sort` returns the elements\nordered by `id`, and `Ohm::Model::Collection#sort_by` receives\na parameter with an attribute name, which will determine the sorting\norder. Both methods receive an options hash which is explained below:\n\n### :order\n\nOrder direction and strategy. You can pass in any of the following:\n\n1. ASC\n2. ASC ALPHA (or ALPHA ASC)\n3. DESC\n4. DESC ALPHA (or ALPHA DESC)\n\nIt defaults to `ASC`.\n\n__Important Note:__ Starting with Redis 2.6, `ASC` and `DESC` only\nwork with integers or floating point data types. If you need to sort\nby an alphanumeric field, add the `ALPHA` keyword.\n\n### :limit\n\nThe offset and limit from which we should start with. Note that\nthis is 0-indexed. It defaults to `0`.\n\nExample:\n\n`limit: [0, 10]` will get the first 10 entries starting from offset 0.\n\n### :by\n\nKey or Hash key with which to sort by. An important distinction with\nusing `Ohm::Model::Collection#sort` and\n`Ohm::Model::Collection#sort_by` is that `sort_by` automatically\nconverts the passed argument with the assumption that it is a hash key\nand it's within the current model you are sorting.\n\n```ruby\nPost.all.sort_by(:title)     # SORT Post:all BY Post:*-\u003etitle\nPost.all.sort(by: :title) # SORT Post:all BY title\n```\n\n__Tip:__ Unless you absolutely know what you're doing, use `sort`\nwhen you want to sort your models by their `id`, and use `sort_by`\notherwise.\n\n### :get\n\nA key pattern to return, e.g. `Post:*-\u003etitle`. As is the case with\nthe `:by` option, using `Ohm::Model::Collection#sort` and\n`Ohm::Model::Collection#sort_by` has distinct differences in\nthat `sort_by` does much of the hand-coding for you.\n\n```ruby\nPost.all.sort_by(:title, get: :title)\n# SORT Post:all BY Post:*-\u003etitle GET Post:*-\u003etitle\n\nPost.all.sort(by: :title, get: :title)\n# SORT Post:all BY title GET title\n```\n\n\nAssociations\n------------\n\nOhm lets you declare `references` and `collections` to represent associations.\n\n```ruby\nclass Post \u003c Ohm::Model\n  attribute :title\n  attribute :body\n  collection :comments, :Comment\nend\n\nclass Comment \u003c Ohm::Model\n  attribute :body\n  reference :post, :Post\nend\n```\n\nAfter this, every time you refer to `post.comments` you will be talking\nabout instances of the model `Comment`. If you want to get a list of IDs\nyou can use `post.comments.ids`.\n\n### References explained\n\nDoing a `Ohm::Model.reference` is actually just a shortcut for\nthe following:\n\n```ruby\n# Redefining our model above\nclass Comment \u003c Ohm::Model\n  attribute :body\n  attribute :post_id\n  index :post_id\n\n  def post=(post)\n    self.post_id = post.id\n  end\n\n  def post\n    Post[post_id]\n  end\nend\n```\n\nThe only difference with the actual implementation is that the model\nis memoized.\n\nThe net effect here is we can conveniently set and retrieve `Post` objects,\nand also search comments using the `post_id` index.\n\n```ruby\nComment.find(post_id: 1)\n```\n\n### Collections explained\n\nThe reason a `Ohm::Model.reference` and a\n`Ohm::Model.collection` go hand in hand, is that a collection is\njust a macro that defines a finder for you, and we know that to find a model\nby a field requires an `Ohm::Model.index` to be defined for the field\nyou want to search.\n\n```ruby\n# Redefining our post above\nclass Post \u003c Ohm::Model\n  attribute :title\n  attribute :body\n\n  def comments\n    Comment.find(post_id: self.id)\n  end\nend\n```\n\nThe only \"magic\" happening is with the inference of the `index` that was used\nin the other model. The following all produce the same effect:\n\n```ruby\n# easiest, with the basic assumption that the index is `:post_id`\ncollection :comments, :Comment\n\n# we can explicitly declare this as follows too:\ncollection :comments, :Comment, :post\n\n# finally, we can use the default argument for the third parameter which\n# is `to_reference`.\ncollection :comments, :Comment, to_reference\n\n# exploring `to_reference` reveals a very interesting and simple concept:\nPost.to_reference == :post\n# =\u003e true\n```\n\nModules\n-------\n\nIf your models are defined inside a module, you will have to define\nthe references and collections as in the following example:\n\n```ruby\nmodule SomeNamespace\n  class Foo \u003c Ohm::Model\n    attribute :name\n  end\n  \n  class Bar \u003c Ohm::Model\n    reference :foo, 'SomeNamespace::Foo'\n  end\nend\n```\n\nIndices\n-------\n\nAn `Ohm::Model.index` is a set that's handled automatically by Ohm. For\nany index declared, Ohm maintains different sets of objects IDs for quick\nlookups.\n\nIn the `Event` example, the index on the name attribute will\nallow for searches like `Event.find(name: \"some value\")`.\n\nNote that the methods `Ohm::Model::Set#find` and\n`Ohm::Model::Set#except` need a corresponding index in order to work.\n\n### Finding records\n\nYou can find a collection of records with the `find` method:\n\n```ruby\n# This returns a collection of users with the username \"Albert\"\nUser.find(username: \"Albert\")\n```\n\n### Filtering results\n\n```ruby\n# Find all users from Argentina\nUser.find(country: \"Argentina\")\n\n# Find all active users from Argentina\nUser.find(country: \"Argentina\", status: \"active\")\n\n# Find all active users from Argentina and Uruguay\nUser.find(status: \"active\").combine(country: [\"Argentina\", \"Uruguay\"])\n\n# Find all users from Argentina, except those with a suspended account.\nUser.find(country: \"Argentina\").except(status: \"suspended\")\n\n# Find all users both from Argentina and Uruguay\nUser.find(country: \"Argentina\").union(country: \"Uruguay\")\n```\n\nNote that calling these methods results in new sets being created\non the fly. This is important so that you can perform further operations\nbefore reading the items to the client.\n\nFor more information, see [SINTERSTORE](http://redis.io/commands/sinterstore),\n[SDIFFSTORE](http://redis.io/commands/sdiffstore) and\n[SUNIONSTORE](http://redis.io/commands/sunionstore)\n\nUniques\n-------\n\nUniques are similar to indices except that there can only be one record per\nentry. The canonical example of course would be the email of your user, e.g.\n\n```ruby\nclass User \u003c Ohm::Model\n  attribute :email\n  unique :email\nend\n\nu = User.create(email: \"foo@bar.com\")\nu == User.with(:email, \"foo@bar.com\")\n# =\u003e true\n\nUser.create(email: \"foo@bar.com\")\n# =\u003e raises Ohm::UniqueIndexViolation\n```\n\nOhm Extensions\n==============\n\nOhm is rather small and can be extended in many ways.\n\nA lot of amazing contributions are available at [Ohm Contrib][contrib]\nmake sure to check them if you need to extend Ohm's functionality.\n\n[contrib]: http://cyx.github.io/ohm-contrib/\n\nUpgrading\n=========\n\nOhm 2 breaks the compatibility with previous versions. If you're upgrading an\nexisting application, it's nice to have a good test coverage before going in.\nTo know about fixes and changes, please refer to the CHANGELOG file.\n\n[redis]: http://redis.io\n[ohm]: http://github.com/soveran/ohm\n[redic]: https://github.com/amakawa/redic\n","funding_links":[],"categories":["Cuba Components \u0026 Related Gems","Data Persistence","Ruby","ORM/ODM","Gems","ORM","Higher level libraries and tools"],"sub_categories":["Redis Clients","Background Jobs"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoveran%2Fohm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoveran%2Fohm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoveran%2Fohm/lists"}