Ruby-coloured glasses View RSS

Tech blog of a rails contractor.
Hide details



packwerk for enforcing boundaries between components 12 Jan 2023 1:31 PM (2 years ago)

Shopify's packwerk seems to be an interesting gem for enforcing boundaries between your components. Rails is a laissez faire system, which works great up until your codebase gets huge and dependency violations start making it hard to separate out merges. You can inprove that by pulling stuff out into separate components (each with their own area of concern), but it's an honour-system and that means that people will forget, or not know that there's a less-coupled way of grabbing out something they really shouldn't be grabbing directly. Having a gem that scans your code to point out when you're doing that helps you keep those components loosely coupled.

Note: I haven't used it in anger - just thought it looked like a neat solution to a perennial issue.

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Rails Active Record .median 19 May 2015 4:17 PM (9 years ago)

Rails neatly provides an .average method to easily find the average value of a column on your db, but it doesn't provide one for finding the median. Thankfully it's really easily to implement

If you just want to add it to one class, put this into the model:

def self.median(column_name)
  median_index = (count / 2)
  # order by the given column and pluck out the value exactly halfway
  order(column_name).offset(median_index).limit(1).pluck(column_name)[0]
end

If you want it to work across all Active Record models in your db... you could add it as a concern (then include it in every class you want it in) or you could just extend ActiveRecord::Base (so it's available to every Active Record, just like .average).

If you want to do that, add the following to /config/initializers/active_record.rb

class ActiveRecord::Base
  def self.median(column_name)
    median_index = (count / 2)
    # order by the given column and pluck out the value exactly halfway
    order(column_name).offset(median_index).limit(1).pluck(column_name)[0]
  end
end

Note: I've tested it even with chained scopes and it seems to work ok on our project (this counts as a final - ie you can't chain after it)

Note: as pointed out to me, the median of an even number of things is the average of the middle two values... and this version doesn't do that - this is a quick-and-dirty version. Feel free to adapt to be mathematically correct if you need that level of precision

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Link: Flaws In Scrum And Agile 8 Mar 2015 4:57 PM (10 years ago)

In The flaws in Scrum and Agile Giles Bowkett explains why Panda Strike feels that Agile is basically a past-technology. Good in it's day, with some useful lessons we can keep, but largely not relevant to the way that distributed teams work these days.

It's a very interesting read, and I find myself agreeing with it.

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

ActiveAdmin simple_table 22 Feb 2015 3:04 PM (10 years ago)

ActiveAdmin by default will build you a table (using table_for) for a collection of objects. But sometimes you just want to throw up a quick table of random values - eg on a dashboard you want a table where the left=column is a description and the right column is, say sets of counts of various things.

ActiveAdmin uses Arbre under the covers to build html for you This means you can use it to laboriously build an html table using all the tags. But if you just want to throw together a super-simple table, this means too much typing.

So I've written a convenience method that will take an array of values and convert it directly to a table.

Note: it does no checking... so make sure your array has the same number of columns for each row

# put this in config/initializers/active_admin.rb
module ActiveAdmin
  module Views
    class SimpleTable < Arbre::HTML::Table
      builder_method :simple_table

      def build(collection)
        table do
         collection.each do |row|
           tr do
             row.each do |col|
               if col.is_a? Numeric
                 td col, style: "text-align: right;"
               else
                 td col
               end
             end
           end
         end
        end
      end
    end
  end
end


# example usage
simple_table [ [ "User count", User.count ],
               [ "Item count", Item.count ],
               [ "Total Wishes", Wish.count ],
               [ "Available Items", Item.available.count ] ]

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Link: run in with turbolinks 29 Nov 2014 11:24 PM (10 years ago)

A quick link today to an article on turbolinks. It includes a couple of different levels of work-around snippets when adding javascripty functionality to your own links so it's pretty useful.

Rails 4: My First Run-in with Turbolinks

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Generate a subset of pseudo-random permutations in non mega-huge time 23 Nov 2014 5:38 PM (10 years ago)

One of the neat things about sending packages via sendle is that you don't have to print out a label or even know the address of your recipient (this is done so the recipient can keep their address private if they want). Instead, we give the sender a pair of unique code-names to put onto the package. So instead of J Random Shop-assistant at EvilCorp getting your full address, instead you write something like "From: Golden Lion To: Red Tiger".

It's fun, as well as effective - like secret-agent names.

but it does present us with an interesting problem - how do we generate unique codenames for potentially many, many packages.

Codenames are assigned for every package, not just for every person - that way your humble shop assistant at EvilCorp can't get any info on your shopping habits based on your codename cropping up time and again either. So that means we need lots and lots of codenames, and they must be pretty randomised too.

As mentioned, our codenames are basically of the form "<adjective> <noun>", and we have a medium-sized stock of adjectives and nouns to build from.

With 100 adjectives and 50 nouns, that's a total of 5000 possible codephrases... but the codephrase generated could be used as either the "from" or "to" (as long as it's not reused for a while), so choosing a pair of such codephrases from a set of 5000 (without repetition) gives us 5000*4999 (or 24995000) possible permutations.

That's a lot.

It takes up a fair bit of db space, and it would take quite some time to generate all the possibilities and randomise them too.

So we decided not to do that... but instead, to generate a much smaller sub-set, on the fly (ie when we got close to running out).

Which leaves us with a different problem... how to do that effectively.

The solution that was in place before I arrived, was done in SQL (because it's always faster to let the db do it, right?), and involved doing a four-way cross-join on the adjectives+nouns... and then ordering them randomly... and using a limit to only actually keep a set amount... and then only inserting into our db those that weren't already there (done using an insert where select = 0)

Sadly, when performance testing, this was discovered to be exceedingly slow... ie when it was triggered, it would take 15min or so to run, causing all other "create a package" processes to block waiting for a new code to be generated... which was unacceptable. Sure, we could run this overnight instead, but hey, what if a Good Problem occurred and we had so many orders one day that we ran out in between overnight-runs?

So instead I came up with a solution that would allow us to quickly generate a small subset of new, randomised permutations without killing throughput as badly. The solution takes about 4 seconds to generate 2000 new permutations, which is enough to get by until it's needed again.

In order to explain what it is, I first need to explain a few non-solutions we hit upon to begin with and why they didn't work for us

Solution 1: random indexing into the arrays

It seems like such a simple problem - we have a small set of adjectives and a small set of nouns, just randomly pick one of each and see if it's not already one that we have... create it if not. so the first solution was just to use "rand" on the size of each array to pick one of each by index.

This works reasonably well when the data are sparse... but when you start filling in those gaps, a lot of time is spent thrashing about randomly trying to find a pair that hasn't already been chosen. And there was no way of simply saying "you've already picked that pair" so it would pick the same pairs over and over and over and keep trying them against the set that had already been created.

This became obviously ridiculous in the specs where I chose two nouns and two adjectives... and it spent roughly 16min thrashing back and forth between "blue dog" and "red cat" trying to randomly guess the last remaining pair ("red dog").

I didn't want that to happen while angry users were waiting to post their parcel in the middle of the xmas rush... we needed a solution that would guarantee that it would not endlessly repeat useless, already-tried pairs.

Solution 2: random ordering of the arrays

The next obvious idea was: shuffle the two arrays so they are (pseudo)randomly sorted.... then just loop through them grabbing the next one in line to try and find_or_create.

eg, using a simple nested loop like:

   adjectives.shuffle.each do |adj|
      nouns.shuffle.each do |noun|
         Code.find_or_create(:code => "#{adj} #{noun}")
      end
   end

This solution works. It guarantees that you only try each option once, and it's very quick. The downside is that the randomness isn't random enough.

Now lets say that after shuffling, your first adjective is "grey"... you now loop through every noun and attach the adjective "grey" to it... and end up with a run of very similar code-phrases like: "grey dog", "grey cat", "grey bird", "grey elephant", "grey tiger", "grey wolf", "grey hippo"... not really very random. Plus, don't forget I'm using pairs of code-phrases. So we'd be getting pairs of code-phrases such as "from: grey dog to: grey cat"

Now right now, we don't have anything set up where the user could potentially abuse this obviously guessable sequence to get ahold of somebody else's package... however someday we may...

but what we were more concerned about was that if all the packages have code words that are very similar... there is grand scope for confusion and mis-labelling - which has happened to some of our customers before, and causes everyone no end of heartache and pain.

What we want is not only for individual code-phrases to be random, but for the sequence to also be kind of random too...

Solution 3: rolling loops

So I came up with a solution I've called "rolling loops".[*]

Let i and j be the loop counters. Normally, you'd match i against every value of j, before then incrementing i again.... but we want both i and j to change every single iteration of j... but if we increment both i and j for each step of the nested loop, how do we roll on to the next loop?

Well, what we *really* want is to loop through every possible permutation of the two arrays... so how about I expand out some of those and show you how to get to the solution.

Lets suppose you have two arrays that are just the numbers 0..2 The normal way to loop gives you a complete set of permutations:

[[0,0] [0,1] [0,2]] [[1,0] [1,1] [1,2]] [[2,0] [2,1] [2,2]]

If we used these pairs of numbers as indexes into the two arrays of words.. we'd get every single pairing of words, but as you can see, in the first set, every word-pair would begin with the first adjective, and in the second set, every code-pair would begin with the section adjective and so on - just as described as a problem above.

But there's another way of grouping the permutations:

[[0,0] [1,1] [2,2]] [[0,1] [1,2] [2,0]] [[0,2] [1,0] [2,1]]

In the first subset, we have numbers that are the same as each other, in the second set, we have numbers that have a difference of 1 (mod the size of the array), and the third set are of diff 2 (mod size)

This guarantees to roll through every permutation, and for every step in the loop it changes both i and j - it's only the *difference between i and j* that changes over the longer timeframe.

That may seem complex when I describe it, but it's really not. If we draw the permutations as a square:

   0,0  1,1  2,2
   0,1  1,2  2,0
   0,2  1,0  2,1

The usual simple nested loop starts at the top left of the square and moves down the column before hopping onto the next column... but the rolling loops simply goes across the rows first instead.

Now at first glance, this may not seem very much more random... but in practice (with the nouns and adjectives all randomised before beginning) it is much better. It will eventually loop around again and the adjectives will start to come in the same order - but we have around 100 adjectives - and it's extremely rare for us to have one customer send 100 parcels all at once.

Benefits of this solution:

There's only one more tweak that my colleague suggested he said that to create a product of each of the adj/noun arrays is actually relatively quick - so in *our* project the final rolling-loop is only applied to two copies of the full product of all nouns and adjectives (randomised separately)

Here's my implementation which will keep generating pairs of code-phrases until we have the specified number.

   def populate_codes_until(final_count)
    current_count = self.count

    # generate every permutation of nouns and adjs 
    # then shuffle and duplicate
    froms = adjs.product(nouns).shuffle
    tos = froms.shuffle
    
    max_val = froms.size - 1
    
    begin
      # here we are iterating the *step size* 
      0.upto(max_val) do |step_size|
        # here we simply step through both arrays
        0.upto(max_val) do |i|

          # figure out j based on step-size and mod
          j = (i + step_size) % (max_val + 1)

          self.find_or_create_by(:from => froms[i].join(" "), :to => tos[j].join(" ")) do
            current_count += 1 # actually created a new one
          end

          return if current_count == final_count
        end
      end
    end
  end

[*]

yes, it's quite possible that this solution already exists somewhere and has a snazzier, and more widely-accepted, name... but I couldn't find it when I went googling, therefore it didn't exist :)

If you do happen to find it, let me know!

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Link: Fundamental Guidelines Of E-Commerce Checkout Design 19 Nov 2014 10:03 PM (10 years ago)

"Here is the harsh reality of e-commerce websites: according to recent e-commerce studies, at least 59.8% of potential customers abandon their shopping cart (MarketingSherpa puts it at 59.8%, SeeWhy at 83% and MarketLive at 62.14%). The main question is why do customers abandon their shopping cart so often?"

Fundamental Guidelines Of E-Commerce Checkout Design condenses a large study into usability into 11 really useful guidelines to make your checkout experience much more likely to convert.

Here's a condensed, tl;dr version:

But I totally recommend reading the whole thing - it's not that long and chock full of useful insights

1. Your Checkout Process Should Be Completely Linear
Non-linear "steps within steps" confuse people
2. Add Descriptions To Form Field Labels
Just because it's obvious to you what it means, doesn't mean it is to somebody else. be unambiguous
3. Avoid Contextual Words Like “Continue”
continue... shopping? or continue... on to the next step. Also don't use "back" or "proceed"
4. Visually Reinforce All Sensitive Fields On The Payment Page
People are leery of putting their CC details into a page that looks thrown together
5. Don’t Use An “Apply” Button In Your Form
it's often not pressed, even when necessary, or mistaken for the main form-submit. use AJAX instead
6. Format Field For Expiration Date Exactly As It Appears On Credit Card
needs no explanation
7. Use Only One Column For Form Fields
multiple, competing interpretations are present for multi-column pages -> all necessary or alternatives?
8. Use Shipping Address As Billing Address By Default
dammit, make people's lives easier, not more difficult. most people ship and bill to the same address
9. Use Clear Error Indications
you'll just have to see the article to get a plethora of ideas of what not to to here
10. Registration Should Be Optional
yes, a thousand times yes! I don't need to register with your company if I buy something from a bricks+mortar store, why force me to online?
11. Don’t Require Seemingly Unnecessary Information
I don't want to be spammed by you, don't force me to give you fake data it'll only hurt both of us

The study found that all of these things led to people abandoning their shopping carts before converting... so they're worth looking into even if you don't care about people and just want to improve your bottom line. but hey, you could also improve people's lives and make their experiences less confusing, less frustrating and smoother. and isn't that what technology is meant to be for?

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Link: Advisory locks in postgres 13 Nov 2014 3:44 PM (10 years ago)

Advisory locks in postgres

"PostgreSQL provides various lock modes to control concurrent access to data in tables. Advisory locks provide a convenient way to obtain a lock from PostgreSQL that is completely application enforced, and will not block writes to the table."

This is not like row-locking your tables, this is to help you ensure concurrency for certain parts of your application. eg that you only enter a complex, resource-intensive part of your codebase once across all your servers. Given that ruby basically isn't very threadsafe - this provides a mechanism for concurrency-locking that is available across your plausibly widely-distributed servers by basically storing the semaphore in the mutually-accessible database.

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

gotcha: redirecting through root-url fails to preserve anchor tag 7 Nov 2014 3:52 PM (10 years ago)

Interesting (and annoying).

If you redirect to your root-path and pass it an anchor tag like "show-help" (in some controller) eg with

redirect_to root_path(:anchor => 'show-help')

It first redirects correctly to /#show-help but it then further redirects to your actual root url... and removes the anchor tag eg /my_dashboard

My expectation would be that it would preserve the anchor tag... eg the final URL would be: /my_dashboard#show-help

But of course, the browser doesn't actually send anchor tags to the server at all... which means that after the first request (to '/'), Rails never gets the anchor tag back again to pass onto the redirected url, which is why it gets dropped.

This is an issue if you really want to open the url with an anchor tag.

The main option you have is to not use root_url, but directly use the actual url you need eg: my_dashboard_url(:anchor => 'show-help'). It's not the best, but at least it works.

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

gotcha: running `rake db:migrate:redo` and testing 1 Nov 2014 3:17 PM (10 years ago)

So... rails *tells* me that rake db:test:prepare is deprecated, and that just running the specs will keep the db up-to-date... but it isn't always so.

If you jigger with a brand new migration and run a rake db:migate:redo - the test db can't deal with that by itself. You either have to run rake db:migate:redo RAILS_ENV=test or rake db:test:prepare to propagate those changes.

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Link: Asshole culture at Uber 26 Oct 2014 2:31 PM (10 years ago)

Uber, darlings of the startup world, have displayed particularly poor taste and utter thoughtlessness towards 50% of the population with a promotion that they have quietly removed (without comment) since it was recently denounced.

The article: The horrific trickle down of Asshole culture: Why I’ve just deleted Uber from my phone describes what happened.

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Link: Good Ruby-gems patterns 20 Oct 2014 11:45 PM (10 years ago)

RubyGems patterns Gives you five good tips to follow to write better ruby gems:

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Link: schedule your sidetiq jobs for a specific timezone 14 Oct 2014 4:55 PM (10 years ago)

Currently, "UTC" and "not UTC" are the only options available for scheduling your sidetiq workers...

Now, UTC is great if you're lucky enough to live in London - in which case, you can stop reading now. Or if all your systems happen to be set that way... though it doesn't let you accommodate local things such as daylight savings time-shifts.

So the only other option provided by sidetiq is "not UTC" which means you default to whatever timezone is set on your server... which is great if your server's local time is set to the timezone you actually live in... but, if, like most of us, your server is in an amazon cluster somewhere in Randomville America or somewhere on the sub-continent, you may not have that luxury either.

Then you need some way of making sure that your sidetiq workers run at 2am *for you*... instead during the middle of the midday-rush, slowing your servers to a crawl right when your users need you most...

Thankfully, here is a useful gist by toolmantim that lets you set the sidetiq time zone to whatever you'd like. eg it lets you do something like the following

  schedule_time_zone "Australia/Sydney"
  recurrence { daily.hour_of_day(2) }

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Change your dropbox passwords... 13 Oct 2014 9:18 PM (10 years ago)

You should: Change Your Password: Hackers Are Leaking Dropbox User Info

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Classic: Beating the Averages by Paul Graham 12 Oct 2014 12:10 AM (10 years ago)

Beating the averages by Paul Graham

It's a classic. It's about lisp as a secret weapon, the building of viaweb and the blub paradox.

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Sidekiq.❨╯°□°❩╯︵┻━┻ 1 Oct 2014 8:55 PM (10 years ago)

No, that's not me expressing my current frustration with Sidekiq not playing nice with mandrill-mailer... It's a real method on Sidekiq...

[1] pry(main)> Sidekiq.❨╯°□°❩╯︵┻━┻
Calm down, bro
=> nil

I found it while scanning the sidekiq test suite

  describe "❨╯°□°❩╯︵┻━┻" do
    before { $stdout = StringIO.new }
    after  { $stdout = STDOUT }

    it "allows angry developers to express their emotional constitution and remedies it" do
      Sidekiq.❨╯°□°❩╯︵┻━┻
      assert_equal "Calm down, bro\n", $stdout.string
    end
  end

<3 <3 <3

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Shellshocked vulnerability bigger than heartbleed - run the fix right now! 25 Sep 2014 3:15 PM (10 years ago)

TL;DR: run apt-get update; apt-get upgrade

Right now

Then keep doing often over the next few days...

If you're on MacOS, here's a good guide on How to patch OSX in the wake of shellshock

"Remember Heartbleed? If you believe the hype today, Shellshock is in that league and with an equally awesome name albeit bereft of a cool logo (someone in the marketing department of these vulns needs to get on that). But in all seriousness, it does have the potential to be a biggie and as I did with Heartbleed, I wanted to put together something definitive both for me to get to grips with the situation and for others to dissect the hype from the true underlying risk."

Everything you need to know about the Shellshock Bash bug

"Which versions of Bash are affected?

The headlines state everything through 4.3 or in other words, about 25 years’ worth of Bash versions. Given everyone keeps comparing this to Heartbleed, consider that the impacted versions of OpenSSL spanned a mere two years which is a drop in the ocean compared to Shellshock. Yes people upgrade their versions, but no they don’t do it consistently and whichever way you cut it, the breadth of at-risk machines is going to be significantly higher with Shellshock than what it was with Heartbleed.

But the risk may well extend beyond 4.3 as well. Already we’re seeing reports of patches not being entirely effective and given the speed with which they’re being rolled out, that’s not all that surprising. This is the sort of thing those impacted by it want to keep a very close eye on, not just “patch and forget”."

heroku are (at this time of writing) working to resolve it on their servers: heroku shellshocked status page

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Classic: Why nerds are unpopular By Paul Graham 23 Sep 2014 4:00 PM (10 years ago)

Why nerds are unpopular is one of Paul Graham's classic essays and well worth reading again. This essay made me re-evaluate my terrible memories of high-school (I think now I wouldn't have changed a thing); and will no doubt shape any recommendations I have for other young nerds who suffer the same terrible problems that come with massive unpopularity at school.

"When we were in junior high school, my friend Rich and I made a map of the school lunch tables according to popularity. This was easy to do, because kids only ate lunch with others of about the same popularity. We graded them from A to E. A tables were full of football players and cheerleaders and so on. E tables contained the kids with mild cases of Down's Syndrome, what in the language of the time we called "retards."
"We sat at a D table, as low as you could get without looking physically different. We were not being especially candid to grade ourselves as D. It would have taken a deliberate lie to say otherwise. Everyone in the school knew exactly how popular everyone else was, including us."

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Stone soup with elephants 17 Sep 2014 4:06 PM (10 years ago)

Motivation can be a problem sometimes.

For example - some days I really just *don't* want to cook. I come home exhausted, and just wish that somebody else would make dinner for me instead... but if I don't do it... I'll be nibbling raw rice and feeling miserable.

Sometimes that calls for pizza... but too much of the unhealthy stuff and I could end up in a downward spiral that makes me feel even worse.

and sometimes I can use one of my motivation tricks to kickstart myself into something more healthy.

I'm sure you've heard the story of stone soup - where a hungry traveller starts with some water and stone, telling the villagers that she's making soup with the stone. They don't believe her, but she asks if they are willing to contribute a carrot here, some parsley there to add to this stone soup - in order to make it the best stone-soup possible, of course... and eventually they all end up eating a thick and tasty soup.

I like to use that story to tempt myself. In my case - I'll pull a jar of pasta sauce out of the cupboard - but its not quite enough on its own, so I figure "well, I'll just add a few vegies"... before I know it, I've cut up enough veggies to make another two meals... and put the pasta sauce bottle back in the cupboard unopened. Stone soup well in hand.

You see, really the trick is just to get started. I generally then find it's not as hard as I first thought.

So where's the IT angle? Well I have found that a lot of my past clients have let things slip... UI-bugs, code reviews, test suites... the usual.

of course, the first step is to recognise that there is a problem... but often they end up doing little more than that, and promising themselves that "we really need to do the Thing"... but it never goes beyond that.

Then they have a big meeting and tell all the developers that "the Thing is really number one priority now". But after the meeting, nothing on their end changes - all the other priorities are still there. Clients need to "just get this one thing fixed, and then you can start on the Thing"... "we just need a few more features to go out the door to keep our customers happy while we work on the Thing"....

and somehow no time actually gets allocated to the Thing.

Sometimes there's a big meeting where blame gets slung like mud "why didn't you work on the Thing? when we said it was priority number 1?"... but that doesn't change the fact that the Thing... really isn't a priority at all. if it was, you'd be allowed time to work on it, instead of all the other urgent things.

Luckily, the stone soup kicker can help here too. Say your Thing is to build a test suite. Your app has 100,000 lines of code... that's a big Thing. But it's not too hard to promise yourself "just write one test for one model"... and you get caught up in it and before you know it, you've cleared out the tests for a whole section of your app.

I think oftentimes the motivation problem is really that you're looking at a whole elephant, wondering how the hell you're going to eat it all... and have forgotten that the way to do it is "one bite at a time". Once you're elbow deep in solving a small piece - it's often easy to get so caught up in the details that they stop bothering you any more, and you can just get on with it.

So - whatever it is, however big it is... just get started. It doesn't have to be a large piece - just one wafer-thin bite...

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Gotchas: rspec expect change gives: "nil is not a symbol" 11 Sep 2014 11:53 PM (10 years ago)

*sigh* There's a small, but meaningful difference between:

   expect {
     my_thing.do_stuff
   }.to change(my_thing.my_value).to(some_value)

and

   expect {
     my_thing.do_stuff
   }.to change{my_thing.my_value}.to(some_value)

(note the braces on the last line...)

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

gotchas: require/permitting an array of ids 5 Sep 2014 5:53 PM (10 years ago)

This one got me the other day... it seems that sometimes the following won't work:

params.require(:my_thing).permit(:widget_ids)

When the set of ids is expected to be an array, instead use

params.require(:my_thing).permit(:widget_ids => [])

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

String#gollumize 30 Aug 2014 11:11 PM (10 years ago)

 class String
   def gollumize 
     self.pluralize + "es" 
   end  
 end  

"pocket".gollumize
=> "pocketses"

...just because :D

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Life at 38 24 Aug 2014 4:11 PM (10 years ago)

When I was 5 I liked to do jigsaw puzzles upside down (to make it harder) and blow bubbles off the balcony -- watching them drift over the street. I liked to walk to school by myself (one block) and learn Origami from the lady in the flats behind us.

When I was 7 I wished on every star that I could have a baby sister. When I was 8, I got one.

When I was 10 I liked to explore the backways of Oyster Bay, picking flowers to make perfume (which smelled terrible). I played fantasy make-believe games with my cousins - involving magic and unicorns, where we saved the world.

When I was 12 I got another sister.... I stopped wishing. :)

When I was 13 I liked to play make-believe with my sisters and all the younger cousins. Gordy and I plotted adventures for us all, in-amongst the bamboo.

When I was 15 I liked to climb up on the roof and watch the clouds drift by. I liked to ride my bike home from school and play LARP in the park across the road.

When I was 17 I liked to swim in the backyard pool, drifting underwater with my hair floating loose around me. I liked to frantically scribble in my diary and day-dream about movies; making up adventure stories or inserting myself as a character in my favourites.

When I was 20 I loved the freedom of being independent, waking up in my own flat to the sound of Cockatoos in the pine-trees. I liked being free to wake up in the afternoon and go for a walk in the twilight, or in the quiet time after the curfew painted the streets with night. I liked staying up all night, having coffee with friends, as television got progressively more idiotic. As the sky began to warm with first light - I went out again. I liked feeling the expectant hush of the cool dawn, then retiring before the hustle woke up.

When I was 22 I loved my writing diary - pouring out my heart or playing with words, crafting new worlds on a page. I liked learning new things -- drifting from subject to subject. I liked psychology, programming and the occult. I loved my cats. I liked it that I got married and was somebody's wife. I liked meditating with my husband -- humming toneful chords without meaning. I liked learning martial arts with him.

When I was 24 I loved my garden. I spent days drifting through it and tending to the plants. I liked picking silver-beet and herbs and cooking up a meal that I'd taken from seed to table. I liked shopping at Bunnings for fruit trees. I liked spending Saturday nights with the Druids, singing, meditating, drinking mead and telling stories. Then the morning-afters, skinny-dipping in the wading-pool as the sun climbed the sky.

When I was 26 I moved interstate by myself, to see the Big City. I liked exploring the back-areas of Chatswood in search of great food. I loved hacking together brilliant solutions for colleagues desperately late on their projects. I liked rock-climbing with my work-mates and playing network games in the training room until late at night. I wrote a dictionary of elvish (quenya).

When I was 28 I loved freedom. The freedom to choose my own time, to choose what to learn, to work on my own projects. I liked smiling at my young class-mates who complained about the work - knowing this was easy compared with Real Work. I liked the meditation of archery. I liked spending my time reading while sipping coffee, or eating noodles at the local hole-in-the-wall. I liked long walks in the evening, scoping out my local territory.

When I was 30 I liked building my own small business, knowing I owned it and I could achieve whatever I wanted. I liked learning medieval crafts alongside eager students, and feasting with friends in a medieval campsite. I liked reading books, sipping coffee after a good workout at the gym. I liked watching myself learn how to walk again.

When I was 32 I enjoyed being a senior developer, being at the top of my form as well as earning high pay. I enjoyed choosing my first major investments in shares and buying my first property. I loved planning what to do with my life, and choosing to gather interesting experiences around me - New Zealand, the Northern Territory, Thailand. I learned photography, spanish and investing. I watched a *lot* of DVDs.

When I was 34 I moved internationally by myself. I loved exploring in a new country: visiting ancient ruins and watching Shakespeare at the Globe. I enjoyed getting better at photography, meeting new people and seeing new places. I built my own startup with friends. I enjoyed my morning coffee at the local in Windsor, and walking past a castle on my way to work in the morning. I loved pottering around in my allotment late into the long, english summer nights.

When I was 36 I loved getting about europe in my first car; learning French, and then eating my way around the French countryside. I enjoyed picking fresh fruit/veg from my allotment and making blackberry pie. I loved the lazy schedule allowed by working from home, and lazy Sunday afternoons drinking velvety latte with a Cranberry-and-orange muffin at my local cafe in Windsor, in view of the castle.

When I was 38 I loved being in the thick of things in the medieval group - I was president of the NSW branch (200 paid members, 10 direct and 8 indirect reports). I helped coordinate others who were running feasts and balls and camps and craft classes. I taught a class of newbies to build websites in Rails in 12 intense-but-rewarding full-time weeks. I enjoyed dinners with my cousins, and weekends going to the markets with them and then spending the day cooking preserves, jams and soups to feast upon in the weeks to come.

This post began as an exercise: "for every five years, write what you enjoyed doing". It helps you find out what you most enjoy - and how your tastes change over time. I also like to do it to remind me of the good things in life - which can be so easy to forget sometimes.

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

STOP YELLING! or how to validate a field isn't in ALLCAPS 18 Aug 2014 5:11 PM (10 years ago)

TuShare lets people give away things they no longer need. It turns out that some people think that putting their headings in ALLCAPS will get more attention... but we think it just annoys people (nobody likes to BE YELLED AT!). Here's a quick snippet that lets you validate that your users aren't YELLING AT YOU! in any given field.

1) create a custom validator in app/validators/not_allcaps_validator.rb with the following code:

# we don't want our users YELLING AT US!
class NotAllcapsValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    if value.present? && (value.upcase == value)
      record.errors[attribute] << (options[:message] || ""is in ALLCAPS. Please don't YELL AT US (we're sensitive types). Instead, just use normal sentence case. I promise we'll still hear you from over here. :)")
    end
  end
end

2) use it in your model like this:

   validate :name, :description, :not_allcaps => true

3) Add a custom matcher for rspec:

# validates that this field has the "not_allcaps" validator
RSpec::Matchers.define :validate_not_allcaps do |attribute|
  match_for_should do
    match do |model|
      model.send("#{attribute}=", "AN ALLCAPS VALUE")
      !model.valid? && model.errors.has_key?(attribute)
    end
 
    failure_message_for_should do |model|
      "#{model.class} should validate that #{attribute.inspect} cannot accept ALLCAPS VALUES"
    end
 
    failure_message_for_should_not do |model|
      "#{model.class} should validate that #{attribute.inspect} can accept ALLCAPS VALUES"
    end 
  end
end

4) test each field in your model specs with:

    it { should validate_not_allcaps(:name) }
    it { should validate_not_allcaps(:description) }

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?

Link: Why I am leaving the best job I ever had 12 Aug 2014 5:09 PM (10 years ago)

an awesome post by Max Schireson to remind us of our priorities in life: Why I am leaving the best job I ever had

"Earlier this summer, Matt Lauer asked Mary Barra, the CEO of GM, whether she could balance the demands of being a mom and being a CEO. The Atlantic asked similar questions of PepsiCo’s female CEO Indra Nooyi. As a male CEO, I have been asked what kind of car I drive and what type of music I like, but never how I balance the demands of being both a dad and a CEO..."

Add post to Blinklist Add post to Blogmarks Add post to del.icio.us Digg this! Add post to My Web 2.0 Add post to Newsvine Add post to Reddit Add post to Simpy Who's linking to this post?