From 045c46366d5077d49805507eca78c4b86e1ff957 Mon Sep 17 00:00:00 2001 From: Petrik Date: Mon, 1 Jun 2026 22:03:59 +0200 Subject: [PATCH] [rage] Add crud --- frameworks/rage/Gemfile | 1 + frameworks/rage/Gemfile.lock | 7 ++ .../app/controllers/application_controller.rb | 46 ++++++++ .../app/controllers/benchmark_controller.rb | 19 +-- .../rage/app/controllers/items_controller.rb | 110 ++++++++++++++++++ frameworks/rage/config/routes.rb | 16 ++- frameworks/rage/meta.json | 1 + 7 files changed, 176 insertions(+), 24 deletions(-) create mode 100644 frameworks/rage/app/controllers/application_controller.rb create mode 100644 frameworks/rage/app/controllers/items_controller.rb diff --git a/frameworks/rage/Gemfile b/frameworks/rage/Gemfile index 7d32315c2..4c8179254 100644 --- a/frameworks/rage/Gemfile +++ b/frameworks/rage/Gemfile @@ -2,5 +2,6 @@ source 'https://rubygems.org' gem 'rage-rb', '~> 1.22' gem 'pg', '~> 1.5' +gem 'redis' gem 'concurrent-ruby' gem 'connection_pool' diff --git a/frameworks/rage/Gemfile.lock b/frameworks/rage/Gemfile.lock index 5784f3743..65c25320c 100644 --- a/frameworks/rage/Gemfile.lock +++ b/frameworks/rage/Gemfile.lock @@ -39,6 +39,10 @@ GEM erb psych (>= 4.0.0) tsort + redis (5.4.1) + redis-client (>= 0.22.0) + redis-client (0.29.0) + connection_pool reline (0.6.3) io-console (~> 0.5) stringio (3.2.0) @@ -55,6 +59,7 @@ DEPENDENCIES connection_pool pg (~> 1.5) rage-rb (~> 1.22) + redis CHECKSUMS concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab @@ -76,6 +81,8 @@ CHECKSUMS rage-rb (1.23.0) sha256=33d2256062a2a10968dc8ba218791ffafe3c8376ddc9f67ee430cfaabb6f5ae2 rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701 rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192 + redis (5.4.1) sha256=b5e675b57ad22b15c9bcc765d5ac26f60b675408af916d31527af9bd5a81faae + redis-client (0.29.0) sha256=0c65bf1f8f6dca22063ddb085c0bb2054feef6f03a84869f4161b18a9a15bea3 reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835 stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 diff --git a/frameworks/rage/app/controllers/application_controller.rb b/frameworks/rage/app/controllers/application_controller.rb new file mode 100644 index 000000000..46741e538 --- /dev/null +++ b/frameworks/rage/app/controllers/application_controller.rb @@ -0,0 +1,46 @@ +class ApplicationController < RageController::API + + CRUD_COLUMNS = 'id, name, category, price, quantity, active, tags, rating_score, rating_count' + SELECT_QUERY = "SELECT #{CRUD_COLUMNS} FROM items WHERE price BETWEEN $1 AND $2 LIMIT $3" + CRUD_GET_SQL = "SELECT #{CRUD_COLUMNS} FROM items WHERE id = $1 LIMIT 1" + CRUD_LIST_SQL = "SELECT #{CRUD_COLUMNS} FROM items WHERE category = $1 ORDER BY id LIMIT $2 OFFSET $3" + CRUD_UPDATE_SQL = "UPDATE items SET name = $1, price = $2, quantity = $3 WHERE id = $4" + CRUD_UPSERT_SQL = <<~SQL + INSERT INTO items + (#{CRUD_COLUMNS}) + VALUES ($1, $2, $3, $4, $5, true, '[\"bench\"]', 0, 0) + ON CONFLICT (id) DO UPDATE SET name = $2, price = $4, quantity = $5 + RETURNING id + SQL + + private + + def self.get_async_db + @async_db ||= begin + return unless ENV['DATABASE_URL'] + ConnectionPool.new(size: pool_size, timeout: 5) do + db = PG.connect(ENV['DATABASE_URL']) + db.prepare('select', SELECT_QUERY) + db.prepare('crud_get', CRUD_GET_SQL) + db.prepare('crud_list', CRUD_LIST_SQL) + db.prepare('crud_update', CRUD_UPDATE_SQL) + db.prepare('crud_upsert', CRUD_UPSERT_SQL) + db + end + end + end + + def self.redis + @redis ||= begin + return unless ENV['REDIS_URL'] + ConnectionPool::Wrapper.new(size: pool_size, timeout: 10) do + Redis.new(url: ENV['REDIS_URL']) + end + end + end + + def self.pool_size + processors = Integer(::Concurrent.available_processor_count) + pool_size = (2 * Math.log(256 / processors)).floor + end +end diff --git a/frameworks/rage/app/controllers/benchmark_controller.rb b/frameworks/rage/app/controllers/benchmark_controller.rb index f1586dc85..733171cbf 100644 --- a/frameworks/rage/app/controllers/benchmark_controller.rb +++ b/frameworks/rage/app/controllers/benchmark_controller.rb @@ -9,7 +9,7 @@ def symbolize_keys! end end -class BenchmarkController < RageController::API +class BenchmarkController < ApplicationController DATA_DIR = ENV.fetch('DATA_DIR', '/data') dataset_path = File.join DATA_DIR, 'dataset.json' @@ -24,8 +24,6 @@ def self.dataset_items = @dataset_items FileUtils.cp_r(File.join(DATA_DIR, 'static'), File.join(Rage.root, 'public', 'static')) - PG_QUERY = 'SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN $1 AND $2 LIMIT $3' - def baseline_one total = params[:a].to_i + params[:b].to_i if request.post? @@ -94,19 +92,4 @@ def upload def not_found head 404 end - - private - - def self.get_async_db - @async_db ||= begin - return unless ENV['DATABASE_URL'] - processors = Integer(::Concurrent.available_processor_count) - pool_size = (2 * Math.log(256 / processors)).floor - ConnectionPool.new(size: pool_size, timeout: 5) do - db = PG.connect(ENV['DATABASE_URL']) - db.prepare('select', PG_QUERY) - db - end - end - end end diff --git a/frameworks/rage/app/controllers/items_controller.rb b/frameworks/rage/app/controllers/items_controller.rb new file mode 100644 index 000000000..488901e5c --- /dev/null +++ b/frameworks/rage/app/controllers/items_controller.rb @@ -0,0 +1,110 @@ +class ItemsController < ApplicationController + def index + category = params[:category] || 'electronics' + page = (params[:page] || 1).to_i + limit = (params[:limit] || 10).to_i + offset = (page - 1) * limit + + rows = self.class.get_async_db&.with do |connection| + connection.exec_prepared('crud_list', [category, limit, offset]) + end || [] + + items = rows.map do |row| + map_row(row) + end + render json: { items: items, total: items.length, page: page, limit: limit } + end + + def show + id = params[:id] + json = self.class.redis&.with do |connection| + connection.get(id.to_s) + end + if json + headers['x-cache'] = ' HIT' + return render json: json + else + headers['x-cache'] = ' MISS' + end + + rows = self.class.get_async_db&.with do |connection| + connection.exec_prepared('crud_get', [id]) + end || [] + + if row = rows.first + item = map_row(row) + json = JSON.generate(item) + self.class.redis&.with do |connection| + connection.set(id.to_s, json) + end + render json: item + else + head 404 + end + end + + def create + id = params[:id] + name = params[:name] || 'New Product' + category = params[:category] || 'electronics' + price = (params[:price] || 0).to_i + quantity = (params[:quantity] || 0).to_i + + self.class.get_async_db&.with do |connection| + connection.exec_prepared('crud_upsert', [id, name, category, price, quantity]) + end + + self.class.redis&.with do |connection| + connection.del(id.to_s) + end + + item = { + 'id' => id, + 'name' => name, + 'category' => category, + 'price' => price, + 'quantity' => quantity + } + + render json: item, status: 201 + end + + def update + id = params[:id] + name = params[:name] || 'New Product' + price = (params[:price] || 0).to_i + quantity = (params[:quantity] || 0).to_i + + row = self.class.get_async_db&.with do |connection| + connection.exec_prepared('crud_update', [name, price, quantity, id]) + end || [] + + self.class.redis&.with do |connection| + connection.del(id.to_s) + end + + item = { + 'id' => id, + 'name' => name, + 'price' => price, + 'quantity' => quantity + } + render json: item + end + + private + + def map_row(row) + mapped_row = { + id: row['id'], + name: row['name'], + category: row['category'], + price: row['price'], + quantity: row['quantity'], + active: row['active'] == 1, + } + mapped_row[:tags] = JSON.parse(row['tags']) if row['tags'] + mapped_row[:rating] = { score: row['rating_score'], count: row['rating_count'] } if row['rating_score'] && row['rating_count'] + mapped_row + end +end diff --git a/frameworks/rage/config/routes.rb b/frameworks/rage/config/routes.rb index f59008714..5899ccb5a 100644 --- a/frameworks/rage/config/routes.rb +++ b/frameworks/rage/config/routes.rb @@ -4,12 +4,16 @@ 'content-type' => 'text/plain' }, ['ok']] end - get '/baseline11', to: 'benchmark#baseline_one' - post '/baseline11', to: 'benchmark#baseline_one' - get '/baseline2', to: 'benchmark#baseline_two' - get '/json/:count', to: 'benchmark#json_endpoint' - get '/async-db', to: 'benchmark#async_db' - post '/upload', to: 'benchmark#upload' + get '/baseline11', to: 'benchmark#baseline_one' + post '/baseline11', to: 'benchmark#baseline_one' + get '/baseline2', to: 'benchmark#baseline_two' + get '/json/:count', to: 'benchmark#json_endpoint' + get '/async-db', to: 'benchmark#async_db' + post '/upload', to: 'benchmark#upload' + get '/crud/items', to: 'items#index' + get '/crud/items/:id', to: "items#show" + post '/crud/items', to: 'items#create' + put '/crud/items/:id', to: 'items#update' # Catch-all for unknown paths → 404 get '*', to: 'benchmark#not_found' diff --git a/frameworks/rage/meta.json b/frameworks/rage/meta.json index 2bc3f6a1d..dd11dd893 100644 --- a/frameworks/rage/meta.json +++ b/frameworks/rage/meta.json @@ -16,6 +16,7 @@ "api-4", "api-16", "async-db", + "crud", "static" ], "maintainers": ["p8"]