Project

rasti-db

0.0
A long-lived project that still receives updates
Database collections and relations
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 0.8
~> 5.0, < 5.11
~> 0.2
~> 12.3
~> 0.12
~> 1.3

Runtime

~> 0.0, >= 0.0.2
~> 1.0, >= 1.0.3
~> 0.5
~> 0.0
~> 5.0
~> 0.1, >= 0.1.3
~> 1.4.8
 Project Readme

Rasti::DB

Gem Version CI Coverage Status Code Climate

Database collections and relations

Installation

Add this line to your application's Gemfile:

gem 'rasti-db'

And then execute:

$ bundle

Or install it yourself as:

$ gem install rasti-db

Usage

Database connection

DB_1 = Sequel.connect ...
DB_2 = Sequel.connect ...

Database schema

DB_1.create_table :users do
  primary_key :id
  String :name, null: false, unique: true
end

DB_1.create_table :posts do
  primary_key :id
  String :title, null: false, unique: true
  String :body, null: false
  Integer :language_id, null: false, index: true
  foreign_key :user_id, :users, null: false, index: true
end

DB_1.create_table :comments do
  primary_key :id
  String :text, null: false
  foreign_key :user_id, :users, null: false, index: true
  foreign_key :post_id, :posts, null: false, index: true
end

DB_1.create_table :categories do
  primary_key :id
  String :name, null: false, unique: true
end

DB_1.create_table :categories_posts do
  foreign_key :category_id, :categories, null: false, index: true
  foreign_key :post_id, :posts, null: false, index: true
  primary_key [:category_id, :post_id]
end

DB_1.create_table :people do
  String :document_number, null: false, primary_key: true
  String :first_name, null: false
  String :last_name, null: false
  Date :birth_date, null: false
  foreign_key :user_id, :users, null: false, unique: true
end

DB_1.create_table :languages_people do
  Integer :language_id, null: false, index: true
  foreign_key :document_number, :people, type: String, null: false, index: true
  primary_key [:language_id, :document_number]
end

DB_2.create_table :languages do
  primary_key :id
  String :name, null: false, unique: true
end

Models

User     = Rasti::DB::Model[:id, :name, :posts, :comments, :person]
Post     = Rasti::DB::Model[:id, :title, :body, :user_id, :user, :comments, :categories]
Comment  = Rasti::DB::Model[:id, :text, :user_id, :user, :post_id, :post]
Category = Rasti::DB::Model[:id, :name, :posts]
Person   = Rasti::DB::Model[:document_number, :first_name, :last_name, :full_name, :birth_date, :user_id, :user]
Language = Rasti::DB::Model[:id, :name, :people]

Collections

class Users < Rasti::DB::Collection
  one_to_many :posts
  one_to_many :comments
  one_to_one :person
end

class Posts < Rasti::DB::Collection
  many_to_one :user
  many_to_many :categories
  one_to_many :comments

  query :created_by do |user_id|
    where user_id: user_id
  end

  query :entitled, -> (title) { where title: title }

  query :commented_by do |user_id|
    chainable do
      dataset.join(qualify(:comments), post_id: :id)
             .where(Sequel[:comments][:user_id] => user_id)
             .select_all(:posts)
             .distinct
    end
  end
end

class Comments < Rasti::DB::Collection
  many_to_one :user
  many_to_one :post
end

class Categories < Rasti::DB::Collection
  many_to_many :posts
end

class People < Rasti::DB::Collection
  set_collection_name :people
  set_primary_key :document_number
  set_foreign_key :document_number
  set_model Person

  many_to_one :user
  many_to_many :languages

  computed_attribute :full_name do
    Rasti::DB::ComputedAttribute.new Sequel.join([:first_name, ' ', :last_name])
  end
end

class Languages < Rasti::DB::Collection
  set_data_source_name :other

  one_to_many :posts
  many_to_many :people, collection: People, relation_data_source_name: :default
end

environment = Rasti::DB::Environment.new default: Rasti::DB::DataSource.new(DB_1),
                                         other: Rasti::DB::DataSource.new(DB_2, 'custom_schema')

users      = Users.new environment
posts      = Posts.new environment
comments   = Comments.new environment
categories = Categories.new environment
people     = People.new environment

Persistence

DB_1.transaction do
  id = users.insert name: 'User 1'
  users.update id, name: 'User updated'
  users.delete id

  users.bulk_insert [{name: 'User 1'}, {name: 'User 2'}]
  users.bulk_update(name: 'User updated') { where id: [1,2] }
  users.bulk_delete { where id: [1,2] }

  posts.insert_relations 1, categories: [2,3]
  posts.delete_relations 1, categories: [2,3]
end

Queries

posts.all # => [Post, ...]
posts.first # => Post
posts.count # => 1

posts.where(id: [1,2]) # => [Post, ...]
posts.where{id > 1}.limit(10).offset(20) } # => [Post, ...]

posts.where(id: [1,2]).raw # => [{id:1, ...}, {id:2, ...}]
posts.where(id: [1,2]).primary_keys # => [1,2]
posts.where(id: [1,2]).pluck(:id) # => [1,2]
posts.where(id: [1,2]).pluck(:id, :title) # => [[1, ...], [2, ...]]

posts.created_by(1) # => [Post, ...]
posts.created_by(1).entitled('...').commented_by(2) # => [Post, ...]
posts.with_categories([1,2]) # => [Post, ...]

posts.graph(:user, :language, :categories, 'comments.user') # => [Post(User, Language, [Categories, ...], [Comments(User)]), ...]

posts.join(:user).where(name: 'User 4') # => [Post, ...]

posts.select_attributes(:id, :title) # => [Post, ...]
posts.exclude_attributes(:id, :title) # => [Post, ...]
posts.all_attributes # => [Post, ...]

posts.graph('user.person').select_graph_attributes(user: [:id], 'user.person': [:last_name, :user_id]) # => [Post, ...]
posts.graph('user.person').exclude_graph_attributes(user: [:name], 'user.person': [:first_name, :last_name]) # => [Post, ...]
posts.graph('user.person').all_graph_attributes('user.person') # => [Post, ...]

posts.each { |post| do_something post } # Iterate posts loading all at first
posts.each(batch_size: 1000) { |post| do_something post } # Iterate posts loading in batches
posts.each_batch(size: 1000) { |posts| do_something posts } # Iterate batches of posts

Natural Query Language

posts.nql('id = 1') # => Equal
posts.nql('id != 1') # => Not equal
posts.nql('title: My post') # => Include
posts.nql('title !: My post') # => Not include
posts.nql('title ~ My post') # => Insensitive like
posts.nql('id > 1') # => Greater
posts.nql('id >= 1') # => Greater or equal
posts.nql('id < 10') # => Less
posts.nql('id <= 10') # => Less or equal

posts.nql('id = 1 | id = 2') # => Or
posts.nql('id > 1 & title: "My post"') # => And
posts.nql('(id > 3 & id < 10) | title: "My post"') # => Precedence

posts.nql('comments.user.person.document_number = 7') # => Nested

Development

Rasti::DB uses treetop to perform queries using natural language. To recompile the syntax, simply run the following command in lib/rasti/db/nql:

tt syntax.treetop -o syntax.rb

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/gabynaiman/rasti-db.

License

The gem is available as open source under the terms of the MIT License.