{"id":22199269,"url":"https://github.com/babbel/grumlin","last_synced_at":"2025-10-13T15:10:10.736Z","repository":{"id":37211677,"uuid":"371498850","full_name":"babbel/grumlin","owner":"babbel","description":"Gremlin language variant for Ruby.","archived":false,"fork":false,"pushed_at":"2025-06-02T16:46:58.000Z","size":1903,"stargazers_count":24,"open_issues_count":8,"forks_count":1,"subscribers_count":34,"default_branch":"main","last_synced_at":"2025-10-07T19:23:33.653Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/babbel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-27T20:40:48.000Z","updated_at":"2025-06-07T06:18:56.000Z","dependencies_parsed_at":"2025-01-15T18:18:53.240Z","dependency_job_id":"b18e5a79-294c-400f-9fd1-11cd26f5ea4c","html_url":"https://github.com/babbel/grumlin","commit_stats":{"total_commits":157,"total_committers":4,"mean_commits":39.25,"dds":0.2993630573248408,"last_synced_commit":"380d54a16ddfd88e9ef03d0f0d1f8a175aa80d24"},"previous_names":[],"tags_count":81,"template":false,"template_full_name":null,"purl":"pkg:github/babbel/grumlin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babbel%2Fgrumlin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babbel%2Fgrumlin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babbel%2Fgrumlin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babbel%2Fgrumlin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/babbel","download_url":"https://codeload.github.com/babbel/grumlin/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babbel%2Fgrumlin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279007136,"owners_count":26084246,"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","status":"online","status_checked_at":"2025-10-11T02:00:06.511Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-12-02T15:13:48.395Z","updated_at":"2025-10-13T15:10:10.705Z","avatar_url":"https://github.com/babbel.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Grumlin\n\n[![Ruby](https://github.com/babbel/grumlin/actions/workflows/main.yml/badge.svg)](https://github.com/babbel/grumlin/actions/workflows/main.yml)\n[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)\n[![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](https://lbesson.mit-license.org/)\n\nGrumlin is a [Gremlin](https://tinkerpop.apache.org/gremlin.html) graph traversal language DSL and client for Ruby. \nSuitable for and tested with [gremlin-server](http://tinkerpop.apache.org/) and [AWS Neptune](https://aws.amazon.com/neptune/).\n\n**Important**: Grumlin and it's author are not affiliated with The Apache Software Foundation which develops gremlin\nand gremlin-server.\n\n**Important**: Grumlin is based on the [async stack](https://github.com/socketry/async) and utilizes \n[async-websocket](https://github.com/socketry/async-websocket). Code using grumlin must be executed in an async\nevent loop.\n\n**Warning**: Grumlin is in development, but ready for simple use cases\n\n## Table of contents\n- [Install](#install)\n- [Usage](#usage)\n- [Development](#development)\n- [Contributing](#contributing)\n- [License](#license)\n- [Code Of Conduct](#code-of-conduct)\n\n\n## Dependencies\n\nGrumlin works with ruby \u003e= 3.1.\n\n## Install\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'grumlin'\n```\n\nAnd then execute:\n\n    $ bundle install\n\nOr install it yourself as:\n\n    $ gem install grumlin\n\n## Usage\n\n### Configuration\n```ruby\nGrumlin.configure do |config|\n  config.url = \"ws://localhost:8182/gremlin\"\n\n  # make sure you select right provider for better compatibility\n  config.provider = :tinkergraph\nend\n```\n\n#### Providers\n\nCurrently `Grumlin` supports 2 providers:\n- tinkergraph (default)\n- neptune\n\nAs different providers may have or may have not support for specific features it's recommended to\nexplicitly specify the provider you use.\n\n#### Provider features\n\nEvery provider is described by a set of features. In the future `Grumlin` may decide to disable or enable \nsome parts of it's functionality to comply provider's supported features.\n\nTo check current providers supported features use\n\n```ruby\nGrumlin.features\n```\n\nCurrent differences between providers:\n\n| Feature      | TinkerGraph                                                                                                                                               |AWS Neptune|\n|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|\n| Transactions | Transaction semantic is ignoroed, data is always writen, `tx.rollback` does nothing, an info is printed every time transactions are used with TinkerGraph |Full support\n\n### Traversing graphs\n\n**Warning**: Not all steps and expressions defined in the reference documentation are supported.\n\n#### Grumlin::Repository\n`Grumlin::Repository` - is a starting point for all traversals. It provides easy access to `g`, `__` and usual gremlin\nexpressions for you class. It has support for defining your own shortcuts and is even shipped with a couple of useful\nshortcuts to make gremlin code more rubyish. **Classes extending `Grumlin::Repository`\nor `Grumlin::Shortcuts` can be inherited**, successors don't need to extend them again and have access to shortcuts\ndefined in the ancestor.\n\n**Definition**\n\n```ruby\nclass MyRepository\n  extend Grumlin::Repository\n  # read_only! - forbids mutating queries for this repository. May be useful for separation reads and writes\n  \n  # It can add shortcuts from another repository or a shortcuts module\n  shortcuts_from ChooseShortcut\n  \n  shortcut :red_triangles do |color|\n    # hasAll unwraps a hash of properties into a chain of `has` steps:\n    # hasAll(name1: :value, name2: :value) == has(:name1, :value).has(:name2, :value)\n    # the `props` shortcut does exactly the same but with `property` steps.\n    hasAll(T.label =\u003e :triangle, color: color)\n  end\n\n  # `default_vertex_properties` and `default_edge_properties`\n  # override `addV` and `addE` according and inject hashes returned from passed\n  # as properties for newly created vertices and edges.\n  # In case if a repository is inherited, newly defined default properties will be merged to\n  # default properties defined in the parent repository.\n  default_vertex_properties do |_label|\n    {\n      created_at: Time.now.to_i\n    }\n  end\n\n  default_edge_properties do |_label|\n    {\n      created_at: Time.now.to_i\n    }\n  end\n\n  # g and __ are already aware of shortcuts\n  query(:triangles_with_color, return_mode: :list) do |color| # :list is the default return mode, also possible: :none, :single, :traversal\n    g.V.hasLabel(:triangle)\n       .hasColor(color)\n  end\n  # Note that when using the `query` one does not need to call a termination step like `next` or `toList`,\n  # repository does it automatically in according to the `return_mode` parameter. \nend\n```\n\nEach `return_mode` is mapped to a particular termination step:\n- `:list` - `toList`\n- `:single` - `next`\n- `:none` - `iterate`\n- `:traversal` - do not execute the query and return the traversal as is\n\n`Grumlin::Repository` also provides a set of generic CRUD operations:\n- `add_vertex(label, id = nil, start: g, **properties)`\n- `add_edge(label, id = nil, from:, to:, start: g, **properties)`\n- `drop_vertex(id, start: g)`\n- `drop_edge(id = nil, from: nil, to: nil, label: nil, start: g)`\n- `drop_in_batches(traversal, batch_size: 10_000)`\n\nand a few methods that emulate upserts:\n- `upsert_vertex(label, id, create_properties: {}, update_properties: {}, on_failure: :retry, start: g, **params)`\n- `upsert_vertices(edges, batch_size: 100, on_failure: :retry, start: g, **params)`\n- `upsert_edge(label, from:, to:, create_properties: {}, update_properties: {}, on_failure: :retry, start: g, **params)`\n- `upsert_edges(edges, batch_size: 100, on_failure: :retry, start: g, **params)`\n\n**Note**: all upsert methods expect your provider has support for user supplied string ids for nodes and edges \nrespectively. For edges and if `create_properties[T.id]` if nil, grumlin will generate a uuid-like id out of `from` and\n`to` vertex ids and edge's label to ensure uniqueness of the edge. If you manually provide an id, it's your \nresponsibility to ensure it's uniquely identifies the edge using it's `from`, `to` and `label`.\n\nAll of them support 3 different modes for error handling: `:retry`, `:ignore` and `:raise`. Retry mode is implemented\nwith [retryable](https://github.com/nfedyashev/retryable). **params will be merged to the default config for upserts\nand passed to `Retryable.retryable`. In case if you want to modify retryable behaviour you are to do so.\n\nIf you want to use these methods inside a transaction simply pass your `gtx` as `start` parameter:\n```ruby\ng.tx do |gtx|\n  add_vertex(:vertex, start: gtx)\nend\n```\n\nIf you don't want to define you own repository, simply use\n\n`Grumlin::Repository.new` returns an instance of an anonymous class extending `Grumlin::Repository`.\n\n**Usage**\n\nTo execute the query defined in a query block one simply needs to call a method with the same name:\n\n`MyRepository.new.triangles_with_color(:red)`\n\nOne can also override the `return_mode`:\n\n`MyRepository.new.triangles_with_color(:red, query_params: { return_mode: :single })`\n\nor even pass a block to the method and a raw traversal will be yielded:\n```ruby\nMyRepository.new.triangles_with_color(:red) do |t|\n  t.has(:other_property, :some_value).toList\nend\n```\nit may be useful for debugging. Note that one needs to call a termination step manually in this case.\n\n`query` also provides a helper for profiling requests:\n`MyRepository.new.triangles_with_color(:red, query_params: { profile: true })`\n\nmethod will return profiling data of the results.\n\n#### Shortcuts\n\n**Shortcuts** is a way to share and organize gremlin code. They let developers define their own steps consisting of\nsequences of standard gremlin steps, other shortcuts and even add new initially unsupported by Grumlin steps.\nRemember ActiveRecord scopes? Shortcuts are very similar.\n\n**Important**: if a shortcut's name matches a name of a method defined on the wrapped object, this shortcut will be\nbe ignored because methods have higher priority.\n\n**Defining**:\n```ruby\n\n# Defining shortcuts\nclass ColorShortcut\n  extend Grumlin::Shortcuts\n\n  # Custom step\n  shortcut :hasColor do |color|\n    has(:color, color)\n  end\nend\n\nclass ChooseShortcut\n  extend Grumlin::Shortcuts\n\n  # Standard Gremlin step\n  shortcut :choose do |*args|\n    step(:choose, *args)\n  end\nend\n\nclass AllShortcuts\n  extend Grumlin::Shortcuts\n\n  # Adding shortcuts from other modules\n  shortcuts_from ColorShortcut\n  shortcuts_from ChooseShortcut\nend\n```\n\n##### Overriding standard steps and shortcuts\n\nSometimes it may be useful to override standard steps. Grumlin does not allow it by default, but one\nis still able to override standard steps if they know what they are doing:\n\n```ruby\nshortcut :addV, override: true do |label|\n  super(label).property(:default, :value)\nend\n```\n\nThis will create a new shortcut that overrides the standard step `addV` and adds default properties to all vertices\ncreated by the repository that uses this shortcut.\n\nShortcuts also can be overridden, but super() is not available.\n\n##### Middlewares\n\nMiddlewares can be used to perform certain actions before and after every query made by `Grumlin`. It can be useful for\nmeasuring query execution time or performing some modification or validation to the query before it reaches the server or\nmodify the response before client gets it.\n\nSee [docs/middlewares.md](docs/middlewares.md) for more info and examples.\n\n#### Transactions\n\nSince 0.22.0 `Grumlin` supports transactions when working with providers that supports them:\n```ruby\n# Using Transaction directly\ntx = g.tx\ngtx = tx.begin\ngtx.addV(:vertex).iterate\ntx.commit # or tx.rollback\n\n# Using with a block\ng.tx do |gtx|\n  gtx.addV(:vertex).iterate\n  # raise Grumlin::Rollback to manually rollback\n  # any other exception will also rollback the transaction and will be reraised \nend # commits automatically\n```\n\n#### IRB\n\nPlease check out [bin/console](bin/console) for inspiration. A similar trick may be applied to PRY.\n\nThen you need to reference it in your application.rb:\n```ruby\nconfig.console = MyRailsConsole\n```\n\n#### Testing\n\nGrumlin provides a couple of helpers to simplify testing code written with it.\n\n##### RSpec\n\nMake sure you have [async-rspec](https://github.com/socketry/async-rspec) installed.\n\n`spec_helper.rb` or `rails_helper.rb`:\n```ruby\nrequire 'async/rspec'\nrequire require \"grumlin/test/rspec\"\n...\nconfig.include_context(Async::RSpec::Reactor) # Runs async reactor\nconfig.include_context(Grumlin::Test::RSpec::GremlinContext) # Injects `g`, `__` and expressions, makes sure client is closed after every test\nconfig.include_context(Grumlin::Test::RSpec::DBCleanerContext) # Cleans the database before every test\n...\n```\n\nIt is highly recommended to use `Grumlin::Repository` and not trying to use lower level APIs as they are subject to \nchange.\n\n#### Using in a web app\n\nAs previously mentioned, `Grumlin` is built on top of the [async stack](https://github.com/socketry/async).\nThis basically means you'd either have to use [Falcon](https://github.com/socketry/falcon) as you application server,\nor you'd need to wrap every place where you use `Grumlin` into an `Async` block:\n\n```ruby\nAsync do\n  MyGrumlinRepository.some_query\nensure\n  Grumlin.close\nend\n```\n\n`Falcon` is preferred because it can keep connections to your Gremlin server open between requests. The only downside\nis that `ActiveRecord` currently does not play well with ruby's fiber scheduler so far, and it can block the event loop.\nWhen using `Falcon` you don't need explicit `Async` blocks.\n\nCurrently it's not recommended to use `ActiveRecord` with `Falcon`. If you still need access to a SQL database from your app,\nconsider using [socketry/db](https://github.com/socketry/db)\n\n#### Rails console\n\nIn order to make it possible to execute gremlin queries from the rails console you need to define\na custom console class. It should look somewhat like\n\n```ruby\nclass Async::RailsConsole\n  extend Grumlin::Repository\n\n  def start\n    self.class.shortcuts_from Shortcuts::Content\n\n    IRB::WorkSpace.prepend(Rails::Console::BacktraceCleaner)\n    IRB::ExtendCommandBundle.include(Rails::ConsoleMethods)\n\n    IRB.setup(binding.source_location[0], argv: [])\n    workspace = IRB::WorkSpace.new(binding)\n\n    begin\n      Async do\n        IRB::Irb.new(workspace).run(IRB.conf)\n      ensure\n        Grumlin.close\n      end\n    rescue StandardError, Interrupt, Async::Stop, IRB::Abort\n      retry\n    end\n  end\n\n  def inspect\n    'main'\n  end\n\n  def to_s\n    inspect\n  end\nend\n```\n\n#### AWS Neptune\n\nSee [docs/neptune.md](./docs/neptune.md)\n\n#### Sidekiq\n\nSee [docs/sidekiq.md](./docs/sidekiq.md)\n\n## Development\n\nBefore running tests make sure you have gremlin-server running on your computer. The simplest way to run it is using \n[docker-compose](https://docs.docker.com/compose/) and provided `docker-compose.yml` and `gremlin_server/Dockerfile`:\n\n    $ docker-compose up -d gremlin_server\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. \nYou can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update \nthe version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, \npush git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n### Adding new steps and expressions\nTo add a new step or an expression simple put it to the corresponding list in [definitions.yml](lib/definitions.yml)\nand run `rake definitions:format`. You don't need to properly sort the lists manually, the rake task will do it for you.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/babbel/grumlin. This project is intended to \nbe a safe, welcoming space for collaboration, and contributors are expected to adhere to the \n[code of conduct](https://github.com/babbel/grumlin/blob/master/CODE_OF_CONDUCT.md).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Grumlin project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/grumlin/blob/master/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbabbel%2Fgrumlin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbabbel%2Fgrumlin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbabbel%2Fgrumlin/lists"}