diff --git a/book/asciidoc/authentication-and-authorization.asciidoc b/book/asciidoc/authentication-and-authorization.asciidoc index 126b7c5..88df8ed 100644 --- a/book/asciidoc/authentication-and-authorization.asciidoc +++ b/book/asciidoc/authentication-and-authorization.asciidoc @@ -79,7 +79,6 @@ Let's jump right in with an example of authentication. For the Google OAuth auth {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} -import Data.Default (def) import Data.Text (Text) import Network.HTTP.Client.Conduit (Manager, newManager) import Yesod @@ -239,15 +238,21 @@ verification link is printed in the console. [source, haskell] ---- +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} import Control.Monad (join) import Control.Monad.Logger (runNoLoggingT) import Data.Maybe (isJust) @@ -258,13 +263,12 @@ import Database.Persist.Sqlite import Database.Persist.TH import Network.Mail.Mime import Text.Blaze.Html.Renderer.Utf8 (renderHtml) -import Text.Hamlet (shamlet) import Text.Shakespeare.Text (stext) import Yesod import Yesod.Auth import Yesod.Auth.Email -share [mkPersist sqlSettings { mpsGeneric = False }, mkMigrate "migrateAll"] [persistLowerCase| +share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| User email Text password Text Maybe -- Password may not be set yet @@ -370,7 +374,7 @@ instance YesodAuthEmail App where mu <- get uid case mu of Nothing -> return Nothing - Just u -> do + Just _u -> do update uid [UserVerified =. True, UserVerkey =. Nothing] return $ Just uid getPassword = liftHandler . runDB . fmap (join . fmap userPassword) . get @@ -422,7 +426,6 @@ your Yesod typeclass instance. Let's see an example. {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} -import Data.Default (def) import Data.Text (Text) import Network.HTTP.Conduit (Manager, newManager, tlsManagerSettings) import Yesod @@ -449,6 +452,7 @@ instance Yesod App where -- anyone can access other pages isAuthorized _ _ = return Authorized +isAdmin :: Handler AuthResult isAdmin = do mu <- maybeAuthId return $ case mu of diff --git a/book/asciidoc/blog-example-advanced.asciidoc b/book/asciidoc/blog-example-advanced.asciidoc index 9e9cf4b..020df02 100644 --- a/book/asciidoc/blog-example-advanced.asciidoc +++ b/book/asciidoc/blog-example-advanced.asciidoc @@ -15,10 +15,22 @@ your individual Haskell files. [source, haskell] ---- -{-# LANGUAGE OverloadedStrings, TypeFamilies, QuasiQuotes, - TemplateHaskell, GADTs, FlexibleContexts, - MultiParamTypeClasses, DeriveDataTypeable, - GeneralizedNewtypeDeriving, ViewPatterns #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE ViewPatterns #-} ---- Now our imports. @@ -37,7 +49,6 @@ import Database.Persist.Sqlite , createSqlitePool, runSqlPersistMPool ) import Data.Time (UTCTime, getCurrentTime) -import Control.Applicative ((<$>), (<*>), pure) import Data.Typeable (Typeable) import Control.Monad.Logger (runStdoutLoggingT) ---- @@ -252,7 +263,7 @@ good idea to always check for pending messages in your defaultLayout function. mmsg <- getMessage ---- -We use widgets to compose together HTML, CSS and Javascript. At the end of the +We use widgets to compose together HTML, CSS and JavaScript. At the end of the day, we need to unwrap all of that into simple HTML. That's what the +widgetToPageContent+ function is for. We're going to give it a widget consisting of the content we received from the individual page (inside), plus a standard diff --git a/book/asciidoc/case-study-sphinx.asciidoc b/book/asciidoc/case-study-sphinx.asciidoc index 79cef6c..f107fc1 100644 --- a/book/asciidoc/case-study-sphinx.asciidoc +++ b/book/asciidoc/case-study-sphinx.asciidoc @@ -372,8 +372,8 @@ several hundred kilobytes. If we take a non-streaming approach, this can lead to huge memory usage and slow response times. So how exactly do we create a streaming response? Yesod provides a helper -function for this case: +responseSourceDB+. This function takes two arguments: -a content type, and a conduit +Source+ providing a stream of blaze-builder +function for this case: +respondSourceDB+. This function takes two arguments: +a content type, and a Conduit providing a stream of blaze-builder ++Builder++s. Yesod then handles all of the issues of grabbing a database connection from the connection pool, starting a transaction, and streaming the response to the user. @@ -387,7 +387,7 @@ is: [source, haskell] ---- -renderBuilder :: Monad m => RenderSettings -> Conduit Event m Builder +renderBuilder :: Monad m => RenderSettings -> ConduitT Event Builder m () ---- In plain English, that means +renderBuilder+ takes some settings (we'll just use @@ -472,7 +472,7 @@ We start the document element with an +id+ attribute, start the content, insert the content, and then close both elements. We use +toPathPiece+ to convert a +DocId+ into a +Text+ value. Next, we need to be able to convert a stream of these entities into a stream of events. For this, we can use the built-in -+concatMap+ function from +Data.Conduit.List+: +CL.concatMap entityToEvents+. ++concatMapC+ function from +Conduit+: +concatMapC entityToEvents+. But what we _really_ want is to stream those events directly from the database. For most of this book, we've used the +selectList+ function, but Persistent @@ -482,18 +482,18 @@ the function: [source, haskell] ---- -docSource :: Source (YesodDB Searcher) X.Event -docSource = selectSource [] [] $= CL.concatMap entityToEvents +docSource :: ConduitT () X.Event (YesodDB Searcher) () +docSource = selectSource [] [] .| concatMapC entityToEvents ---- -The $= operator joins together a source and a conduit into a new source. Now +The .| operator combines two Conduits together into a new Conduit. Now that we have our +Event+ source, all we need to do is surround it with the -document start and end events. With +Source+'s +Monad+ instance, this is a +document start and end events. With +ConduitT+'s +Monad+ instance, this is a piece of cake: [source, haskell] ---- -fullDocSource :: Source (YesodDB Searcher) X.Event +fullDocSource :: ConduitT () X.Event (YesodDB Searcher) () fullDocSource = do mapM_ yield startEvents docSource @@ -508,33 +508,35 @@ getXmlpipeR :: Handler TypedContent getXmlpipeR = respondSourceDB "text/xml" $ fullDocSource - $= renderBuilder def - $= CL.map Chunk + .| renderBuilder def + .| mapC Chunk ---- === Full code [source, haskell] ---- +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE ViewPatterns #-} -import Control.Applicative ((<$>), (<*>)) +import Conduit import Control.Monad (forM) import Control.Monad.Logger (runStdoutLoggingT) -import Data.Conduit -import qualified Data.Conduit.List as CL import Data.Maybe (catMaybes) -import Data.Monoid (mconcat) import Data.Text (Text) import qualified Data.Text as T -import Data.Text.Lazy.Encoding (decodeUtf8) import qualified Data.XML.Types as X import Database.Persist.Sqlite import Text.Blaze.Html (preEscapedToHtml) @@ -719,8 +721,8 @@ getXmlpipeR :: Handler TypedContent getXmlpipeR = respondSourceDB "text/xml" $ fullDocSource - $= renderBuilder def - $= CL.map Chunk + .| renderBuilder def + .| mapC Chunk entityToEvents :: (Entity Doc) -> [X.Event] entityToEvents (Entity docid doc) = @@ -731,14 +733,14 @@ entityToEvents (Entity docid doc) = , X.EventEndElement document ] -fullDocSource :: Source (YesodDB Searcher) X.Event +fullDocSource :: ConduitT () X.Event (YesodDB Searcher) () fullDocSource = do mapM_ yield startEvents docSource mapM_ yield endEvents -docSource :: Source (YesodDB Searcher) X.Event -docSource = selectSource [] [] $= CL.concatMap entityToEvents +docSource :: ConduitT () X.Event (YesodDB Searcher) () +docSource = selectSource [] [] .| concatMapC entityToEvents toName :: Text -> X.Name toName x = X.Name x (Just "http://sphinxsearch.com/") (Just "sphinx") diff --git a/book/asciidoc/deploying-your-webapp.asciidoc b/book/asciidoc/deploying-your-webapp.asciidoc index aeac91c..fb11ea3 100644 --- a/book/asciidoc/deploying-your-webapp.asciidoc +++ b/book/asciidoc/deploying-your-webapp.asciidoc @@ -93,7 +93,7 @@ out a safe storage solution for the client session key. There are two commonly used features in the Yesod world: serving your site over HTTPS, and placing your static files on a separate domain name. While both of these are good practices, when combined they can lead to problems if you're not -careful. In particular, most web browsers will not load up Javascript files +careful. In particular, most web browsers will not load up JavaScript files from a non-HTTPS domain name if your HTML is served from an HTTPS domain name. In this situation, you'll need to do one of two things: @@ -233,23 +233,13 @@ responsible to run your own process. I strongly recommend a monitoring utility which will automatically restart your application in case it crashes. There are many great options out there, such as angel or daemontools. -To give a concrete example, here is an Upstart config file. The file must be -placed in +/etc/init/mysite.conf+: - ----- -description "My awesome Yesod application" -start on runlevel [2345]; -stop on runlevel [!2345]; -respawn -chdir /home/michael/sites/mysite -exec /home/michael/sites/mysite/dist/build/mysite/mysite ----- - -Once this is in place, bringing up your application is as simple as -+sudo start mysite+. A similar systemd configuration file placed in +To give a concrete example, here is a systemd configuration file placed in +/etc/systemd/system/yesod-sample.service+: ---- +[Unit] +Description=My awesome Yesod application + [Service] ExecStart=/home/sibi/.local/bin/my-yesod-executable Restart=always @@ -344,7 +334,7 @@ A similar approach, without requiring the QtWebkit library, is wai-handler-launch, which launches a Warp server and then opens up the user's default web browser. There's a little trickery involved here: in order to know that the user is still using the site, +wai-handler-launch+ inserts a "ping" -Javascript snippet to every HTML page it serves. It +wai-handler-launch+ +JavaScript snippet to every HTML page it serves. It +wai-handler-launch+ doesn't receive a ping for two minutes, it shuts down. === CGI on Apache @@ -381,7 +371,7 @@ and the last line does the actual rewriting. === FastCGI on lighttpd For this example, I've left off some of the basic FastCGI settings like -mime-types. I also have a more complex file in production that prepends "www." +MIME types. I also have a more complex file in production that prepends "www." when absent and serves static files from a separate domain. However, this should serve to show the basics. diff --git a/book/asciidoc/forms.asciidoc b/book/asciidoc/forms.asciidoc index ea1b8f6..426d394 100644 --- a/book/asciidoc/forms.asciidoc +++ b/book/asciidoc/forms.asciidoc @@ -11,7 +11,7 @@ solution that addresses the following problems: * Generate HTML code for displaying the form. -* Generate Javascript to do clientside validation and provide more +* Generate JavaScript to do clientside validation and provide more user-friendly widgets, such as date pickers. * Build up more complex forms by combining together simpler forms. @@ -20,7 +20,7 @@ solution that addresses the following problems: The yesod-form package provides all these features in a simple, declarative API. It builds on top of Yesod's widgets to simplify styling of forms and -applying Javascript appropriately. And like the rest of Yesod, it uses +applying JavaScript appropriately. And like the rest of Yesod, it uses Haskell's type system to make sure everything is working correctly. === Synopsis @@ -32,9 +32,8 @@ Haskell's type system to make sure everything is working correctly. {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} -import Control.Applicative ((<$>), (<*>)) -import Data.Text (Text) -import Data.Time (Day) +import Data.Text (Text) +import Data.Time (Day) import Yesod import Yesod.Form.Jquery @@ -555,8 +554,7 @@ we could code something like this. {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} -import Control.Applicative -import Data.Text (Text) +import Data.Text (Text) import Yesod data App = App @@ -663,7 +661,7 @@ monadic form with an applicative one and the code would still work. Applicative and monadic forms handle both the generation of your HTML code and the parsing of user input. Sometimes, you only want to do the latter, such as when there's an already-existing form in HTML somewhere, or if you want to -generate a form dynamically using Javascript. In such a case, you'll want input +generate a form dynamically using JavaScript. In such a case, you'll want input forms. These work mostly the same as applicative and monadic forms, with some differences: @@ -693,8 +691,7 @@ you need to correct. With input forms, the user simply gets an error message. {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} -import Control.Applicative -import Data.Text (Text) +import Data.Text (Text) import Yesod data App = App @@ -853,8 +850,7 @@ Let's see an example of using these two functions: {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} -import Control.Applicative -import Data.Text (Text) +import Data.Text (Text) import Data.Time import Yesod @@ -928,7 +924,7 @@ whether the field is required or optional. The result is six helper functions: +areq+, +aopt+, +mreq+, +mopt+, +ireq+, and +iopt+. Forms have significant power available. They can automatically insert -Javascript to help you leverage nicer UI controls, such as a jQuery UI date +JavaScript to help you leverage nicer UI controls, such as a jQuery UI date picker. Forms are also fully i18n-ready, so you can support a global community of users. And when you have more specific needs, you can slap on some validation functions to an existing field, or write a new one from scratch. diff --git a/book/asciidoc/json-web-service.asciidoc b/book/asciidoc/json-web-service.asciidoc index 59ac2ef..c119157 100644 --- a/book/asciidoc/json-web-service.asciidoc +++ b/book/asciidoc/json-web-service.asciidoc @@ -16,13 +16,12 @@ This plays out as: [source, haskell] ---- {-# LANGUAGE OverloadedStrings #-} +import Conduit (runConduit, (.|)) import Control.Exception (SomeException) import Control.Exception.Lifted (handle) import Control.Monad.IO.Class (liftIO) import Data.Aeson (Value, encode, object, (.=)) import Data.Aeson.Parser (json) -import Data.ByteString (ByteString) -import Data.Conduit (($$)) import Data.Conduit.Attoparsec (sinkParser) import Network.HTTP.Types (status200, status400) import Network.Wai (Application, Response, responseLBS) @@ -34,7 +33,7 @@ main = run 3000 app app :: Application app req sendResponse = handle (sendResponse . invalidJson) $ do - value <- sourceRequestBody req $$ sinkParser json + value <- runConduit $ sourceRequestBody req .| sinkParser json newValue <- liftIO $ modValue value sendResponse $ responseLBS status200 diff --git a/book/asciidoc/persistent.asciidoc b/book/asciidoc/persistent.asciidoc index 1a3f60d..7861de0 100644 --- a/book/asciidoc/persistent.asciidoc +++ b/book/asciidoc/persistent.asciidoc @@ -47,7 +47,7 @@ Persistent on its own. === Synopsis -The required dependencies for the below are: persistent, persistent-sqlite and persistent-template. +The required dependencies for the below are: persistent and persistent-sqlite. [source, haskell] @@ -66,6 +66,7 @@ The required dependencies for the below are: persistent, persistent-sqlite and p {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} import Control.Monad.IO.Class (liftIO) import Database.Persist import Database.Persist.Sqlite @@ -176,11 +177,17 @@ data PersistValue | PersistNull | PersistList [PersistValue] | PersistMap [(Text, PersistValue)] - | PersistObjectId ByteString - -- ^ Intended especially for MongoDB backend - | PersistDbSpecific ByteString - -- ^ Using 'PersistDbSpecific' allows you to use types - -- specific to a particular backend + | -- | Intended especially for MongoDB backend + PersistObjectId ByteString + | -- | Intended especially for PostgreSQL backend for text arrays + PersistArray [PersistValue] + | -- | This constructor is used to specify some raw literal value for the + -- backend. The 'LiteralType' value specifies how the value should be + -- escaped. This can be used to make special, custom types avaialable + -- in the back end. + -- + -- @since 2.12.0.0 + PersistLiteral_ LiteralType ByteString ---- A +PersistValue+ correlates to a column in a SQL database. In our person example @@ -226,11 +233,10 @@ entities once. Let's see a quick example: {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -import Database.Persist import Database.Persist.TH import Database.Persist.Sqlite -import Control.Monad.IO.Class (liftIO) mkPersist sqlSettings [persistLowerCase| Person @@ -270,22 +276,22 @@ data Person = Person type PersonId = Key Person instance PersistEntity Person where - newtype Key Person = PersonKey (BackendKey SqlBackend) + newtype Key Person = PersonKey {unPersonKey :: (BackendKey SqlBackend)} deriving (PersistField, Show, Eq, Read, Ord) -- A Generalized Algebraic Datatype (GADT). -- This gives us a type-safe approach to matching fields with -- their datatypes. - data EntityField Person typ where - PersonId :: EntityField Person PersonId - PersonName :: EntityField Person String - PersonAge :: EntityField Person Int + data EntityField Person typ + = (typ ~ PersonId) => PersonId + | (typ ~ String) => PersonName + | (typ ~ Int) => PersonAge data Unique Person type PersistEntityBackend Person = SqlBackend toPersistFields (Person name age) = - [ SomePersistField name - , SomePersistField age + [ toPersistValue name + , toPersistValue age ] fromPersistValues [nameValue, ageValue] = Person @@ -295,29 +301,32 @@ instance PersistEntity Person where -- Information on each field, used internally to generate SQL statements persistFieldDef PersonId = FieldDef - (HaskellName "Id") - (DBName "id") + (FieldNameHS "Id") + (FieldNameDB "id") (FTTypeCon Nothing "PersonId") SqlInt64 [] True NoReference + ... persistFieldDef PersonName = FieldDef - (HaskellName "name") - (DBName "name") + (FieldNameHS "name") + (FieldNameDB "name") (FTTypeCon Nothing "String") SqlString [] True NoReference + ... persistFieldDef PersonAge = FieldDef - (HaskellName "age") - (DBName "age") + (FieldNameHS "age") + (FieldNameDB "age") (FTTypeCon Nothing "Int") SqlInt64 [] True NoReference + ... ---- As you might expect, our +Person+ datatype closely matches the definition we @@ -347,12 +356,11 @@ pass it off to other Persistent functions. {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} -import Control.Monad.IO.Class (liftIO) import Database.Persist import Database.Persist.Sqlite import Database.Persist.TH -import Control.Monad.IO.Unlift import Data.Text import Control.Monad.Reader import Control.Monad.Logger @@ -466,15 +474,11 @@ to _ask_ it to help. Let's see what this looks like: {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} -import Control.Monad.IO.Class (liftIO) import Database.Persist import Database.Persist.Sqlite import Database.Persist.TH -import Control.Monad.IO.Unlift -import Data.Text -import Control.Monad.Reader -import Control.Monad.Logger import Conduit share [mkPersist sqlSettings, mkEntityDefList "entityDefs", mkMigrate "migrateAll"] [persistLowerCase| @@ -522,7 +526,7 @@ Persistent provides a helper function, +mkMigrate+: {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} -import Database.Persist +{-# LANGUAGE TypeOperators #-} import Database.Persist.Sqlite import Database.Persist.TH @@ -622,10 +626,10 @@ in Haskell as a data constructor. {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} import Database.Persist import Database.Persist.Sqlite import Database.Persist.TH -import Data.Time import Control.Monad.IO.Class (liftIO) share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| @@ -733,16 +737,16 @@ All the select functions use a similar interface, with slightly different output [options="header"] |=============== |Function|Returns -|selectSource|A +Source+ containing all the IDs and values from the database. This allows you to write streaming code. +|selectSource|A +ConduitT+ containing all the IDs and values from the database. This allows you to write streaming code. -NOTE: A +Source+ is a stream of data, and is part of the +conduit+ package. I +NOTE: A +ConduitT+ is a monad transformer designed for streaming data, and is part of the +conduit+ package. I recommend reading the link:https://github.com/snoyberg/conduit[Official Conduit tutorial] to get started. |selectList|A list containing all the IDs and values from the database. All records will be loaded into memory. |selectFirst|Takes just the first ID and value from the database, if available -|selectKeys|Returns only the keys, without the values, as a +Source+. +|selectKeys|Returns only the keys, without the values, as a +ConduitT+. |=============== +selectList+ is the most commonly used, so we will cover it specifically. Understanding the others should be trivial after that. @@ -1026,6 +1030,7 @@ database, we want to just use the current date-time for that timestamp. {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} import Database.Persist import Database.Persist.Sqlite import Database.Persist.TH @@ -1075,7 +1080,7 @@ favorite programming language: {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} -import Database.Persist +{-# LANGUAGE TypeOperators #-} import Database.Persist.Sqlite import Database.Persist.TH import Data.Time @@ -1100,9 +1105,8 @@ itself; you still need to fill in all values. This will only affect the database schema and automatic migrations. We need to surround the string with single quotes so that the database can -properly interpret it. Finally, Persistent can use double quotes for containing -white space, so if we want to set someone's default home country to be El -Salvador: +properly interpret it. This also allows us to use values containing white space, +so if we want to set someone's default home country to be El Salvador: [source, haskell] @@ -1121,7 +1125,7 @@ Salvador: {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} -import Database.Persist +{-# LANGUAGE TypeOperators #-} import Database.Persist.Sqlite import Database.Persist.TH import Data.Time @@ -1132,7 +1136,7 @@ Person age Int Maybe created UTCTime default=CURRENT_TIME language String default='Haskell' - country String "default='El Salvador'" + country String default='El Salvador' deriving Show |] @@ -1152,7 +1156,7 @@ share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| Person sql=the-person-table id=numeric_id firstName String sql=first_name lastName String sql=fldLastName - age Int "sql=The Age of the Person" + age Int sql="The Age of the Person" PersonName firstName lastName deriving Show |] @@ -1184,11 +1188,11 @@ the related entity. So if a person has many cars: {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} import Database.Persist import Database.Persist.Sqlite import Database.Persist.TH import Control.Monad.IO.Class (liftIO) -import Data.Time share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| Person @@ -1233,10 +1237,10 @@ want to track which people have shopped in which stores: {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} import Database.Persist import Database.Persist.Sqlite import Database.Persist.TH -import Data.Time share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| Person @@ -1414,6 +1418,7 @@ derivePersistField "Employment" {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} import Database.Persist.Sqlite import Database.Persist.TH import Employment @@ -1476,12 +1481,11 @@ operators. But this is still a good example, so let's roll with it. {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +import Conduit import Database.Persist.TH import Data.Text (Text) import Database.Persist.Sqlite -import Control.Monad.IO.Class (liftIO) -import Data.Conduit -import qualified Data.Conduit.List as CL share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| Person @@ -1501,7 +1505,7 @@ main = runSqlite ":memory:" $ do -- Persistent does not provide the LIKE keyword, but we'd like to get the -- whole Snoyman family... let sql = "SELECT name FROM Person WHERE name LIKE '%Snoyman'" - rawQuery sql [] $$ CL.mapM_ (liftIO . print) + runConduit $ rawQuery sql [] .| mapM_C (liftIO . print) ---- There is also higher-level support that allows for automated data marshaling. @@ -1534,6 +1538,7 @@ the database via the +runDB+ method. Let's see this in action. {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} {-# LANGUAGE ViewPatterns #-} import Yesod import Database.Persist.Sqlite @@ -1609,11 +1614,11 @@ functions we've spoken about so far, such as +insert+ and +selectList+. [NOTE] ==== -The type of +runDB+ is +YesodDB site a -> HandlerT site IO a+. +YesodDB+ is defined as: +The type of +runDB+ is +YesodDB site a -> HandlerFor site a+. +YesodDB+ is defined as: [source, haskell] ---- -type YesodDB site = ReaderT (YesodPersistBackend site) (HandlerT site IO) +type YesodDB site = ReaderT (YesodPersistBackend site) (HandlerFor site) ---- Since it is built on top of the +YesodPersistBackend+ associated type, it uses @@ -1661,6 +1666,7 @@ To keep the examples in this chapter simple, we've used the SQLite backend. Just {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} import Control.Monad.IO.Class (liftIO) import Control.Monad.Logger (runStderrLoggingT) import Database.Persist @@ -1715,5 +1721,5 @@ Persistent integrates directly into the general Yesod workflow. Not only do helper packages like +yesod-persistent+ provide a nice layer, but packages like +yesod-form+ and +yesod-auth+ also leverage Persistent's features as well. -For more information on the syntax of entity declarations, database connection, etc. -Checkout https://github.com/yesodweb/persistent/tree/master/docs +For more information on the syntax of entity declarations, database connection, +etc., check out https://github.com/yesodweb/persistent/tree/master/docs. diff --git a/book/asciidoc/restful-content.asciidoc b/book/asciidoc/restful-content.asciidoc index b425bf9..e48bb82 100644 --- a/book/asciidoc/restful-content.asciidoc +++ b/book/asciidoc/restful-content.asciidoc @@ -38,13 +38,13 @@ bank account to another. +PUT+:: Create a new resource on the server, or replace an existing one. This method _is_ safe to be called multiple times. -+PATCH+:: Updates the resource partially on the server. When you want -to update one or more field of the resource, this method should be preferred. ++PATCH+:: Updates the resource partially on the server. When you want to update +one or more fields of the resource, this method should be preferred. +DELETE+:: Just like it sounds: wipe out a resource on the server. Calling multiple times should be OK. -To a certain extent, this fits in very well with Haskell philosophy: a +GET+ +To a certain extent, this fits in very well with Haskell's philosophy: a +GET+ request is similar to a pure function, which cannot have side effects. In practice, your +GET+ functions will probably perform +IO+, such as reading information from a database, logging user actions, and so on. @@ -144,12 +144,12 @@ main = warp 3000 App The +selectRep+ function says ``I'm about to give you some possible representations''. Each +provideRep+ call provides an alternate representation. -Yesod uses the Haskell types to determine the mime type for each +Yesod uses the Haskell types to determine the MIME type for each representation. Since +shamlet+ (a.k.a. simple Hamlet) produces an +Html+ -value, Yesod can determine that the relevant mime type is +text/html+. -Similarly, +object+ generates a JSON value, which implies the mime type +value, Yesod can determine that the relevant MIME type is +text/html+. +Similarly, +object+ generates a JSON value, which implies the MIME type +application/json+. +TypedContent+ is a data type provided by Yesod for some -raw content with an attached mime type. We'll cover it in more detail in a +raw content with an attached MIME type. We'll cover it in more detail in a little bit. To test this out, start up the server and then try running the following @@ -162,14 +162,14 @@ curl http://localhost:3000 --header "accept: text/html" curl http://localhost:3000 ---- -Notice how the response changes based on the accept header value. Also, when +Notice how the response changes based on the +Accept+ header value. Also, when you leave off the header, the HTML response is displayed by default. The rule -here is that if there is no accept header, the first representation is -displayed. If an accept header is present, but we have no matches, then a 406 +here is that if there is no +Accept+ header, the first representation is +displayed. If an +Accept+ header is present, but we have no matches, then a 406 "not acceptable" response is returned. By default, Yesod provides a convenience middleware that lets you set the -accept header via a query string parameter. This can make it easier to test ++Accept+ header via a query string parameter. This can make it easier to test from your browser. To try this out, you can visit link:http://localhost:3000/?_accept=application/json[http://localhost:3000/?_accept=application/json]. @@ -210,7 +210,7 @@ main = L.putStrLn $ encode $ Person "Michael" 28 ---- I won't go into further detail on +aeson+, as -link:https://www.fpcomplete.com/haddocks/aeson[the Haddock documentation] +link:https://hackage.haskell.org/package/aeson[the Haddock documentation] already provides a great introduction to the library. What I've described so far is enough to understand our convenience functions. @@ -307,7 +307,7 @@ main = warp 3000 App ==== New datatypes Let's say I've come up with some new data format based on using Haskell's -+Show+ instance; I'll call it ``Haskell Show'', and give it a mime type of ++Show+ instance; I'll call it ``Haskell Show'', and give it a MIME type of +text/haskell-show+. And let's say that I decide to include this representation from my web app. How do I do it? For a first attempt, let's use the +TypedContent+ datatype directly. @@ -357,7 +357,7 @@ There are a few important things to note here. aeson's +Value+. * We're using the +TypedContent+ constructor directly. It takes two arguments: - a mime type, and the raw content. Note that +ContentType+ is simply a type + a MIME type, and the raw content. Note that +ContentType+ is simply a type alias for a strict +ByteString+. That's all well and good, but it bothers me that the type signature for @@ -418,7 +418,7 @@ other in this way. +ToTypedContent+ is used internally by Yesod, and is called on the result of all handler functions. As you can see, the implementation is fairly trivial, -simply stating the mime type and then calling out to +toContent+. +simply stating the MIME type and then calling out to +toContent+. Finally, let's make this a bit more complicated, and get this to play well with +selectRep+. @@ -479,14 +479,14 @@ main = warp 3000 App The important addition here is the +HasContentType+ instance. This may seem redundant, but it serves an important role. We need to be able to determine the -mime type of a possible representation _before creating that representation_. +MIME type of a possible representation _before creating that representation_. +ToTypedContent+ only works on a concrete value, and therefore can't be used before creating the value. +getContentType+ instead takes a proxy value, indicating the type without providing anything concrete. NOTE: If you want to provide a representation for a value that doesn't have a +HasContentType+ instance, you can use the +provideRepType+ function, which -requires you to explicitly state the mime type present. +requires you to explicitly state the MIME type present. === Other request headers diff --git a/book/asciidoc/routing-and-handlers.asciidoc b/book/asciidoc/routing-and-handlers.asciidoc index 4ee33d3..729d301 100644 --- a/book/asciidoc/routing-and-handlers.asciidoc +++ b/book/asciidoc/routing-and-handlers.asciidoc @@ -353,7 +353,7 @@ should remember that the function can be used in your +Handler+ functions. There's nothing too surprising about this type. This function returns some HTML content, represented by the +Html+ data type. But clearly Yesod would not be useful if it only allowed HTML responses to be generated. We want to respond with -CSS, Javascript, JSON, images, and more. So the question is: what data types +CSS, JavaScript, JSON, images, and more. So the question is: what data types can be returned? In order to generate a response, we need to know two pieces of information: @@ -533,9 +533,9 @@ cacheSeconds:: Set a Cache-Control header to indicate how many seconds this response can be cached. This can be particularly useful if you are using link:http://www.varnish-cache.org[varnish on your server]. -neverExpires:: Set the Expires header to the year 2037. You can use this with -content which should never expire, such as when the request path has a hash -value associated with it. +neverExpires:: Set the Expires header to a date one year from now. You can use +this with content which should never expire, such as when the request path has a +hash value associated with it. alreadyExpired:: Sets the Expires header to the past. diff --git a/book/asciidoc/scaffolding-and-the-site-template.asciidoc b/book/asciidoc/scaffolding-and-the-site-template.asciidoc index 55062e5..19c2b7a 100644 --- a/book/asciidoc/scaffolding-and-the-site-template.asciidoc +++ b/book/asciidoc/scaffolding-and-the-site-template.asciidoc @@ -165,8 +165,7 @@ implementations of +Yesod+ typeclass methods. The +Import+ module was born out of a few commonly recurring patterns. -* I want to define some helper functions (maybe the +<> = mappend+ - operator) to be used by all handlers. +* I want to define some helper functions to be used by all handlers. * I'm always adding the same five import statements (+Data.Text+, +Control.Applicative+, etc) to every handler module. @@ -210,7 +209,7 @@ You can use the +stack exec \-- yesod add-handler+ command to automate the last === widgetFile -It's very common to want to include CSS and Javascript specific to a page. You +It's very common to want to include CSS and JavaScript specific to a page. You don't want to have to remember to include those Lucius and Julius files manually every time you refer to a Hamlet file. For this, the site template provides the +widgetFile+ function. @@ -280,16 +279,16 @@ transmission of cookies for static file requests, and also lets you offload static file hosting to a CDN or a service like Amazon S3. See the comments in the file for more details. -Another optimization is that CSS and Javascript included in your widgets will +Another optimization is that CSS and JavaScript included in your widgets will not be included inside your HTML. Instead, their contents will be written to an external file, and a link given. This file will be named based on a hash of the contents as well, meaning: . Caching works properly. -. Yesod can avoid an expensive disk write of the CSS/Javascript file contents if a file with the same hash already exists. +. Yesod can avoid an expensive disk write of the CSS/JavaScript file contents if a file with the same hash already exists. -Finally, all of your Javascript is automatically minified via hjsmin. +Finally, all of your JavaScript is automatically minified via hjsmin. === Environment variables diff --git a/book/asciidoc/sessions.asciidoc b/book/asciidoc/sessions.asciidoc index 2673508..a800e32 100644 --- a/book/asciidoc/sessions.asciidoc +++ b/book/asciidoc/sessions.asciidoc @@ -136,8 +136,6 @@ a value for a key, and +deleteSession+ clears a value for a key. {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE MultiParamTypeClasses #-} -import Control.Applicative ((<$>), (<*>)) -import qualified Web.ClientSession as CS import Yesod data App = App diff --git a/book/asciidoc/shakespearean-templates.asciidoc b/book/asciidoc/shakespearean-templates.asciidoc index a452462..f2c0729 100644 --- a/book/asciidoc/shakespearean-templates.asciidoc +++ b/book/asciidoc/shakespearean-templates.asciidoc @@ -24,7 +24,7 @@ them to enhance Yesod application development. === Synopsis There are four main languages at play: Hamlet is an HTML templating language, -Julius is for Javascript, and Cassius and Lucius are both for CSS. Hamlet and +Julius is for JavaScript, and Cassius and Lucius are both for CSS. Hamlet and Cassius are both whitespace-sensitive formats, using indentation to denote nesting. By contrast, Lucius is a superset of CSS, keeping CSS's braces for denoting nesting. Julius is a simple passthrough language for producing @@ -467,7 +467,7 @@ example. ---- {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE OverloadedStrings #-} -import Text.Hamlet (HtmlUrl, hamlet) +import Text.Hamlet (hamlet) import Text.Blaze.Html.Renderer.String (renderHtml) import Data.Text (Text, append, pack) import Control.Arrow (second) diff --git a/book/asciidoc/sql-joins.asciidoc b/book/asciidoc/sql-joins.asciidoc index e7e3771..dd645d3 100644 --- a/book/asciidoc/sql-joins.asciidoc +++ b/book/asciidoc/sql-joins.asciidoc @@ -30,17 +30,22 @@ the blog title and the author: [source, haskell] ---- +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE EmptyDataDecls #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE ViewPatterns #-} -import Control.Monad.Logger import Data.Text (Text) import Database.Persist.Sqlite import Yesod @@ -326,7 +331,7 @@ getHomeR = do render <- getUrlRenderParams respondSourceDB typeHtml $ do sendChunkText "