Hugh Howey

February 7, 2014 11:11 am

Howey_SAND_OMNIBUS_EbookEdition-600Hugh Howey is an author and he's a fantastic writer.  But he's not your typical author.  Hugh Howey is on the forefront of the future of publishing.  His website gives a status report of the progress he's making on his current projects.  And he engages directly with is fans (I've actually emailed him a few times to report typos I've seen and he's responded each time).

But those are just niceties that any author could be doing.  What sets Howey apart is his understanding that technology has changed the game.  eReaders, on-demand printing, and the Internet overall with easy access to self-run websites and easy-to-use payment systems have changed everything (Amazon is a big player in this new ecosystem).  And not just his understanding about this changing world but his embracing it and pushing it forward.  He's showing everyone else how to be an author in the 21st century.

I'm a big fan of Howey's.  The first reason is that he's just a fantastic writer.  When I read his work my eyes just glide over the words.  His writing isn't necessarily simple, but it has fantastic flow (his blog post about vocabulary, rhythm, and plot elucidates the point).  It's rare that I stumble over a sentence and have to back up and try again when reading his work.  It's incredible and it makes you want to just keep reading because you barely notice that you are, in fact, reading.

I started with Howey by reading The Wool Omnibus based off a recommendation from the GeekDad blog.  I was hooked.  I needed more.  I quickly devoured the Shift stories that act as a prequel to Wool.  And then I anxiously waited for the conclusion, Dust.  Since then I've breezed through Half Way Home and was just in time to catch the release of the Sand Omnibus.  And I still want more.  (Word of warning: Howey's characters due use expletives on a not-irregular basis, in case that bothers you.)

But he's more than just a fantastic author.  As I said, he sees where publishing is going.

Howey refuses to cripple his eBook works with DRM.  He understands how DRM hurts the very people that support him.  I really appreciate this.

He refuses to sell his soul to a publishing company.  While he now has made deals with publishing companies it's always on his terms (something they told him repeatedly was never going to happen, until it did; they caved, he won).

He refuses to gouge his customers, because to him we're not customers, were fans.  And you need to treat your fans right.  His works are always made available at reasonable prices right out of the gate.  The cost of seeing if you like Howey's writing is so small that you don't even have to think about it.  No one likes dropping $20 on a book and not liking it.  Libraries are great for mitigating this issue, but $0.99 for the first slice of a story is great too.  In fact, right now, you can pick up Wool Part 1 for FREE, that's right, $0.00 for the eBook.  Even if you don't have an eReader you can read it on your computer, or your phone, or your tablet to see if you like it.  Then you can buy the physical book if you want or pick up the rest of the parts in eBook form.

He refuses to restrict other authors' creativity and interests.  He has given his blessing to other authors to explore the worlds he has created.  This is why if you look on Amazon you will find dozens of fan-fiction stories set in his worlds.  He understands that not only does this not hurt him or his work in any way; it helps his fans build a robust and excited community.  A community that will eagerly fall over itself to buy his next story and see where they can take those worlds.

I anxiously await more stories from Howey.

Your (The Reader's) Privacy

February 6, 2014 4:02 pm

As I mentioned in an earlier post, one of my goals in moving to a self-hosted blogging platform was to protect the privacy of my readers.  WordPress, by default, has a few tendrils that slither out across the Internet and violate that privacy letting any number of 3rd-party websites track your movements across the web.

By default it: loads special fonts from Google's servers; uses the Gravatar service to show an image next to your name if you leave a comment; sends your email address and other data off to a third party to run heuristics as part of the bundled anti-spam tool.

I've excised my installation of these issues.  When you load this blog I do not enable any other services to track your movements.

Instead of using the bundled anti-spam tool, Akismet, which sends your data to a 3rd party, I'm using some less powerful, but locally controlled, methods that include things like a simple checkbox on the comment page to confirm you're not a spam bot.  These more primitive methods of fighting spam would probably not work for a high traffic site (thus high value target), but will probably be just fine for us.

You may have noticed the social media sharing buttons at the top of each post.  Normally this would allow each of those services to track your visit to this site.  But these buttons are a little different than what you're probably used to.

These buttons require two clicks to work.  One click activates the button and connects to the associated social media service (which in turns means that they are now able to track your visit to this site).  A second click does the normally expected behavior for sharing something to that service.  This privacy-protecting method of enabling social media sharing is powered by the Social Share Privacy project.  The project is still a little rough around the edges, but it does work (though it took me longer than I expected to configure).

The final piece that I'm working on is migrating all the old media to my server.  Currently the pictures and videos in old posts are still hosted on Blogger's (a.k.a., Google's) servers.  I'm slowly working through bringing those files to my server so that you don't need to interact with any 3rd party to visit this blog.

Also, if you really like, you're free to visit this site using an encrypted (https) connection.  However, at the moment I'm still using a self-signed certificate (so your browser will warn you that it's not secure, just add an exception).  It's a lie to say it's not secure, what it really means is that I just haven't paid one of the certificate companies to verify my identity.  The connection will still be encrypted as any other.  If you really care I'd be happy to take your phone call and verify that the certificate your browser sees is in fact the one I created.

"Homeland"

10:28 am

I was casually reading this Consumerist story and the use of the word "homeland" was kind of disturbing.  Even after more than 10 years, "homeland" still has a disturbing, big-brothery, propaganda-y, dystopian-ness to it.  Maybe "even after" is the wrong phrase, maybe it is exactly due to how it has been used over the last decade that gives it the same, if not more of an, overtone of unpleasantness, of wrongness, even sinisterness.

The usage was a quote from some unnamed "senior U.S. official" in this form:

While we are not aware of a specific threat to the homeland at this time, this routine communication is an important part of our commitment to making sure we meet that priority...

I just can't read that sentence without "homeland" pricking me deep in my psyche and bothering me deeply.

The whole thing bothers me.  Someone "official" telling everyone that they should be afraid not for any particular reason ("not aware of a specific threat"), but just because they want you to remember you're supposed to be afraid or something.

I dislike what we did to our country after September 11, 2001.  Terrorists killed a few thousand people; tragic--but an absolute pittance compared to car accidents (33,000 per year), heart disease (600,000 per year), and suicide (38,000 per year).  Terrorists killed a few thousand people in 2001, but we've been terrorizing ourselves for over 12 years.  We pushed an agenda of fear and allowed that agenda to either erode or destroy founding ideals of liberty and freedom.

I'm hoping we can begin to change that; but quotes like this one seem to have no purpose other than to continue pushing an agenda of fear-mongering.

I'm hoping we can begin to change that because of articles like this one from Ars Technica discussing that one of the authors of the PATRIOT Act is trying to undo some of the damage he's done and reign in some of these activities (not that he admits this is his fault in any way, of course).  It is just really sad that these people were not listening 10 years ago when others were warning how dangerous these laws would be.

Criteria Aggregator: Dynamic Criteria Queries in Grails

February 5, 2014 2:42 pm

A few years ago I wrote about dynamically building criteria queries in Grails.  Well the use case we had disappeared and I didn't do much of anything with dynamic criteria queries until recently.  As I've mentioned in an update on that post, you probably just want to use DetachedCriteria now instead of taking this route, but DetachedCriteria don't support the same range of functionality that normal criteria queries do.

I ran into this limitation when I needed to use subqueries which DetachedCriteria do not support but standard criteria queries do.  After spending hours trying to work around the limitation and still use DetachedCriteria I decided to give up and create a nice way of using the dynamic criteria queries based on my past experience, the comments on that post, and my greatly enlarged knowledge of Grails.

So I created a nice clean class called "CriteriaAggregator":

package org.example.package
import grails.orm.HibernateCriteriaBuilder

// Aggregate query criteria for a Domain Class
// Example: def qa = new CriteriaAggregator(MyDomainClass)
//    qa.addCriteria { idEq(12345L) }
//     def results = qa.get()
public class CriteriaAggregator {
    private Class forClass
    private List<Closure> criteriaClosures

    // forClass should be a Grails DomainClass; but since Grails injects rather than inherits I can't specify the type better than "Class"
    public CriteriaAggregator(Class forClass) {this.forClass = forClass; criteriaClosures = new ArrayList<Closure>(10)}

    // criteriaClosure is the exact same type of closure you'd pass to DomainClass.withCriteria(criteriaClosure)
    public void addCriteria(Closure criteriaClosure) {criteriaClosures << criteriaClosure}

    public long count() {return runQuery('get') {projections {rowCount()}}}
    public def get(Closure additionalCriteria=null) {return runQuery('get', additionalCriteria)} // Query must return only a single row
    public def list(Closure additionalCriteria=null) {return runQuery('list', additionalCriteria)}

    private def runQuery(String method, Closure additionalCriteria=null) {
        HibernateCriteriaBuilder criteriaBuilder = forClass.createCriteria()
        def critClosures = criteriaClosures // Bizarre that criteriaClosures won't evaluate properly inside the "$method" closure, but it won't so this works around that issue
        criteriaBuilder."$method" {
            critClosures.each{closure -> closure.delegate = criteriaBuilder; closure()}
            if (additionalCriteria) {additionalCriteria.delegate = criteriaBuilder; additionalCriteria()}
        }
    }
}

This wraps and defers the creation of the normal criteria builder allowing you to build up the criteria dynamically and execute it when desired.

Suppose you had a Customer domain class with many Orders.  Orders have a date, paymentMethod, and totalPrice.

def customerQueryAggregator = new CriteriaAggregator(Customer)
customerQueryAggregator.addCriteria {
  orders {
    def now = new Date()
    between('date', now-7, now)
  }
}
customerQueryAggregator.addCriteria {
  orders {
    eq('paymentMethod', 'cash')
  }
}

def numCustomersInPastWeekPayingCash = customerQueryAggregator.count()
println numCustomersInPastWeekPayingCash

def highRollersInPastWeek = customerQueryAggregator.list{
  orders {
    gt('totalPrice', 50000)
  }
}
println highRollersInPastWeek

This is obviously a trivial example where you don't need to use the aggregator.  But it's just to illustrate the usage.  I'm using it to build up a query based on a set of options provided by a caller as part of a reusable and flexible service.

Using this I can handle subqueries like so:

import grails.gorm.DetachedCriteria as GrailsDetachedCriteria
import org.hibernate.criterion.Subqueries
import grails.orm.HibernateCriteriaBuilder

def customerQueryAggregator = new CriteriaAggregator(Customer)

GrailsDetachedCriteria largeOrderSubquery = new GrailsDetachedCriteria(Order).build {
  eqProperty 'order.id', 'this.id'
  gt 'totalPrice', 50000
  projections {property 'id'}
}

customerQueryAggregator.addCriteria {
  add(Subqueries.exists(HibernateCriteriaBuilder.getHibernateDetachedCriteria(largeOrderSubquery)))
}

Update April 4, 2014:

I ended up having some trouble with the proper definition of table aliases (it always wanted to call "order" "this" even though we already had a "this") while handling the GrailsDetachedCriteria subqueries.  To work around it I ended up dropping back to straight Hibernate, but it still works with the CriteriaAggregator.

import org.hibernate.criterion.DetachedCriteria as HibernateDetachedCriteria
import org.hibernate.criterion.Restrictions
import org.hibernate.criterion.Disjunction
import org.hibernate.criterion.Subqueries
import org.hibernate.criterion.Projections

def customerQueryAggregator = new CriteriaAggregator(Customer)

HibnerateDetachedCriteria largeOrderSubquery = new HibernateDetachedCriteria.forClass(Order.class, 'order')
largeOrderSubquery.add(Restrictions.eqProperty('order.id', 'this.id'))
largeOrderSubquery.add(Restrictions.gt('order.totalPrice', 50000))
largeOrderSubquery.setProjection(Projections.property('order.id'))

customerQueryAggregator.addCriteria {
  add(Subqueries.exists(largeOrderSubquery))
}

My Custom Charging Box

February 2, 2014 9:31 pm

I decided to be crafty.  I had an idea for a decent looking box from which to charge gadgets.  I finally found a box I liked (a cigar box, as it turns out).  And then we went off to Jo-Ann Fabrics for other materials (Josh, if you're reading this, I was reminiscing of our Jo-Ann Fabrics trips back in the day).

I was looking in the upholstery fabric section, but not finding anything smaller than about 10 feet long.  I did see some options in the remnants of some suede that would probably have worked.  But then Jess stumbled upon some swatches of leather on the other side of the store which were perfectly sized.  They also had a strap of leather that gave me an additional idea to give it an old strapped trunk sort of look.

You'll quickly see that I'm not exactly a fine craftsman, but it came out well enough and I only threated to give up 3 or 4 times (me and reality don't get along so well; my job has "undo").  It was kind of fun and now it cleans up the mess of charging cords that normally are strewn about the house and kitchen counters.