From 1398acb94e486c1b922c16c62e353190f2b8b6ff Mon Sep 17 00:00:00 2001 From: Txus Date: Mon, 10 Oct 2022 09:43:47 +0000 Subject: [PATCH] Add F.with_meta to attach metadata to an arbitrary node --- Gemfile.lock | 2 +- lib/fql/query/dsl.rb | 52 ++++++++++++++++++++++++++++++------------ lib/fql/version.rb | 2 +- rbi/fql.rbi | 42 +++++++++++++++++++++------------- spec/fql/query_spec.rb | 13 +++++++++++ 5 files changed, 78 insertions(+), 33 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 2bf0c0f..746c56e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - fql (0.2.0) + fql (0.2.1) activerecord (~> 6.0) i18n (~> 1.8) sorbet-rails (~> 0.7.3) diff --git a/lib/fql/query/dsl.rb b/lib/fql/query/dsl.rb index 9e99c35..89dae80 100644 --- a/lib/fql/query/dsl.rb +++ b/lib/fql/query/dsl.rb @@ -7,22 +7,32 @@ class Query module DSL extend T::Sig + module Node + extend T::Sig + + sig { params(base: Module).void } + def self.included(base) + base.include T::Struct::ActsAsComparable + base.const :metadata, T::Hash[Symbol, T.untyped], default: {} + end + end + class And < T::Struct - include T::Struct::ActsAsComparable + include Node const :lhs, T.untyped # BoolExpr, but Sorbet can't do recursive type aliases const :rhs, T.untyped # BoolExpr, but Sorbet can't do recursive type aliases end class Or < T::Struct - include T::Struct::ActsAsComparable + include Node const :lhs, T.untyped # BoolExpr, but Sorbet can't do recursive type aliases const :rhs, T.untyped # BoolExpr, but Sorbet can't do recursive type aliases end class Not < T::Struct - include T::Struct::ActsAsComparable + include Node const :expr, T.untyped # BoolExpr, but Sorbet can't do recursive type aliases end @@ -30,14 +40,14 @@ class Not < T::Struct # Resolve a relation. # The special relation named `:self` resolves to the root entity. class Rel < T::Struct - include T::Struct::ActsAsComparable + include Node const :name, T::Array[Symbol] end # Resolve an attribute of a relation. class Attr < T::Struct - include T::Struct::ActsAsComparable + include Node const :target, Rel const :name, Symbol @@ -45,14 +55,14 @@ class Attr < T::Struct # Resolve a variable at runtime that will be passed to the interpreter. class Var < T::Struct - include T::Struct::ActsAsComparable + include Node const :name, Symbol end # Resolve a variable at runtime that will be passed to the interpreter. class Call < T::Struct - include T::Struct::ActsAsComparable + include Node const :name, Symbol const :arguments, T::Array[T.untyped] @@ -66,56 +76,56 @@ class Call < T::Struct # Determine equality between two values. class Eq < T::Struct - include T::Struct::ActsAsComparable + include Node const :lhs, ValueExpr const :rhs, T.any(ValueExpr, NilClass) end class Gt < T::Struct - include T::Struct::ActsAsComparable + include Node const :lhs, ValueExpr const :rhs, ValueExpr end class Lt < T::Struct - include T::Struct::ActsAsComparable + include Node const :lhs, ValueExpr const :rhs, ValueExpr end class Gte < T::Struct - include T::Struct::ActsAsComparable + include Node const :lhs, ValueExpr const :rhs, ValueExpr end class Lte < T::Struct - include T::Struct::ActsAsComparable + include Node const :lhs, ValueExpr const :rhs, ValueExpr end class OneOf < T::Struct - include T::Struct::ActsAsComparable + include Node const :member, ValueExpr const :set, T::Array[Primitive] end class Contains < T::Struct - include T::Struct::ActsAsComparable + include Node const :lhs, ValueExpr const :rhs, String end class MatchesRegex < T::Struct - include T::Struct::ActsAsComparable + include Node const :lhs, ValueExpr const :rhs, String @@ -124,6 +134,18 @@ class MatchesRegex < T::Struct module Methods extend T::Sig + sig do + type_parameters(:T) + .params( + metadata: T::Hash[Symbol, T.untyped], + node: T.all(T.type_parameter(:T), Node) + ) + .returns(T.type_parameter(:T)) + end + def with_meta(metadata, node) + node.tap { |n| n.metadata.merge!(metadata) } + end + sig { params(lhs: BoolExpr, rhs: BoolExpr).returns(And) } def and(lhs, rhs) And.new(lhs: lhs, rhs: rhs) diff --git a/lib/fql/version.rb b/lib/fql/version.rb index 3c3a328..2d1b93f 100644 --- a/lib/fql/version.rb +++ b/lib/fql/version.rb @@ -1,4 +1,4 @@ # typed: false module FQL - VERSION = "0.2.0".freeze + VERSION = "0.2.1".freeze end diff --git a/rbi/fql.rbi b/rbi/fql.rbi index 97ecc3b..d80c3fb 100644 --- a/rbi/fql.rbi +++ b/rbi/fql.rbi @@ -1,6 +1,6 @@ # typed: strong module FQL - VERSION = "0.2.0".freeze + VERSION = "0.2.1".freeze class Library extend T::Sig @@ -267,111 +267,121 @@ module FQL ValueExpr = T.type_alias { T.any(Attr, Rel, Var, Call, Primitive, T::Array[Primitive]) } Expr = T.type_alias { T.any(BoolExpr, ValueExpr) } + module Node + extend T::Sig + + sig { params(base: Module).void } + def self.included(base); end + end + class And < T::Struct prop :lhs, T.untyped, immutable: true prop :rhs, T.untyped, immutable: true - include T::Struct::ActsAsComparable + include Node end class Or < T::Struct prop :lhs, T.untyped, immutable: true prop :rhs, T.untyped, immutable: true - include T::Struct::ActsAsComparable + include Node end class Not < T::Struct prop :expr, T.untyped, immutable: true - include T::Struct::ActsAsComparable + include Node end class Rel < T::Struct prop :name, T::Array[Symbol], immutable: true - include T::Struct::ActsAsComparable + include Node end class Attr < T::Struct prop :target, Rel, immutable: true prop :name, Symbol, immutable: true - include T::Struct::ActsAsComparable + include Node end class Var < T::Struct prop :name, Symbol, immutable: true - include T::Struct::ActsAsComparable + include Node end class Call < T::Struct prop :name, Symbol, immutable: true prop :arguments, T::Array[T.untyped], immutable: true - include T::Struct::ActsAsComparable + include Node end class Eq < T::Struct prop :lhs, ValueExpr, immutable: true prop :rhs, T.any(ValueExpr, NilClass), immutable: true - include T::Struct::ActsAsComparable + include Node end class Gt < T::Struct prop :lhs, ValueExpr, immutable: true prop :rhs, ValueExpr, immutable: true - include T::Struct::ActsAsComparable + include Node end class Lt < T::Struct prop :lhs, ValueExpr, immutable: true prop :rhs, ValueExpr, immutable: true - include T::Struct::ActsAsComparable + include Node end class Gte < T::Struct prop :lhs, ValueExpr, immutable: true prop :rhs, ValueExpr, immutable: true - include T::Struct::ActsAsComparable + include Node end class Lte < T::Struct prop :lhs, ValueExpr, immutable: true prop :rhs, ValueExpr, immutable: true - include T::Struct::ActsAsComparable + include Node end class OneOf < T::Struct prop :member, ValueExpr, immutable: true prop :set, T::Array[Primitive], immutable: true - include T::Struct::ActsAsComparable + include Node end class Contains < T::Struct prop :lhs, ValueExpr, immutable: true prop :rhs, String, immutable: true - include T::Struct::ActsAsComparable + include Node end class MatchesRegex < T::Struct prop :lhs, ValueExpr, immutable: true prop :rhs, String, immutable: true - include T::Struct::ActsAsComparable + include Node end module Methods extend T::Sig + sig { type_parameters(:T).params(metadata: T::Hash[Symbol, T.untyped], node: T.all(T.type_parameter(:T), Node)).returns(T.type_parameter(:T)) } + def with_meta(metadata, node); end + sig { params(lhs: BoolExpr, rhs: BoolExpr).returns(And) } def and(lhs, rhs); end diff --git a/spec/fql/query_spec.rb b/spec/fql/query_spec.rb index f87a8c0..00909cd 100644 --- a/spec/fql/query_spec.rb +++ b/spec/fql/query_spec.rb @@ -30,4 +30,17 @@ expect(result).to eq(F.not(expression)) end end + + describe "metadata" do + let(:expression) do + F.eq( + F.with_meta({ name: "the user id" }, F.var(:foo)), + 3 + ) + end + + it "can be attached to an arbitrary node" do + expect(expression.lhs.metadata).to eq(name: "the user id") + end + end end