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:
- When the object is built with constructor without providing the value (See above).
- When an object is semi-fetched through the database query. This is useful to ignore some large fields non-interesting in the body of the current operation.
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:
String
=>text
Numbers
(any from 8 to 64 bits, float, double, big number, big float, big decimal) =>int, large int, numeric(arbitrary precision number) etc... (depends of your choice)
Bool
=>text or bool
Time
=>timestamp without timezone or text
JSON::Any
=>json and jsonb
Nilable
=>NULL
(treated as special !)
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:
collection#first
will be throwing error if noorder_by
has been setup
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
- Clear::ErrorMessages
- Clear::Model::ClassMethods
- Clear::Model::Connection
- Clear::Model::FullTextSearchable
- Clear::Model::HasColumns
- Clear::Model::HasFactory
- Clear::Model::HasHooks
- Clear::Model::HasRelations
- Clear::Model::HasSaving
- Clear::Model::HasScope
- Clear::Model::HasSerialPkey
- Clear::Model::HasTimestamps
- Clear::Model::HasValidation
- Clear::Model::Initializer
- Clear::Model::JSONDeserialize
Direct including types
Defined in:
clear/extensions/full_text_searchable/full_text_searchable.crclear/model/collection.cr
clear/model/errors.cr
clear/model/model.cr
Instance Method Summary
-
#==(model : self)
Comparison between models is made by comparing their primary keys.
-
#__pkey__
Alias method for primary key.
- #cache : Clear::Model::QueryCache | Nil
Macro Summary
-
scope(name, &block)
A scope allow you to filter in a very human way a set of data.
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
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.
Macro Detail
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.