jennifer

Active Record pattern implementation with flexible query chainable builder and migration system orm chainable-methods activerecord mysql postgresql
0.3.3 released

Jennifer Build Status Latest Release

Another one ActiveRecord pattern realization for Crystal with grate query DSL and migration mechanism.

Please visit extended wiki to find extended information and instrcutions.

Installation

Add this to your application's shard.yml:

dependencies:
  jennifer:
    github: imdrasil/jennifer.cr

Also you need to choose one of existing adapters for your db: mysql or postgres.

Usage

Jennifer allows you to maintain everything for your models - from db migrations and field mapping to callbacks and building queries. All configuration instructions could be found on wiki page.

Migration

To start using Jennifer firstly generate migration:

$ crystal sam.cr -- generate:migration CreateContact

and fill created migration file with next content:

class CreateContact20170119011451314 < Jennifer::Migration::Base
  def up
      create_enum(:gender_enum, ["male", "female"]) # postgres specific command
      create_table(:contacts) do |t|
        t.string :name, {:size => 30}
        t.integer :age
        t.integer :tags, {:array => true}
        t.field :gender, :gender_enum
        t.timestamps
      end
  end

  def down
    drop_table :contacts
    drop_enum(:gender_enum)
  end
end

and run

$ crystal sam.cr -- db:setup

to create database and run newly added migration.

For command management Jennifer uses Sam.

Model

Several model examples

class Contact < Jennifer::Model::Base
  with_timestamps
  mapping(
    id: {type: Int32, primary: true},
    name: String,
    gender: {type: String, default: "male", null: true},
    age: {type: Int32, default: 10},
    description: {type: String, null: true},
    created_at: {type: Time, null: true},
    updated_at: {type: Time, null: true}
  )

  has_many :facebook_profiles, FacebookProfile
  has_and_belongs_to_many :countries, Country
  has_and_belongs_to_many :facebook_many_profiles, FacebookProfile, join_foreign: :profile_id
  has_one :passport, Passport

  validates_inclucion :age, 13..75
  validates_length :name, minimum: 1, maximum: 15
  validates_with_method :name_check

  scope :main { where { _age > 18 } }
  scope :older { |age| where { _age >= age } }
  scope :ordered { order(name: :asc) }

  def name_check
    if @description && @description.not_nil!.size > 10
      errors.add(:description, "Too large description")
    end
  end
end

class Passport < Jennifer::Model::Base
  mapping(
    enn: {type: String, primary: true},
    contact_id: {type: Int32, null: true}
  )

  validates_with [EnnValidator]
  belongs_to :contact, Contact
end

class Profile < Jennifer::Model::Base
  mapping(
    id: {type: Int32, primary: true},
    login: String,
    contact_id: Int32?,
    type: String
  )

  belongs_to :contact, Contact
end

class FacebookProfile < Profile
  sti_mapping(
    uid: String
  )

  has_and_belongs_to_many :facebook_contacts, Contact, foreign: :profile_id
end

class Country < Jennifer::Model::Base
  mapping(
    id: {type: Int32, primary: true},
    name: String
  )

  validates_exclusion :name, ["asd", "qwe"]
  validates_uniqueness :name

  has_and_belongs_to_many :contacts, Contact
end

Quering

Jennifer allows you to query db using flexible dsl:

Contact.all.left_join(Passport) { _contact_id == _contact__id }
            .order("contacts.id": :asc)
            .with(:passport).to_a
Contact.all.includes(:countries).where { __countries { _name.like("%tan%") } }
Contact.all.group(:gender).group_avg(:age, PG::Numeric)

Much more about query dsl could be found on wiki page

Important restrictions

  • sqlite3 has a lot of limitations so it's support will be added not soon

Test

The fastest way to rollback all changes in DB after test case - transaction. So add:

Spec.before_each do
  Jennifer::Adapter.adapter.begin_transaction
end

Spec.after_each do
  Jennifer::Adapter.adapter.rollback_transaction
end

to your spec_helper.cr. Also just regular deleting or truncation could be used but transaction provide 15x speed up (at least for postgres; mysql gets less impact).

This functions can be safely used only under test environment.

Development

There are still a lot of work to do. Tasks for next versions:

  • [ ] add SQLite support
  • [ ] increase test coverage to acceptable level
  • [ ] add json operators
  • [ ] add possibility for #group accept any sql string
  • [ ] add polymorphic associations
  • [ ] add through to relations
  • [ ] add subquery support
  • [ ] add join table option for all relations
  • [ ] refactor many-to-many relation
  • [ ] add seeds
  • [ ] rewrite tests to use minitest
  • [ ] add self documentation
  • [ ] add views support (materialized as well)

Before development create db user (information is in /spec/config.cr file), run

$ crystal example/migrate.cr -- db:setup

Support both MySql and PostgreSQL are critical. By default postgres are turned on. To run tests with mysql use next:

$ DB=mysql crystal spec

Documentation

I try to keep current README with uptodate information. Self documentation is not fully support yet but you can compile docs using shell script:

$ ./generate-docs.sh

It also depends on choosed adapter (postgres is by default).

Contributing

  1. Fork it
  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

Please ask me before starting work on smth.

Also if you want to use it in your application (for now shard is almost ready for use in production) - ping me please, my email you can find in my profile.

To run tests use regular crystal spec. All migrations is under ./examples/migrations directory.

Contributors

  • imdrasil Roman Kalnytskyi - creator, maintainer
jennifer:
  github: imdrasil/jennifer.cr
  version: ~> 0.3.3
License MIT
Crystal 0.23.1

Authors

Dependencies 4

  • accord ~> 1.1.0
    {'github' => 'neovintage/accord', 'version' => '~> 1.1.0'}
  • ifrit
    {'github' => 'imdrasil/ifrit'}
  • inflector ~> 0.1.8
    {'github' => 'phoffer/inflector.cr', 'version' => '~> 0.1.8'}
  • sam 0.2.1
    {'github' => 'imdrasil/sam.cr', 'version' => '0.2.1'}

Development Dependencies 5

  • factory
    {'github' => 'imdrasil/factory'}
  • minitest
    {'github' => 'ysbaddaden/minitest.cr'}
  • mysql
    {'github' => 'crystal-lang/crystal-mysql'}
  • pg
    {'github' => 'will/crystal-pg'}
  • sqlite3
    {'github' => 'crystal-lang/crystal-sqlite3'}
Last synced .
search fire star recently