werner / active_record.cr

Active Record pattern implementation for Crystal. Don't confuse with Ruby's activerecord: aim of this is to be true to OO techniques and true to the Active Record pattern. Small, simple, useful for non-complex domain models. For complex domain models better use Data Mapper pattern.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

active_record

Active Record pattern implementation for Crystal.

Don't confuse with Ruby's activerecord: aim of this is to be true to OO techniques and true to the Active Record pattern. Small, simple, useful for non-complex domain models. For complex domain models better use Data Mapper pattern.

Work in progress

TODO

  • Implement model definition syntax
  • Implement field_level
  • Implement NullAdapter (in-memory, for specs)
  • Implement #create, .create and .read
  • Implement .where
  • Implement query_level
  • Implement #update and #delete
  • Implement better query DSL
  • Default table_name implementation
  • Implement mysql adapter and set it to default
  • Populate this list further by making some simple app on top of it
  • Describe in readme how to implement your own adapter
  • Support more types (currently only Int|String are supported)
  • Implement sqlite driver and adapter
  • Implement postgres driver and adapter and set it to default

Installation

Add it to Projectfile

deps do
  github "waterlink/active_record.cr"
end

Usage

require "active_record"

Define your model

class Person < ActiveRecord::Model

  # Set adapter, defaults to mysql (subject to change to postgres)
  # adapter sqlite

  # Set table name, defaults to "#{lowercase_name}s"
  # table_name people

  # Database fields
  primary id                 :: Int
  field last_name            :: String
  field first_name           :: String
  field number_of_dependents :: Int

  # Domain logic
  def get_tax_exemption
    # ...
  end

  def get_taxable_earnings
    # ...
  end

end

Create new record

# Combine .new(..) and #create
Person.new({ "first_name"           => "John",
             "last_name"            => "Smith",
             "number_of_dependents" => 3 }).create #=> #<Person: ...>

# Or shortcut with .create(..)
Person.create({ "first_name"           => "John",
                "last_name"            => "Smith",
                "number_of_dependents" => 3 })     #=> #<Person: ...>

Read existing record by id

Person.read(127)  #=> #<Person: @id=127, ...>

Query multiple records

# Query by hash
Person.where({ "number_of_dependents" => 0 })   #=> [#<Person: ...>, #<Person: ...>, ...]

# Or construct a query object
include ActiveRecord::CriteriaHelper
Person.where(criteria("number_of_dependents") > 3)    #=> [#<Person: ...>, #<Person: ...>, ...]

See Query DSL

Update existing record

person = Person.read(127)
person.number_of_dependents = 0
person.update

Delete existing record

Person.read(127).delete

Enforcing encapsulation

If you care about OO techniques, code quality and handling complexity, please enable this for you models.

class Person < ActiveRecord::Model

  # Default is public for ease of use
  field_level :private
  # field_level :protected

  query_level :private
  # default is public, there is no point in protected here

  # ...
end

# Enforces you to maintain encapsulation, ie: not expose your data -
# put behavior in the same place the data it needs
person = Person.find(127)
person.first_name   #=> Error: unable to call private method first_name

# Enforces you to maintain DRYness to some extent, ie: not leak
# knowledge about your database structure, but put it in active record
# model and expose your own nit-picked methods
Person.where({ :first_name => "John" })    #=> Error: unable to call private method where

Query DSL

Examples (comment is in format [sql_query, params]):

criteria("person_id") == 3                            # [person_id = :1, { "1" => 3 }]
criteria("person_id") == criteria("other_person_id")  # [person_id = other_person_id, {}]

criteria("number") <= 3                               # [number < :1, { "1" => 3 }]

(!(criteria("number") <= 3))                          # [(NOT (number <= :1)) AND (number <> :2),
  .and(criteria("number") != 5)                       #  { "1" => 3, "2" => 5 }]

criteria("subject_id").is_not_null                    # [(subject_id) IS NOT NULL, {}]

Supported comparison operators: == != > >= < <=

Supported logic operators: or | and & xor ^ not !

Supported is operators: is_true is_not_true is_false is_not_false is_unknown is_not_unknown is_null is_not_null

Development

After cloning the project:

cd active_record.cr
crystal deps   # install dependencies
crystal spec   # run specs

Just use normal TDD development style.

Contributing

  1. Fork it ( https://github.com/waterlink/active_record.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

  • waterlink Oleksii Fedorov - creator, maintainer

About

Active Record pattern implementation for Crystal. Don't confuse with Ruby's activerecord: aim of this is to be true to OO techniques and true to the Active Record pattern. Small, simple, useful for non-complex domain models. For complex domain models better use Data Mapper pattern.

License:MIT License


Languages

Language:Crystal 100.0%