Replace ActiveRecord with Sequel

This commit is contained in:
0x1eef 2024-03-28 02:22:08 -03:00
parent 689b650a75
commit 4cbd4d6f39
16 changed files with 96 additions and 88 deletions

View file

@ -18,7 +18,7 @@ within your local network. It is both easy to use, and easy to install.
* The server is powered by [rack](https://github.com/rack/rack) and [puma](https://github.com/puma/puma): * The server is powered by [rack](https://github.com/rack/rack) and [puma](https://github.com/puma/puma):
- Accepts GraphQL requests at `/graphql`. - Accepts GraphQL requests at `/graphql`.
- Serves client (HTML, JS, CSS). - Serves client (HTML, JS, CSS).
- Dependencies: ActiveRecord, SQLite3, ruby-graphql. - Dependencies: Sequel, SQLite3, ruby-graphql.
* The client is a statically compiled [nanoc](https://github.com/nanoc/nanoc) website: * The client is a statically compiled [nanoc](https://github.com/nanoc/nanoc) website:
- Dependencies: webpack, typescript, react. - Dependencies: webpack, typescript, react.
* The CLI controls the web server: * The CLI controls the web server:

View file

@ -4,6 +4,8 @@ module Twenty::Command::SQLiteMixin
def run_command(options) def run_command(options)
path = options.database || Twenty.default_database path = options.database || Twenty.default_database
Twenty.establish_connection(path:) Twenty.establish_connection(path:)
require "twenty-server/migration"
require "twenty-server/model"
super(options) super(options)
end end
end end

View file

@ -2,13 +2,8 @@
module Twenty module Twenty
require "fileutils" require "fileutils"
require "active_record" require "sequel"
require_relative "twenty-server/path" require_relative "twenty-server/path"
require_relative "twenty-server/graphql"
require_relative "twenty-server/rack"
require_relative "twenty-server/migration"
require_relative "twenty-server/model"
extend FileUtils
extend Path extend Path
## ##
@ -26,24 +21,24 @@ module Twenty
# #
# @return [void] # @return [void]
def self.establish_connection(path:) def self.establish_connection(path:)
ActiveRecord::Base.establish_connection( @connection = Sequel.connect(
adapter: "sqlite3", adapter: "sqlite",
database: path, database: path
pool: 16
) )
end end
## def self.connection
# Prepares the parent directory of the database. establish_connection unless @connection
# @return [void] @connection
# @api private end
def self.prepare_dir
mkdir_p(datadir) begin
mkdir_p(tmpdir) FileUtils.mkdir_p(datadir)
touch(default_database) FileUtils.mkdir_p(tmpdir)
FileUtils.touch(default_database)
rescue => ex rescue => ex
warn "prepare_dir error: #{ex.message} (#{ex.class})" warn "prepare_dir error: #{ex.message} (#{ex.class})"
end end
private_class_method :prepare_dir require_relative "twenty-server/graphql"
prepare_dir require_relative "twenty-server/rack"
end end

View file

@ -6,7 +6,7 @@ module Twenty::GraphQL::Mutation
argument :input, Twenty::GraphQL::Input::TaskInput argument :input, Twenty::GraphQL::Input::TaskInput
def resolve(input:) def resolve(input:)
Twenty::Task.new(input.to_h).save! Twenty::Task.create(input.to_h)
{"errors" => []} {"errors" => []}
rescue => ex rescue => ex
{"errors" => [ex.message]} {"errors" => [ex.message]}

View file

@ -7,8 +7,9 @@ module Twenty::GraphQL::Mutation
argument :input, Twenty::GraphQL::Input::TaskInput argument :input, Twenty::GraphQL::Input::TaskInput
def resolve(task_id:, input:) def resolve(task_id:, input:)
task = Twenty::Task.find_by(id: task_id) Twenty::Task
task.update!(input.to_h) .with_pk!(task_id)
.update!(input.to_h)
{"errors" => []} {"errors" => []}
rescue => ex rescue => ex
{"errors" => [ex.message]} {"errors" => [ex.message]}

View file

@ -11,15 +11,16 @@ module Twenty::GraphQL::Type
end end
field :projects, [Project], null: false field :projects, [Project], null: false
def find_task(task_id:) def find_task(task_id:)
Twenty::Task.find_by(id: task_id) Twenty::Task.with_pk!(task_id)
end end
def tasks(status:, project_id: nil) def tasks(status:, project_id: nil)
tasks = Twenty::Task tasks = Twenty::Task
.where(status:) .by_status(status)
.order(updated_at: :desc) .order("updated_at DESC")
project_id ? tasks.where(project_id:) : tasks (project_id ? tasks.where(project_id:) : tasks).all
end end
def projects def projects

View file

@ -1,34 +1,25 @@
# frozen_string_literal: true # frozen_string_literal: true
module Twenty::Migration module Twenty::Migration
require "sequel/extensions/migration"
## ##
# @return [String] # @return [String]
# Returns the path to twenty's migrations. # Returns the path to twenty's migrations.
def self.migrations_path def self.migrations_path
[File.join(__dir__, "migration")] File.join(__dir__, "migration")
end end
## ##
# Runs migrations (if neccessary). # Runs migrations (if neccessary).
# @return [void] # @return [void]
def self.run! def self.run!
context.migrate Sequel::Migrator.run(Twenty.connection, migrations_path)
end end
ActiveRecord.timestamped_migrations = false
## ##
# @return [Boolean] # @return [Boolean]
# Returns true when there are pending migrations. # Returns true when there are pending migrations.
def self.pending_migrations? def self.pending_migrations?
context.open.pending_migrations.any? Sequel::Migrator.is_current?(Twenty.connection, migrations_path)
end end
##
# @return [ActiveRecord::MigrationContext]
# Returns an instance of
# {ActiveRecord::MigrationContext ActiveRecord::MigrationContext}.
def self.context
@context ||= ActiveRecord::MigrationContext.new(migrations_path)
end
private_class_method :context
end end

View file

@ -1,16 +1,16 @@
# frozen_string_literal: true Sequel.migration do
up do
class CreateProjects < ActiveRecord::Migration[7.1] create_table(:projects) do
def up primary_key :id
create_table(:projects) do |t| String :name, null: false
t.string :name, null: false String :path, null: false
t.string :path, null: false DateTime :created_at
t.timestamps DateTime :updated_at
end end
add_index :projects, [:name, :path], unique: true add_index :projects, [:name, :path], unique: true
end end
def down down do
drop_table(:projects) drop_table(:projects)
end end
end end

View file

@ -1,17 +1,19 @@
# frozen_string_literal: true # frozen_string_literal: true
class CreateTasks < ActiveRecord::Migration[7.1] Sequel.migration do
def up up do
create_table(:tasks) do |t| create_table(:tasks) do |t|
t.string :title, null: false primary_key :id
t.text :content, null: false String :title, null: false
t.integer :status, null: false, default: 0 Text :content, null: false
t.belongs_to :project, null: false Integer :status, null: false, default: 0
t.timestamps DateTime :created_at
DateTime :updated_at
foreign_key :project_id, :projects, null: false
end end
end end
def down down do
drop_table(:tasks) drop_table(:tasks)
end end
end end

View file

@ -1,12 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
class AddColorToProjects < ActiveRecord::Migration[7.1] Sequel.migration do
def up up do
default = Twenty::ColorableMixin.random_color default = Twenty::ColorableMixin.random_color
add_column :projects, :color, :string, null: false, default: add_column :projects, :color, :string, null: false, default:
end end
def down down do
drop_column :projects, :color drop_column :projects, :color
end end
end end

View file

@ -1,6 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
class Twenty::Model < ActiveRecord::Base module Twenty::Model
def self.included(model)
model.plugin(:validation_class_methods)
model.plugin(:timestamps, update_on_create: true)
end
require_relative "model/mixin/colorable_mixin" require_relative "model/mixin/colorable_mixin"
require_relative "model/project" require_relative "model/project"
require_relative "model/task" require_relative "model/task"

View file

@ -14,8 +14,9 @@ module Twenty::ColorableMixin
"#993366", "#2200AA", "#557788", "#998877", "#BB4400" "#993366", "#2200AA", "#557788", "#998877", "#BB4400"
] ]
def self.included(klass) def before_validation
klass.before_validation :set_random_color, on: :create super if defined?(super)
set_random_color
end end
def random_color def random_color
@ -25,6 +26,7 @@ module Twenty::ColorableMixin
private private
def set_random_color def set_random_color
return if id
self.color = random_color self.color = random_color
end end
end end

View file

@ -1,17 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
class Twenty::Project < Twenty::Model class Twenty::Project < Sequel::Model
include Twenty::Model
include Twenty::ColorableMixin include Twenty::ColorableMixin
self.table_name = "projects"
## validates_presence_of :name
# Validations validates_presence_of :path
validates :name, presence: true one_to_many :tasks, class_name: "Twenty::Task"
validates :path, presence: true
##
# Associations
has_many :tasks, class_name: "Twenty::Task"
## ##
# @return [String] # @return [String]

View file

@ -1,18 +1,32 @@
# frozen_string_literal: true # frozen_string_literal: true
class Twenty::Task < Twenty::Model class Twenty::Task < Sequel::Model
self.table_name = "tasks" include Twenty::Model
STATUS = {backlog: 0, ready: 1, in_progress: 2, complete: 3} plugin(:enum)
enum :status, STATUS, default: :backlog STATUS_MAP = {backlog: 0, ready: 1, in_progress: 2, complete: 3}
STATUS_KEYS = STATUS_MAP.keys
enum :status, STATUS_MAP
## def self.by_status(status)
# Validations if STATUS_KEYS.any? { _1.to_s == status.to_s }
validates :title, presence: true public_send(status)
validates :content, presence: true else
validates :project, presence: true where(id: nil)
end
## end
# Associations
belongs_to :project, class_name: "Twenty::Project" validates_presence_of :title
validates_presence_of :content
validates_presence_of :project
validates_inclusion_of :status, in: [STATUS_KEYS, *STATUS_KEYS.map(&:to_s)]
many_to_one :project, class_name: "Twenty::Project"
def status=(v)
super(v.to_sym)
end
def status
super.to_s
end
end end

View file

@ -15,7 +15,7 @@ Gem::Specification.new do |gem|
gem.require_paths = ["lib"] gem.require_paths = ["lib"]
gem.summary = "twenty: server-side component" gem.summary = "twenty: server-side component"
gem.description = gem.summary gem.description = gem.summary
gem.add_runtime_dependency "activerecord", "~> 7.1" gem.add_runtime_dependency "sequel", "~> 5.78"
gem.add_runtime_dependency "sqlite3", "~> 1.6" gem.add_runtime_dependency "sqlite3", "~> 1.6"
gem.add_runtime_dependency "graphql", "~> 2.2" gem.add_runtime_dependency "graphql", "~> 2.2"
gem.add_runtime_dependency "server.rb", "~> 0.1" gem.add_runtime_dependency "server.rb", "~> 0.1"

View file

@ -15,7 +15,7 @@ Gem::Specification.new do |gem|
gem.require_paths = ["lib"] gem.require_paths = ["lib"]
gem.summary = "twenty: server-side component" gem.summary = "twenty: server-side component"
gem.description = gem.summary gem.description = gem.summary
gem.add_runtime_dependency "activerecord", "~> 7.1" gem.add_runtime_dependency "sequel", "~> 5.78"
gem.add_runtime_dependency "sqlite3", "~> 1.6" gem.add_runtime_dependency "sqlite3", "~> 1.6"
gem.add_runtime_dependency "graphql", "~> 2.2" gem.add_runtime_dependency "graphql", "~> 2.2"
gem.add_runtime_dependency "server.rb", "~> 0.1" gem.add_runtime_dependency "server.rb", "~> 0.1"