module Onyx::SQL::Serializable

Overview

Include this module to make an object serializable from DB::ResultSet. This is particulary useful for reusable business objects. In an Onyx::SQL::Serializable object, all its instance variables must be nilable:

struct PopularProducts
  include Onyx::SQL::Serializable

  @id : Int32?
  @popularity : Float64?
end

rs = db.query("SELECT id, popularity ...")
popular_products = PopularProducts.from_rs(rs) # => Array(PopularProducts)

Remember that a row's column names must match the object's instance variables. You still can use Field and Reference annotations, though:

struct PopularProducts
  include Onyx::SQL::Serializable

  # .from_rs would now expect a column named "the_id" instead of "id"
  @[Onyx::SQL::Field(key: "the_id")]
  @id : Int32?
end

Read more about Reference serialization below.

You can also use Repository#query method as soon as it calls .from_rs with result set columns matching the desired keys. Keep that in mind when using Query, because it could return columns with invalid or unknown (to this serializable object) names, resulting in a DB::MappingException runtime error.

Model includes Serializable and it also has Model.schema DSL macro, so if you want to map a model, using Model module over Serializable is preferrable.

Serializing references

Before reading this section, make yourself familiar with Reference annotation.

Let's consider this classic example:

class User
  include Onyx::SQL::Model

  schema users do
    pkey id : Int32
    type username : String
    type authored_posts : Array(Post), foreign_key: "author_id"
  end
end

class Post
  include Onyx::SQL::Model

  schema posts do
    pkey id : Int32
    type body : String
    type author : User, key: "author_id"
  end
end

Direct non-enumerable references

Post#author is a direct non-enumerable reference and it can be preloaded from a result set, as the row is linear and sufficient to store all the author's data:

| post_id | post_content | author_id | author_username |
| ------- + ------------ + --------- + --------------- |
| 17      | "Hello"      | 42        | "John"          |

However, a Post can have multiple direct references and they all can have overlapping column names, so to avoid it, there is a concept of select markers. A marker wraps a reference's column in the result set, allowing to distinguish between references:

| id | content | _author | id | username | _author |
| -- + ------- + ------- + -- + -------- + ------- +
| 17 | "Hello" |         | 42 | "John"   |         |

In this example, columns named "_author" are markers and they're essentialy empty strings. It results in ability to properly preload references, in this case:

post = Post.from_rs(rs).first
pp post # => <Post @id=17 @content="Hello" @author=<User @id=42 @username="John">>

Foreign non-enumerable references

In one-to-one relations, a model can have a foreign non-enumerable reference, for example:

class User
  schema users do
    pkey id : Int32
    type settings : Settings, foreign_key: "user_id"
  end
end

class Settings
  schema settings do
    pkey id : Int32
    type foo : String
    type user : User, key: "user_id"
  end
end

In this case, you can preload User#settings along with a User instance, because it fits a single row:

user = User
  .where(id: 42)
  .select(:id, :username)
  .join(settings: true) do |q|
    q.select(:foo)
  end
end

The row would look like this:

| id | username | _settings | foo   | _settings |
| -- + -------- + --------- + ----- + --------- +
| 42 | "John"   |           | "bar" |           |

And it would be perfectly parseable by .from_rs.

Enumerable references

Now let's consider User#authored_posts, which is a foreign enumerable reference. You can not effectively put many posts in a single row, that's why it's impossible to preload enumerable references at all. If you want to get all posts authored by a user, you should query the "posts" table, not the "users", and receive a collection of Post rows.

This restriction applies both to direct and foreign enumerable references.

Included Modules

Defined in:

onyx-sql/serializable.cr

Class Method Summary

Class Method Detail

def self.from_rs(rs : DB::ResultSet) : Array(self) #

Initialize an array of self from a database result set. Raises DB::MappingException if there is an unknown column, so make sure the result set's rows contain only well-known columns.


[View source]