From 7bcc48f670c3457f683ac4b816b70b75476213da Mon Sep 17 00:00:00 2001 From: tavrelkate Date: Sun, 2 Nov 2025 15:14:51 +0300 Subject: [PATCH 1/6] README, CONTRIBUTING. --- CONTRIBUTING.md | 54 +++++++------------- README.md | 132 ++++++++++++++++++++++++++---------------------- 2 files changed, 91 insertions(+), 95 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 97024a1..e32a5e8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,56 +1,40 @@ -# Contribution Guide -**Contributions are very welcome!** Whether it's a small typo fix, a new operator, a better example, or a larger refactor — your help makes this gem better. If you're unsure where to start, open an issue and we can figure it out together. +# Contribution Guide -## Quick links (README) -- **Install & Quick start:** see [README → Install](./README.md#install) and [Quick start](./README.md#quick-start) -- **How the engine works (value vs lazy/enumerable):** see [README → How](./README.md#how) -- **Supported operations:** see [README → Supported Operations (Built‑in)](./README.md#supported-operations-built-in) -- **Adding operations:** see [README → Adding Operations](./README.md#adding-operations) -- **JsonLogic semantics (comparisons & truthiness):** see [README → JsonLogic Semantic](./README.md#jsonlogic-semantic) -- **Compliance & tests:** see [README → Compliance and tests](./README.md#compliance-and-tests) +**Contributions of every size are very welcome!** Whether it's a small typo fix, a new operator, a better example, or a larger refactor — your help makes this gem better. If you're unsure where to start, open an issue and we can figure it out together. -## Running tests & compliance +We keep this gem small and sharp. If you can make it simpler – do it. If you can make it clearer – do it. -Use the **same commands** as in the README’s [Compliance and tests](./README.md#compliance-and-tests) section. +## Quick links +See **[README](./README.md)** — everything you need to understand the JsonLogic rule tree. ## How to contribute -1. Fork the repo and create a branch from `main`. -2. Make your change (code, docs, or tests). -3. Include examples for new operators or pretty mappings. -4. Update `README.md` and docs if public behavior changes. -5. Run your tests. -6. Open a Pull Request and describe: - - What changed and why - - Any breaking impacts - - Before/after output if applicable +Fork. Branch. Change. Test. PR. + ### Adding an operator -Operator creation and registration are described in the README’s [Adding Operations](./README.md#adding-operations) section. +Read **[§ Adding Operations](./README.md#adding-operations)**. Prefer the class‑based API. The Proc/Lambda DSL is fine for a quick spike; promote to a class before merge. + +Auto‑registration works for classes under [lib/json_logic/operations/](./lib/json_logic/operations/). -Auto-registration is enabled for classes under `lib/json_logic/operations/`. ## Coding style -- **Follow [README → Security](./README.md#security)** -- Prefer simple, composable code. -- Match the semantics from the official docs: - - Operations: - - Truthiness: -- When relevant, use `using JsonLogic::Semantics` to align comparisons/truthiness with JsonLogic. +- **Follow [§ Security](./README.md#security)**. +- **Follow [§ JsonLogic Semantic](./README.md#jsonlogic-semantic)**. +- Prefer small, composable code with real examples. ## PR checklist -- [ ] Tests or examples included (when applicable) -- [ ] Compliance suite passes (see [README → Compliance and tests](./README.md#compliance-and-tests)) -- [ ] README/docs updated if user‑facing behavior changed +- [ ] Tests or examples included (when applicable). +- [ ] Compliance suite passes (see **[§ Compliance and tests](./README.md#compliance-and-tests)**). +- [ ] **[README](./README.md)** and related docs updated if user‑facing behavior changed. +- [ ] Version bumped in [Gem Version File](./lib/json_logic/version.rb). +- [ ] **[CHANGELOG](./CHANGELOG.md)** updated. ## Versioning -We use **Semantic Versioning** (MAJOR.MINOR.PATCH). - -- Bump `lib/json_logic/version.rb` using SemVer. -- Update `CHANGELOG.md`. +We use **[Semantic Versioning](https://semver.org/)**. diff --git a/README.md b/README.md index 26a23f3..b05b69a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ + + # json-logic-rb Ruby implementation of [JsonLogic](https://jsonlogic.com/) — simple and extensible. Ships with a compliance runner for the official test suite. @@ -27,9 +29,20 @@ JsonLogic rules are JSON trees. The engine walks that tree and returns a Ruby va ## Install +Download the gem locally ```bash gem install json-logic-rb ``` +If needed – add to your `Gemfile` + +```ruby +gem "json-logic-rb" +``` + +Then install: +```shell +bundle install +``` ## Quick start @@ -51,7 +64,7 @@ JsonLogic.apply({ "var" => "user.age" }, { "user" => { "age" => 42 } }) ## How -There are **two types of operations** in this implementation: Default Operations and Lazy Operations. +There are **two types of operations**: Default Operations and Lazy Operations. ### 1. Default Operations @@ -66,19 +79,19 @@ This matches the reference behavior for arithmetic, comparisons, string operatio ### 2. Lazy Operations -Some operations must control **whether** and **when** their arguments are evaluated. They implement branching, short-circuiting, or “apply a rule per item” semantics. For these **Lazy Operations**, the engine passes **raw sub-rules** and current data. The operator then evaluates only the sub-rules it actually needs. +Some operations must control **whether** and **when** their arguments are evaluated. They implement branching, short-circuiting, or “apply a rule per item” semantics. For these **Lazy Operations**, the engine passes raw sub-rules and data. The operator then evaluates only the sub-rules it actually needs. **Groups and references:** - **Branching / boolean control** — `if`, `?:`, `and`, `or`, `var` - [Logic & boolean operations](https://jsonlogic.com/operations.html#logic-and-boolean-operations) • [Truthiness](https://jsonlogic.com/truthy.html) + [Logic & boolean operations](https://jsonlogic.com/operations.html#logic-and-boolean-operations) - **Enumerable operators** — `map`, `filter`, `reduce`, `all`, `none`, `some` [Array operations](https://jsonlogic.com/operations.html#array-operations) **How enumerable per-item evaluation works:** -1. The first argument is a rule that returns the list of items — evaluated **once** to a Ruby array. +1. The first argument is a rule that returns the list of items — evaluated once to a Ruby array. 2. The second argument is the per-item rule — evaluated **for each item** with that item as the **current root**. 3. For `reduce`, the current item is also available as `"current"`, and the running total as `"accumulator"`. @@ -110,11 +123,9 @@ JsonLogic.apply( ### Why laziness matters? -Lazy operations **prevent evaluation** of branches you do not need. If division by zero raised an error (hypothetically), lazy control would avoid it: +Lazy operations prevent evaluation of branches you do not need. If division by zero raised an error (hypothetically), lazy control would avoid it. ```ruby -# "or" short-circuits: 1 is truthy, so the right side is NOT evaluated. -# If the right side were evaluated eagerly, it would attempt 1/0 (error). JsonLogic.apply({ "or" => [1, { "/" => [1, 0] }] }) # => 1 ``` @@ -124,7 +135,7 @@ JsonLogic.apply({ "or" => [1, { "/" => [1, 0] }] }) ## Supported Operations (Built‑in) -Below is a list that mirrors the sections on [jsonlogic.com/operations.html](https://jsonlogic.com/operations.html) and shows what this gem (library) implements. From the reference page’s list, everything except `log` is implemented. +Below is a list that mirrors the sections on [Json Logic Website Opeations](https://jsonlogic.com/operations.html) and shows what this gem implements. | Operator | Supported | |---|---:| @@ -169,93 +180,93 @@ Below is a list that mirrors the sections on [jsonlogic.com/operations.html](htt ## Adding Operations -Need a custom operation? It’s straightforward. +Need a custom Operation? It’s straightforward. Start small with a Proc or Lambda. If needed – promote it to a Class. -### Quick — register a Proc or Lambda -Register little anonymous functions, by passing a Proc or Lambda. -```ruby -JsonLogic.add_operation("times2") { |(value), _| value.to_i * 2 } -``` +### Enable JsonLogic Semantics (optional) +Enable semantics to mirror JsonLogic’s comparison/truthiness in Ruby. -Once the function added, you can use it in your logic. +See [§JsonLogic Semantic](#jsonlogic-semantic) for details. -```ruby -JsonLogic.apply({ "times2" => [21] }) -# => 42 -``` -Is useful for rapid prototyping with minimal boilerplate; -Later you can “promote” it into a full class or use additional features. +### Parameters +Operator function use a consistent call shape: -### 1) Pick the Operation type -Choose one of: -- **Default** -```ruby -class JsonLogic::Operations::StartsWith < JsonLogic::Operation; end -``` -For anonymous functions: +- First parameter: **array of operator arguments** (you can destructure it). + +- Second parameter: current **data**. ```ruby -JsonLogic.add_operation("starts_with", lazy: false) do; end +->((string, prefix), data) { string.to_s.start_with?(prefix.to_s) } ``` -- **Lazy** + +### Proc / Lambda + +Pick the Operation type. + +[Default Operation](#1-default-operations) mode passes values. + ```ruby -class JsonLogic::Operations::StartsWith < JsonLogic::LazyOperation; end +JsonLogic.add_operation("starts_with") do |(string_value, prefix_value), _data| + string_value.to_s.start_with?(prefix_value.to_s) +end ``` -For anonymous functions: +[Lazy Operation](#2-lazy-operations) mode passes raw rules (you evaluate them): + ```ruby -JsonLogic.add_operation("starts_with", lazy: true) do; end +JsonLogic.add_operation("starts_with", lazy: true) do |(string_rule, prefix_rule), data| + string_value = JsonLogic.apply(string_rule, data) + prefix_value = JsonLogic.apply(prefix_rule, data) + string_value.to_s.start_with?(prefix_value.to_s) +end ``` -See [§How](#how) for details. +See [§How](https://github.com/tavrelkate/json-logic-rb?tab=readme-ov-file#how) for details. -### 2) Enable JsonLogic Semantics (optional) -Enable semantics to mirror JsonLogic’s comparison/truthiness in Ruby: +Use immediately: ```ruby -using JsonLogic::Semantics +JsonLogic.apply({ "starts_with" => [ { "var" => "email" }, "admin@" ] }) ``` -See [§JsonLogic Semantic](#jsonlogic-semantic) for details. - -### 3) Create an Operation and provide a machine name - -Operation methods use a consistent call shape. +### Class -- The first parameter is the **array of operator arguments**. -- The second is the current **data**. +Pick the Operation type. +It has the same call shape. - -Thanks to Ruby’s destructuring, you can unpack the argument array right in the method signature. +[Default Operation](#1-default-operations) – Inherit `JsonLogic::Operation`. ```ruby class JsonLogic::Operations::StartsWith < JsonLogic::Operation def self.name = "starts_with" - def call((str, prefix), _data) - # str, prefix are ALREADY evaluated to Ruby values - str.to_s.start_with?(prefix.to_s) - end + def call(string_value, prefix_value), _data) = string_value.to_s.start_with?(prefix_value.to_s) end ``` -### 4) Register the new operation +[Lazy Operation](#2-lazy-operations) – Inherit `JsonLogic::LazyOperation`. + +Register explicitly: ```ruby JsonLogic::Engine.default.registry.register(JsonLogic::Operations::StartsWith) ``` -After registration, use it in rules: +Now, Class is ready to use. -```json -{ "starts_with": [ { "var": "email" }, "admin@" ] } +```ruby +JsonLogic.apply({ "starts_with" => [ { "var" => "email" }, "admin@" ] }) ``` + + + + + ## JsonLogic Semantic All supported Operations follow JsonLogic semantics. @@ -295,14 +306,14 @@ In Ruby, only `false` and `nil` are falsey. In JsonLogic empty strings and empty # => true ``` -While JsonLogic as was mentioned before has it's own truthiness: +While JsonLogic as was mentioned before has it's own truthiness. **In Ruby (with JsonLogic Semantic):** ```ruby -include JsonLogic::Semantics +using JsonLogic::Semantics -truthy?([]) +!![] # => false ``` @@ -343,9 +354,10 @@ ruby script/compliance.rb spec/tmp/tests.json ## Security -- Rules are **data**, not code; no Ruby eval. -- Operations are **pure** (no IO, no network, no shell). -- Rules have **no write** access to anything. +- RULES ARE DATA; NO RUBY EVAL; +- OPERATIONS ARE PURE; NO IO, NO NETWORK; NO SHELL; +- RULES HAVE NO WRITE ACCESS TO ANYTHING; + ## License From 439185e99ebfa4ca96414aeb4252fe02e4deddd7 Mon Sep 17 00:00:00 2001 From: tavrelkate Date: Sun, 2 Nov 2025 15:29:46 +0300 Subject: [PATCH 2/6] README. --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b05b69a..e513e4f 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,13 @@ Download the gem locally ```bash gem install json-logic-rb ``` -If needed – add to your `Gemfile` +If needed – add to your Gemfile ```ruby gem "json-logic-rb" ``` -Then install: +Then install ```shell bundle install ``` @@ -68,7 +68,7 @@ There are **two types of operations**: Default Operations and Lazy Operations. ### 1. Default Operations -For **Default Operations**, the engine **evaluates all arguments first** and then calls the operator with the **resulting Ruby values**. +For **Default Operations**, the it **evaluates all arguments first** and then calls the operator with the **resulting Ruby values**. This matches the reference behavior for arithmetic, comparisons, string operations, and other pure operations that do not control evaluation order. **Groups and references:** @@ -130,7 +130,7 @@ JsonLogic.apply({ "or" => [1, { "/" => [1, 0] }] }) # => 1 ``` -> In this gem `/` returns `nil` on divide‑by‑zero, but these examples show **why** lazy evaluation is required by the spec: branching and boolean operators must **not** evaluate unused branches. +> In this gem `/` returns `nil` on divide‑by‑zero, but these examples show why lazy evaluation is required by the spec: branching and boolean operators must not evaluate unused branches. ## Supported Operations (Built‑in) @@ -185,7 +185,7 @@ Need a custom Operation? It’s straightforward. Start small with a Proc or Lamb ### Enable JsonLogic Semantics (optional) -Enable semantics to mirror JsonLogic’s comparison/truthiness in Ruby. +Enable semantics to mirror JsonLogic’s comparison and truthiness in Ruby. See [§JsonLogic Semantic](#jsonlogic-semantic) for details. @@ -292,12 +292,13 @@ As JsonLogic primary developed in JavaScript it inherits JavaScript's type coerc ```ruby using JsonLogic::Semantics -1 >= "1.0" # => true +1 >= "1.0" +# => true ``` ### Truthiness -JsonLogic’s truthiness differs from Ruby’s (see ). +JsonLogic’s truthiness differs from Ruby’s (see [Json Logic Website Truthy and Falsy](https://jsonlogic.com/truthy.html)). In Ruby, only `false` and `nil` are falsey. In JsonLogic empty strings and empty arrays are also falsey. **In Ruby:** From 6e5792aa96934cb812b33abe5ca78b661815f51f Mon Sep 17 00:00:00 2001 From: tavrelkate Date: Sun, 2 Nov 2025 15:36:39 +0300 Subject: [PATCH 3/6] README, CONTRIBUTING. --- CONTRIBUTING.md | 6 +++--- README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e32a5e8..9f9be10 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ We keep this gem small and sharp. If you can make it simpler – do it. If you c ## Quick links -See **[README](./README.md)** — everything you need to understand the JsonLogic rule tree. +See **[README](./README.md)** — everything you need to understand the JsonLogic rule tree specifics in Ruby. ## How to contribute Fork. Branch. Change. Test. PR. @@ -16,7 +16,7 @@ Fork. Branch. Change. Test. PR. ### Adding an operator -Read **[§ Adding Operations](./README.md#adding-operations)**. Prefer the class‑based API. The Proc/Lambda DSL is fine for a quick spike; promote to a class before merge. +Read **[§ Adding Operations](./README.md#adding-operations)**. Prefer the class‑based API. The Proc & Lambda DSL is fine for a quick spike; promote to a class before merge. Auto‑registration works for classes under [lib/json_logic/operations/](./lib/json_logic/operations/). @@ -31,7 +31,7 @@ Auto‑registration works for classes under [lib/json_logic/operations/](./lib/j - [ ] Tests or examples included (when applicable). - [ ] Compliance suite passes (see **[§ Compliance and tests](./README.md#compliance-and-tests)**). -- [ ] **[README](./README.md)** and related docs updated if user‑facing behavior changed. +- [ ] **[README](./README.md)** updated if user‑facing behavior changed. - [ ] Version bumped in [Gem Version File](./lib/json_logic/version.rb). - [ ] **[CHANGELOG](./CHANGELOG.md)** updated. diff --git a/README.md b/README.md index e513e4f..3ce1dfd 100644 --- a/README.md +++ b/README.md @@ -299,7 +299,7 @@ using JsonLogic::Semantics ### Truthiness JsonLogic’s truthiness differs from Ruby’s (see [Json Logic Website Truthy and Falsy](https://jsonlogic.com/truthy.html)). -In Ruby, only `false` and `nil` are falsey. In JsonLogic empty strings and empty arrays are also falsey. +In Ruby, only `false` and `nil` are falsey. In JsonLogic empty strings and empty arrays are falsey too. **In Ruby:** ```ruby From 6644d9a6bd54e720a2fc72b898d0059811ac95a4 Mon Sep 17 00:00:00 2001 From: tavrelkate Date: Sun, 2 Nov 2025 15:51:47 +0300 Subject: [PATCH 4/6] README. --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3ce1dfd..6d1ff80 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ + # json-logic-rb Ruby implementation of [JsonLogic](https://jsonlogic.com/) — simple and extensible. Ships with a compliance runner for the official test suite. @@ -64,11 +65,11 @@ JsonLogic.apply({ "var" => "user.age" }, { "user" => { "age" => 42 } }) ## How -There are **two types of operations**: Default Operations and Lazy Operations. +There are two types of operations: [Default Operations](#1-default-operations) and [Lazy Operations](#2-lazy-operations) ### 1. Default Operations -For **Default Operations**, the it **evaluates all arguments first** and then calls the operator with the **resulting Ruby values**. +For **Default Operations**, the it evaluates all arguments first and then calls the operator with the resulting Ruby values. This matches the reference behavior for arithmetic, comparisons, string operations, and other pure operations that do not control evaluation order. **Groups and references:** @@ -79,7 +80,7 @@ This matches the reference behavior for arithmetic, comparisons, string operatio ### 2. Lazy Operations -Some operations must control **whether** and **when** their arguments are evaluated. They implement branching, short-circuiting, or “apply a rule per item” semantics. For these **Lazy Operations**, the engine passes raw sub-rules and data. The operator then evaluates only the sub-rules it actually needs. +Some operations must control whether and when their arguments are evaluated. They implement branching, short-circuiting, or “apply a rule per item” semantics. For these **Lazy Operations**, the engine passes raw sub-rules and data. The operator then evaluates only the sub-rules it actually needs. **Groups and references:** @@ -92,7 +93,7 @@ Some operations must control **whether** and **when** their arguments are evalua **How enumerable per-item evaluation works:** 1. The first argument is a rule that returns the list of items — evaluated once to a Ruby array. -2. The second argument is the per-item rule — evaluated **for each item** with that item as the **current root**. +2. The second argument is the per-item rule — evaluated for each item with that item as the current root. 3. For `reduce`, the current item is also available as `"current"`, and the running total as `"accumulator"`. @@ -123,14 +124,15 @@ JsonLogic.apply( ### Why laziness matters? -Lazy operations prevent evaluation of branches you do not need. If division by zero raised an error (hypothetically), lazy control would avoid it. +Lazy operations prevent evaluation of branches you do not need. +If hypothetically division by zero raises an error, lazy control would avoid it. ```ruby JsonLogic.apply({ "or" => [1, { "/" => [1, 0] }] }) # => 1 ``` -> In this gem `/` returns `nil` on divide‑by‑zero, but these examples show why lazy evaluation is required by the spec: branching and boolean operators must not evaluate unused branches. +> In this gem division returns nil on divide‑by‑zero, but this example show why lazy evaluation is required by the spec: branching and boolean operators must not evaluate unused branches. ## Supported Operations (Built‑in) From ec19e0ded4ff33c743e2a4c0788a991786295a4a Mon Sep 17 00:00:00 2001 From: tavrelkate Date: Sun, 2 Nov 2025 15:57:36 +0300 Subject: [PATCH 5/6] README. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6d1ff80..252a602 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ JsonLogic.apply( ### Why laziness matters? Lazy operations prevent evaluation of branches you do not need. + If hypothetically division by zero raises an error, lazy control would avoid it. ```ruby From e29e7e555c629b0cfc8f4bf945a903de85b709be Mon Sep 17 00:00:00 2001 From: tavrelkate Date: Sun, 2 Nov 2025 16:00:27 +0300 Subject: [PATCH 6/6] README. --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 252a602..9a783cc 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,6 @@ JsonLogic.apply( Lazy operations prevent evaluation of branches you do not need. If hypothetically division by zero raises an error, lazy control would avoid it. - ```ruby JsonLogic.apply({ "or" => [1, { "/" => [1, 0] }] }) # => 1 @@ -236,9 +235,7 @@ JsonLogic.apply({ "starts_with" => [ { "var" => "email" }, "admin@" ] }) ### Class -Pick the Operation type. - -It has the same call shape. +Pick the Operation type. It has the same call shape. [Default Operation](#1-default-operations) – Inherit `JsonLogic::Operation`.