diff --git a/guides/models/README.md b/guides/models/README.md index a10ce9e..9d538b8 100644 --- a/guides/models/README.md +++ b/guides/models/README.md @@ -1,16 +1,17 @@ # Models -Amber is independent of your choice of model architectures. We \(Core members\) have built the ORM **Granite**. We also think **Jennifer** built by [Roman Kalnytskyi](https://github.com/imdrasil) is a good option. +The default ORM for Amber is [**Granite**](https://github.com/amberframework/granite), a lightweight Crystal ORM with adapters for MySQL, PostgreSQL and SQLite. Although Granite is the default configuration, the Amber framework supports alternate ORMs such as [**Jennifer**](https://github.com/imdrasil/jennifer.cr) built by [Roman Kalnytskyi](https://github.com/imdrasil) is a good option. ## Granite -Granite provides a light weight ORM that is focused on taking the results of your query and mapping them to your model. It does not try to shield you from the SQL that lies underneath the mapping. It provides a couple conveniences like `save` and `destroy` for simple INSERT, UPDATE and DELETE statements. It provides `find`, `find_by` and `all` to query the database. It also has basic one-to-many relationships with `belong_to` and `has_many`. +Granite provides a light weight ORM that is focused on taking the results of your query and mapping them to your model. It does not try to shield you from the SQL that lies underneath the mapping. Out of the box, Granite provides the several common search, read and write methods for your models such as `save`, `find_by`, `destroy`, _etc_, that map onto standard SQL INSERT, UPDATE and DELETE statements. Granite also supports basic one-to-many relationships with `belong_to` and `has_many` macros that will be familiar to developers experienced with ActiveRecord. [Granite Documentation](https://docs.amberframework.org/granite) + ## Jennifer -Jennifer provides an more full featured ORM with a full featured DSL for queries and follows the ActiveRecord pattern found in Rails. If you are looking for a full featured ORM, Jennifer may be your choice of ORM. +Jennifer is a full featured ORM with a dedicate DSL that follows the ActiveRecord pattern found in Rails closely. If you are looking for a more full featured ORM Jennifer is a great choice. {% page-ref page="jennifer/" %} diff --git a/guides/models/jennifer/README.md b/guides/models/jennifer/README.md index 6552407..b0d8050 100644 --- a/guides/models/jennifer/README.md +++ b/guides/models/jennifer/README.md @@ -2,49 +2,82 @@ ## What is Jennifer? -**Jennifer** is an ActiveRecord pattern for Crystal with great query DSL and migration mechanism. +**Jennifer** is an ActiveRecord-like ORM for Crystal with a custom DSL for queries and migrations. {% hint style="info" %} This section is based on [Jennifer Docs](https://github.com/imdrasil/jennifer.cr/blob/master/docs/index.md). Also see [Amber Jennifer Example App](https://github.com/eliasjpr/amber-jennnifer-app-example). {% endhint %} ## Installing Jennifer for Amber +By default, Amber applications ship with +[Granite](https://docs.amberframework.org/granite) a lightweight ORM for Crystal applications. To begin using Jennifer in your Amber application you will need to follow a few steps to get up and running. ### Generate a normal amber app +Begin by generating a new amber project (if you have not already). -```text +```bash amber new {project} +cd project ``` ### Update your project shard.yml +Once your Amber project is created, you will need to update your `shard.yml` file to include Jennifer as a dependency. -Add this to your application's +Add the following code snippet to your application's `shard.yml` file: ```yaml -# Add the following dependencies +# Jennifer is an ORM jennifer: github: imdrasil/jennifer.cr +# SAM is a CLI utility for running migrations, creating or dropping tables sam: github: imdrasil/sam.cr + +``` + +In addition to the Jennifer dependency, you may have noticed we included an additional shard called `sam`. The `sam` shard is simple command line utility for running common database tasks such as creating migrations, adding, or dropping tables. While technically optional it is recommended. + +Once you've added the required dependencies, you will need to decide what database you would like to use with your Amber application. By default, Jennifer ships with SQLite support, however, should you want to use PostgreSQL or MySQL, you will need to add in a compatible database adapter to your `shard.yml` file. + +```yaml +# snipped shards + +# Your choice of database adapter + +# PostgreSQL +pg: + github: will/crystal-pg + version: "= 0.21.0" + +# MySQL +crystal-mysql: + github: crystal-lang/crystal-mysql + version: "= 0.11.0" ``` -> Choose one of existing adapters for your db: [mysql](https://github.com/crystal-lang/crystal-mysql) or [postgres](https://github.com/will/crystal-pg). +You can read about the Crystal community adapters below: -Then in the console +* [MySQL](https://github.com/crystal-lang/crystal-mysql) +* [PostgreSQL](https://github.com/will/crystal-pg). -```text +Once you have updated your dependencies and chosen an appropriate adapter (if any), proceed to update your project's shards by running: + +```bash shards update ``` +This command will begin installing Jennifer and its associated dependencies into your Amber project. + ### Setup your database information +Once Jennifer is up an running as a dependency in your project, you will need to create a new database configuration. Inside of your project's `config` directory create a new file called `config/database.yml` and fill in the code snippet below: ```yaml defaults : &defaults host: localhost adapter: postgres - user: root - password: somepassword + user: + password: migration_files_path: db/migrations # this is the default location for all migrations development: @@ -56,7 +89,10 @@ test: <<: *defaults ``` -### Create a **jennifer.c**r under the **/config** directory +The `config/database.yml` describes the various databases Jennifer will use in your project's different environments. + +### Add A Jennifer Initializer to Configuration +With your `config/database.yml` in place, we need to inform Amber about Jennifer and tell the framework how we would like to configure the ORM. To begin, you will need to create a `config/jennifer.cr` file in your project's `config` directory. Below is a basic setup for Jennifer and you are also encourage to [see an example Amber Jennifer App] for more inspiration. ```ruby require "amber" @@ -79,8 +115,9 @@ Note that we pass the `AMBER_ENV` to `Jennifer::Config.read` this will allow Jen {% endhint %} ### Create a sam.cr in {project/src} +As mentioned previously, Jennifer uses Sam for running tasks pertinent to ORM operations. Sam is a Make-like utility which allows to specify tasks like Ruby's Rake do using plain Crystal. For how to use [Sam](https://github.com/imdrasil/sam.cr) visit the Github repository [https://github.com/imdrasil/sam.cr](https://github.com/imdrasil/sam.cr) -Jennifer uses Sam for running tasks pertinent to ORM operations. Sam is a Make-like utility which allows to specify tasks like Ruby's Rake do using plain Crystal. For how to use [Sam](https://github.com/imdrasil/sam.cr) visit the Github repository [https://github.com/imdrasil/sam.cr](https://github.com/imdrasil/sam.cr) +Create a new `sam.cr` file inside your project's `src` directory. ```ruby # src/sam.cr @@ -95,9 +132,10 @@ load_dependencies "jennifer" Sam.help ``` -### Edit your src/{project}.cr file +This file operationalizes `sam` to begin running tasks. You can run `crystal sam.cr -- help` to get a list of available tasks once the file is added. -This should be done before you load your application configurations \(or at least models\). With Amber this is very easy. +### Including Jennifer in Server Bootstrap +The final step in configuration is to include Jennifer in your `src/{project}.cr` file. This should be done before you load your application configurations (or at least models). ```ruby require "jennifer" diff --git a/guides/models/jennifer/migrations.md b/guides/models/jennifer/migrations.md index 3b21949..67f203f 100644 --- a/guides/models/jennifer/migrations.md +++ b/guides/models/jennifer/migrations.md @@ -4,11 +4,11 @@ This section is based on [Jennifer Docs](https://github.com/imdrasil/jennifer.cr/blob/master/docs/index.md). {% endhint %} -To generate a migration run `crystal sam.cr generate:migration your_migration_name` +To generate a migration run `crystal src/sam.cr generate:migration your_migration_name` ## Migration DSL - -Generator will create template file for you with next name pattern **timestamp\_migration\_name.cr**. Empty file looks like this: +The generator will create template file for you with a consistent name pattern **timestamp\_migration\_name.cr**. +The empty file looks like this: ```ruby class YourCamelcasedMigrationName20170119011451314 < Jennifer::Migration::Base @@ -20,7 +20,7 @@ class YourCamelcasedMigrationName20170119011451314 < Jennifer::Migration::Base end ``` -`up`method is needed for placing your db changes there, `down`- for reverting your changes back. +The `up` method is where your database changes go, whereas, `down` is used for reverting your changes back. Example for creating table: diff --git a/guides/models/jennifer/models.md b/guides/models/jennifer/models.md index 2cfad2a..6fe17bc 100644 --- a/guides/models/jennifer/models.md +++ b/guides/models/jennifer/models.md @@ -1,14 +1,24 @@ # Models - {% hint style="info" %} -This section is based on [Jennifer Docs](https://github.com/imdrasil/jennifer.cr/blob/master/docs/index.md). +In this section, we'll provide a brief overview of how to get use out of Jennifer ORM. Note that this section is based on [Jennifer Docs](https://github.com/imdrasil/jennifer.cr/blob/master/docs/index.md). {% endhint %} -## Several model examples +## Jennifer in Action +### Mappings +Jennifer ORM uses a variety of macros to programatically define your model's associations and attributes. An important macro in particular is `mapping` which is used to describe model attributes. If a field has no extra parameter, you can specify name and type \(type in case of crystal language\): `field_name: :Type`. But you can use a tuple and provide the following parameters: + +| argument | description | +| --- | --- | +| `:type` | crystal data type \(don't use question mark - for now you can use only `:null` option\) | +| `:primary` | mark field as primary key \(default is `false`\) | +| `:null` | allows field to be `nil` \(default is `false` for all fields except primary key | +| `:default` | default value which is set in creating a **new** object | +| `:getter` | if getter should be created \(default - `true`\) | +| `:setter` | if setter should be created \(default - `true`\) | +Let's look at an example ```ruby class Contact < Jennifer::Model::Base - with_timestamps mapping( id: {type: Int32, primary: true}, name: String, @@ -18,106 +28,15 @@ class Contact < Jennifer::Model::Base created_at: {type: Time, null: true}, updated_at: {type: Time, null: true} ) - - has_many :addresses, Address - has_many :facebook_profiles, FacebookProfile - has_and_belongs_to_many :countries, Country - has_and_belongs_to_many :facebook_many_profiles, FacebookProfile, join_foreign: :profile_id - has_one :main_address, Address, {where { _main }} - has_one :passport, Passport - - validates_inclusion :age, 13..75 - validates_length :name, minimum: 1, maximum: 15 - validates_with_method :name_check - - scope :main { where { _age > 18 } } - scope :older { |age| where { _age >= age } } - scope :ordered { order(name: :asc) } - - def name_check - if @description && @description.not_nil!.size > 10 - errors.add(:description, "Too large description") - end - end -end - -class Address < Jennifer::Model::Base - mapping( - id: {type: Int32, primary: true}, - main: Bool, - street: String, - contact_id: {type: Int32, null: true}, - details: {type: JSON::Any, null: true} - ) - validates_format :street, /st\.|street/ - - belongs_to :contact, Contact - - scope :main { where { _main } } -end - -class Passport < Jennifer::Model::Base - mapping( - enn: {type: String, primary: true}, - contact_id: {type: Int32, null: true} - ) - - validates_with [EnnValidator] - belongs_to :contact, Contact -end - -class Profile < Jennifer::Model::Base - mapping( - id: {type: Int32, primary: true}, - login: String, - contact_id: Int32?, - type: String - ) - - belongs_to :contact, Contact -end - -class FacebookProfile < Profile - sti_mapping( - uid: String - ) - - has_and_belongs_to_many :facebook_contacts, Contact, foreign: :profile_id -end - -class TwitterProfile < Profile - sti_mapping( - email: String - ) -end - -class Country < Jennifer::Model::Base - mapping( - id: {type: Int32, primary: true}, - name: String - ) - - validates_exclusion :name, ["asd", "qwe"] - validates_uniqueness :name - - has_and_belongs_to_many :contacts, Contact -end ``` -`mapping` macros stands for describing all model attributes. If a field has no extra parameter, you can specify name and type \(type in case of crystal language\): `field_name: :Type`. But you can use a tuple and provide the following parameters: - -| argument | description | -| --- | --- | -| `:type` | crystal data type \(don't use question mark - for now you can use only `:null` option\) | -| `:primary` | mark field as primary key \(default is `false`\) | -| `:null` | allows field to be `nil` \(default is `false` for all fields except primary key | -| `:default` | default value which is set in creating a **new** object | -| `:getter` | if getter should be created \(default - `true`\) | -| `:setter` | if setter should be created \(default - `true`\) | +In the above codeblock Jennifer's `mapping` macro is used to define a variety of attributes for the `Contact` model which map onto the database table neatly. Note that there are a variety of optional configurations such as `default` values and `null` security that can be appended to the `mapping` definition. -> By default, expect that all fields are defined in the model. If that is not true, you should pass `false` as the second argument and override the `::field_count` method to represent the correct field count. +{% hint style="info" %} + By default, expect that all fields are defined in the model. If that is not true, you should pass `false` as the second argument and override the `::field_count` method to represent the correct field count. +{% endhint %} -It defines the following methods: +Using a mapping will cause Jennifer to define the following methods on your model: | method | args | description | | --- | --- | --- | @@ -153,9 +72,64 @@ It defines the following methods: The model is automatically associated with a table by its underscored and pluralized class name, but a custom name can be defined using the `::table_name` method in its own body before using any relation \(`::singular_table_name` - for singular variant\). +### Relations +Jennifer provides 4 types of relations out of the box: `has_many`, `belongs_to`, `has_and_belongs_to_many`, and `has_one`. While they sound similar they generate slightly different methods and behaviour. + +```ruby +class Contact < Jennifer::Model::Base + mapping( + id: {type: Int32, primary: true}, + name: String, + gender: {type: String, default: "male", null: true}, + age: {type: Int32, default: 10}, + description: {type: String, null: true}, + created_at: {type: Time, null: true}, + updated_at: {type: Time, null: true} + ) + + has_many :addresses, Address + has_many :facebook_profiles, FacebookProfile +end +``` + +Relation take the following arguments: + +* Relation name +* Target class +* `request` - additional request \(will be used inside of where clause\) - optional +* `foreign` - name of foreign key - optional; by default it uses the singularized table name + "\_id" +* `primary` - primary field name - optional; by default it uses the default primary field of the class. + +`has_and_belongs_to_many` also accepts the next 2 arguments and uses regular arguments in a slightly different way: + +* `join_table` - join table name; by default, relation table names are in alphabetic order, joined by underscores. +* `join_foreign` - foreign key for current model \(left foreign key of join table\) +* `foreign` - used as right foreign key +* `primary` - used as primary field of current table; for now it properly works only if both models in this relation have a primary field named `id` + +All relation macros provide the following methods: + +* `#{{relation_name}}` - cache relation object \(or array of them\) and returns it. +* `#{{relation_name}}_reload` - reload relation and returns it. +* `#{{relation_name}}_query` - returns query which is used to get objects of this object relation entities from the db. +* `#remove_{{relation_name}}` - removes given object from relation. +* `#add_{{relation_name}}` - adds given object to relation or builds it from has and adds. + +This allows for dynamically adding/removing objects to relations and automatically sets a foreign id: + +```ruby +contact = Contact.all.find! +contact.add_addresses({:main => true, :street => "some street", :details => nil}) + +address = contact.addresses.last +contact.remove_addresses(address) +``` + +`belongs_to` and `has_one` add extra method `#relation_name!` which also adds assertion for `nil`. + ### STI -Single table inheritance could be used in this way: +Single table inheritance can be used in this way: ```ruby class Profile < Jennifer::Model::Base @@ -184,25 +158,21 @@ class TwitterProfile < Profile end ``` -Subclass extends superclass definition with new fields and uses string field `type` to identify itself. +The subclass extends the superclass definition with new fields and uses string field `type` to identify itself. > Now `Profile.all` will return objects of `Profile` class, not taking into account the `type` field and will raise an exception if the superclass doesn't override `::field_count`. -### Prepared queries \(scopes\) - -You can specify a prepared query statement. +### Scopes (aka. Prepared queries) +Scopes Jennifer's way of creating reusable prepared query statements. The `scope` macro allows you to define a Crystal block containing the desired prepared query statement, and creates a class method on the model to execute. The block is executed in the query context and provides a chainability with various Jennifer queries such as join, where, having, _etc_. ```ruby -scope :query_name { where { c("some_field") > 10 } } -scope :query_with_arguments { |a, b| where { (c("f1") == a) && (c("f2").in(b) } } +class Contact < Jennifer::Base::Model + scope :query_name { where { c("some_field") > 10 } } + scope :query_with_arguments { |a, b| where { (c("f1") == a) && (c("f2").in(b) } } +end ``` -As you can see, arguments are next: - -* scope \(query\) name -* block to be executed in query contex \(any query part could be passed: join, where, having, etc.\) - -These are chainable, so you could do: +Scopes are helpful as they can be chained together to create complex queries which are readable and cognitively ergonomic. ```ruby ModelName.all.where { _some_field > 1 } @@ -210,48 +180,8 @@ ModelName.all.where { _some_field > 1 } .order(f1: :asc).no_argument_query ``` -### Relations - -There are 4 types of relations: has\_many, has\_and\_belongs\_to\_many, belongs\_to and has\_one. All of them have the same semantic but generate slightly different methods. - -They take the following arguments: - -* relation name -* target class -* `request` - additional request \(will be used inside of where clause\) - optional -* `foreign` - name of foreign key - optional; by default it uses the singularized table name + "\_id" -* `primary` - primary field name - optional; by default it uses the default primary field of the class. - -has\_and\_belongs\_to\_many also accepts the next 2 arguments and uses regular arguments slightly in another way: - -* `join_table` - join table name; by default, relation table names are in alphabetic order, joined by underscores. -* `join_foreign` - foreign key for current model \(left foreign key of join table\) -* `foreign` - used as right foreign key -* `primary` - used as primary field of current table; for now it properly works only if both models in this relation have a primary field named `id` - -All relation macros provide the following methods: - -* `#{{relation_name}}` - cache relation object \(or array of them\) and returns it. -* `#{{relation_name}}_reload` - reload relation and returns it. -* `#{{relation_name}}_query` - returns query which is used to get objects of this object relation entities from the db. -* `#remove_{{relation_name}}` - removes given object from relation. -* `#add_{{relation_name}}` - adds given object to relation or builds it from has and adds. - -This allows for dynamically adding/removing objects to relations and automatically sets a foreign id: - -```ruby -contact = Contact.all.find! -contact.add_addresses({:main => true, :street => "some street", :details => nil}) - -address = contact.addresses.last -contact.remove_addresses(address) -``` - -`belongs_to` and `has_one` add extra method `#relation_name!` which also adds assertion for `nil`. - ### Validations - -[accord](https://github.com/neovintage/accord) is used for validation. There are several general macros for declaring validations: +Jennifer uses [accord](https://github.com/neovintage/accord) for validations. Validations are mechanisms to run pre-flight checks on your models to ensure desired fields are present or values belong to a particular set. There are several general macros for declaring validations: * `validates_with_method(*names)` - accepts method name/names * `validates_inclusion(field, value)` - checks if `value` includes `@{{field}}` @@ -263,8 +193,7 @@ contact.remove_addresses(address) If a record is not valid, it will not be saved. ### Callbacks - -Macros for defining callbacks: +Callbacks are Jennifer's way of providing hooks into the lifecycle of a record. The available macros for defining callbacks include: * `before_save` * `after_save` @@ -276,12 +205,10 @@ Macros for defining callbacks: They accept method names. ### Timestamps - `with_timestamps` macros add callbacks for `created_at` and `updated_at` fields in an update. ### Destroy - -To destroy an object, use `#delete` \(called without callback\) or `#destroy`. To destroy several objects by their ids, use the class method: +To destroy an object, use `.delete` \(called without callback\) or `.destroy`. To destroy several objects by their ids, use the class method: ```ruby ids = [1, 20, 18] @@ -291,8 +218,7 @@ Country.delete(1,2,3) ``` ### Update - -There are several ways to update an object. Some of them were mentioned in the mapping section. A few extra methods to do this: +There are several ways to update an object. Some of them were mentioned in the mapping section. A few extra methods to do this include: * `#update_column(name, value)` - sets attribute directly and stores it to the db without any callback. * `#update_columns(values)` - same for several columns. @@ -300,28 +226,27 @@ There are several ways to update an object. Some of them were mentioned in the m * `#set_attribute(name, value)` - set attribute by given name. ## Query DSL - -My favorite part. Jennifer allows you to build lazy evaluated queries with chaining syntax, but some methods can only be used at the end of a chain \(such as `#first` or `#pluck`\). Here is a list of all DSL methods: +Jennifer allows you to build lazy evaluated queries with a chainable syntax. However, some methods can only be used at the end of a chain (such as `.first` or `.pluck`). Here is a list of all DSL methods: ### Find - -An object can be retreived by id using `find` \(returns `T?`\) and `find!` \(returns `T` or raise `RecordNotFound` exception\) methods. +An object can be retrieved by id using `find` \(returns `T?`\) and `find!` \(returns `T` or raise `RecordNotFound` exception\) methods. ```ruby Contact.find!(1) ``` ### Where - `all` retrieves everything \(only at the beginning; creates empty request\). ```ruby Contact.all ``` -Specifying a where clause is really flexible. This method accepts a block which represents the where clause of the request \(or its part - you can chain several `where` methods and they will be concatenated using `AND`\). +`where` clauses are extremely flexible. The `where` method accepts a block which represents the where clause of the query statement. You can chain several `where` methods together and they will be concatenated using `AND`. + +To specify a field, use `c` method which accepts a string as the field name. As mentioned, after declaring model attributes, you can use their names inside of a block: `_field_name` if it is for the current table and `ModelName._field_name` if for another model. As a method name, you can specify an attribute of the model or table using underscores: `_some_model_or_table_name__field_name` - model/table name is separated from field name by "_\_". You can specify relation in space of which you want to declare condition using double_ at the beginning and block. -To specify a field, use `c` method which accepts a string as the field name. As I've mentioned, after declaring model attributes, you can use their names inside of a block: `_field_name` if it is for the current table and `ModelName._field_name` if for another model. As a method name, you can specify an attribute of the model or table using underscores: `_some_model_or_table_name__field_name` - model/table name is separated from field name by "_\_". You can specify relation in space of which you want to declare condition using double_ at the beginning and block. Several examples: +Below are a few examples: ```ruby Contact.where { c("id") == 1 } @@ -330,7 +255,7 @@ Contact.all.join(Address) { Contact._id == _contact_id } Contact.all.relation(:addresses).where { __addresses { _id > 1 } } ``` -Also you can use `primary` to mention primary field: +You can also use `primary` to mention primary field: ```ruby Passport.where { primary.like("%123%") } @@ -370,19 +295,18 @@ And Postgres specific: | `contained` | `<@` | | `overlap` | `&&` | -To specify exact sql query use `#sql` method: +Jennifer supports literal SQL query statements via the `.sql` method: ```ruby # it behaves like regular criteria Contact.all.where { sql("age > ?", [15]) & (_name == "Stephan") } ``` -Query will be inserted "as is". Usage of `#sql` allows to use nested plain request. +The query will be inserted "as is". Usage of `.sql` allows the use of nested plain request. **Tips** - -* all regexp methods accept string representation of regexp -* use parentheses for binary operators \(`&` and `|`\) +* All regular expression methods accept string representation of the regular expression +* Use parentheses for binary operators \(`&` and `|`\) * `nil` given to `!=` and `==` will be transformed to `IS NOT NULL` and `IS NULL` * `is` and `not` operator accepts values: `:nil`, `nil`, `:unknown`, `true`, `false` @@ -395,8 +319,7 @@ Address.where { _contact_id.is(nil) } ``` ### Select - -Raw sql for `SELECT` clause could be passed into `#select` method. This has the highest priority in forming this query part. +Raw SQL for `SELECT` clause can be passed into `.select` method. This has the highest priority in forming this query part. ```ruby Contact.all.select("COUNT(id) as count, contacts.name").group("name") @@ -404,8 +327,11 @@ Contact.all.select("COUNT(id) as count, contacts.name").group("name") ``` ### From +Also you can provide a sub-query to specify the FROM clause. -Also you can provide a subquery to specify the FROM clause \(but be careful with source fields during result retrieving and mapping to objects\) +{% hint style="danger" %} +Be careful when using sub-queries with source fields during result retrieving and mapping to objects. +{% endhint %} ```ruby Contact.all.from("select * from contacts where id > 2") @@ -413,7 +339,6 @@ Contacts.all.from(Contact.where { _id > 2 }) ``` ### Delete and Destroy - For now they both are the same - creates delete query with given conditions. `destroy` first loads objects and runs callbacks and then calls delete on each. It can be only at the end of chain. @@ -423,7 +348,6 @@ Address.where { _main.not }.delete ``` ### Joins - To join another table you can use `join` method passing model class or table name \(`String`\) and join type \(default is `:inner`\). ```ruby @@ -444,16 +368,14 @@ Contact.all.right_join("addresses") { _contacts__id == c("contact_id") } > For now Jennifer provides manual aliasing as a second argument for `#join` and automatic when using `#includes` and `#with` methods. For details check out the code. ### Relation - -To join model relation \(has\_many, belongs\_to and has\_one\) pass it's name and join type: +To join a model relation such as `has_many`, `belongs_to` and `has_one` you can pass it's name and join type as: ```ruby Contact.all.relation("addresses").relation(:passport, type: :left) ``` ### Includes - -To preload some relation use `includes` and pass relation name: +To preload a relation use `includes` and pass relation name: ```ruby Contact.all.includes("addresses") @@ -467,7 +389,7 @@ If there are several includes with same table - Jennifer will auto alias tables. Contact.all.group("name", "id").pluck(:name, :id) ``` -`#group` allows to add columns for `GROUP BY` section. If passing arguments are tuple of strings or just one string - all columns will be parsed as current table columns. If there is a need to group on joined table or using fields from several tables use next: +`.group` allows to add columns for `GROUP BY` section. If passing arguments are tuple of strings or just one string - all columns will be parsed as current table columns. If there is a need to group on joined table or using fields from several tables use next: ```ruby Contact.all.relation("addresses").group(addresses: ["street"], contacts: ["name"]) @@ -477,12 +399,11 @@ Contact.all.relation("addresses").group(addresses: ["street"], contacts: ["name" Here keys should be _table names_. ### Having - ```ruby Contact.all.group("name").having { _age > 15 } ``` -`#having` allows adding `HAVING` to the query. It accepts a block the same way as `#where` does. +`.having` allows adding `HAVING` to the query. It accepts a block the same way as `.where` does. ### Exists @@ -490,7 +411,7 @@ Contact.all.group("name").having { _age > 15 } Contact.where { _age > 42 }.exists? # returns true or false ``` -`#exists?` check if there is any record with provided conditions. Can only be at the end of the query chain - it hits the db. +`.exists?` check if there is any record with provided conditions. Can only be at the end of the query chain - it hits the db. ### Distinct @@ -498,7 +419,7 @@ Contact.where { _age > 42 }.exists? # returns true or false Contant.all.distinct("age") # returns array of ages (Array(DB::Any | Int16 | Int8)) ``` -`#distinct` retrieves from db column values without repeats. Can accept column name and as an optional second parameter - table name. Can only be at the end of the query chain - it hits the db. +`.distinct` retrieves database column values without repeats. It can accept column names and the table name as an optional second parameter. `distinct` can only be at the end of the query chain as it results in a direct database query. ### Aggregation @@ -592,7 +513,7 @@ Will not trigers any callback. ### Eager load -As was said, Jennifer provides lazy query evaluation, so it will be performed only after trying to access an element from the collection \(any array method - it implements Enumerable\). Also you can extract the first entity via `first`. If you are sure that at least one entity in the db satisfies your query, you can call `#first!`. +As mentioned previously, Jennifer provides lazy query evaluation, this means it will perform the operation only after trying to access an element from the collection \(any array method - it implements Enumerable\). Also you can extract the first entity via `first`. If you are sure that at least one entity in the database satisfies your query, you can call `.first!`. To extract only some fields rather than entire objects, use `pluck`: @@ -643,9 +564,8 @@ This functionality could be useful to clear the db between test cases. * sqlite3 has a lot of limitations so its support will not be added soon. -## Test - -The fastest way to rollback all changes in the DB after a test case - transaction. So add: +## Testing +The quickest way to rollback any changes made to the database after a test case is using transaction. You can ensure smooth testing by adding the following to your `spec_helper.cr`: ```ruby Spec.before_each do @@ -657,7 +577,7 @@ Spec.after_each do end ``` -to your `spec_helper.cr`. Also just regular deleting or truncation could be used but transactions provide 15x speed up \(at least for postgres; mysql gets less impact\). +Also just regular deleting or truncation could be used but transactions provide 15x speed up \(at least for postgres; mysql gets less impact\). {% hint style="info" %} This functions can be safely used only under test environment.