Merge pull request #11 from vapor/hash

hash + fluent
This commit is contained in:
Tanner 2016-08-05 11:25:04 -04:00 committed by GitHub
commit 5a1305f997
6 changed files with 601 additions and 0 deletions

View File

@ -56,6 +56,24 @@ menu:
guide-validation:
text: Validation
relativeUrl: guide/validation.html
guide-hash:
text: Hash
relativeUrl: guide/hash.html
fluent:
name: Fluent
items:
fluent-driver:
text: Driver
relativeUrl: fluent/driver.html
fluent-model:
text: Model
relativeUrl: fluent/model.html
fluent-query:
text: Query
relativeUrl: fluent/query.html
fluent-relation:
text: Relation
relativeUrl: fluent/relation.html
routing:
name: Routing
items:

52
fluent/driver.md Normal file
View File

@ -0,0 +1,52 @@
---
currentMenu: fluent-driver
---
# Driver
Drivers are what power Fluent under the hood. Fluent comes with a memory driver by default and there are many providers for databases like MySQL, SQLite, Mongo, PostgreSQL, and more available as providers.
![Drivers and Providers](https://cloud.githubusercontent.com/assets/1342803/17418823/73f1d1d2-5a68-11e6-9bed-90f42ce7781d.png)
This graphic shows the relation between Drivers and Providers using MySQL as an example. This distinction allows Fluent to be used independently from Vapor.
If you want to use Fluent without Vapor, you will import Drivers into your package. If you are using Vapor, you will import Providers.
Search GitHub for:
- [Fluent Drivers](https://github.com/vapor?utf8=✓&query=-driver)
- [Vapor Providers](https://github.com/vapor?utf8=✓&query=-provider)
Not all drivers have providers yet, and not all drivers or providers are up to date with the latest Vapor 0.16. This is a great way to contribute!
## Creating a Driver
Fluent is a power, database agnostic package for persisting your models. It was designed from the beginnign to work with both SQL and NoSQL databases alike.
Any database that conforms to `Fluent.Driver` will be able to power the models in Fluent and Vapor.
The protocol itself is fairly simple:
```swift
public protocol Driver {
var idKey: String { get }
func query<T: Entity>(_ query: Query<T>) throws -> Node
func schema(_ schema: Schema) throws
func raw(_ raw: String, _ values: [Node]) throws -> Node
}
```
### ID Key
The ID key will be used to power functionality like `User.find()`. In SQL, this is often `id`. In MongoDB, it is `_id`.
### Query
This method will be called for every query made by Fluent. It is the drivers job to properly understand all of the properties on `Query` and return the desired rows, document, or other data as represented by `Node`.
### Schema
The schema method will be called before the database is expected to accept queries for a schema. For some NoSQL databases like MongoDB, this can be ignored. For SQL, this is where `CREATE` and other such commands should be called according to `Schema`.
### Raw
This is an optional method that can be used by any Fluent driver that accepts string queries. If your database does not accept such queries, an error can be thrown.

216
fluent/model.md Normal file
View File

@ -0,0 +1,216 @@
---
currentMenu: fluent-model
---
# Model
`Model` is the base protocol for any of your application's models, especially those you want to persist.
> `Model` is only available in Vapor, the Fluent equivalent is `Entity`
## Example
Let's create a simple `User` model.
```swift
final class User {
var name: String
init(name: String) {
self.name = name
}
}
```
The first step to conforming to `Model` is to import Vapor and Fluent.
```swift
import Vapor
import Fluent
```
Then add the conformance to your class.
```swift
final class User: Model {
...
}
```
The compiler will inform you that some methods need to be implemented to conform.
### ID
The first required property is an identifier. This property will contain the identifier when the model is fetched from the database. If it is nil, it will be set when the model is saved.
```swift
final class User: Model {
var id: Node?
...
}
```
### Node Initializable
The next requirement is a way to create the model from the persisted data. Model uses `NodeInitializable` to achieve this.
```swift
final class User: Model {
init(node: Node, in context: Context) throws {
id = try node.extract("id")
name = try node.extract("name")
}
...
}
```
The keys `id` and `name` are what we expect the columns or fields in the database to be named. The `extract` call is marked with a `try` because it will throw an error if the value is not present or is the wrong type.
### Node Representable
Now that we have covered initializing the model, we need to show it how to save back into the database. Model uses `NodeRepresentable` to achieve this.
```swift
final class User: Model {
func makeNode() throws -> Node {
return try Node(node: [
"id": id,
"name": name
])
}
...
}
```
When a `User` is saved, the `makeNode()` method will be called and the resulting `Node` will be saved to the database. The keys `id` and `name` are what we expect the columns or fields in the database to be named.
## Preparations
Some databases, like MySQL, need to be prepared for a new schema. In MySQL, this means creating a new table.
### Prepare
Let's assume we are using a SQL database. To prepare the database for our `User` class, we need to create a table. If you are using a database like Mongo, you can leave this method unimplemented.
```swift
final class User {
static func prepare(_ database: Database) throws {
try database.create("users") { users in
users.id()
users.string("name")
}
}
...
}
```
Here we create a table named `users` that has an identifier field and a string field with the key `name`. This matches both our `init(node: Node)` and `makeNode() -> Node` methods.
### Revert
An optional preparation reversion can be created. This will be run if `vapor run prepare --revert` is called.
```swift
final class User {
static func revert(_ database: Database) throws {
try database.delete("users")
}
...
}
```
Here we are deleting the table named `users`.
### Droplet
To run these prepations when the applications boots, you must add the Model to the `Droplet`.
```swift
let drop = Droplet(preparations: [User.self])
```
## Full Model
This is what our final `User` model looks like:
```swift
import Vapor
import Fluent
final class User: Model {
var id: Node?
var name: String
init(name: String) {
self.name = name
}
init(node: Node, in context: Context) throws {
id = try node.extract("id")
name = try node.extract("name")
}
func makeNode() throws -> Node {
return try Node(node: [
"id": id,
"name": name
])
}
static func prepare(_ database: Database) throws {
try database.create("users") { users in
users.id()
users.string("name")
}
}
static func revert(_ database: Database) throws {
try database.delete("users")
}
}
```
## Interacting
Now that `User` conforms to `Model`, it has a plethora of new methods like `find()`, `query()`, `makeJSON()` and more.
### Fetch
Models can be fetched by their database identifier.
```swift
let user = try User.find(42)
```
### Save
Newly created models can be saved to the database.
```swift
var user = User(name: "Vapor")
try user.save()
print(user.id) // prints the new id
```
### Delete
Persisted models with identifiers can be deleted.
```swift
try user.delete()
```
## Model vs. Entity
Model has a couple of extra conformances that a pure Fluent entity doesn't have.
```swift
public protocol Model: Entity, JSONRepresentable, StringInitializable, ResponseRepresentable {}
```
As can be seen in the protocol, Vapor models can automatically convert to `JSON`, `Response`, and even be used in type-safe routing.

120
fluent/query.md Normal file
View File

@ -0,0 +1,120 @@
---
currentMenu: fluent-query
---
# Query
The `Query` class is what powers every interaction with Fluent. Whether you're fetching a model with `.find()` or saving to the database, there is a `Query` involved somewhere.
## Querying Models
Every type that conforms to [Model](model.md) gets a static `.query()` method.
```swift
let query = try User.query()
```
This is how you can create a `Query<User>`.
### No Database
The `.query()` method is marked with `try` because it can throw an error if the Model has not had its static database property set.
```swift
User.database = drop.database
```
This property is set automatically when you pass the Model as a preparation.
## Filter
The most common types of queries involve filtering data.
```swift
let smithsQuery = try User.query().filter("last_name", "Smith")
```
Here is the short hand for adding an `equals` filter to the query. As you can see, queries can be chained together.
In additional to `equals`, there are many other types of `Filter.Comparison`.
```swift
let over21 = try User.query().filter("age", .greaterThanOrEquals, 21)
```
### Scope
Filters can also be run on sets.
```swift
let coolPets = try Pet.query().filter("type", .in, ["Dog", "Ferret"])
```
Here only Pets of type Dog _or_ Ferret are returned. The opposite works for `notIn`.
### Contains
Partially matching filters can also be applied.
```swift
let statesWithNew = try State.query().filter("name", contains: "New")
```
## Retrieving
There are two methods for running a query.
### All
All of the matching entities can be fetched. This returns an array of `[Model]`, in this case users.
```swift
let usersOver21 = try User.query().filter("age", .greaterThanOrEquals, 21).all()
```
### First
The first matching entity can be fetch. This returns an optional `Model?`, in this case a user.
```swift
let firstSmith = try User.query().filter("last_name", "Smith").first()
```
## Union
Other Models can be joined onto your query to assist in filtering. The results must still be either `[Model]` or `Model?` for whichever type created the query.
```swift
let usersWithCoolPets = try User.query()
.union(Pet.self)
.filter(Pet.self, "type", .in, ["Dog", "Ferret"])
```
Here the `User` collection is unioned to the `Pet` collection. Only `User`s who have a dog or a ferret will be returned.
### Keys
The `union` method assumes that the querying table has a foreign key identifier to the joining table.
The above example with users and pets assumes the following schema.
```
users
- id
- pet_id
pets
- id
```
Custom foreign keys can be provided through overloads to `union`.
## Raw Queries
Since Fluent is focused on interacting with models, each Query requires a model type. If you want to do raw database queries that aren't based on a model, you should use the underlying Fluent Driver to do so.
```swift
if let mysql = drop.database?.driver as? MySQLDriver {
let version = try mysql.raw("SELECT @@version")
}
```

159
fluent/relation.md Normal file
View File

@ -0,0 +1,159 @@
---
currentMenu: fluent-relation
---
# Relation
Relations allow foreign key based connections between database entities. This is common in SQL-based databases, but can also be used with NoSQL.
Fluent's relations are named as follows:
- Parent (BelongsTo)
- Children (HasMany, HasOne)
- Siblings (ManyToMany, BelongsToMany)
## Parent
The parent relation should be called on an entity that has a foreign key to another entity. For example, assume the following schema:
```
pets
- id
- owner_id
- name
- type
owner
- id
- name
```
Here each pet can have one owner. To access that owner from the pet, call `.parent()`.
```swift
let pet: Pet = ...
let owner = try pet.parent(pet.ownerId, Owner.self).get()
```
The parent method requires the foreign key for the parent as well as the type.
### Convenience
To make requesting a parent easier, a method can be added to the model.
```swift
extension Pet {
func owner() throws -> Parent<Owner> {
return try parent(ownerId)
}
}
```
Since we are extending `Pet`, we no longer need to use `pet.` before the `ownerId`. Furthermore, because we are providing the type information about `Owner` in the return type of the method, we no longer need to pass that as an argument.
The `Parent<T>` type is a queryable object, meaning you could `delete()` the parent, `filter()`, etc.
```swift
try pet.owner().delete()
```
To fetch the parent, you must call `get()`.
```swift
let owner = try pet.owner().get()
```
## Children
`Children` are the opposite side of the `Parent` relationship. Assuming the schema from the previous example, the pets could be retreived from an owner like so:
```swift
let owner: Owner = ...
let pets = owner.children(Pet.self).all()
```
Here only the type of child is required.
### Convenience
Similarly to `Parent`, convenience methods can be added for children.
```swift
extension Owner {
func pets() throws -> Children<Pet> {
return try children()
}
}
```
Since the type information is clear from the return value, `Pet.self` does not need to be passed to `children()`.
The `Children<T>` is also a queryable object like `Parent<T>`. You can call `first()`, `all()`, `filter()`, etc.
```swift
let coolPets = try owner.pets().filter("type", .in, ["Dog", "Ferret"]).all()
```
## Siblings
`Sibilings` work differently from `Children` or `Parent` since they require a `Pivot`.
For an example, let's say we want to allow our pets to have multiple toys. But we also want the toys to be shared by multiple pets. We need a pivot entity for this.
```
pets
- id
- type
- owner_id
toys
- id
- name
pets_toys
- id
- pet_id
- toy_id
```
Here you can see the pivot entity, `pets_toys`, or `Pivot<Pet, Toy>`.
### Convenience
Let's add the convenience methods to `Pet`.
```swift
extension Pet {
func toys() throws -> Siblings<Toy> {
return try siblings()
}
}
```
And the opposite for `Toy`.
```
extension Toy {
func pets() throws -> Siblings<Pet> {
return try siblings()
}
}
```
Now you are free to query pets and toys simiarly to children.
```swift
let pet: Pet = ...
let toys = pet.toys().all()
```
### Preparation
To prepare for a relationship with a `Pivot`, simply add the pivot to the `Droplet`'s preparations.
```swift
let drop = Droplet(preparations: [
Toy.self,
Pet.self,
Pivot<Toy, Pet>.self
])
```

36
guide/hash.md Normal file
View File

@ -0,0 +1,36 @@
---
currentMenu: guide-hash
---
# Hash
Vapor makes hashing strings easy.
## Example
To hash a string, use the `hash` class on `Droplet`.
```swift
let hashed = drop.hash.make("vapor")
```
## SHA2Hasher
By default, Vapor uses a SHA2Hasher with 256 bits. You can change this by giving the `Droplet` a different hasher.
```swift
let sha512 = SHA2Hasher(variant: .sha512)
let drop = Droplet(hash: sha512)
```
### Protocol
You can also create your own hasher. You just need to conform to the `Hash` protocol.
```swift
public protocol Hash: class {
var key: String { get set }
func make(_ string: String) -> String
}
```