module Clear::Model

Overview

Model definition is made by adding the Clear::Model mixin in your class.

Simple Model

class MyModel
  include Clear::Model

  column my_column : String
end

We just created a new model, linked to your database, mapping the column my_column of type String (text in postgres).

Now, you can play with your model:

row = MyModel.new # create an empty row
row.my_column = "This is a content"
row.save! # insert the new row in the database !

By convention, the table name will follow an underscore, plural version of your model: my_models. A model into a module will prepend the module name before, so Logistic::MyModel will check for logistic_my_models in your database. You can force a specific table name using:

class MyModel
  include Clear::Model
  self.table = "another_table_name"
end

Presence validation

Unlike many ORM around, Clear carry about non-nullable pattern in crystal. Meaning column my_column : String assume than a call to row.my_column will return a String.

But it exists cases where the column is not yet initialized:

For example, this code will compile:

row = MyModel.new # create an empty row
puts row.my_column

However, it will throw a runtime exception You cannot access to the field 'my_column' because it never has been initialized

Same way, trying to save the object will raise an error:

row.save      # Will return false
pp row.errors # Will tell you than `my_column` presence is mandatory.

Thanks to expressiveness of the Crystal language, we can handle presence validation by simply using the Nilable type in crystal:

class MyModel
  include Clear::Model

  column my_column : String? # Now, the column can be NULL or text in postgres.
end

This time, the code above will works; in case of no value, my_column will be nil by default.

Querying your code

Whenever you want to fetch data from your database, you must create a new collection query:

MyModel.query #Will setup a vanilla 'SELECT * FROM my_models'

Queries are fetchable using each:

MyModel.query.each do |model|
  # Do something with your model here.
end

Refining your query

A collection query offers a lot of functionalities. You can read the API for more informations.

Column type

By default, Clear map theses columns types:

NOTE: The crystal-pg gems map also some structures like GIS coordinates, but their implementation is not tested in Clear. Use them at your own risk. Tell me if it's working 😉

If you need to map special structure, see Mapping Your Data guides for more informations.

Primary key

Primary key is essential for relational mapping. Currently Clear support only one column primary key.

A model without primary key can work in sort of degraded mode, throwing error in case of using some methods on them:

To setup a primary key, you can add the modifier primary: true to the column:

class MyModel
  include Clear::Model

  column id : Int32, primary: true, presence: false
  column my_column : String?
end

Note the flag presence: false added to the column. This tells Clear than presence checking on save is not mandatory. Usually this happens if you setup a default value in postgres. In the case of our primary key id, we use a serial auto-increment default value. Therefore, saving the model without primary key will works. The id will be fetched after insertion:

m = MyModel
m.save!
m.id # Now the id value is setup.

Helpers

Clear provides various built-in helpers to facilitate your life:

Timestamps

class MyModel
  include Clear::Model
  timestamps # Will map the two columns 'created_at' and 'updated_at', and map some hooks to update their values.
end

Theses fields are automatically updated whenever you call save methods, and works as Rails ActiveRecord.

With Serial Pkey

class MyModel
  include Clear::Model
  with_serial_pkey "my_primary_key"
end

Basically rewrite column id : UInt64, primary: true, presence: false

Argument is optional (default = id)

Included Modules

Direct including types

Defined in:

clear/extensions/full_text_searchable/full_text_searchable.cr
clear/model/collection.cr
clear/model/errors.cr
clear/model/model.cr

Instance Method Summary

Macro Summary

Class methods inherited from module Clear::Model::FullTextSearchable

to_tsq(text) to_tsq

Instance methods inherited from module Clear::Model::HasValidation

add_error(column, reason)
add_error(reason)
add_error
, clear_errors clear_errors, error? error?, errors : Array(Error) errors, print_errors print_errors, valid! valid!, valid? valid?, validate validate

Instance methods inherited from module Clear::Model::HasSaving

delete delete, persisted? : Bool persisted?, reload : self reload, save(on_conflict : Clear::SQL::InsertQuery -> | Nil = nil)
save(&block)
save
, save!(on_conflict : Clear::SQL::InsertQuery -> | Nil = nil)
save!(&block : Clear::SQL::InsertQuery -> )
save!
, update(**args) update, update!(**args) update!

Instance methods inherited from module Clear::Model::HasColumns

[](x) : Clear::SQL::Any [], []?(x) : Clear::SQL::Any []?, reset(h : Hash(String, _))
reset(h : Hash(Symbol, _))
reset(**t : **T) forall T
reset
, set(h : Hash(String, _))
set(h : Hash(Symbol, _))
set(**t : **T) forall T
set
, to_h(full = false) to_h, update_h update_h

Instance methods inherited from module Clear::Model::HasHooks

has_trigger?(event_name : Symbol, direction : Symbol) has_trigger?, trigger_after_events(event_name) trigger_after_events, trigger_before_events(event_name) trigger_before_events, with_triggers(event_name, &) with_triggers

Instance methods inherited from module Clear::ErrorMessages

build_error_message(message : String, ways_to_resolve : Tuple | Array = Tuple.new, manual_pages : Tuple | Array = Tuple.new) build_error_message, converter_error(from, to) converter_error, format_width(x, w = 80) format_width, illegal_setter_access_to_undefined_column(name) illegal_setter_access_to_undefined_column, lack_of_primary_key(model_name) lack_of_primary_key, migration_already_down(number) migration_already_down, migration_already_up(number) migration_already_up, migration_drop_irreversible(name) migration_drop_irreversible, migration_irreversible(name = nil, operation = nil) migration_irreversible, migration_not_found(number) migration_not_found, migration_not_unique(numbers) migration_not_unique, no_migration_yet(version) no_migration_yet, null_column_mapping_error(name, type) null_column_mapping_error, order_by_error_invalid_order(current_order) order_by_error_invalid_order, polymorphic_nil(through) polymorphic_nil, polymorphic_unknown_class(class_name) polymorphic_unknown_class, query_building_error(message) query_building_error, uid_not_found(class_name) uid_not_found, uninitialized_db_connection(connection) uninitialized_db_connection

Instance Method Detail

def ==(model : self) #

Comparison between models is made by comparing their primary keys.


[View source]
def __pkey__ #

Alias method for primary key.

If Model#id IS the primary key, then calling Model#__pkey__ is exactly the same as Model#id.

This method exists to tremendously simplify the meta-programming code. If no primary key has been setup to this model, raise an exception.


[View source]
def cache : Clear::Model::QueryCache | Nil #

[View source]

Macro Detail

macro scope(name, &block) #

A scope allow you to filter in a very human way a set of data.

Usage:

 scope("admin"){ where({role: "admin"}) }

for example, instead of writing:

  User.query.where{ (role == "admin") & (active == true) }

You can write:

  User.admin.active

Scope can be used for other purpose than just filter (e.g. ordering), but I would not recommend it.


[View source]