jennifer
Jennifer

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
- Fork it
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- 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