This is for someone else. I’m including some comments for explanation, but the target reader(s) will understand. Sorry, folks, it was more readable than email.
from Entries controller – gets appropriate entries, plus start and end date.
1: class EntriesController < ApplicationController
2: before_filter :login_required
3:
4: def index
5:
6: @week_num = 0
7: if !params[:week].blank?
8: @week_num = params[:week].to_i
9: end
10:
11: @entries, @begin_date, @end_date = current_user.entries.find_for_week @week_num
12:
13: respond_to do |format|
14: format.html
15: format.js {render :layout => false, :partial => "list", :locals => { :entries => @entries, :begin_date => @begin_date, :end_date => @end_date }}
16: end
17: end
18:
19: ...
20:
21: end
Entry model method – does the dirty work of getting the records. returns records plus dates.
1: class Entry < ActiveRecord::Base
2: belongs_to :user
3:
4: def self.find_for_week(num)
5: # finds weeks in reverse order. 0 is current week. 1 is last week, 2 is 2 weeks ago. etc.
6: #assume monday is first day of week
7: t_now = Time.now - (7*24*60*60 * num)
8: t_wday = t_now.wday==0 ? 6 : t_now.wday-1
9: t_mon_begin = t_now - (t_wday)*24*60*60 - t_now.hour*60*60 - t_now.min*60 - t_now.sec
10: t_sun_end = t_mon_begin + 7*24*60*60 - 1
11: entries = find_all_by_on_date(t_mon_begin..t_sun_end, :order => 'on_date DESC') # ..finds all within range.
12: return entries, t_mon_begin, t_sun_end
13: end
14:
15: end
index.html.erb – renders the header, new link, and partial with list. update div is for the ajax call.
1:
2: <h2>Time Entries</h2>
3: <p><%= link_to 'new', new_entry_path, :class=>'applink', :id => 'new' %></p>
4:
5: <div id="update">
6: <%= render :partial => "list", :locals => { :entries => @entries } %>
7: </div>
_list.html.erb – renders links for ajax navigation, and list of records.
1: <% if @week_num > 0 %>
2: <%= link_to 'Next Week', '/entries?week=' + (@week_num.to_i - 1).to_s, :class => "get"%>
3: <% end %>
4: <%= link_to 'Prev Week', '/entries?week=' + (@week_num.to_i + 1).to_s, :class => "get"%>
5: <input type='hidden' value='<%= @week_num.to_i + 1 %>' id='weeknumber' />
6:
7: <% if flash[:notice] or flash[:message] or flash[:error] %>
8: <div id="flash" class="flash_html">
9: <%= flash[:notice] unless flash[:notice].blank?%>
10: <%= flash[:error] unless flash[:error].blank?%>
11: <%= flash[:message] unless flash[:message].blank?%>
12: </div>
13: <% end %>
14: <br/>
15:
16: <p> For week starting on: <%= @begin_date.strftime('%a, %b %d %Y') %></p>
17: <table cellpadding='3px' cellspacing='3px' border='0px'>
18: <tr>
19: <th width="30px"></th>
20: <th width="100px">Action</th>
21: <th width="100px">Date</th>
22: <th width="60px">Hours</th>
23: <th width="150px">Project Code</th>
24: <th width="200px">Description</th>
25: </tr>
26: <%= render :partial => "entry_row", :collection => @entries %>
27: </table>
_entry_row.html.erb – renders the actual row data
1: <tr>
2: <td><%= entry_row_counter+1 %></td>
3: <td><%= link_to 'edit', edit_entry_path(entry_row) %> | <%= link_to("delete", {:action => "destroy", :id => entry_row}, :confirm => "Are you sure you want to delete this entry?", :method => :delete) %></td>
4: <td><%=h entry_row.on_date.strftime('%b %d %Y') %></td>
5: <td><%=h entry_row.hours %></td>
6: <td><%=h entry_row.code %></td>
7: <td><%=h entry_row.description %></td>
8: </tr>
from application.js – jquery javascript used for ajax call, updating html, and setting click method on certain links
1: jQuery.fn.getWithAjax = function() {
2: this.unbind('click', false);
3: this.click(function() {
4: //$.get($(this).attr("href"), $(this).serialize(), null, "script");
5: $.ajax({
6: url: $(this).attr("href"),
7: type: 'get',
8: dataType: 'html',
9: success: function(msg){
10: $('#update').html(msg);
11: ajaxLinks();
12: }
13: });
14: return false;
15: })
16: return this;
17: };
18:
19: //This will "ajaxify" the links
20: function ajaxLinks(){
21: $('.ajaxForm').submitWithAjax();
22: $('a.get').getWithAjax();
23: $('a.post').postWithAjax();
24: $('a.put').putWithAjax();
25: $('a.delete').deleteWithAjax();
26: }
27:
28: $(document).ready(function() {
29: //default click event for all links
30: $('#applink').unbind('click', false);
31: $('#applink').click(function() {
32: self.location = $(this).attr("href");
33: return false;
34: })
35:
36: $.ajaxSettings.accepts.html = $.ajaxSettings.accepts.script;
37:
38: $(document).ajaxSend(function(event, request, settings) {
39: if (typeof(window.AUTH_TOKEN) == "undefined") return;
40: // <acronym title="Internet Explorer 6">IE6</acronym> fix for http://dev.jquery.com/ticket/3155
41: if (settings.type == 'GET' || settings.type == 'get') return;
42: settings.data = settings.data || "";
43: settings.data += (settings.data ? "&" : "") + "authenticity_token=" + encodeURIComponent(window.AUTH_TOKEN);
44: });
45:
46: $(document).ajaxSend(function(event, request, settings) {
47: if ( settings.type == 'post' ) {
48: settings.data = (settings.data ? settings.data + "&" : "")
49: + "authenticity_token=" + encodeURIComponent( AUTH_TOKEN );
50: }
51: });
52: ajaxLinks();
53:
54: $(document).bind('keyup', 'Ctrl+l', function(){ self.location = "/login"; } );
55: $(document).bind('keyup', 'Ctrl+r', function(){ self.location = "/register"; } );
56: $(document).bind('keyup', 'Ctrl+e', function(){ self.location = "/entries"; } );
57: $(document).bind('keyup', 'Ctrl+o', function(){ self.location = "/logout"; } );
58: $(document).bind('keyup', 'Ctrl+n', function(){
59: //alert($('a#new').length);
60: if($('a#new').length) {
61: //$('a.new').trigger('click');
62: $('a#new').click();
63: }
64: } );
65: });
screenshot – looks like this because a good css and design haven’t been added. this is after navigating back a couple weeks; notice the url shows it wasn’t just navigating pages.

I started thinking, again, about writing my own blog engine ever since Rob Conery threw out the challenge. He’s followed through on that himself too. My excuses are pretty simple and real: Don’t have the time and have too much other non-paid work already. Still, I keep thinking about it because I’m not completely satisfied with blogengine.net. It’s a fine blog engine, but it’s doesn’t integrate well with the rest of this site. Also, I’d like to see something handled differently; more that I could do by contributing to the project.
The thing is, it’s not really that hard to build a simple blog engine. As with anything, I’m sure it takes a little more to make it really good, but it’s just not that hard.
Think about it.. what entities do you need assuming this is database driven? Users, Posts, Comments, Categories, and some settings. You’d probably want a built in post editor; there’s also the fairly simple Metaweblog api which would let you support Windows Live Writer. I’d suggest supporting future posts; that’s just checking the publish date before showing a post. Support saving a post as a draft is simple too. It only gets more complicated with extra features like full-text searching, or roles for multiple authors.
So if someone just started a project, used FluentMigrator to manage the schema, and Fluent-NHibernate to wire up the models.. well.. you’re a fair ways to creating your own blog engine. Oh, and use Free Text Box for the built-in html editor to make it easy. I’m not necessarily going to do this; just thinking out loud.
Update
So I got the csinc site switched over to using v3 api instead of the v2 CloudDB strongly typed service. Generally, I like it because it allows greater flexibility. That could have been true for v2 api, but I haven’t tried it so I can’t say. The CloudDB hasn’t put together the docs for this yet, so I’ll show you some code to use it below.
Why use it
First, it keeps the website portable. It doesn’t matter where it’s hosted because the datastore doesn’t change. Second, We can tweak content and even some behavior/layout stuff easily by making the change using the clouddb website.
Implementation
I’m not entirely settled with our implementation so far. How we read columns and set object properties needs work. Also, I re-factored the base to pass in a context object basically just to pass around a pointer to the IWindsorContainer instance so it would work in the web app and in the test project. It’s one of those quick hacks which you’re not quite happy with but don’t really have another good idea at the time. I just wanted my tests to pass.
Getting started
First you have to get an invite, and then set up a clouddb account. Once done, add the clouddb api url as a webreference and begin coding. Start with the login code:
1: public static Token Login(string user, string password)
2: {
3: using (CloudDB cdb = new CloudDB())
4: {
5: return Login(cdb, user, password);
6: }
7: }
8:
9: public static Token Login(CloudDB cdb, string user, string password)
10: {
11: Token tok = cdb.LogIn(user, password).Result;
12: return tok;
13: }
Using an instance of CloudDB and the Token, you call whatever api method you need. Our site only uses reads so far. So here’s how we get a list of rows:
1: public static RowsResponseData GetRowData(CloudDB cdb, Token token, string appName, string entityName, Predicate[] predicates)
2: {
3: App app = cdb.GetApplicationByName(token, token.UserID, appName, false, false).Result;
4: Entity e = cdb.GetEntityByName(token, app, entityName, false).Result;
5: RowsResponseData rowData = cdb.SearchEntity(token, app, e, predicates, 0, 0, 0, 0).Results;
6: return rowData;
7: }
In the above code, the Predicate array is used to specify which rows to get back from clouddb. That’s how we get a single match if we want. Just query for the id or other unique criteria. Here’s how we handle that in our record base class:
1: public static T GetRecord(IAppContext context, string cacheKey, string entityName, Predicate[] predicates)
2: {
3: CachedItem ci = DataCache.GetItem(cacheKey);
4: if (ci != null) return (T)ci.Data;
5:
6: try
7: {
8: ICloudConnectionSettings connectionSettings = context.Container.Resolve<ICloudConnectionSettings>();
9: RowsResponseData rowData = CloudDBDataAccess.GetRowData(connectionSettings.CloudDBApplicationName, entityName, connectionSettings.CloudDBUser, connectionSettings.CloudDBPassword, predicates);
10: T item = new T();
11: item.Init(rowData.Data, 0);
12: DataCache.Add(cacheKey, item);
13: return item;
14: }
15: catch (Exception er)
16: {
17: T item = default(T);
18: DataCache.Add(cacheKey, item);
19: return item;
20: }
21: }
You’ll notice that caching is also handled at that layer so the specific model doesn’t have to know or think about it. (Yes, there are things I can do to improve this. Remember, time for our own site isn’t a high priority versus time for clients.)
..and a sample usage:
1: protected bool ShowDynamicContent()
2: {
3: return ShowDynamicContent(Request.FilePath.ToLower());
4: }
5:
6: protected bool ShowDynamicContent(string contentName)
7: {
8: var pageContent = PageContent.GetByPath(new AppContext(), contentName);
9: if (pageContent != null && pageContent.IsPublished)
10: {
11: PropertyBag["contentTitle"] = pageContent.Title;
12: PropertyBag["contentDescription"] = pageContent.Description;
13: PropertyBag["content"] = pageContent.Content;
14: RenderView("../shared/contentview");
15: return true;
16: }
17: return false;
18: }
1: public void Index()
2: {
3: //show dynamic content if available, defaults to view file content if not
4: ShowDynamicContent();
5: }
Good to know
Clouddb data is described with Entity objects representing the table, Column objects describing the columns for data, and Row objects containing the data. Think of it a little like a DataSet in .Net. You’ll need to know a little about the columns in the data, and then read the values from the Rows array. Also, you’'ll need to know the application name, which is the specific clouddb database in your account.
That’s a simple overview. Hopefully it helps. There is more to it, but that should get you started.

OOOPPSS! I was a little sloppy in my implementation of CloubDB, and got busted. I wasn’t handling connection errors when fetching site content, and the connection went down last night. So over night visitors were greeted by a nice, pretty .Net exception as the content on pages.
So I was greeted by email alerts about the site being down when I got up this morning. Good to know that works; and it was a 5 minute fix. The lesson is: don’t cut corners even when you think it doesn’t matter. There was actually had a big spike in site traffic over night.