/pon.cr

Maiha's private ORM for Crystal

Primary LanguageCrystalMIT LicenseMIT

pon.cr Build Status

Maiha's private ORM for Crystal.

tested
  • crystal: 1.6.0-alpine
    • db: crystal-db-0.10.1
    • mysql: crystal-mysql-0.13.0
    • sqlite3: crystal-sqlite3-0.18.0
    • pg: will/crystal-pg-0.23.2
  • mysql:5.6
  • postgres:12.4

Usage

require "pon"
require "pon/adapter/mysql"  # "mysql", "pg", "sqlite"

# Enum support!
enum Code
  OK  = 200
  ERR = 500
end

# Model definition
class Job < Pon::Model
  adapter mysql              # "mysql", "pg", "sqlite"
  field   name : String
  field   time : Time
  field   code : Code = Code::OK  # default value
end

Pon::Adapter::Mysql.setting.url = "mysql://root@127.0.0.1:3306/test"

# Create table
Job.migrate! # drop and create the table
Job.count                 # => 0

# CRUD
job = Job.new(name: "foo")
job.name                  # => "foo"
job.time?                 # => nil
job.time                  # raises Pon::ValueNotFound
job.code                  # => Code::OK
job.save                  # => true
Job.find(job.id).code.ok? # => true
Job.create!(name: "bar", code: Code::ERR)

# Finder
Job.all.size                      # => 2
Job.all(where: "code = 200").size # => 1
Job.all(["code"]).map(&.name?)    # => [nil, nil]

# And more useful features
Job.pluck(["name"]) # => [["foo"], ["bar"]]
Job.count_by_code   # => {Code::OK => 1, Code::ERR => 1}

DataTypes

The following data types are supported (tested).

  • Bool
  • Enum(Int32)
  • Int32
  • String
  • Time

See defined fields written in spec/spec_helper.cr.

API : Adapter

  def exec(sql) : Nil
  def lastval : Int64
  def scalar(*args)
  def reset! : Nil

  # CRUD
  def insert(fields, params)
  def all(fields : Array(String), types, condition = nil, **opts)
  def one?(id, fields : Array(String), as types : Tuple)
  def count : Int32
  def delete(key) : Bool
  def delete : Nil
  def truncate : Nil

  # ODBC
  def databases : Array(String)
  def tables : Array(String)

  # Experimental
  def transaction(&block) : Nil # only sqlite and pg

API : Model

class Pon::Model
  # Databases
  def self.adapter : Adapter(A)
  def self.migrator : Migrator
  def self.migrate! : Nil

  # Core
  def self.table_name : String
  def new_record? : Bool
  def to_h : Hash(String, ALL_TYPES)

  # CRUD
  def self.create! : M
  def self.create : M
  def self.count : Int32
  def self.all : Array(M)
  def self.all(query_string : String) : Array(M)
  def self.where(condition : String) : Array(M)
  def self.first : M
  def self.first? : M?
  def save : Bool
  def save! : Bool
  def self.delete_all
  def delete

  # Field "foo"
  def foo : T
  def foo? : T?
  def [](key) : T
  def []?(key) : T?
  def []=(key, val)

  # Aggregations
  def self.count_by_xxx : Hash(Type, Int64)

  # Misc
  def self.pluck(fields : Array(String))

API : Query

class JobName < Pon::Query
  adapter pg
  field name : String
  field size : Int32 = "length(name)"

  from <<-SQL
    FROM  jobs
    ORDER BY {{order}}
    LIMIT {{limit}}
    SQL

  def self.all(limit = 10, order = "name")
    context.bind(limit: limit, order: order).all
  end
end

JobName.all(limit: 2).map(&.name) # => ["bar", "foo"]

Connection Pool

The Adapter.database is pooled because it uses DB::Database. The default max_pool_size is 5. It can be overridden by Setting and reset!.

Job.adapter.database.pool.stats.max_connections # => 5

Pon::Adapter::Pg.setting.max_pool_size = 3
Pon::Adapter.reset!

Job.adapter.database.pool.stats.max_connections # => 3

Logging

Pon.query_logging=(v : Bool) # writes queries into the logger or not (default: true)
Pon.query_logging?           # => true

# when Log is enabled
Pon.logger=(v : Logger)      # replace Logger instance

# when Logger is enabled
Pon.log.backend = Log::IOBackend.new(io: File.open("sql.log", "w+"))
Pon.log.level = :debug

Roadmap

  • Adapter Core
    • connect lazily
    • share same connection between adapters
    • exec plain sql
    • exec prepared statement
    • all
    • count
    • scalar
    • quote
    • escape
    • migrator
  • Adapter Drivers
    • reset connections
    • RDB
      • mysql
      • pg
      • sqlite
    • KVS
      • redis
  • Core
    • pluralize table names
    • special types (Enum)
    • custom type
    • multibytes
    • record status
    • inspect class and records
    • callbacks
    • validations
    • natural keys
    • default values
  • CRUD
    • create
    • delete, delete_all
    • find
    • save
  • Finder
    • all, count, first
    • pluck (with casting)
    • field aliases
  • Aggregations
    • count_by_xxx
  • Relations
    • belongs_to
    • has_many
    • has_many through
  • Query
    • typed column access
    • arbitrary select query
    • prepared statement
  • Misc
    • bulk insert
    • upsert

Installation

Add this to your application's shard.yml:

dependencies:
  pon:
    github: maiha/pon.cr
    version: 1.6.0

  # one of following adapter
  mysql:
    github: crystal-lang/crystal-mysql
  sqlite3:
    github: crystal-lang/crystal-sqlite3
  pg:
    github: will/crystal-pg

Development

$ make spec

Contributing

  1. Fork it ( https://github.com/maiha/pon.cr/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors

  • maiha maiha - creator, maintainer

Thanks / Inspiration