Dynamic ASP.NET MVC - Part 7, Oak Projections in Massive
- TOC
- Part 1, Setting Up Your Environment
- Part 2, Creating a Better "Compiler"
- Part 3, Your First Failing Test
- Part 4, Dynamic Data Access
- Part 5, Dynamic Data to Views
- Part 6, Mixing in Behavior
- Part 7, Oak Projections in Massive
So far we've got blog titles showing up on the front page and we've added the ability to create new blog posts. Let's enhance the front page a little bit. Instead of just showing the title, we'll add the title and a summary of the blog posts on the front page (the summary being the first 50 characters of the body).
Extending the Blog Model to Incorporate Summary
We could just do a trim of the body inside of razor, but that's not really reusable. So we are going to add the property to Blog:
- fire up specwatchr
- add a folder under the test project called Models
- in the test project under Models, create a class called describe_Blog.cs and Save All
- add the following specification to force the implementation of the Summary property:
//test class for blog class describe_Blog : nspec { //member variable to store the blog dynamic blog; //summary field that will get inspected during an assertion string summary; void describe_summary() { //set the summary property of the blog act = () => summary = blog.Summary; //given the blog's body is null context["body is null"] = () => { before = () => GivenBlog(withBody: null); //the summary should be empty string it["summary returns empty string"] = () => summary.should_be(""); }; } //helper method that creates a new blog with body void GivenBlog(string withBody) { blog = new Blog(new { Body = withBody }); } } - here is the implementation to make the test pass:
public class Blog : Gemini { public Blog(object valueType) : base(valueType) { } public bool IsValid() { return !string.IsNullOrEmpty(Expando.Title); } public string Validate() { if(!IsValid()) { return "Title Required."; } return ""; } //property is added to Blog and returns empty string. public string Summary { get { return ""; } } } -
these were the next two tests I created for summary:
context["body is under 50 characters"] = () => { before = () => GivenBlog(withBody: "Blog under 50 characters"); it["returns body verbatim"] = () => summary.should_be("Blog under 50 characters"); }; context["body over 50 characters"] = () => { before = () => GivenBlog(withBody: "01234567890123456789012345678901234567890123456789OVER50"); it["returns the first 50 characters"] = () => summary.should_be( "01234567890123456789012345678901234567890123456789"); }; -
and the implementation that makes the tests go green:
public string Summary { get { if (Expando.Body == null) return ""; if (Expando.Body.Length > 50) return Expando.Body.Substring(0, 50); return Expando.Body; } }
Projections
Now that Blog has the concept of Summary, we need to ensure that all the queries from the Massive return Blog. Inside of oak, Massive has been changed to incorporate the concept of "Projections". To demonstrate:
- Here is the failing test I specified in describe_HomeController.cs:
void navigating_to_the_home_page() { act = () => { actionResult = homeController.Index(); }; context["given a blog with title 'Hello'"] = () => { //change the before to include a body before = () => new { Title = "Hello", Body = "Lorem Ipsum" }.InsertInto("Blogs"); it["gives blogs containing a blog named 'Hello'"] = () => { var first = (actionResult.ViewBag.Blogs as IEnumerable<dynamic>).First(); (first.Title as string).should_be("Hello"); }; //add a specification stating that the blog has a summary it["provides summary for blog"] = () => { var first = (actionResult.ViewBag.Blogs as IEnumerable<dynamic>).First(); (first.Summary as string).should_be("Lorem Ipsum"); }; }; } -
and here is the Projection that makes the test go green:
public class Blogs : DynamicRepository { public Blogs() { //provide a Func<dynamic, dyanmic> //that will get applied to all of the Massive //methods Projection = (d) => new Blog(d); } }
By providing this Func<dynamic, dyanmic>, all queries in Massive will return the projection defined in the constructor.
Changing the View to Include the Summary
Now that we have added the Oak Projection. We can show the Summary in the view. We'll (of course), start with the SeedController:
- update the sample entries method to incorporate blog body:
[HttpPost] public ActionResult SampleEntries() { var blogPosts = new[] { new { Title = "My First Blog Post", Body = "First Body" }, new { Title = "Another Blog Post", Body = "Another Body" } , new { Title = "Sample", Body = "Sample Body" } , new { Title = "Yet Another One", Body = "Yet Another Body" } }; foreach (var blog in blogPosts) { new { Title = blog.Title, Body = blog.Body }.InsertInto("Blogs"); } return new EmptyResult(); } - now we can update the view (run rake sample to insert the sample entries):
@{ ViewBag.Title = "Blogs"; } <h2>Blogs</h2> @foreach (var blog in ViewBag.Blogs) { @blog.Title<br /> @blog.Summary<br /> <hr /> }(to view changes, run rake sample in a new command window)
Last Dialog With Myself
Why isn't the Projection concept in Massive?
Dunno. But the beauty of open source and NuGet is the ability to bring source into your solution and do what you deem necessary to make things work for your application. All the projects (oak, specwatchr, rake-dot-net, nspec, and massive) are on git hub.
There is so much open source stuff in this project, is it safe to rely on so many external dependencies that aren't provided to me by Microsoft. What if these things change? Open source software varies in quality and support. How do I know they wont make a breaking change in future versions.
I can appreciate this sentiment. The open source libraries are all under permissive licenses (it's okay to use for commercial purposes). Hate to say this, but at some point in time, the open source libraries will evolve and will break current implementations (the .Net framework has had breaking changes too by the way). But. That's. Why. We. Write. Tests. It allows us to be receptive to change. So when libraries change, just try incorporating the changes into your project. If it doesn't break any of your tests, then you're gold.
Taking a test driven approach won't catch all bugs.
You're right. And for those instances, you'll get the yellow screen of death. It's okay :-) Just write the failing test, make it pass, and that same bug will never show up again.
There is still a lot of dynamic stuff in this solution. What if something changes. The test can't catch everything.
Try it out. Change something in this solution and see if you get a failing test. One of my colleagues at Improving Enterprises named Greg Vaughn made a point that really resonated with me: The definition of legacy code is a code base you are afraid to change (regardless of whether it has tests or not). If you don't feel confident about making changes in your source code (statically typed or dynamic...tests or no tests), then you've got some larger problems to solve. If dynamic still feels like too much, a lot of the concepts discussed in this series can still be applied to your existing solutions. Wouldn't you agree?
NSpec is freaking awesome, it provides a great way to incrementally define specifications and context.
I know! Thanks to Matt Florence for creating such a great testing framework.
Parting Thoughts
I think this picture says it all:
"We accept the reality of the world with which we are presented." The Truman Show (1998) iwdrm.tumblr.com
Always challenge what you have been taught or told. And more importantly, always have a humble and open mind to new ways of developing software.
Written: 6/27/2011