From 1873f9d6fc67d62c6d939657be3ee17bca3d06ba Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Tue, 8 Nov 2016 09:47:33 -0600 Subject: [PATCH] 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 --- src/MyWebLog.Data.RethinkDB/Category.fs | 8 +- src/MyWebLog.Data.RethinkDB/Extensions.fs | 4 +- src/MyWebLog.Data.RethinkDB/Page.fs | 98 ++++++---- src/MyWebLog.Data.RethinkDB/Post.fs | 226 ++++++++++++---------- src/MyWebLog.Data.RethinkDB/WebLog.fs | 23 ++- 5 files changed, 206 insertions(+), 153 deletions(-) diff --git a/src/MyWebLog.Data.RethinkDB/Category.fs b/src/MyWebLog.Data.RethinkDB/Category.fs index 9b3cc32..79eef22 100644 --- a/src/MyWebLog.Data.RethinkDB/Category.fs +++ b/src/MyWebLog.Data.RethinkDB/Category.fs @@ -9,7 +9,7 @@ let private r = RethinkDb.Driver.RethinkDB.R let private category (webLogId : string) (catId : string) = r.Table(Table.Category) .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 let getAllCategories conn (webLogId : string) = @@ -99,7 +99,7 @@ let deleteCategory conn (cat : Category) = let! posts = r.Table(Table.Post) .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 conn |> Async.AwaitTask posts @@ -119,7 +119,7 @@ let deleteCategory conn (cat : Category) = do! r.Table(Table.Category) .Get(cat.Id) .Delete() - .RunResultAsync(conn) + .RunResultAsync conn } |> Async.RunSynchronously @@ -127,7 +127,7 @@ let deleteCategory conn (cat : Category) = let tryFindCategoryBySlug conn (webLogId : string) (slug : string) = async { let! cat = r.Table(Table.Category) - .GetAll(r.Array(webLogId, slug)).OptArg("index", "Slug") + .GetAll(r.Array (webLogId, slug)).OptArg("index", "Slug") .RunResultAsync conn return cat |> List.tryHead } diff --git a/src/MyWebLog.Data.RethinkDB/Extensions.fs b/src/MyWebLog.Data.RethinkDB/Extensions.fs index d0bf11c..82c7645 100644 --- a/src/MyWebLog.Data.RethinkDB/Extensions.fs +++ b/src/MyWebLog.Data.RethinkDB/Extensions.fs @@ -3,8 +3,6 @@ module MyWebLog.Data.RethinkDB.Extensions open System.Threading.Tasks -let await task = task |> Async.AwaitTask |> Async.RunSynchronously - // H/T: Suave type AsyncBuilder with /// An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on @@ -15,4 +13,4 @@ type AsyncBuilder with /// a standard .NET task which does not commpute a value member x.Bind(t : Task, f : unit -> Async<'R>) : Async<'R> = async.Bind(Async.AwaitTask t, f) - member x.ReturnFrom(t : Task<'T>) = Async.AwaitTask t \ No newline at end of file + member x.ReturnFrom(t : Task<'T>) = Async.AwaitTask t diff --git a/src/MyWebLog.Data.RethinkDB/Page.fs b/src/MyWebLog.Data.RethinkDB/Page.fs index 93874cd..8cb0fb2 100644 --- a/src/MyWebLog.Data.RethinkDB/Page.fs +++ b/src/MyWebLog.Data.RethinkDB/Page.fs @@ -7,39 +7,55 @@ let private r = RethinkDb.Driver.RethinkDB.R /// Try to find a page by its Id, optionally including revisions let tryFindPageById conn webLogId (pageId : string) includeRevs = - let pg = r.Table(Table.Page) - .Get(pageId) - match includeRevs with true -> pg.RunResultAsync(conn) | _ -> pg.Without("Revisions").RunResultAsync(conn) - |> await - |> box - |> function - | null -> None - | page -> let pg : Page = unbox page - match pg.WebLogId = webLogId with true -> Some pg | _ -> None + async { + let q = + r.Table(Table.Page) + .Get pageId + let! thePage = + match includeRevs with + | true -> q.RunResultAsync conn + | _ -> q.Without("Revisions").RunResultAsync conn + 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 let tryFindPageByPermalink conn (webLogId : string) (permalink : string) = - r.Table(Table.Page) - .GetAll(r.Array(webLogId, permalink)).OptArg("index", "Permalink") - .Without("Revisions") - .RunResultAsync(conn) - |> await - |> List.tryHead + async { + let! pg = + r.Table(Table.Page) + .GetAll(r.Array (webLogId, permalink)).OptArg("index", "Permalink") + .Without("Revisions") + .RunResultAsync conn + return List.tryHead pg + } + |> Async.RunSynchronously /// Get a list of all pages (excludes page text and revisions) let findAllPages conn (webLogId : string) = - r.Table(Table.Page) - .GetAll(webLogId).OptArg("index", "WebLogId") - .OrderBy("Title") - .Without("Text", "Revisions") - .RunResultAsync(conn) - |> await + async { + return! + r.Table(Table.Page) + .GetAll(webLogId).OptArg("index", "WebLogId") + .OrderBy("Title") + .Without("Text", "Revisions") + .RunResultAsync conn + } + |> Async.RunSynchronously /// Add a page let addPage conn (page : Page) = - r.Table(Table.Page) - .Insert(page) - .RunResultAsync(conn) |> await |> ignore + async { + do! r.Table(Table.Page) + .Insert(page) + .RunResultAsync conn + } + |> (Async.RunSynchronously >> ignore) type PageUpdateRecord = { Title : string @@ -51,22 +67,30 @@ type PageUpdateRecord = /// Update a page let updatePage conn (page : Page) = match tryFindPageById conn page.WebLogId page.Id false with - | Some _ -> r.Table(Table.Page) - .Get(page.Id) - .Update({ PageUpdateRecord.Title = page.Title - Permalink = page.Permalink - PublishedOn = page.PublishedOn - UpdatedOn = page.UpdatedOn - Text = page.Text - Revisions = page.Revisions }) - .RunResultAsync(conn) |> await |> ignore + | Some _ -> + async { + do! r.Table(Table.Page) + .Get(page.Id) + .Update({ PageUpdateRecord.Title = page.Title + Permalink = page.Permalink + PublishedOn = page.PublishedOn + UpdatedOn = page.UpdatedOn + Text = page.Text + Revisions = page.Revisions }) + .RunResultAsync conn + } + |> (Async.RunSynchronously >> ignore) | _ -> () /// Delete a page let deletePage conn webLogId pageId = match tryFindPageById conn webLogId pageId false with - | Some _ -> r.Table(Table.Page) - .Get(pageId) - .Delete() - .RunResultAsync(conn) |> await |> ignore + | Some _ -> + async { + do! r.Table(Table.Page) + .Get(pageId) + .Delete() + .RunResultAsync conn + } + |> (Async.RunSynchronously >> ignore) | _ -> () diff --git a/src/MyWebLog.Data.RethinkDB/Post.fs b/src/MyWebLog.Data.RethinkDB/Post.fs index e2c60b2..f7da374 100644 --- a/src/MyWebLog.Data.RethinkDB/Post.fs +++ b/src/MyWebLog.Data.RethinkDB/Post.fs @@ -8,25 +8,31 @@ let private r = RethinkDb.Driver.RethinkDB.R /// Shorthand to select all published posts for a web log let private publishedPosts (webLogId : string)= 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 let private toPostList conn pageNbr nbrPerPage (filter : ReqlExpr) = - filter - .OrderBy(r.Desc("PublishedOn")) - .Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage) - .RunResultAsync(conn) - |> await + async { + return! + filter + .OrderBy(r.Desc "PublishedOn") + .Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage) + .RunResultAsync conn + } + |> Async.RunSynchronously /// Shorthand to get a newer or older post let private adjacentPost conn (post : Post) (theFilter : ReqlExpr -> obj) (sort : obj) = - (publishedPosts post.WebLogId) - .Filter(theFilter) - .OrderBy(sort) - .Limit(1) - .RunResultAsync(conn) - |> await - |> List.tryHead + async { + let! post = + (publishedPosts post.WebLogId) + .Filter(theFilter) + .OrderBy(sort) + .Limit(1) + .RunResultAsync conn + return List.tryHead post + } + |> Async.RunSynchronously /// Find a newer post 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 let findPageOfCategorizedPosts conn webLogId (categoryId : string) pageNbr nbrPerPage = (publishedPosts webLogId) - .Filter(ReqlFunction1(fun p -> upcast p.["CategoryIds"].Contains(categoryId))) + .Filter(ReqlFunction1 (fun p -> upcast p.["CategoryIds"].Contains categoryId)) |> toPostList conn pageNbr nbrPerPage /// Get a page of published posts tagged with a given tag let findPageOfTaggedPosts conn webLogId (tag : string) pageNbr nbrPerPage = (publishedPosts webLogId) - .Filter(ReqlFunction1(fun p -> upcast p.["Tags"].Contains(tag))) + .Filter(ReqlFunction1 (fun p -> upcast p.["Tags"].Contains tag)) |> toPostList conn pageNbr nbrPerPage /// 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 let tryFindNewerCategorizedPost conn (categoryId : string) post = 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 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 -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 let tryFindOlderCategorizedPost conn (categoryId : string) post = 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 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 let findPageOfAllPosts conn (webLogId : string) pageNbr nbrPerPage = // FIXME: sort unpublished posts by their last updated date - r.Table(Table.Post) - .GetAll(webLogId).OptArg("index", "WebLogId") - .OrderBy(r.Desc("PublishedOn")) - .Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage) - .RunResultAsync(conn) - |> await + async { + return! + r.Table(Table.Post) + .GetAll(webLogId).OptArg("index", "WebLogId") + .OrderBy(r.Desc "PublishedOn") + .Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage) + .RunResultAsync conn + } + |> Async.RunSynchronously /// Try to find a post by its Id and web log Id let tryFindPost conn webLogId postId : Post option = - r.Table(Table.Post) - .Get(postId) - .Filter(ReqlFunction1(fun p -> upcast p.["WebLogId"].Eq(webLogId))) - .RunResultAsync(conn) - |> await - |> box - |> function null -> None | post -> Some <| unbox post + async { + let! p = + r.Table(Table.Post) + .Get(postId) + .Filter(ReqlFunction1 (fun p -> upcast p.["WebLogId"].Eq webLogId)) + .RunResultAsync conn + return match box p with null -> None | post -> Some <| unbox post + } + |> Async.RunSynchronously /// Try to find a post by its permalink let tryFindPostByPermalink conn webLogId permalink = - r.Table(Table.Post) - .GetAll(r.Array(webLogId, permalink)).OptArg("index", "Permalink") - .Filter(ReqlFunction1(fun p -> upcast p.["Status"].Eq(PostStatus.Published))) - .Without("Revisions") - .Merge(ReqlFunction1(fun p -> - upcast r.HashMap("Categories", r.Table(Table.Category) - .GetAll(p.["CategoryIds"]) - .Without("Children") - .OrderBy("Name") - .CoerceTo("array")))) - .Merge(ReqlFunction1(fun p -> - upcast r.HashMap("Comments", r.Table(Table.Comment) - .GetAll(p.["id"]).OptArg("index", "PostId") - .OrderBy("PostedOn") - .CoerceTo("array")))) - .RunResultAsync(conn) - |> await - |> List.tryHead + async { + let! post = + r.Table(Table.Post) + .GetAll(r.Array (webLogId, permalink)).OptArg("index", "Permalink") + .Filter(ReqlFunction1 (fun p -> upcast p.["Status"].Eq PostStatus.Published)) + .Without("Revisions") + .Merge(ReqlFunction1 (fun p -> + upcast r.HashMap( + "Categories", r.Table(Table.Category) + .GetAll(r.Args p.["CategoryIds"]) + .Without("Children") + .OrderBy("Name") + .CoerceTo("array")).With( + "Comments", r.Table(Table.Comment) + .GetAll(p.["id"]).OptArg("index", "PostId") + .OrderBy("PostedOn") + .CoerceTo("array")))) + .RunResultAsync conn + return List.tryHead post + } + |> Async.RunSynchronously /// Try to find a post by its prior permalink let tryFindPostByPriorPermalink conn (webLogId : string) (permalink : string) = - r.Table(Table.Post) - .GetAll(webLogId).OptArg("index", "WebLogId") - .Filter(ReqlFunction1(fun p -> - upcast p.["PriorPermalinks"].Contains(permalink).And(p.["Status"].Eq(PostStatus.Published)))) - .Without("Revisions") - .RunResultAsync(conn) - |> await - |> List.tryHead + async { + let! post = + r.Table(Table.Post) + .GetAll(webLogId).OptArg("index", "WebLogId") + .Filter(ReqlFunction1 (fun p -> + upcast p.["PriorPermalinks"].Contains(permalink).And(p.["Status"].Eq PostStatus.Published))) + .Without("Revisions") + .RunResultAsync conn + return List.tryHead post + } + |> Async.RunSynchronously /// Get a set of posts for RSS let findFeedPosts conn webLogId nbr : (Post * User option) list = + let tryFindUser userId = + async { + let! u = + r.Table(Table.User) + .Get(userId) + .RunAtomAsync conn + return match box u with null -> None | user -> Some <| unbox user + } + |> Async.RunSynchronously (publishedPosts webLogId) - .Merge(ReqlFunction1(fun post -> - upcast r.HashMap("Categories", r.Table(Table.Category) - .GetAll(post.["CategoryIds"]) - .OrderBy("Name") - .Pluck("id", "Name") - .CoerceTo("array")))) + .Merge(ReqlFunction1 (fun post -> + upcast r.HashMap( + "Categories", r.Table(Table.Category) + .GetAll(r.Args post.["CategoryIds"]) + .OrderBy("Name") + .Pluck("id", "Name") + .CoerceTo("array")))) |> toPostList conn 1 nbr - |> List.map (fun post -> post, r.Table(Table.User) - .Get(post.AuthorId) - .RunAtomAsync(conn) - |> await - |> box - |> function null -> None | user -> Some <| unbox user) + |> List.map (fun post -> post, tryFindUser post.AuthorId) /// Add a post let addPost conn post = - r.Table(Table.Post) - .Insert(post) - .RunResultAsync(conn) - |> await - |> ignore + async { + do! r.Table(Table.Post) + .Insert(post) + .RunResultAsync conn + } + |> (Async.RunSynchronously >> ignore) /// Update a post let updatePost conn (post : Post) = - r.Table(Table.Post) - .Get(post.Id) - .Replace( { post with Categories = [] - Comments = [] } ) - .RunResultAsync(conn) - |> await - |> ignore + async { + do! r.Table(Table.Post) + .Get(post.Id) + .Replace( { post with Categories = [] + Comments = [] } ) + .RunResultAsync conn + } + |> (Async.RunSynchronously >> ignore) /// Save a post let savePost conn (post : Post) = match post.Id with - | "new" -> let newPost = { post with Id = string <| System.Guid.NewGuid() } - r.Table(Table.Post) - .Insert(newPost) - .RunResultAsync(conn) - |> await - |> ignore - newPost.Id - | _ -> r.Table(Table.Post) - .Get(post.Id) - .Replace( { post with Categories = [] - Comments = [] } ) - .RunResultAsync(conn) - |> await - |> ignore - post.Id + | "new" -> + let newPost = { post with Id = string <| System.Guid.NewGuid() } + async { + do! r.Table(Table.Post) + .Insert(newPost) + .RunResultAsync conn + } + |> Async.RunSynchronously + newPost.Id + | _ -> + async { + do! r.Table(Table.Post) + .Get(post.Id) + .Replace( { post with Categories = [] + Comments = [] } ) + .RunResultAsync conn + } + |> Async.RunSynchronously + post.Id diff --git a/src/MyWebLog.Data.RethinkDB/WebLog.fs b/src/MyWebLog.Data.RethinkDB/WebLog.fs index 34a29c0..6d236f8 100644 --- a/src/MyWebLog.Data.RethinkDB/WebLog.fs +++ b/src/MyWebLog.Data.RethinkDB/WebLog.fs @@ -11,13 +11,14 @@ let tryFindWebLogByUrlBase conn (urlBase : string) = let! cursor = r.Table(Table.WebLog) .GetAll(urlBase).OptArg("index", "UrlBase") - .Merge(ReqlFunction1(fun w -> - upcast r.HashMap("PageList", r.Table(Table.Page) - .GetAll(w.G("id")).OptArg("index", "WebLogId") - .Filter(ReqlFunction1(fun pg -> upcast pg.["ShowInPageList"].Eq(true))) - .OrderBy("Title") - .Pluck("Title", "Permalink") - .CoerceTo("array")))) + .Merge(ReqlFunction1 (fun w -> + upcast r.HashMap( + "PageList", r.Table(Table.Page) + .GetAll(w.G("id")).OptArg("index", "WebLogId") + .Filter(ReqlFunction1 (fun pg -> upcast pg.["ShowInPageList"].Eq true)) + .OrderBy("Title") + .Pluck("Title", "Permalink") + .CoerceTo("array")))) .RunCursorAsync conn return cursor |> Seq.tryHead } @@ -27,9 +28,11 @@ let tryFindWebLogByUrlBase conn (urlBase : string) = let findDashboardCounts conn (webLogId : string) = async { return! - r.Expr( r.HashMap("Pages", r.Table(Table.Page ).GetAll(webLogId).OptArg("index", "WebLogId").Count())) - .Merge(r.HashMap("Posts", r.Table(Table.Post ).GetAll(webLogId).OptArg("index", "WebLogId").Count())) - .Merge(r.HashMap("Categories", r.Table(Table.Category).GetAll(webLogId).OptArg("index", "WebLogId").Count())) + r.Expr( + r.HashMap( + "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 conn } |> Async.RunSynchronously