Posts with many categories now render

- Fixed a problem with the RethinkDB query that was assembling
categories for a single post (yay, @1lann on Slack!)
- Finished conversion of data access functions to use async CEs
This commit is contained in:
Daniel J. Summers 2016-11-08 09:47:33 -06:00
parent e5700727e9
commit 1873f9d6fc
5 changed files with 206 additions and 153 deletions

View File

@ -9,7 +9,7 @@ let private r = RethinkDb.Driver.RethinkDB.R
let private category (webLogId : string) (catId : string) = let private category (webLogId : string) (catId : string) =
r.Table(Table.Category) r.Table(Table.Category)
.Get(catId) .Get(catId)
.Filter(ReqlFunction1(fun c -> upcast c.["WebLogId"].Eq(webLogId))) .Filter(ReqlFunction1 (fun c -> upcast c.["WebLogId"].Eq webLogId))
/// Get all categories for a web log /// Get all categories for a web log
let getAllCategories conn (webLogId : string) = let getAllCategories conn (webLogId : string) =
@ -99,7 +99,7 @@ let deleteCategory conn (cat : Category) =
let! posts = let! posts =
r.Table(Table.Post) r.Table(Table.Post)
.GetAll(cat.WebLogId).OptArg("index", "WebLogId") .GetAll(cat.WebLogId).OptArg("index", "WebLogId")
.Filter(ReqlFunction1(fun p -> upcast p.["CategoryIds"].Contains(cat.Id))) .Filter(ReqlFunction1 (fun p -> upcast p.["CategoryIds"].Contains cat.Id))
.RunResultAsync<Post list> conn .RunResultAsync<Post list> conn
|> Async.AwaitTask |> Async.AwaitTask
posts posts
@ -119,7 +119,7 @@ let deleteCategory conn (cat : Category) =
do! r.Table(Table.Category) do! r.Table(Table.Category)
.Get(cat.Id) .Get(cat.Id)
.Delete() .Delete()
.RunResultAsync(conn) .RunResultAsync conn
} }
|> Async.RunSynchronously |> Async.RunSynchronously
@ -127,7 +127,7 @@ let deleteCategory conn (cat : Category) =
let tryFindCategoryBySlug conn (webLogId : string) (slug : string) = let tryFindCategoryBySlug conn (webLogId : string) (slug : string) =
async { async {
let! cat = r.Table(Table.Category) let! cat = r.Table(Table.Category)
.GetAll(r.Array(webLogId, slug)).OptArg("index", "Slug") .GetAll(r.Array (webLogId, slug)).OptArg("index", "Slug")
.RunResultAsync<Category list> conn .RunResultAsync<Category list> conn
return cat |> List.tryHead return cat |> List.tryHead
} }

View File

@ -3,8 +3,6 @@ module MyWebLog.Data.RethinkDB.Extensions
open System.Threading.Tasks open System.Threading.Tasks
let await task = task |> Async.AwaitTask |> Async.RunSynchronously
// H/T: Suave // H/T: Suave
type AsyncBuilder with type AsyncBuilder with
/// An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on /// An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on

View File

@ -7,39 +7,55 @@ let private r = RethinkDb.Driver.RethinkDB.R
/// Try to find a page by its Id, optionally including revisions /// Try to find a page by its Id, optionally including revisions
let tryFindPageById conn webLogId (pageId : string) includeRevs = let tryFindPageById conn webLogId (pageId : string) includeRevs =
let pg = r.Table(Table.Page) async {
.Get(pageId) let q =
match includeRevs with true -> pg.RunResultAsync<Page>(conn) | _ -> pg.Without("Revisions").RunResultAsync<Page>(conn) r.Table(Table.Page)
|> await .Get pageId
|> box let! thePage =
|> function match includeRevs with
| null -> None | true -> q.RunResultAsync<Page> conn
| page -> let pg : Page = unbox page | _ -> q.Without("Revisions").RunResultAsync<Page> conn
match pg.WebLogId = webLogId with true -> Some pg | _ -> None return
match box thePage with
| null -> None
| page ->
let pg : Page = unbox page
match pg.WebLogId = webLogId with true -> Some pg | _ -> None
}
|> Async.RunSynchronously
/// Find a page by its permalink /// Find a page by its permalink
let tryFindPageByPermalink conn (webLogId : string) (permalink : string) = let tryFindPageByPermalink conn (webLogId : string) (permalink : string) =
r.Table(Table.Page) async {
.GetAll(r.Array(webLogId, permalink)).OptArg("index", "Permalink") let! pg =
.Without("Revisions") r.Table(Table.Page)
.RunResultAsync<Page list>(conn) .GetAll(r.Array (webLogId, permalink)).OptArg("index", "Permalink")
|> await .Without("Revisions")
|> List.tryHead .RunResultAsync<Page list> conn
return List.tryHead pg
}
|> Async.RunSynchronously
/// Get a list of all pages (excludes page text and revisions) /// Get a list of all pages (excludes page text and revisions)
let findAllPages conn (webLogId : string) = let findAllPages conn (webLogId : string) =
r.Table(Table.Page) async {
.GetAll(webLogId).OptArg("index", "WebLogId") return!
.OrderBy("Title") r.Table(Table.Page)
.Without("Text", "Revisions") .GetAll(webLogId).OptArg("index", "WebLogId")
.RunResultAsync<Page list>(conn) .OrderBy("Title")
|> await .Without("Text", "Revisions")
.RunResultAsync<Page list> conn
}
|> Async.RunSynchronously
/// Add a page /// Add a page
let addPage conn (page : Page) = let addPage conn (page : Page) =
r.Table(Table.Page) async {
.Insert(page) do! r.Table(Table.Page)
.RunResultAsync(conn) |> await |> ignore .Insert(page)
.RunResultAsync conn
}
|> (Async.RunSynchronously >> ignore)
type PageUpdateRecord = type PageUpdateRecord =
{ Title : string { Title : string
@ -51,22 +67,30 @@ type PageUpdateRecord =
/// Update a page /// Update a page
let updatePage conn (page : Page) = let updatePage conn (page : Page) =
match tryFindPageById conn page.WebLogId page.Id false with match tryFindPageById conn page.WebLogId page.Id false with
| Some _ -> r.Table(Table.Page) | Some _ ->
.Get(page.Id) async {
.Update({ PageUpdateRecord.Title = page.Title do! r.Table(Table.Page)
Permalink = page.Permalink .Get(page.Id)
PublishedOn = page.PublishedOn .Update({ PageUpdateRecord.Title = page.Title
UpdatedOn = page.UpdatedOn Permalink = page.Permalink
Text = page.Text PublishedOn = page.PublishedOn
Revisions = page.Revisions }) UpdatedOn = page.UpdatedOn
.RunResultAsync(conn) |> await |> ignore Text = page.Text
Revisions = page.Revisions })
.RunResultAsync conn
}
|> (Async.RunSynchronously >> ignore)
| _ -> () | _ -> ()
/// Delete a page /// Delete a page
let deletePage conn webLogId pageId = let deletePage conn webLogId pageId =
match tryFindPageById conn webLogId pageId false with match tryFindPageById conn webLogId pageId false with
| Some _ -> r.Table(Table.Page) | Some _ ->
.Get(pageId) async {
.Delete() do! r.Table(Table.Page)
.RunResultAsync(conn) |> await |> ignore .Get(pageId)
.Delete()
.RunResultAsync conn
}
|> (Async.RunSynchronously >> ignore)
| _ -> () | _ -> ()

View File

@ -8,25 +8,31 @@ let private r = RethinkDb.Driver.RethinkDB.R
/// Shorthand to select all published posts for a web log /// Shorthand to select all published posts for a web log
let private publishedPosts (webLogId : string)= let private publishedPosts (webLogId : string)=
r.Table(Table.Post) r.Table(Table.Post)
.GetAll(r.Array(webLogId, PostStatus.Published)).OptArg("index", "WebLogAndStatus") .GetAll(r.Array (webLogId, PostStatus.Published)).OptArg("index", "WebLogAndStatus")
/// Shorthand to sort posts by published date, slice for the given page, and return a list /// Shorthand to sort posts by published date, slice for the given page, and return a list
let private toPostList conn pageNbr nbrPerPage (filter : ReqlExpr) = let private toPostList conn pageNbr nbrPerPage (filter : ReqlExpr) =
filter async {
.OrderBy(r.Desc("PublishedOn")) return!
.Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage) filter
.RunResultAsync<Post list>(conn) .OrderBy(r.Desc "PublishedOn")
|> await .Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage)
.RunResultAsync<Post list> conn
}
|> Async.RunSynchronously
/// Shorthand to get a newer or older post /// Shorthand to get a newer or older post
let private adjacentPost conn (post : Post) (theFilter : ReqlExpr -> obj) (sort : obj) = let private adjacentPost conn (post : Post) (theFilter : ReqlExpr -> obj) (sort : obj) =
(publishedPosts post.WebLogId) async {
.Filter(theFilter) let! post =
.OrderBy(sort) (publishedPosts post.WebLogId)
.Limit(1) .Filter(theFilter)
.RunResultAsync<Post list>(conn) .OrderBy(sort)
|> await .Limit(1)
|> List.tryHead .RunResultAsync<Post list> conn
return List.tryHead post
}
|> Async.RunSynchronously
/// Find a newer post /// Find a newer post
let private newerPost conn post theFilter = adjacentPost conn post theFilter <| r.Asc "PublishedOn" let private newerPost conn post theFilter = adjacentPost conn post theFilter <| r.Asc "PublishedOn"
@ -42,141 +48,163 @@ let findPageOfPublishedPosts conn webLogId pageNbr nbrPerPage =
/// Get a page of published posts assigned to a given category /// Get a page of published posts assigned to a given category
let findPageOfCategorizedPosts conn webLogId (categoryId : string) pageNbr nbrPerPage = let findPageOfCategorizedPosts conn webLogId (categoryId : string) pageNbr nbrPerPage =
(publishedPosts webLogId) (publishedPosts webLogId)
.Filter(ReqlFunction1(fun p -> upcast p.["CategoryIds"].Contains(categoryId))) .Filter(ReqlFunction1 (fun p -> upcast p.["CategoryIds"].Contains categoryId))
|> toPostList conn pageNbr nbrPerPage |> toPostList conn pageNbr nbrPerPage
/// Get a page of published posts tagged with a given tag /// Get a page of published posts tagged with a given tag
let findPageOfTaggedPosts conn webLogId (tag : string) pageNbr nbrPerPage = let findPageOfTaggedPosts conn webLogId (tag : string) pageNbr nbrPerPage =
(publishedPosts webLogId) (publishedPosts webLogId)
.Filter(ReqlFunction1(fun p -> upcast p.["Tags"].Contains(tag))) .Filter(ReqlFunction1 (fun p -> upcast p.["Tags"].Contains tag))
|> toPostList conn pageNbr nbrPerPage |> toPostList conn pageNbr nbrPerPage
/// Try to get the next newest post from the given post /// Try to get the next newest post from the given post
let tryFindNewerPost conn post = newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn)) let tryFindNewerPost conn post = newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt post.PublishedOn)
/// Try to get the next newest post assigned to the given category /// Try to get the next newest post assigned to the given category
let tryFindNewerCategorizedPost conn (categoryId : string) post = let tryFindNewerCategorizedPost conn (categoryId : string) post =
newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn) newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn)
.And(p.["CategoryIds"].Contains(categoryId))) .And(p.["CategoryIds"].Contains categoryId))
/// Try to get the next newest tagged post from the given tagged post /// Try to get the next newest tagged post from the given tagged post
let tryFindNewerTaggedPost conn (tag : string) post = let tryFindNewerTaggedPost conn (tag : string) post =
newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn).And(p.["Tags"].Contains(tag))) newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn).And(p.["Tags"].Contains tag))
/// Try to get the next oldest post from the given post /// Try to get the next oldest post from the given post
let tryFindOlderPost conn post = olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn)) let tryFindOlderPost conn post = olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt post.PublishedOn)
/// Try to get the next oldest post assigned to the given category /// Try to get the next oldest post assigned to the given category
let tryFindOlderCategorizedPost conn (categoryId : string) post = let tryFindOlderCategorizedPost conn (categoryId : string) post =
olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn) olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn)
.And(p.["CategoryIds"].Contains(categoryId))) .And(p.["CategoryIds"].Contains categoryId))
/// Try to get the next oldest tagged post from the given tagged post /// Try to get the next oldest tagged post from the given tagged post
let tryFindOlderTaggedPost conn (tag : string) post = let tryFindOlderTaggedPost conn (tag : string) post =
olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn).And(p.["Tags"].Contains(tag))) olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn).And(p.["Tags"].Contains tag))
/// Get a page of all posts in all statuses /// Get a page of all posts in all statuses
let findPageOfAllPosts conn (webLogId : string) pageNbr nbrPerPage = let findPageOfAllPosts conn (webLogId : string) pageNbr nbrPerPage =
// FIXME: sort unpublished posts by their last updated date // FIXME: sort unpublished posts by their last updated date
r.Table(Table.Post) async {
.GetAll(webLogId).OptArg("index", "WebLogId") return!
.OrderBy(r.Desc("PublishedOn")) r.Table(Table.Post)
.Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage) .GetAll(webLogId).OptArg("index", "WebLogId")
.RunResultAsync<Post list>(conn) .OrderBy(r.Desc "PublishedOn")
|> await .Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage)
.RunResultAsync<Post list> conn
}
|> Async.RunSynchronously
/// Try to find a post by its Id and web log Id /// Try to find a post by its Id and web log Id
let tryFindPost conn webLogId postId : Post option = let tryFindPost conn webLogId postId : Post option =
r.Table(Table.Post) async {
.Get(postId) let! p =
.Filter(ReqlFunction1(fun p -> upcast p.["WebLogId"].Eq(webLogId))) r.Table(Table.Post)
.RunResultAsync<Post>(conn) .Get(postId)
|> await .Filter(ReqlFunction1 (fun p -> upcast p.["WebLogId"].Eq webLogId))
|> box .RunResultAsync<Post> conn
|> function null -> None | post -> Some <| unbox post return match box p with null -> None | post -> Some <| unbox post
}
|> Async.RunSynchronously
/// Try to find a post by its permalink /// Try to find a post by its permalink
let tryFindPostByPermalink conn webLogId permalink = let tryFindPostByPermalink conn webLogId permalink =
r.Table(Table.Post) async {
.GetAll(r.Array(webLogId, permalink)).OptArg("index", "Permalink") let! post =
.Filter(ReqlFunction1(fun p -> upcast p.["Status"].Eq(PostStatus.Published))) r.Table(Table.Post)
.Without("Revisions") .GetAll(r.Array (webLogId, permalink)).OptArg("index", "Permalink")
.Merge(ReqlFunction1(fun p -> .Filter(ReqlFunction1 (fun p -> upcast p.["Status"].Eq PostStatus.Published))
upcast r.HashMap("Categories", r.Table(Table.Category) .Without("Revisions")
.GetAll(p.["CategoryIds"]) .Merge(ReqlFunction1 (fun p ->
.Without("Children") upcast r.HashMap(
.OrderBy("Name") "Categories", r.Table(Table.Category)
.CoerceTo("array")))) .GetAll(r.Args p.["CategoryIds"])
.Merge(ReqlFunction1(fun p -> .Without("Children")
upcast r.HashMap("Comments", r.Table(Table.Comment) .OrderBy("Name")
.GetAll(p.["id"]).OptArg("index", "PostId") .CoerceTo("array")).With(
.OrderBy("PostedOn") "Comments", r.Table(Table.Comment)
.CoerceTo("array")))) .GetAll(p.["id"]).OptArg("index", "PostId")
.RunResultAsync<Post list>(conn) .OrderBy("PostedOn")
|> await .CoerceTo("array"))))
|> List.tryHead .RunResultAsync<Post list> conn
return List.tryHead post
}
|> Async.RunSynchronously
/// Try to find a post by its prior permalink /// Try to find a post by its prior permalink
let tryFindPostByPriorPermalink conn (webLogId : string) (permalink : string) = let tryFindPostByPriorPermalink conn (webLogId : string) (permalink : string) =
r.Table(Table.Post) async {
.GetAll(webLogId).OptArg("index", "WebLogId") let! post =
.Filter(ReqlFunction1(fun p -> r.Table(Table.Post)
upcast p.["PriorPermalinks"].Contains(permalink).And(p.["Status"].Eq(PostStatus.Published)))) .GetAll(webLogId).OptArg("index", "WebLogId")
.Without("Revisions") .Filter(ReqlFunction1 (fun p ->
.RunResultAsync<Post list>(conn) upcast p.["PriorPermalinks"].Contains(permalink).And(p.["Status"].Eq PostStatus.Published)))
|> await .Without("Revisions")
|> List.tryHead .RunResultAsync<Post list> conn
return List.tryHead post
}
|> Async.RunSynchronously
/// Get a set of posts for RSS /// Get a set of posts for RSS
let findFeedPosts conn webLogId nbr : (Post * User option) list = let findFeedPosts conn webLogId nbr : (Post * User option) list =
let tryFindUser userId =
async {
let! u =
r.Table(Table.User)
.Get(userId)
.RunAtomAsync<User> conn
return match box u with null -> None | user -> Some <| unbox user
}
|> Async.RunSynchronously
(publishedPosts webLogId) (publishedPosts webLogId)
.Merge(ReqlFunction1(fun post -> .Merge(ReqlFunction1 (fun post ->
upcast r.HashMap("Categories", r.Table(Table.Category) upcast r.HashMap(
.GetAll(post.["CategoryIds"]) "Categories", r.Table(Table.Category)
.OrderBy("Name") .GetAll(r.Args post.["CategoryIds"])
.Pluck("id", "Name") .OrderBy("Name")
.CoerceTo("array")))) .Pluck("id", "Name")
.CoerceTo("array"))))
|> toPostList conn 1 nbr |> toPostList conn 1 nbr
|> List.map (fun post -> post, r.Table(Table.User) |> List.map (fun post -> post, tryFindUser post.AuthorId)
.Get(post.AuthorId)
.RunAtomAsync<User>(conn)
|> await
|> box
|> function null -> None | user -> Some <| unbox user)
/// Add a post /// Add a post
let addPost conn post = let addPost conn post =
r.Table(Table.Post) async {
.Insert(post) do! r.Table(Table.Post)
.RunResultAsync(conn) .Insert(post)
|> await .RunResultAsync conn
|> ignore }
|> (Async.RunSynchronously >> ignore)
/// Update a post /// Update a post
let updatePost conn (post : Post) = let updatePost conn (post : Post) =
r.Table(Table.Post) async {
.Get(post.Id) do! r.Table(Table.Post)
.Replace( { post with Categories = [] .Get(post.Id)
Comments = [] } ) .Replace( { post with Categories = []
.RunResultAsync(conn) Comments = [] } )
|> await .RunResultAsync conn
|> ignore }
|> (Async.RunSynchronously >> ignore)
/// Save a post /// Save a post
let savePost conn (post : Post) = let savePost conn (post : Post) =
match post.Id with match post.Id with
| "new" -> let newPost = { post with Id = string <| System.Guid.NewGuid() } | "new" ->
r.Table(Table.Post) let newPost = { post with Id = string <| System.Guid.NewGuid() }
.Insert(newPost) async {
.RunResultAsync(conn) do! r.Table(Table.Post)
|> await .Insert(newPost)
|> ignore .RunResultAsync conn
newPost.Id }
| _ -> r.Table(Table.Post) |> Async.RunSynchronously
.Get(post.Id) newPost.Id
.Replace( { post with Categories = [] | _ ->
Comments = [] } ) async {
.RunResultAsync(conn) do! r.Table(Table.Post)
|> await .Get(post.Id)
|> ignore .Replace( { post with Categories = []
post.Id Comments = [] } )
.RunResultAsync conn
}
|> Async.RunSynchronously
post.Id

View File

@ -11,13 +11,14 @@ let tryFindWebLogByUrlBase conn (urlBase : string) =
let! cursor = let! cursor =
r.Table(Table.WebLog) r.Table(Table.WebLog)
.GetAll(urlBase).OptArg("index", "UrlBase") .GetAll(urlBase).OptArg("index", "UrlBase")
.Merge(ReqlFunction1(fun w -> .Merge(ReqlFunction1 (fun w ->
upcast r.HashMap("PageList", r.Table(Table.Page) upcast r.HashMap(
.GetAll(w.G("id")).OptArg("index", "WebLogId") "PageList", r.Table(Table.Page)
.Filter(ReqlFunction1(fun pg -> upcast pg.["ShowInPageList"].Eq(true))) .GetAll(w.G("id")).OptArg("index", "WebLogId")
.OrderBy("Title") .Filter(ReqlFunction1 (fun pg -> upcast pg.["ShowInPageList"].Eq true))
.Pluck("Title", "Permalink") .OrderBy("Title")
.CoerceTo("array")))) .Pluck("Title", "Permalink")
.CoerceTo("array"))))
.RunCursorAsync<WebLog> conn .RunCursorAsync<WebLog> conn
return cursor |> Seq.tryHead return cursor |> Seq.tryHead
} }
@ -27,9 +28,11 @@ let tryFindWebLogByUrlBase conn (urlBase : string) =
let findDashboardCounts conn (webLogId : string) = let findDashboardCounts conn (webLogId : string) =
async { async {
return! return!
r.Expr( r.HashMap("Pages", r.Table(Table.Page ).GetAll(webLogId).OptArg("index", "WebLogId").Count())) r.Expr(
.Merge(r.HashMap("Posts", r.Table(Table.Post ).GetAll(webLogId).OptArg("index", "WebLogId").Count())) r.HashMap(
.Merge(r.HashMap("Categories", r.Table(Table.Category).GetAll(webLogId).OptArg("index", "WebLogId").Count())) "Pages", r.Table(Table.Page ).GetAll(webLogId).OptArg("index", "WebLogId").Count()).With(
"Posts", r.Table(Table.Post ).GetAll(webLogId).OptArg("index", "WebLogId").Count()).With(
"Categories", r.Table(Table.Category).GetAll(webLogId).OptArg("index", "WebLogId").Count()))
.RunResultAsync<DashboardCounts> conn .RunResultAsync<DashboardCounts> conn
} }
|> Async.RunSynchronously |> Async.RunSynchronously