Ohm

repository·master·Indexed 23 days ago

https://github.com/soveran/ohm

An object-hash mapping library for Redis that provides an ORM-like experience for storing Ruby objects. Ohm supports various attribute types including strings, sets, lists, atomic counters, and references for model associations. It features built-in support for indexing, uniqueness constraints, and collection filtering via find, combine, except, and union methods. The library utilizes the Redic client for Redis communication and provides tools for sorting collections and serializing models to JSON.

Tokens
3.5K
Snippets
12
Records
25
Agent score
30%

What's inside Ohm

  1. Define model associations with references and collections

    master

    Ohm uses references and collections to represent relationships between models.

    References

    reference :field, :Model is a shortcut that:

    1. Adds an attribute for the foreign key (e.g., post_id).
    2. Creates an index on that foreign key.
    3. Defines a setter field=(model) that sets the ID.
    4. Defines a getter field that returns the associated model instance (memoized).

    Collections

    A collection is a macro that defines a finder method. It assumes an index exists on the foreign key in the target model. You can declare it explicitly or use inference.

    Example Implementation

    class Post < Ohm::Model
      attribute :title
      attribute :body
      collection :comments, :Comment
    end
    
    class Comment < Ohm::Model
      attribute :body
      reference :post, :Post
    end

    In this setup, post.comments returns a collection of Comment instances, and comment.post returns the associated Post object.

  2. Define Ohm models and attributes

    master

    Ohm models map objects to Redis keys. You define the schema using various attribute types. All models automatically include an id attribute.

    class Event < Ohm::Model
      attribute :name
      reference :venue, :Venue
      set :participants, :Person
      counter :votes
    
      index :name
    end
  3. Understand Ohm persistence strategies

    master

    Ohm uses different persistence behaviors depending on the attribute type:

    1. attribute: Values are only persisted to Redis after calling .save on the object.
    2. list, set, and counter: Operations on these types are performed immediately in Redis and do not require a .save call. This is because these types require the object to already have an assigned id to function.
  4. Define associations within modules

    master

    If your models are namespaced within a Ruby module, you must provide the full string name of the referenced class to the reference method.

    module SomeNamespace
      class Foo < Ohm::Model
        attribute :name
      end
      
      class Bar < Ohm::Model
        reference :foo, 'SomeNamespace::Foo'
      end
    end
  5. Connect to a Redis database

    master

    Ohm uses the Redic client to communicate with Redis. By default, Ohm connects to redis://127.0.0.1:6379. You can configure the global connection using Ohm.redis= with a Redic instance.

    Individual models can also override the connection if they need to point to a different database.

    require "ohm"
    
    # Set global connection
    Ohm.redis = Redic.new("redis://127.0.0.1:6379")
    
    # Override connection for a specific model
    class User < Ohm::Model
    end
    
    User.redis = Redic.new(ENV["REDIS_URL2"])
  6. Define an Ohm Model

    master

    All models must inherit from Ohm::Model. Models define their schema using macros like attribute, index, unique, counter, set, and list.

    Attributes are stored in a Redis Hash. Counters are stored in a separate Redis Hash. Sets and Lists are stored as separate Redis keys that reference model IDs.

    class User < Ohm::Model
      attribute :name
      index :name
    
      attribute :email
      unique :email
    
      counter :points
    
      set :posts, :Post
    end
  7. Work with Sets in models

    master

    If you define a set attribute, you can use the add method to include model instances. You can then iterate over the set like a standard Ruby collection.

    class Event < Ohm::Model
      attribute :name
      set :attendees, :Person
    end
    
    event = Event.create(name: "Conference")
    event.attendees.add(Person.create(name: "Albert"))
    
    event.attendees.each do |person|
      puts person.name
    end
  8. Track arbitrary keys with `track`

    master

    You can instruct Ohm to track arbitrary Redis keys and tie them to the object's lifecycle. When the model instance is deleted, the tracked keys are also deleted. Tracked keys are scoped to the instance: if the model ID is 42 and you track :text, the key will be ModelName:42:text.

    class Log < Ohm::Model
      track :text
      
      def append(msg)
        redis.call("APPEND", key[:text], msg)
      end
      
      def tail(n = 100)
        redis.call("GETRANGE", key[:text], -(n), -1)
      end
    end
    
    log = Log.create
    log.append("hello\n")
    log.tail
    # => "hello\n"
  9. Sort collections with sort and sort_by

    master

    Ohm provides two primary methods for sorting collections (like Ohm::Model::Set):

    1. Ohm::Model::Collection#sort: Returns elements ordered by their id. Use this when you want to sort by the model's primary identifier.
    2. Ohm::Model::Collection#sort_by: Receives an attribute name to determine the sorting order. It automatically converts the argument into a hash key within the current model.

    Both methods accept an options hash with the following keys:

    • :order: The direction and strategy. Options: ASC (default), ASC ALPHA (or ALPHA ASC), DESC, DESC ALPHA (or ALPHA DESC). Note: For alphanumeric fields, you must use the ALPHA keyword because Redis ASC/DESC only works with integers or floats.
    • :limit: An array representing [offset, limit]. It is 0-indexed. Example: limit: [0, 10] gets the first 10 entries.
    • :by: The key or Hash key to sort by. When using sort, you must specify this. When using sort_by, it is inferred from the first argument.
    • :get: A key pattern to return (e.g., Post:*->title).

    Tip: Use sort for IDs and sort_by for everything else.

    Post.all.sort_by(:title)     # SORT Post:all BY Post:*->title
    Post.all.sort(by: :title)    # SORT Post:all BY title
    
    # Using options
    Post.all.sort_by(:title, get: :title)
    # SORT Post:all BY Post:*->title GET Post:*->title
    
    Post.all.sort(by: :title, get: :title)
    # SORT Post:all BY title GET title
  10. Find and filter records using indices

    master

    An Ohm::Model.index creates a set that Ohm manages automatically to allow quick lookups. Once an index is defined on an attribute, you can use the find method to retrieve records.

    Filtering Methods

    Ohm provides several ways to refine queries. These methods return new sets, allowing for method chaining:

    • find(key: value): Finds records matching the criteria.
    • combine(key: [values]): Performs a set intersection (AND logic).
    • except(key: value): Performs a set difference (NOT logic).
    • union(key: value): Performs a set union (OR logic).
    # Find all users from Argentina
    User.find(country: "Argentina")
    
    # Find all active users from Argentina
    User.find(country: "Argentina", status: "active")
    
    # Find all active users from Argentina and Uruguay
    User.find(status: "active").combine(country: ["Argentina", "Uruguay"])
    
    # Find all users from Argentina, except those with a suspended account
    User.find(country: "Argentina").except(status: "suspended")
    
    # Find all users both from Argentina and Uruguay
    User.find(country: "Argentina").union(country: "Uruguay")