mudis-ql 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe MudisQL::Scope do
4
+ let(:namespace) { "test_products" }
5
+ let(:store) { MudisQL::Store.new(namespace) }
6
+ let(:scope) { described_class.new(store) }
7
+
8
+ before do
9
+ Mudis.serializer = JSON
10
+
11
+ # Seed test data
12
+ store.write("prod1", { name: "Laptop", price: 1200, category: "electronics", stock: 5 })
13
+ store.write("prod2", { name: "Mouse", price: 25, category: "electronics", stock: 50 })
14
+ store.write("prod3", { name: "Desk", price: 300, category: "furniture", stock: 10 })
15
+ store.write("prod4", { name: "Chair", price: 150, category: "furniture", stock: 20 })
16
+ store.write("prod5", { name: "Keyboard", price: 75, category: "electronics", stock: 30 })
17
+ end
18
+
19
+ describe "#where" do
20
+ it "filters by exact match" do
21
+ results = scope.where(category: "electronics").all
22
+ expect(results.size).to eq(3)
23
+ expect(results.map { |r| r["name"] }).to contain_exactly("Laptop", "Mouse", "Keyboard")
24
+ end
25
+
26
+ it "filters with proc" do
27
+ results = scope.where(price: ->(p) { p > 100 }).all
28
+ expect(results.size).to eq(3)
29
+ expect(results.map { |r| r["name"] }).to contain_exactly("Laptop", "Desk", "Chair")
30
+ end
31
+
32
+ it "filters with regex" do
33
+ results = scope.where(name: /^[KM]/).all
34
+ expect(results.size).to eq(2)
35
+ expect(results.map { |r| r["name"] }).to contain_exactly("Mouse", "Keyboard")
36
+ end
37
+
38
+ it "filters with range" do
39
+ results = scope.where(price: 50..200).all
40
+ expect(results.size).to eq(2)
41
+ expect(results.map { |r| r["name"] }).to contain_exactly("Chair", "Keyboard")
42
+ end
43
+
44
+ it "chains multiple conditions" do
45
+ results = scope
46
+ .where(category: "electronics")
47
+ .where(price: ->(p) { p < 100 })
48
+ .all
49
+
50
+ expect(results.size).to eq(2)
51
+ expect(results.map { |r| r["name"] }).to contain_exactly("Mouse", "Keyboard")
52
+ end
53
+ end
54
+
55
+ describe "#order" do
56
+ it "orders ascending by default" do
57
+ results = scope.order(:price).all
58
+ prices = results.map { |r| r["price"] }
59
+ expect(prices).to eq([25, 75, 150, 300, 1200])
60
+ end
61
+
62
+ it "orders descending" do
63
+ results = scope.order(:price, :desc).all
64
+ prices = results.map { |r| r["price"] }
65
+ expect(prices).to eq([1200, 300, 150, 75, 25])
66
+ end
67
+
68
+ it "orders by string field" do
69
+ results = scope.order(:name).all
70
+ names = results.map { |r| r["name"] }
71
+ expect(names).to eq(["Chair", "Desk", "Keyboard", "Laptop", "Mouse"])
72
+ end
73
+ end
74
+
75
+ describe "#limit" do
76
+ it "limits the number of results" do
77
+ results = scope.limit(3).all
78
+ expect(results.size).to eq(3)
79
+ end
80
+
81
+ it "works with ordering" do
82
+ results = scope.order(:price).limit(2).all
83
+ expect(results.map { |r| r["name"] }).to eq(["Mouse", "Keyboard"])
84
+ end
85
+ end
86
+
87
+ describe "#offset" do
88
+ it "skips the first N results" do
89
+ results = scope.order(:price).offset(2).all
90
+ expect(results.size).to eq(3)
91
+ expect(results.map { |r| r["name"] }).to eq(["Chair", "Desk", "Laptop"])
92
+ end
93
+
94
+ it "works with limit for pagination" do
95
+ page1 = scope.order(:name).limit(2).offset(0).all
96
+ page2 = scope.order(:name).limit(2).offset(2).all
97
+
98
+ expect(page1.map { |r| r["name"] }).to eq(["Chair", "Desk"])
99
+ expect(page2.map { |r| r["name"] }).to eq(["Keyboard", "Laptop"])
100
+ end
101
+ end
102
+
103
+ describe "#first" do
104
+ it "returns the first matching result" do
105
+ result = scope.where(category: "furniture").order(:price).first
106
+ expect(result["name"]).to eq("Chair")
107
+ end
108
+
109
+ it "returns nil when no results" do
110
+ result = scope.where(category: "nonexistent").first
111
+ expect(result).to be_nil
112
+ end
113
+ end
114
+
115
+ describe "#last" do
116
+ it "returns the last result" do
117
+ result = scope.order(:price).last
118
+ expect(result["name"]).to eq("Laptop")
119
+ end
120
+ end
121
+
122
+ describe "#count" do
123
+ it "counts matching records" do
124
+ count = scope.where(category: "electronics").count
125
+ expect(count).to eq(3)
126
+ end
127
+
128
+ it "counts all records when no filters" do
129
+ count = scope.count
130
+ expect(count).to eq(5)
131
+ end
132
+ end
133
+
134
+ describe "#exists?" do
135
+ it "returns true when records exist" do
136
+ expect(scope.where(category: "electronics").exists?).to be true
137
+ end
138
+
139
+ it "returns false when no records match" do
140
+ expect(scope.where(category: "nonexistent").exists?).to be false
141
+ end
142
+ end
143
+
144
+ describe "#pluck" do
145
+ it "extracts a single field" do
146
+ names = scope.order(:name).pluck(:name)
147
+ expect(names).to eq(["Chair", "Desk", "Keyboard", "Laptop", "Mouse"])
148
+ end
149
+
150
+ it "extracts multiple fields" do
151
+ pairs = scope.where(category: "furniture").pluck(:name, :price)
152
+ expect(pairs).to contain_exactly(["Chair", 150], ["Desk", 300])
153
+ end
154
+ end
155
+
156
+ describe "complex queries" do
157
+ it "combines multiple operations" do
158
+ results = scope
159
+ .where(category: "electronics")
160
+ .where(stock: ->(s) { s >= 25 })
161
+ .order(:price)
162
+ .limit(2)
163
+ .all
164
+
165
+ expect(results.map { |r| r["name"] }).to eq(["Mouse", "Keyboard"])
166
+ end
167
+ end
168
+ end
169
+
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe MudisQL::Store do
4
+ let(:namespace) { "test_users" }
5
+ let(:store) { described_class.new(namespace) }
6
+
7
+ before do
8
+ # Ensure mudis is configured
9
+ Mudis.serializer = JSON
10
+ end
11
+
12
+ describe "#initialize" do
13
+ it "sets the namespace" do
14
+ expect(store.namespace).to eq(namespace)
15
+ end
16
+ end
17
+
18
+ describe "#write and #read" do
19
+ it "writes and reads values from mudis" do
20
+ store.write("user1", { name: "Alice", age: 30 })
21
+ result = store.read("user1")
22
+
23
+ expect(result).to eq({ "name" => "Alice", "age" => 30 })
24
+ end
25
+
26
+ it "supports TTL" do
27
+ store.write("user1", { name: "Bob" }, expires_in: 1)
28
+ expect(store.read("user1")).not_to be_nil
29
+
30
+ sleep 1.1
31
+ expect(store.read("user1")).to be_nil
32
+ end
33
+ end
34
+
35
+ describe "#delete" do
36
+ it "removes a key from the cache" do
37
+ store.write("user1", { name: "Charlie" })
38
+ expect(store.read("user1")).not_to be_nil
39
+
40
+ store.delete("user1")
41
+ expect(store.read("user1")).to be_nil
42
+ end
43
+ end
44
+
45
+ describe "#keys" do
46
+ it "returns all keys in the namespace" do
47
+ store.write("user1", { name: "Alice" })
48
+ store.write("user2", { name: "Bob" })
49
+
50
+ keys = store.keys
51
+ expect(keys).to contain_exactly("user1", "user2")
52
+ end
53
+ end
54
+
55
+ describe "#all" do
56
+ it "returns all records with their keys" do
57
+ store.write("user1", { name: "Alice", age: 30 })
58
+ store.write("user2", { name: "Bob", age: 25 })
59
+
60
+ results = store.all
61
+ expect(results).to contain_exactly(
62
+ { "name" => "Alice", "age" => 30, "_key" => "user1" },
63
+ { "name" => "Bob", "age" => 25, "_key" => "user2" }
64
+ )
65
+ end
66
+
67
+ it "handles non-hash values" do
68
+ store.write("simple", "just a string")
69
+
70
+ results = store.all
71
+ expect(results).to contain_exactly(
72
+ { "_key" => "simple", "value" => "just a string" }
73
+ )
74
+ end
75
+ end
76
+ end
77
+
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe MudisQL do
4
+ it "has a version number" do
5
+ expect(MudisQL::VERSION).not_to be nil
6
+ end
7
+
8
+ describe ".from" do
9
+ it "returns a Scope object" do
10
+ scope = MudisQL.from("test")
11
+ expect(scope).to be_a(MudisQL::Scope)
12
+ end
13
+
14
+ it "supports optional namespace (defaults to nil)" do
15
+ scope = MudisQL.from
16
+ expect(scope).to be_a(MudisQL::Scope)
17
+ expect(scope.instance_variable_get(:@store).namespace).to be_nil
18
+ end
19
+
20
+ it "works with keys without explicit namespace" do
21
+ Mudis.write("test:key1", { name: "No namespace" })
22
+ Mudis.write("test:key2", { name: "Also no namespace" })
23
+
24
+ # Note: MudisQL.from with no namespace won't list keys since Mudis.keys requires namespace
25
+ # But individual key access works fine
26
+ scope = MudisQL.from
27
+ expect(scope).to be_a(MudisQL::Scope)
28
+ end
29
+ end
30
+
31
+ describe ".configure" do
32
+ it "yields a configuration object" do
33
+ expect { |b| MudisQL.configure(&b) }.to yield_with_args(MudisQL::Configuration)
34
+ end
35
+
36
+ it "allows setting default_limit" do
37
+ MudisQL.configure do |config|
38
+ config.default_limit = 50
39
+ end
40
+
41
+ expect(MudisQL.configuration.default_limit).to eq(50)
42
+ end
43
+ end
44
+
45
+ describe ".metrics" do
46
+ it "returns a MetricsScope object" do
47
+ metrics = MudisQL.metrics
48
+ expect(metrics).to be_a(MudisQL::MetricsScope)
49
+ end
50
+ end
51
+ end
52
+
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mudis-ql
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - kiebor81
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-12-07 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: mudis
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.9.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.9.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: climate_control
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 1.1.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 1.1.0
40
+ - !ruby/object:Gem::Dependency
41
+ name: rspec
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.12'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.12'
54
+ - !ruby/object:Gem::Dependency
55
+ name: simplecov
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.22'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.22'
68
+ description: Mudis-QL extends Mudis by providing a SQL-like query interface for data
69
+ stored in the mudis cache
70
+ executables: []
71
+ extensions: []
72
+ extra_rdoc_files: []
73
+ files:
74
+ - README.md
75
+ - lib/mudis-ql.rb
76
+ - lib/mudis-ql/metrics_scope.rb
77
+ - lib/mudis-ql/scope.rb
78
+ - lib/mudis-ql/store.rb
79
+ - lib/mudis-ql/version.rb
80
+ - spec/mudis-ql/error_handling_spec.rb
81
+ - spec/mudis-ql/integration_spec.rb
82
+ - spec/mudis-ql/metrics_scope_spec.rb
83
+ - spec/mudis-ql/performance_spec.rb
84
+ - spec/mudis-ql/scope_spec.rb
85
+ - spec/mudis-ql/store_spec.rb
86
+ - spec/mudis-ql_spec.rb
87
+ homepage: https://github.com/kiebor81/mudis-ql
88
+ licenses:
89
+ - MIT
90
+ metadata:
91
+ homepage_uri: https://github.com/kiebor81/mudis-ql
92
+ source_code_uri: https://github.com/kiebor81/mudis-ql
93
+ changelog_uri: https://github.com/kiebor81/mudis-ql/blob/main/CHANGELOG.md
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '3.0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubygems_version: 3.6.2
109
+ specification_version: 4
110
+ summary: A simple query DSL for Mudis cache
111
+ test_files:
112
+ - spec/mudis-ql/error_handling_spec.rb
113
+ - spec/mudis-ql/integration_spec.rb
114
+ - spec/mudis-ql/metrics_scope_spec.rb
115
+ - spec/mudis-ql/performance_spec.rb
116
+ - spec/mudis-ql/scope_spec.rb
117
+ - spec/mudis-ql/store_spec.rb
118
+ - spec/mudis-ql_spec.rb