Coder Social home page Coder Social logo

terrestrial's Introduction

Terrestrial

TL;DR

  • A Ruby ORM that enables DDD and clean architectural styles.
  • Persists plain objects while supporting arbitrarily deeply nested / circular associations
  • Provides excellent database and query building support courtesy of the Sequel library

Terrestrial is a new, currently experimental data mapper ORM implementation for Ruby.

The aim is to provide a convenient way to query and persist graphs of Ruby objects (think models with associations), while keeping those object completely isolated and decoupled from the database.

In contrast to Ruby's many active record implementations, domain objects require no special inherited or mixed in behavior in order to be persisted. In fact Terrestrial has no specific requirements for domain objects at all. While there is a simple default, .new and #to_h, you may define arbitrary functions (per mapping) and expose no reader methods at all.

Features

  • Absolute minimum coupling between domain and persistence
  • Persistence of plain or arbitrary objects
  • Associations (belongs_to, has_many, has_many_through)
  • Automatic 'convention over configuration' that is fully customizable
  • Lazy loading of associations
  • Optional eager loading to avoid the n + 1 query problem
  • Dirty tracking for database write efficiency
  • Predefined queries, scopes or subsets

There are some conspicuous missing features that you may want to read more about. If you want to contribute to solving any of the problems listed please open an issue to discuss.

Terrestrial does not reinvent the wheel with querying abstraction and migrations, instead these responsibilities are delegated to Sequel such that its full power can be utilised.

For querying, migrations and creating your database connection see the Sequel documentation.

Getting started

Please try this out, experiment, open issues and pull requests. Please read the code of conduct first.

  # 1. Define some domain objects, structs will surfice for the example

  User = Struct.new(:id, :first_name, :last_name, :email, :posts)
  Post = Struct.new(:id, :author, :subject, :body, :created_at, :categories)
  Category = Struct.new(:id, :name, :posts)

  ## Also assume that a conventional database schema (think Rails) is in place,
  ## a column for each of the struct's attributes will be present. The posts
  ## table will have `author_id` as a foreign key to the users table. There is
  ## a join table named `categories_to_posts` which facilitates the many to
  ## many relationship.

  # 2. Configure a Sequel database connection

  ## Terrestrial does not manage your connection for you.
  ## Example assumes Postgres however Sequel supports many other databases.

  DB = Sequel.postgres(
    host: ENV.fetch("PGHOST"),
    user: ENV.fetch("PGUSER"),
    database: ENV.fetch("PGDATABASE"),
  )

  # 3. Configure mappings and associations

  ## This is kept separate from your domain models as knowledge of the schema
  ## is required to wire them up.

  CONFIG = Terrestrial.config(DB)
    .setup_mapping(:users) { |users|
      users.class(User) # Specify a class and the constructor will be used
      users.has_many(:posts, foreign_key: :author_id)
    }
    .setup_mapping(:posts) { |posts|
      # To avoid directly specifiying a class, a factory function can be used instead
      posts.factory(->(attrs) { Post.new(attrs) })
      posts.belongs_to(:author, mapping_name: :users)
      posts.has_many_through(:categories)
    }
    .setup_mapping(:categories) { |categories|
      categories.class(Category)
      categories.has_many_through(:posts)
    }

  # 4. Create an object store by combining a connection and a configuration

  OBJECT_STORE = Terrestrial.object_store(config: CONFIG)

  ## You are not limited to one object store configuration or one database
  ## connection. To handle complex situations you may create several segregated
  ## mappings and object stores for your separate aggregate roots, potentially
  ## utilising multiple databases and different domain object
  ## classes/compositions.

  # 5. Create some objects

  user = User.new(
    "2f0f791c-47cf-4a00-8676-e582075bcd65",
    "Hansel",
    "Trickett",
    "[email protected]",
    [],
  )

  user.posts << Post.new(
    "9b75fe2b-d694-4b90-9137-6201d426dda2",
    user,
    "Things that I like",
    "I like fish and scratching",
    Time.parse("2015-10-03 21:00:00 UTC"),
    [],
  )

  # 6. Save them

  OBJECT_STORE[:users].save(user)

  ## Only the (aggregate) root object needs to be passed to the mapper.

  # 7. Query

  user = OBJECT_STORE[:users].where(id: "2f0f791c-47cf-4a00-8676-e582075bcd65").first

  # => #<struct User
  #  id="2f0f791c-47cf-4a00-8676-e582075bcd65",
  #  first_name="Stephen",
  #  last_name="Best",
  #  email="[email protected]",
  #  posts=#<Terrestrial::CollectionMutabilityProxy:7ff57192d510 >,

Running the tests

ENV vars

The test suite expects the following standard Postgres environment variables.

  • PGHOST
  • PGUSER
  • PGDATABASE

Create a test database

This will create a database named from the value of PGDATABASE

$ bundle exec rake db:create

Run all tests RSpec and Cucumber

The RSpec tests run twice, once against Sequel/Postgres and again against an in-memory datastore.

Cucumber runs only against the Sequel/Postgres backend.

$ bin/test

Should anything go awry

Drop the test database and start fresh

$ bundle exec rake db:drop

Installation

This library is still pre 1.0 so please lock down your version and update with care.

Add the following to your Gemfile.

gem "terrestrial", "0.0.3"

And then execute:

$ bundle

Or install it manually:

$ gem install terrestrial

terrestrial's People

Contributors

bestie avatar mikejustdoit avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.