{"id":17482256,"url":"https://github.com/hausgold/boltless","last_synced_at":"2025-10-12T01:16:24.321Z","repository":{"id":65343344,"uuid":"524878167","full_name":"hausgold/boltless","owner":"hausgold","description":"A Ruby driver for the neo4j HTTP API","archived":false,"fork":false,"pushed_at":"2024-09-25T13:23:42.000Z","size":179,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-10-18T15:52:46.029Z","etag":null,"topics":["api","api-client","api-wrapper","driver","gem","graph-database","neo4j","neo4j-driver","neo4j-http-api","oss","ruby","ruby-gem"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/boltless","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/hausgold.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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":"2022-08-15T06:27:20.000Z","updated_at":"2024-09-25T13:23:46.000Z","dependencies_parsed_at":"2024-11-14T18:45:51.731Z","dependency_job_id":null,"html_url":"https://github.com/hausgold/boltless","commit_stats":{"total_commits":27,"total_committers":2,"mean_commits":13.5,"dds":0.2222222222222222,"last_synced_commit":"fc0d617e03f89488c20d6776516daf1c763d665a"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hausgold%2Fboltless","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hausgold%2Fboltless/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hausgold%2Fboltless/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hausgold%2Fboltless/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hausgold","download_url":"https://codeload.github.com/hausgold/boltless/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241718080,"owners_count":20008500,"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":["api","api-client","api-wrapper","driver","gem","graph-database","neo4j","neo4j-driver","neo4j-http-api","oss","ruby","ruby-gem"],"created_at":"2024-10-18T23:05:06.702Z","updated_at":"2025-10-12T01:16:24.315Z","avatar_url":"https://github.com/hausgold.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Boltless](doc/assets/project.svg)\n\n[![Continuous Integration](https://github.com/hausgold/boltless/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/hausgold/boltless/actions/workflows/test.yml)\n[![Gem Version](https://badge.fury.io/rb/boltless.svg)](https://badge.fury.io/rb/boltless)\n[![Test Coverage](https://automate-api.hausgold.de/v1/coverage_reports/boltless/coverage.svg)](https://knowledge.hausgold.de/coverage)\n[![Test Ratio](https://automate-api.hausgold.de/v1/coverage_reports/boltless/ratio.svg)](https://knowledge.hausgold.de/coverage)\n[![API docs](https://automate-api.hausgold.de/v1/coverage_reports/boltless/documentation.svg)](https://www.rubydoc.info/gems/boltless)\n\nThis is a Ruby driver/client for the [neo4j](https://neo4j.com/) [HTTP\nAPI](https://neo4j.com/docs/http-api/current/introduction/). Due to\ndriver/version incompatibility with the [neo4j-ruby-driver (via\nFFI/Seabolt)](https://github.com/neo4jrb/neo4j-ruby-driver) with MRI/CRuby and\nneo4j 4.x, we came up with the HTTP API solution. This driver/client was built\nwith performance in mind, to lower the JSON/HTTP API overhead (compared to the\nBolt protocol (binary)) as much as possible.\n\nAll the documented features of the HTTP API are implemented. JSON streams are\nenabled to improve performance and lower memory overhead on the server side.\n(unfortunately the HTTP API does not stream results in the\n[ndjson](http://ndjson.org/) format, which would even be better)\n\nThe driver uses a connection pool out of persistent HTTP connections which are\ntransparently being re-used. The HTTP transportation is done via\n[http.rb](https://github.com/httprb/http), a client library which implements\nthe HTTP protocol natively and outsources the parsing to native extensions.\n\nDue to the fact that the neo4j HTTP API produces and consumes data in the JSON\nformat, we chose the best in class JSON parsing/serializing libraries\navailable. For parsing (consuming the HTTP API response, database data) we use\n[FastJsonparser](https://github.com/anilmaurya/fast_jsonparser) a Ruby binding\nto the [simdjson library](https://simdjson.org/). And for serializing (sending\nthe Cypher statements) we use [Oj](https://github.com/ohler55/oj), a fast\nObject marshaller.\n\nWe focus on the bare-metal/low-level neo4j interaction, with raw Cypher. As an\nalternative solution with higher abstraction check out\n[neo4j-http](https://github.com/doximity/neo4j-http). It provides some clever\nCRUD abstrctions like `.upsert_node()` or `.find_nodes_by()`.\n\n- [Installation](#installation)\n- [Requirements](#requirements)\n- [Usage](#usage)\n  - [Configuration](#configuration)\n  - [Quick Start](#quick-start)\n  - [Transactions](#transactions)\n    - [Auto commit/rollback](#auto-commitrollback)\n    - [One Shot Transactions](#one-shot-transactions)\n  - [Result Mapping](#result-mapping)\n  - [Addtional Result Data/Formats](#addtional-result-dataformats)\n    - [Include query statistics](#include-query-statistics)\n    - [Return results in graph format](#return-results-in-graph-format)\n  - [Utilities](#utilities)\n    - [Prevent Cypher-Injection](#prevent-cypher-injection)\n    - [Prepare a Cypher statement](#prepare-a-cypher-statement)\n  - [Operations](#operations)\n- [Performance](#performance)\n- [Development](#development)\n- [Code of Conduct](#code-of-conduct)\n- [Contributing](#contributing)\n- [Releasing](#releasing)\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'boltless'\n```\n\nAnd then execute:\n\n```bash\n$ bundle\n```\n\nOr install it yourself as:\n\n```bash\n$ gem install boltless\n```\n\n## Requirements\n\n* [neo4j](https://neo4j.com/) (\u003e=3.5, all featured tested with 4.4.x)\n* [Ruby](https://www.ruby-lang.org/) (\u003e=3.2, tested on CRuby/MRI only, may work\n  with other implementations as well)\n\n## Usage\n\n### Configuration\n\nYou can configure the Boltless gem in serveral ways. The most relevant settings\nshould be set on an initializer file when used together with Rails. Here comes\na self descriptive example:\n\n```ruby\n# All the configured values here represent the Gem defaults.\nBoltless.configure do |config|\n  # The base URL of the neo4j HTTP API (port 7474 for HTTP, port 7473\n  # for HTTPS when configured at server side)\n  config.base_url = 'http://neo4j:7474'\n\n  # The username for the neo4j database (used for HTTP Basic Authentication)\n  config.username = 'neo4j'\n\n  # The password for the neo4j database (used for HTTP Basic Authentication)\n  config.password = 'neo4j'\n\n  # The default user database of the neo4j instance/cluster, for the\n  # community edition this is always +neo4j+, only a single user database is\n  # supported with the community editon. You can always specify which\n  # database to operate on, on each top-level querying method (eg.\n  # +Boltless.execute(.., database: 'custom')+\n  config.default_db = 'neo4j'\n\n  # The size of the connection pool, make sure it matches your application\n  # server (eg. Puma) thread pool size, in order to avoid\n  # timeouts/bottlenecks\n  config.connection_pool_size = 10\n\n  # The seconds to wait for a connection from the pool,\n  # when all connections are currently in use\n  config.connection_pool_timeout = 15.seconds\n\n  # The overall timeout for a single HTTP request (including connecting,\n  # transmitting and response completion)\n  config.request_timeout = 10.seconds\n\n  # We allow the neo4j server to bootup for the configured time. This allows\n  # parallel starts of the user application and the neo4j server, without\n  # glitching.\n  config.wait_for_upstream_server = 30.seconds\n\n  # Configure a logger for the gem\n  config.logger = Logger.new($stdout).tap do |logger|\n    logger.level = :warn\n  end\n\n  # Whenever we should log the neo4j queries, including benchmarking. When\n  # disabled we reduce the logging overhead even more as no debug logs hit\n  # the logger at all. Enable this for testing purposes or for local\n  # development. (Heads up: No parameter sanitation is done, so passwords etc\n  # will be logged with this enabled) Setting the value to +:debug+ will\n  # print the actual Cypher statements before any request is sent. This may\n  # be helpful inspection of slow/never-ending Cypher statements.\n  config.query_log_enabled = false\n\n  # We allow the http.rb gem to be configured by the user for special needs.\n  # Just assign a user given block here and you can reconfigure the client.\n  # Just make sure to return the configured +HTTP::Client+ instance\n  # afterwards.\n  config.http_client_configure = proc do |connection|\n    # Enable the request/response instrumentation\n    # connection.use(instrumentation: {\n    #   instrumenter: ActiveSupport::Notifications.instrumenter\n    # })\n\n    # Return the configured connection\n    connection\n  end\n\n  # We allow to check/modify the raw neo4j response body string before it\n  # gets JSON parsed and handled. This may be handy for some low-level\n  # analyses on the raw responses. The first argument of the user defined\n  # block will be the raw response body string and the second it the full\n  # HTTP response object (+HTTP::Response+). The user defined block MUST\n  # return a JSON parsable string (eg. the original input) in order to\n  # continue regular processing.\n  config.raw_response_handler = proc do |body, response|\n    # Do some checks on the raw/unparsed response body ..\n\n    # Return the original (or modified) response body\n    body\n  end\nend\n```\n\n### Quick Start\n\nTL;DR: You just want to run a single Cypher statement, do it like that:\n\n```ruby\nresult = Boltless.execute!('RETURN date() AS date')\n# =\u003e #\u003cBoltless::Result\n#      columns=[:date],\n#      rows=[#\u003cBoltless::ResultRow columns=[:date],\n#                                  values=[\"2022-08-16\"],\n#                                  meta=[{:type=\u003e\"date\"}], graph=nil\u003e],\n#      stats=nil\u003e\n\nresult.value\n# =\u003e \"2022-08-16\"\n```\n\nThis way the Boltless gem sends a single HTTP request to the neo4j HTTP API to\nperform the statement and it fetches the results in a lazily mapped way. With\nthe result mapping you have to pay a little performance penalty, but its only\n~3.57% overhead, so its enabled by default. To turn it off, add `raw_results:\ntrue` as addtional keyword argument to all top-level calls. (eg.\n`Boltless.execute!('RETURN 1', raw_results: true)`)\n\n### Transactions\n\nWe allow the usage of the full [neo4j Cypher transaction\nAPI](https://neo4j.com/docs/http-api/current/actions/transaction-flow/).\n\n![neo4j Transaction Workflow](./doc/assets/http-cypher-transaction-api-flow.png)\n\n* **BEGIN**: `Boltless.transaction(\u0026block)`, `Boltless.transaction!(\u0026block)`\n  * **RUN**: `tx.run`, `tx.run!`, `tx.run_in_batch`, `tx.run_in_batch!`\n  * **COMMIT**: `tx.commit`, `tx.commit!`\n  * **ROLLBACK**: `tx.rollback`, `tx.rollback!`\n* **BEGIN \u0026 COMMIT**: `Boltless.execute`, `Boltless.execute!`, (aliases are:\n  `Boltless.query`, `Boltless.query!`, `Boltless.read`, `Boltless.read!`,\n  `Boltless.write`, `Boltless.write!`)\n  `Boltless.one_shot(\u0026block)`, `Boltless.one_shot!(\u0026block)`\n\nAll the transaction workflow/Cypher executing methods are available in a bang\nand non-bang variant. The bang variants will raise on network issues (like\nconnection failures, timeouts, etc), user issues (like try a write operation\ninside a read-only transaction, Cypher syntax errors, etc). The non-bang\nvariants will suppress these and will return `nil` or `false` (based on the\ncontext). This reflects common Rails/ActiveRecord method behaviour (eg.\n`Model.save`/`Model.save!`).\n\n#### Auto commit/rollback\n\nWhen using the `Boltless.transaction` or `Boltless.transaction!` methods, we\nautomatically handle the transaction for you. After the end of the user given\nblock, the transaction will be committed. When an exception is raised from\nwithin the user given block, then the transaction is automatically rolled back.\nThis is also true for bad interactions with neo4j (eg. sending an Cypher\nstatement with syntax errors, etc).\n\nHere we setup a transaction with the read-write mode,\non the default database (`neo4j`):\n\n```ruby\nBoltless.transaction! do |tx|\n  count = tx.run('MATCH (n) RETURN count(n)').value\n  # =\u003e 5\n\n  # You can also run multiple Cypher statements as\n  # batch (within a single HTTP request) like this:\n  tx.run_in_batch!(\n    ['CREATE (n:User { name: $name, email: $email })',\n     name: 'Klaus', email: 'klaus@example.com'],\n    ['CREATE (n:User { name: $name, email: $email })',\n     name: 'Bernd', email: 'bernd@example.com'],\n    ['CREATE (n:User { name: $name, email: $email })',\n     name: 'Uwe', email: 'uwe@example.com']\n  )\n\n  # Works also for read/query statements:\n  pp tx.run_in_batch!(\n    ['RETURN 1 AS count'],\n    ['RETURN date()'],\n    ['MATCH (n:User { name: $name }) RETURN n.email', name: 'Klaus'],\n    ['MATCH (n:User) RETURN n.name AS name, n.email AS email LIMIT 10']\n  ).map(\u0026:values)\n  # =\u003e [\n  #      # Mapped result values of first statement\n  #      [{:count=\u003e1}],\n  #      # Mapped result values of second statement\n  #      [{:date=\u003e\"2022-08-16\"}],\n  #      # Mapped result values of third statement\n  #      [],\n  #      # Mapped result values of fourth statement\n  #      [\n  #        {:name=\u003e\"Klaus\", :email=\u003e\"klaus@example.com\"},\n  #        {:name=\u003e\"Bernd\", :email=\u003e\"bernd@example.com\"},\n  #        {:name=\u003e\"Uwe\", :email=\u003e\"uwe@example.com\"}\n  #      ]\n  #    ]\n\n  # The result of the user block is returned as result\n  tx.run('MATCH (n) WHERE n.name = $name RETURN count(n)', name: 'Klaus')\nend\n# =\u003e #\u003cBoltless::Result\n#      columns=[:\"count(n)\"],\n#      rows=[#\u003cBoltless::ResultRow\n#              columns=[:\"count(n)\"],\n#              values=[9],\n#              meta=[nil],\n#              graph=nil\u003e],\n#      stats=nil\u003e\n```\n\n#### One Shot Transactions\n\nThe neo4j HTTP API allows to send one or multiple Cypher statements in a single\nshot transaction endpoint. This can be used with the Boltless gem in\ntwo-flavors. The first is `Boltless.execute(!)` (and their aliases\n`Boltless.query(!)`, `Boltless.read(!)`, `Boltless.write(!)`). This one allows\na single Cypher query to be send in this one-shot manner. The other is\n`Boltless.one_shot(!)` which allows to send one/more Cypher statements from a\nuser given block.\n\n```ruby\n# Setup a read-only one-shot transaction (see +BEGIN \u0026 COMMIT+)\nBoltless.one_shot!(:read) do |tx|\n  # In this scope you can just prepare the statements which are sent in \"one\n  # shot\" (a single HTTP request) to the neo4j HTTP API, therefore you cannot\n  # access the result of each statement after it was added\n\n  tx.add('MATCH (n) RETURN count(n)')\n  # =\u003e \u003cBoltless::StatementCollector ..\u003e\n\n  tx.add('MATCH (n) WHERE n.name = $name RETURN count(n)', name: 'Klaus')\n  # =\u003e \u003cBoltless::StatementCollector ..\u003e\nend\n# =\u003e [\n#      # Result of the first statement\n#      #\u003cBoltless::Result ...\u003e,\n#      # Result of the second statement\n#      #\u003cBoltless::Result ...\u003e\n#    ]\n```\n\n### Result Mapping\n\nThe Boltless gem allows you to access the Cypher statement result data in a\nconvenient way like this:\n\n```ruby\nBoltless.execute!(\n  'MATCH (n:User) RETURN n.name AS name, n.email AS email LIMIT 10',\n).map { |row| \"#{row[:name]} (#{row[:email]})\" }\n# =\u003e [\"Klaus (klaus@example.com)\", \"Bernd (bernd@example.com)\",\n#     \"Uwe (uwe@example.com)\"]\n```\n\nIf you prefer to work on the raw HTTP API results (eg. for performance\nreasons), you can add `raw_results: true` as addtional keyword argument to all\ntop-level calls. This is how it looks like:\n\n```ruby\nBoltless.execute!(\n  'MATCH (n:User) RETURN n.name AS name, n.email AS email LIMIT 10',\n  raw_results: true\n)\n# =\u003e {:columns=\u003e[\"name\", \"email\"],\n#     :data=\u003e\n#      [{:row=\u003e[\"Klaus\", \"klaus@example.com\"], :meta=\u003e[nil, nil]},\n#       {:row=\u003e[\"Bernd\", \"bernd@example.com\"], :meta=\u003e[nil, nil]},\n#       {:row=\u003e[\"Uwe\", \"uwe@example.com\"], :meta=\u003e[nil, nil]}]}\n```\n\nCheckout the [fixtures directory](./spec/fixtures/files) for more HTTP API\nresponse examples.\n\n### Addtional Result Data/Formats\n\n#### Include query statistics\n\nWe allow to fetch the query statistics on each individual Cypher statement. The\nonly thing you need to do is to specify `with_stats: true` as Cypher statement\nparameter, like this:\n\n```ruby\nBoltless.execute!(\n  'CREATE (n:User { name: $name, email: $email })',\n  name: 'Monika',\n  email: 'monika@example.com',\n  with_stats: true\n)\n# =\u003e #\u003cBoltless::Result\n#     columns=[],\n#     rows=[],\n#     stats={:contains_updates=\u003etrue,\n#      :nodes_created=\u003e1,\n#      :nodes_deleted=\u003e0,\n#      :properties_set=\u003e2,\n#      :relationships_created=\u003e0,\n#      :relationship_deleted=\u003e0,\n#      :labels_added=\u003e1,\n#      :labels_removed=\u003e0,\n#      :indexes_added=\u003e0,\n#      :indexes_removed=\u003e0,\n#      :constraints_added=\u003e0,\n#      :constraints_removed=\u003e0,\n#      :contains_system_updates=\u003efalse,\n#      :system_updates=\u003e0}\u003e\n```\n\nSee the [HTTP API\ndocumentation](https://neo4j.com/docs/http-api/current/actions/include-query-statistics/)\nfor further details.\n\n#### Return results in graph format\n\n\u003e If you want to understand the graph structure of nodes and relationships\n\u003e returned by your query, you can specify the `graph` results data format. This\n\u003e is useful when you want to visualize the graph structure. The format collates\n\u003e all the nodes and relationships from all columns of the result, and also\n\u003e flattens collections of nodes and relationships, including paths.\n\n```ruby\nBoltless.execute!(\n  'MATCH (n:User) RETURN n.name AS name, n.email AS email LIMIT 10',\n  result_as_graph: true\n)\n# =\u003e #\u003cBoltless::Result\n#     columns=[:name, :email],\n#     rows=[#\u003cBoltless::ResultRow\n#       columns=[:name, :email],\n#       values=[\"Klaus\", \"klaus@example.com\"],\n#       meta=[nil, nil],\n#       graph={:nodes=\u003e[], :relationships=\u003e[]}\u003e,\n#      [+3 ..]],\n#     stats=nil\u003e\n```\n\nSee the [HTTP API\ndocumentation](https://neo4j.com/docs/http-api/current/actions/return-results-in-graph-format/)\nfor further details.\n\n### Utilities\n\nThe Boltless gem ships with a set of helpers and utilities for the daily\nbusiness of a graph database developer. Check out the following sections!\n\n#### Prevent Cypher-Injection\n\nJust like SQL-Injection, the same thing is doable at your neo4j database with\nthe Cypher query language. The ultimate Pro Tip is to avoid constructing a\nCypher query (concatenation) with user given inputs. Instead use Cypher\nparameters, just like in ActiveRecord/Hibernate this way:\n\n```ruby\nclass UsersController \u003c ApplicationController\n  # [..]\n\n  def new\n    # Bad: Don't do this, because of Cypher-Injection.\n    Boltless.execute!(\"CREATE (n:User { name: '#{params[:name]}' })\")\n\n    # Good: Use Cypher parameters.\n    Boltless.execute!('CREATE (n:User { name: $name })', name: params[:name])\n  end\nend\n```\n\nYay. This is great. But sometimes this is just not sufficient, because you\ncannot wildy place parameters inside the Cypher statement. Like this:\n\n```cypher\n// We want to check if a specific user node has a relationship to a specific\n// payment node, which reflects that the user can read and write it\n MATCH (s:User { id: $subject_id })\n       -[r:READ|WRITE]-\u003e\n       (o:Payment { id: $payment_id })\nRETURN 1\n LIMIT 1\n```\n\nThen you cannot use a Cypher parameter for the `s:User` (like\n`s:$subject_label`) or `-[r:READ:WRITE]-\u003e` (like `-[r:${types}]-\u003e`) parts\ndirectly. You could do some glichting with the where statement, but this would\nbe ugly or non-index using.\n\nTherefore, the Boltless gem ships an utilities to help you with that. See\n[Prepare a Cypher statement](#prepare-a-cypher-statement).\n\n#### Prepare a Cypher statement\n\nOk, you are aware of the Cypher parameters and their shortcommings. The\nBoltless gem comes with a utilities named `Boltless.build_cypher`. Use it like\nthat:\n\n```ruby\ncypher = Boltless.build_cypher(\n  subject_label: subject.model_name,\n  object_label: object.model_name,\n  predicate_types: [:read, :write]\n) do\n  \u003c\u003c~CYPHER\n     MATCH (s:%\u003csubject_label\u003es { id: $subject_id })\n           -[r:%\u003cpredicate_types\u003es]-\u003e\n           (o:%\u003cobject_label\u003es { object_id: $object_id })\n    RETURN 1\n     LIMIT 1\n  CYPHER\nend\n# =\u003e \" MATCH (s:User { id: $subject_id })\\n\" +\n#    \"       -[r:READ|WRITE]-\u003e\\n\" +\n#    \"       (o:Payment { id: $object_id })\\n\" +\n#    \"RETURN 1\\n\" +\n#    \" LIMIT 1\"\n```\n\nThis utility takes care of correct Cypher escaping, based on the parameters (by\nname) you pass in. Parameters ending with:\n\n* `_label(s)` are treated as node labels and are camelized\n  and joined with colons\n* `_type(s)` are treated as relationship types and are uppercased\n  and joined with pipes\n* `_str(s)` are treated as free-text, it will be escaped properly\n  with double quotes\n\nWhen the given string(s) include special characters they will be escaped with\nbackticks respectively.\n\n**Heads up!** Do not use the `Boltless.build_query` utility blindly with\n**ALL** of your variables. Whenever possible use the native Cypher parameters\nas this helps the neo4j database to re-use already parsed Cypher statements,\nand therefore results in better performance.\n\nFurthermore, you can use the `Boltless.build_cypher` utility directly within\ntransaction contexts like this:\n\n```ruby\n# Can directly be used in a regular transaction:\nBoltless.transaction! do |tx|\n  # tx: Boltless::Transaction\n\n  cypher = tx.build_cypher(\n    # [your parameters]\n  ) do\n    # [your Cypher template]\n  end\n  tx.run!(cypher, param_a: 7892)\nend\n\n# And also in a one-shot transaction:\nBoltless.one_shot! do |tx|\n  # tx: Boltless::StatementCollector\n\n  cypher = tx.build_cypher(\n    # [your parameters]\n  ) do\n    # [your Cypher template]\n  end\n  tx.add(cypher, param_a: 7892)\nend\n```\n\n### Operations\n\nWe also allow to handle the common neo4j database operations in a seamless way.\nThis includes adding/dropping indexes/constraints, full database cleanups and\nindex/constraints listing. Take a look at the following example:\n\n```ruby\n# Add a new index for user (+User+ labeled nodes) +id+ properties,\n# this may be helpful for querying/merging later\nBoltless.add_index(name: 'user_id',\n                   for: '(n:User)',\n                   on: 'n.id')\n\n# Add a new constraint, to enforce unique user\n# emails across all +User+ labeled nodes\nBoltless.add_constraint(name: 'uniq_user_email',\n                        for: '(n:User)',\n                        require: 'n.email IS UNIQUE')\n\n# Fetch the index names of the +neo4j+ database,\n# you can also specify the +database+ argument to every querying\n# method in order to specify the database in use\nBoltless.index_names(database: 'neo4j')\n# =\u003e [\"user_id\"]\n\n# Fetch the constraint names of the +neo4j+ database (by default)\nBoltless.constraint_names\n# =\u003e [\"uniq_user_email\"]\n\n# Drop an index by its name. When it does not exists\n# this method does not raise errors.\nBoltless.drop_index('user_id')\n\n# Drop a constraint by its name. When it does not exists\n# this method does not raise errors.\nBoltless.drop_constraint('uniq_user_email')\n\n# Clear the neo4j database (remove all indexes, constraints,\n# nodes and relationships)\nBoltless.clear_database!\n```\n\n## Performance\n\nFetching a large result (68.845 rows, 8.73 MB raw JSON) from neo4j results in:\n\n* 1.52971222s (avg), when the raw result is used (no restructuring in Ruby)\n* 1.58435011s (avg, ±1.0x same-ish, ~3.57%), when the result is restructured in\n  Ruby (but lazily mapped)\n\nAnother interesting comparison looks at some of our production workloads:\n\n| Operation(avg)  | Bolt (neo4j-ruby-driver)   | HTTP API (neo4j-http)   | HTTP API (Boltless)    |\n| --------------- | -------------------------- | ----------------------- | ---------------------  |\n| read many rows  | 191ms (±1x base)           | 316ms (1.7x slower)     | 258ms (1.4x slower)    |\n| read single row | 52ms (±1x base)            | 52ms (±1.0x same-ish)   | 53ms (±1.0x same-ish)  |\n| write           | 350ms (±1x base)           | 289ms (1.2x faster)     | 291ms (1.2x faster)    |\n|                 | 198ms (±1x base)           | 219ms (1.1x slower)     | 201ms (±1.0x same-ish) |\n\n## Development\n\nAfter checking out the repo, run `make install` to install dependencies. Then,\nrun `make test` to run the tests. You can also run `make shell-irb` for an\ninteractive prompt that will allow you to experiment.\n\n## Code of Conduct\n\nEveryone interacting in the project codebase, issue tracker, chat\nrooms and mailing lists is expected to follow the [code of\nconduct](./CODE_OF_CONDUCT.md).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at\nhttps://github.com/hausgold/boltless. Make sure that every pull request adds\na bullet point to the [changelog](./CHANGELOG.md) file with a reference to the\nactual pull request.\n\n## Releasing\n\nThe release process of this Gem is fully automated. You just need to open the\nGithub Actions [Release\nWorkflow](https://github.com/hausgold/boltless/actions/workflows/release.yml)\nand trigger a new run via the **Run workflow** button. Insert the new version\nnumber (check the [changelog](./CHANGELOG.md) first for the latest release) and\nyou're done.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhausgold%2Fboltless","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhausgold%2Fboltless","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhausgold%2Fboltless/lists"}