trifle-stats 1.3.1 → 1.6.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer.json +1 -1
  3. data/.devops/docker/codespaces/Dockerfile +1 -1
  4. data/.devops/docker/environment/Dockerfile +3 -2
  5. data/.devops/docker/local/Dockerfile +1 -0
  6. data/.devops/docker/local/docker-compose.yml +30 -0
  7. data/.github/workflows/ruby.yml +85 -6
  8. data/.gitignore +1 -0
  9. data/.ruby-version +1 -1
  10. data/.tool-versions +1 -1
  11. data/Gemfile +1 -1
  12. data/Gemfile.lock +8 -7
  13. data/README.md +31 -0
  14. data/lib/trifle/stats/aggregator/avg.rb +32 -0
  15. data/lib/trifle/stats/aggregator/max.rb +29 -0
  16. data/lib/trifle/stats/aggregator/min.rb +29 -0
  17. data/lib/trifle/stats/aggregator/sum.rb +29 -0
  18. data/lib/trifle/stats/driver/mongo.rb +81 -27
  19. data/lib/trifle/stats/driver/postgres.rb +14 -2
  20. data/lib/trifle/stats/driver/process.rb +12 -0
  21. data/lib/trifle/stats/driver/redis.rb +19 -4
  22. data/lib/trifle/stats/driver/sqlite.rb +15 -2
  23. data/lib/trifle/stats/formatter/category.rb +32 -0
  24. data/lib/trifle/stats/formatter/timeline.rb +29 -0
  25. data/lib/trifle/stats/mixins/packer.rb +16 -1
  26. data/lib/trifle/stats/nocturnal.rb +24 -0
  27. data/lib/trifle/stats/operations/status/beam.rb +31 -0
  28. data/lib/trifle/stats/operations/status/scan.rb +35 -0
  29. data/lib/trifle/stats/operations/timeseries/classify.rb +1 -1
  30. data/lib/trifle/stats/operations/timeseries/increment.rb +1 -1
  31. data/lib/trifle/stats/operations/timeseries/set.rb +1 -1
  32. data/lib/trifle/stats/operations/timeseries/values.rb +25 -7
  33. data/lib/trifle/stats/series.rb +64 -0
  34. data/lib/trifle/stats/transponder/average.rb +31 -0
  35. data/lib/trifle/stats/transponder/ratio.rb +31 -0
  36. data/lib/trifle/stats/transponder/standard_deviation.rb +35 -0
  37. data/lib/trifle/stats/version.rb +1 -1
  38. data/lib/trifle/stats.rb +36 -3
  39. data/trifle-stats.gemspec +1 -0
  40. metadata +18 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e169bb4a0d962b9d16ff5ba4b437ffc1948223a8d3e3ef599baa7c510b57c6f
4
- data.tar.gz: 01637b0e31df2f49107cce66cd02addb4463a96fd96d66d6f41ca1a44b0f94e7
3
+ metadata.gz: eece6621a74a0dfc38e7e1932897ad0481e3b85744ec85d016320cc6c7e5b395
4
+ data.tar.gz: a90a4b7bf5e885fdc77af3638b2fcb0fdef0f48e17001b2da925161164fd31f4
5
5
  SHA512:
6
- metadata.gz: 773d0d82001e990090a7fed8ac1e129d2235b62d02d258f5da28c89f6578166ab63aa69f8795861a2870646e236a0e408ac9053b00c705486105cee3e0160dd1
7
- data.tar.gz: abf4dd13853d4911bd0fead246b609ac3f28470018af6c5647588f0ccae1228083391c96b6d7dd443b60f8346ff65dcd23fcc5d2f2b90e205cf057581970a177
6
+ metadata.gz: 7bb55a42c115a0e353277b7445f33db93bc16a6011202de515adb63126d0d154615bbc2b13bfad45bd6ca990aedec32de4911c5843553b2c5148676c01d21b99
7
+ data.tar.gz: 475e37cab204a8b07ea90ead1c209cb784844ba3683884362f284d41c0f2e8170f9f7ec858125dece018312561d934efd555a41ac6457ec16af2d31e36be0695
data/.devcontainer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "dockerComposeFile": ".devops/docker/codespaces/docker-compose.yml",
3
3
  "service": "app",
4
- "workspaceFolder": "/",
4
+ "workspaceFolder": "/workspaces/trifle-stats",
5
5
  "shutdownAction": "stopCompose"
6
6
  }
@@ -1 +1 @@
1
- FROM trifle/environment:1.0.4
1
+ FROM trifle/environment:ruby_3.1.0_1
@@ -21,6 +21,7 @@ RUN apt-get update -q \
21
21
  libssl-dev \
22
22
  libreadline-dev \
23
23
  zlib1g-dev \
24
+ libsqlite3-dev \
24
25
  tmux \
25
26
  htop \
26
27
  vim \
@@ -45,8 +46,8 @@ RUN git clone https://github.com/asdf-vm/asdf.git ${ASDF_ROOT} --branch v0.10.0
45
46
  RUN asdf plugin-add ruby https://github.com/asdf-vm/asdf-ruby.git
46
47
 
47
48
  # install ruby
48
- ENV RUBY_VERSION 3.0.0
49
- RUN ASDF_RUBY_BUILD_VERSION=v20201225 asdf install ruby ${RUBY_VERSION} \
49
+ ENV RUBY_VERSION 3.1.0
50
+ RUN asdf install ruby ${RUBY_VERSION} \
50
51
  && asdf global ruby ${RUBY_VERSION}
51
52
 
52
53
  RUN gem install bundler
@@ -0,0 +1 @@
1
+ FROM trifle/environment:ruby_3.1.0_1
@@ -0,0 +1,30 @@
1
+ version: "3.7"
2
+ name: "stats"
3
+ services:
4
+ postgres:
5
+ image: postgres:latest
6
+ environment:
7
+ POSTGRES_PASSWORD: password
8
+ redis:
9
+ image: redis:latest
10
+ mongo:
11
+ image: mongo:latest
12
+ app:
13
+ command: /bin/sh -c "while sleep 1000; do :; done"
14
+ build:
15
+ context: ../../..
16
+ dockerfile: .devops/docker/local/Dockerfile
17
+ depends_on:
18
+ - postgres
19
+ - redis
20
+ - mongo
21
+ environment:
22
+ PGHOST: postgres
23
+ PGUSER: postgres
24
+ REDIS_HOST: redis
25
+ REDIS_URL: redis://redis:6379
26
+ expose:
27
+ - 4000
28
+ volumes:
29
+ - ../../..:/workspaces/stats
30
+ working_dir: /workspaces/stats
@@ -16,21 +16,100 @@ on:
16
16
  jobs:
17
17
  test:
18
18
  runs-on: ubuntu-latest
19
+
20
+ services:
21
+ postgres:
22
+ image: postgres:15
23
+ env:
24
+ POSTGRES_PASSWORD: postgres
25
+ POSTGRES_USER: postgres
26
+ POSTGRES_DB: test_db
27
+ options: >-
28
+ --health-cmd pg_isready
29
+ --health-interval 10s
30
+ --health-timeout 5s
31
+ --health-retries 5
32
+ ports:
33
+ - 5432:5432
34
+
35
+ redis:
36
+ image: redis:7
37
+ options: >-
38
+ --health-cmd "redis-cli ping"
39
+ --health-interval 10s
40
+ --health-timeout 5s
41
+ --health-retries 5
42
+ ports:
43
+ - 6379:6379
44
+
45
+ mongodb:
46
+ image: mongo:6.0
47
+ env:
48
+ MONGO_INITDB_ROOT_USERNAME: root
49
+ MONGO_INITDB_ROOT_PASSWORD: password
50
+ options: >-
51
+ --health-cmd "mongosh --eval 'db.adminCommand(\"ping\")'"
52
+ --health-interval 10s
53
+ --health-timeout 5s
54
+ --health-retries 5
55
+ ports:
56
+ - 27017:27017
57
+
19
58
  strategy:
20
59
  matrix:
21
- ruby-version: ["3.0"]
60
+ ruby-version: ["3.1"]
61
+
62
+ env:
63
+ # Database configuration
64
+ DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
65
+ REDIS_URL: redis://localhost:6379/0
66
+ MONGODB_URL: mongodb://root:password@localhost:27017/test_db?authSource=admin
22
67
 
23
68
  steps:
24
- - uses: actions/checkout@v2
69
+ - uses: actions/checkout@v4
70
+
25
71
  - name: Set up Ruby
26
- # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
27
- # change this to (see https://github.com/ruby/setup-ruby#versioning):
28
- # uses: ruby/setup-ruby@v1
29
- uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
72
+ uses: ruby/setup-ruby@v1
30
73
  with:
31
74
  ruby-version: ${{ matrix.ruby-version }}
32
75
  bundler-cache: true # runs 'bundle install' and caches installed gems automatically
76
+
77
+ - name: Wait for services to be ready
78
+ run: |
79
+ # Wait for PostgreSQL
80
+ until pg_isready -h localhost -p 5432 -U postgres; do
81
+ echo "Waiting for PostgreSQL..."
82
+ sleep 2
83
+ done
84
+
85
+ # Wait for Redis
86
+ until timeout 1 bash -c "</dev/tcp/localhost/6379"; do
87
+ echo "Waiting for Redis..."
88
+ sleep 2
89
+ done
90
+
91
+ # Wait for MongoDB
92
+ until timeout 1 bash -c "</dev/tcp/localhost/27017"; do
93
+ echo "Waiting for MongoDB..."
94
+ sleep 2
95
+ done
96
+
97
+ - name: Setup Database
98
+ run: |
99
+ # Create database if needed (adjust based on your setup)
100
+ # bundle exec rails db:create db:migrate
101
+ env:
102
+ DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
103
+
33
104
  - name: Rspec
34
105
  run: bundle exec rspec
106
+ env:
107
+ DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
108
+ REDIS_URL: redis://localhost:6379/0
109
+ MONGODB_URL: mongodb://root:password@localhost:27017/test_db?authSource=admin
110
+
35
111
  - name: Rubocop
36
112
  run: bundle exec rubocop
113
+
114
+ # - name: Performance
115
+ # run: cd spec/performance && bundle install && ruby run.rb 1000 '{"a":1,"b":2,"c":1,"d":2,"e":1,"f":2,"g":1,"h":2,"i":1,"j":2,"k":1,"l":2,"m":1,"n":2,"o":1,"p":2,"q":1,"r":2,"s":1,"t":2,"u":1,"v":2,"w":1}'
data/.gitignore CHANGED
@@ -12,3 +12,4 @@
12
12
  /.byebug_history
13
13
  stats.db
14
14
  .DS_Store
15
+ spec/performance/.byebug_history
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-3.0.0
1
+ ruby-3.1.0
data/.tool-versions CHANGED
@@ -1 +1 @@
1
- ruby 3.0.0
1
+ ruby 3.1.0
data/Gemfile CHANGED
@@ -8,4 +8,4 @@ gem "rspec", "~> 3.0"
8
8
  gem "mongo", require: false
9
9
  gem "pg", require: false
10
10
  gem "redis", require: false
11
- gem "sqlite", require: false
11
+ gem "sqlite3", require: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- trifle-stats (1.3.1)
4
+ trifle-stats (1.6.0)
5
5
  tzinfo (~> 2.0)
6
6
 
7
7
  GEM
@@ -10,9 +10,10 @@ GEM
10
10
  ast (2.4.2)
11
11
  bson (4.12.1)
12
12
  byebug (11.1.3)
13
- concurrent-ruby (1.1.10)
13
+ concurrent-ruby (1.3.5)
14
14
  diff-lcs (1.4.4)
15
15
  dotenv (2.7.6)
16
+ mini_portile2 (2.8.9)
16
17
  mongo (2.14.0)
17
18
  bson (>= 4.8.2, < 5.0.0)
18
19
  parallel (1.20.1)
@@ -49,9 +50,10 @@ GEM
49
50
  rubocop-ast (1.4.1)
50
51
  parser (>= 2.7.1.5)
51
52
  ruby-progressbar (1.11.0)
52
- sqlite (1.0.2)
53
- sqlite3 (1.4.4)
54
- tzinfo (2.0.5)
53
+ sqlite3 (2.7.3)
54
+ mini_portile2 (~> 2.8.0)
55
+ sqlite3 (2.7.3-x86_64-darwin)
56
+ tzinfo (2.0.6)
55
57
  concurrent-ruby (~> 1.0)
56
58
  unicode-display_width (1.7.0)
57
59
 
@@ -69,8 +71,7 @@ DEPENDENCIES
69
71
  redis
70
72
  rspec (~> 3.0)
71
73
  rubocop (= 1.0.0)
72
- sqlite
73
- sqlite3 (>= 1.4.4)
74
+ sqlite3
74
75
  trifle-stats!
75
76
 
76
77
  BUNDLED WITH
data/README.md CHANGED
@@ -124,6 +124,37 @@ Trifle::Stats.values(key: 'event::logs', from: Time.now, to: Time.now, range: :d
124
124
  => {:at=>[2021-01-25 00:00:00 +0200], :values=>[{"count"=>1, "duration"=>5, "lines"=>361}]}
125
125
  ```
126
126
 
127
+ ## Testing
128
+
129
+ ### Testing Principles
130
+
131
+ Tests are structured to be simple, isolated, and mirror the class structure. Each test is independent and self-contained.
132
+
133
+ #### Key Rules:
134
+
135
+ 1. **Keep tests simple and isolated** - Each test should focus on a single class/method
136
+ 2. **Independent tests** - Tests should not depend on each other and can be run in any order
137
+ 3. **Self-contained setup** - Every test configures its own variables and dependencies
138
+ 4. **Single layer testing** - Test only the specific class, not multiple layers of functionality
139
+ 5. **Use appropriate stubbing** - When testing operations, stub driver methods. Let driver tests verify driver behavior
140
+ 6. **Repeat yourself** - It's okay to repeat setup code for clarity and independence
141
+
142
+ #### Driver Testing:
143
+
144
+ - Driver tests use **real database connections** (Redis, PostgreSQL, MongoDB, SQLite)
145
+ - Clean data between tests to ensure isolation
146
+ - Use appropriate test databases (e.g., Redis database 15, test-specific DB names)
147
+ - The **Process driver** is ideal for testing environments as it uses in-memory storage
148
+
149
+ #### Test Structure:
150
+
151
+ Tests follow the same structure as the classes they test:
152
+ - `spec/stats/driver/` - Driver class tests
153
+ - `spec/stats/operations/` - Operation class tests
154
+ - `spec/stats/mixins/` - Mixin tests
155
+
156
+ This approach makes it easier to see initial configuration and expected results for each test.
157
+
127
158
  ## Contributing
128
159
 
129
160
  Bug reports and pull requests are welcome on GitHub at https://github.com/trifle-io/trifle-stats.
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trifle
4
+ module Stats
5
+ class Aggregator
6
+ class Avg
7
+ Trifle::Stats::Series.register_aggregator(:avg, self)
8
+
9
+ def aggregate(series:, path:, slices: 1)
10
+ return [] if series[:at].empty?
11
+
12
+ keys = path.split('.')
13
+ result = series[:values].map do |data|
14
+ data.dig(*keys)
15
+ end
16
+ sliced(result: result, slices: slices)
17
+ end
18
+
19
+ private
20
+
21
+ def sliced(result:, slices:)
22
+ result[(result.count - (result.count / slices * slices))..].each_slice(result.count / slices).map do |slice|
23
+ sum = slice.compact.sum
24
+ count = slice.compact.count
25
+
26
+ count.zero? ? 0 : sum / count
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trifle
4
+ module Stats
5
+ class Aggregator
6
+ class Max
7
+ Trifle::Stats::Series.register_aggregator(:max, self)
8
+
9
+ def aggregate(series:, path:, slices: 1)
10
+ return [] if series[:at].empty?
11
+
12
+ keys = path.split('.')
13
+ result = series[:values].map do |data|
14
+ data.dig(*keys)
15
+ end
16
+ sliced(result: result, slices: slices)
17
+ end
18
+
19
+ private
20
+
21
+ def sliced(result:, slices:)
22
+ result[(result.count - (result.count / slices * slices))..].each_slice(result.count / slices).map do |slice|
23
+ slice.compact.max
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trifle
4
+ module Stats
5
+ class Aggregator
6
+ class Min
7
+ Trifle::Stats::Series.register_aggregator(:min, self)
8
+
9
+ def aggregate(series:, path:, slices: 1)
10
+ return [] if series[:at].empty?
11
+
12
+ keys = path.split('.')
13
+ result = series[:values].map do |data|
14
+ data.dig(*keys)
15
+ end
16
+ sliced(result: result, slices: slices)
17
+ end
18
+
19
+ private
20
+
21
+ def sliced(result:, slices:)
22
+ result[(result.count - (result.count / slices * slices))..].each_slice(result.count / slices).map do |slice|
23
+ slice.compact.min
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trifle
4
+ module Stats
5
+ class Aggregator
6
+ class Sum
7
+ Trifle::Stats::Series.register_aggregator(:sum, self)
8
+
9
+ def aggregate(series:, path:, slices: 1)
10
+ return [] if series[:at].empty?
11
+
12
+ keys = path.split('.')
13
+ result = series[:values].map do |data|
14
+ data.dig(*keys)
15
+ end
16
+ sliced(result: result, slices: slices)
17
+ end
18
+
19
+ private
20
+
21
+ def sliced(result:, slices:)
22
+ result[(result.count - (result.count / slices * slices))..].each_slice(result.count / slices).map do |slice|
23
+ slice.compact.sum
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -7,55 +7,109 @@ module Trifle
7
7
  module Driver
8
8
  class Mongo
9
9
  include Mixins::Packer
10
- attr_accessor :client, :collection_name, :separator
10
+ attr_accessor :client, :collection_name
11
11
 
12
- def initialize(client, collection_name: 'trifle_stats')
12
+ def initialize(client, collection_name: 'trifle_stats', joined_identifier: true, expire_after: nil)
13
13
  @client = client
14
14
  @collection_name = collection_name
15
+ @joined_identifier = joined_identifier
16
+ @expire_after = expire_after
15
17
  @separator = '::'
16
18
  end
17
19
 
18
- def self.setup!(client, collection_name: 'trifle_stats')
19
- client[collection_name].create
20
- client[collection_name].indexes.create_one({ key: 1 }, unique: true)
20
+ def self.setup!(client, collection_name: 'trifle_stats', joined_identifier: true, expire_after: nil)
21
+ collection = client[collection_name]
22
+ collection.create
23
+ if joined_identifier
24
+ collection.indexes.create_one({ key: 1 }, unique: true)
25
+ else
26
+ collection.indexes.create_one({ key: 1, range: 1, at: -1 }, unique: true)
27
+ end
28
+ collection.indexes.create_one({ expire_at: 1 }, expire_after_seconds: 0) if expire_after
29
+ end
30
+
31
+ def description
32
+ "#{self.class.name}(#{@joined_identifier ? 'J' : 'S'})"
33
+ end
34
+
35
+ def separator
36
+ @joined_identifier ? @separator : nil
21
37
  end
22
38
 
23
39
  def inc(keys:, **values)
24
40
  data = self.class.pack(hash: { data: values })
25
41
 
26
- collection.bulk_write(
27
- keys.map do |key|
28
- upsert_operation('$inc', pkey: key.join(separator), data: data)
29
- end
30
- )
42
+ operations = keys.map do |key|
43
+ filter = key.identifier(separator)
44
+ expire_at = @expire_after ? key.at + @expire_after : nil
45
+
46
+ upsert_operation('$inc', filter: filter, data: data, expire_at: expire_at)
47
+ end
48
+
49
+ collection.bulk_write(operations)
31
50
  end
32
51
 
33
52
  def set(keys:, **values)
34
53
  data = self.class.pack(hash: { data: values })
35
54
 
36
- collection.bulk_write(
37
- keys.map do |key|
38
- upsert_operation('$set', pkey: key.join(separator), data: data)
39
- end
40
- )
55
+ operations = keys.map do |key|
56
+ filter = key.identifier(separator)
57
+ expire_at = @expire_after ? key.at + @expire_after : nil
58
+
59
+ upsert_operation('$set', filter: filter, data: data, expire_at: expire_at)
60
+ end
61
+
62
+ collection.bulk_write(operations)
63
+ end
64
+
65
+ def ping(key:, **values)
66
+ data = self.class.pack(hash: { data: values, at: key.at })
67
+ identifier = key.identifier(separator)
68
+ expire_at = @expire_after ? key.at + @expire_after : nil
69
+
70
+ operations = [
71
+ upsert_operation('$set', filter: identifier.slice(:key), data: data, expire_at: expire_at)
72
+ ]
73
+
74
+ collection.bulk_write(operations)
41
75
  end
42
76
 
43
- def upsert_operation(operation, pkey:, data:)
44
- {
45
- update_many: {
46
- filter: { key: pkey },
47
- update: { operation => data },
48
- upsert: true
49
- }
77
+ def upsert_operation(operation, filter:, data:, expire_at: nil)
78
+ # Merge if $set and $set
79
+ update_data = operation == '$set' && expire_at ? data.merge(expire_at: expire_at) : data
80
+
81
+ # Add if $inc and $set
82
+ update = {
83
+ operation => update_data,
84
+ **(operation != '$set' && expire_at ? { '$set' => { expire_at: expire_at } } : {})
50
85
  }
86
+
87
+ { update_many: { filter: filter, update: update, upsert: true } }
51
88
  end
52
89
 
53
- def get(keys:)
54
- pkeys = keys.map { |key| key.join(separator) }
55
- data = collection.find(key: { '$in' => pkeys })
56
- map = data.inject({}) { |o, d| o.merge(d['key'] => d['data']) }
90
+ def get(keys:) # rubocop:disable Metrics/AbcSize
91
+ combinations = keys.map { |key| key.identifier(separator) }
92
+ data = collection.find('$or' => combinations)
93
+ map = data.inject({}) do |o, d|
94
+ o.merge(
95
+ Nocturnal::Key.new(
96
+ key: d['key'], range: d['range'], at: d['at']
97
+ ).identifier(separator) => d['data']
98
+ )
99
+ end
100
+
101
+ combinations.map { |combination| map[combination] || {} }
102
+ end
103
+
104
+ def scan(key:)
105
+ return [] if @joined_identifier
106
+
107
+ data = collection.find(
108
+ **key.identifier(separator)
109
+ ).sort(at: -1).first # rubocop:disable Style/RedundantSort
110
+ return [] if data.nil?
57
111
 
58
- pkeys.map { |pkey| map[pkey] || {} }
112
+ [data['at'], data['data']]
59
113
  end
60
114
 
61
115
  private
@@ -19,6 +19,10 @@ module Trifle
19
19
  client.exec("CREATE TABLE #{table_name} (key VARCHAR(255) PRIMARY KEY, data JSONB NOT NULL DEFAULT '{}'::jsonb)") # rubocop:disable Layout/LineLength
20
20
  end
21
21
 
22
+ def description
23
+ "#{self.class.name}(J)"
24
+ end
25
+
22
26
  def inc(keys:, **values)
23
27
  data = self.class.pack(hash: values)
24
28
  client.transaction do |c|
@@ -33,7 +37,7 @@ module Trifle
33
37
  <<-SQL
34
38
  INSERT INTO #{table_name} (key, data) VALUES ('#{key}', '#{data.to_json}')
35
39
  ON CONFLICT (key) DO UPDATE SET data =
36
- #{data.inject("to_jsonb(#{table_name}.data)") { |o, (k, v)| "jsonb_set(#{o}, '{#{k}}', (COALESCE(trifle_stats.data->>'#{k}', '0')::int + #{v})::text::jsonb)" }};
40
+ #{data.inject("to_jsonb(#{table_name}.data)") { |o, (k, v)| "jsonb_set(#{o}, '{#{k}}', (COALESCE(#{table_name}.data->>'#{k}', '0')::int + #{v})::text::jsonb)" }};
37
41
  SQL
38
42
  end
39
43
 
@@ -51,7 +55,7 @@ module Trifle
51
55
  <<-SQL
52
56
  INSERT INTO #{table_name} (key, data) VALUES ('#{key}', '#{data.to_json}')
53
57
  ON CONFLICT (key) DO UPDATE SET data =
54
- #{data.inject("to_jsonb(#{table_name}.data)") { |o, (k, v)| "jsonb_set(#{o}, '{#{k}}', (#{v})::text::jsonb)" }}
58
+ #{data.inject("to_jsonb(#{table_name}.data)") { |o, (k, v)| "jsonb_set(#{o}, '{#{k}}', '#{v.to_json}'::jsonb)" }}
55
59
  SQL
56
60
  end
57
61
 
@@ -78,6 +82,14 @@ module Trifle
78
82
  SELECT * FROM #{table_name} WHERE key IN ('#{keys.join("', '")}');
79
83
  SQL
80
84
  end
85
+
86
+ def ping(*)
87
+ []
88
+ end
89
+
90
+ def scan(*)
91
+ []
92
+ end
81
93
  end
82
94
  end
83
95
  end
@@ -12,6 +12,10 @@ module Trifle
12
12
  @separator = '::'
13
13
  end
14
14
 
15
+ def description
16
+ "#{self.class.name}(J)"
17
+ end
18
+
15
19
  def inc(keys:, **values)
16
20
  keys.map do |key|
17
21
  self.class.pack(hash: values).each do |k, c|
@@ -39,6 +43,14 @@ module Trifle
39
43
  )
40
44
  end
41
45
  end
46
+
47
+ def ping(*)
48
+ []
49
+ end
50
+
51
+ def scan(*)
52
+ []
53
+ end
42
54
  end
43
55
  end
44
56
  end
@@ -9,15 +9,20 @@ module Trifle
9
9
  include Mixins::Packer
10
10
  attr_accessor :client, :prefix, :separator
11
11
 
12
- def initialize(client = ::Redis.current, prefix: 'trfl')
12
+ def initialize(client, prefix: 'trfl')
13
13
  @client = client
14
14
  @prefix = prefix
15
15
  @separator = '::'
16
16
  end
17
17
 
18
+ def description
19
+ "#{self.class.name}(J)"
20
+ end
21
+
18
22
  def inc(keys:, **values)
19
23
  keys.map do |key|
20
- pkey = ([prefix] + key).join(separator)
24
+ key.prefix = prefix
25
+ pkey = key.join(separator)
21
26
 
22
27
  self.class.pack(hash: values).each do |k, c|
23
28
  client.hincrby(pkey, k, c)
@@ -27,7 +32,8 @@ module Trifle
27
32
 
28
33
  def set(keys:, **values)
29
34
  keys.map do |key|
30
- pkey = ([prefix] + key).join(separator)
35
+ key.prefix = prefix
36
+ pkey = key.join(separator)
31
37
 
32
38
  client.hmset(pkey, *self.class.pack(hash: values))
33
39
  end
@@ -35,13 +41,22 @@ module Trifle
35
41
 
36
42
  def get(keys:)
37
43
  keys.map do |key|
38
- pkey = ([prefix] + key).join(separator)
44
+ key.prefix = prefix
45
+ pkey = key.join(separator)
39
46
 
40
47
  self.class.unpack(
41
48
  hash: client.hgetall(pkey)
42
49
  )
43
50
  end
44
51
  end
52
+
53
+ def ping(*)
54
+ []
55
+ end
56
+
57
+ def scan(*)
58
+ []
59
+ end
45
60
  end
46
61
  end
47
62
  end