Matthew Lindfield Seager

Using Capybara with Minitest

Note to future self:

Capybara matchers: www.rubydoc.info/github/jn…

Mintiest assertions: www.rubydoc.info/github/jn…

page.has_select?('Language', selected: ['English', 'German']) translates to assert_select 'Language', selected: ['English', 'German']

www.ghostcassette.com/function-…

Interesting read on Function Composition in Ruby 2.6… but I’m still struggling to understand why I’d want to use it in my own code. Feels like I’m missing some key part of the puzzle!

I encountered Rails 6’s actionable error messages for the first time in the wild today…

I saw an error message about migrations pending and I’d already hit ⌘ ⇥ to switch to Terminal.app before it dawned on me I can now click a button to run the migrations from the browser 🎉

Webpacker wasn’t compiling or showing any errors/warnings in a new Rails 6 app after adding in Bootstrap and jQuery…

Eventually I stumbled on a way to figure out what was going wrong:
rails assets:precompile

Pointed me at a typo in environment.js that I’d missed 8 times 😣

Glenn Vanderburg with a very compelling talk on why “engineering” is the appropriate name for the science and art of designing and making software development: www.youtube.com/watch

Enjoyed reading about Micro.blog in the New Yorker: www.newyorker.com/tech/anna…

I won’t “be trapped on a platform that owns everything [I’ve] written and is doing everything it can to exploit [my] data and attention” 👍

blog.skylight.io/the-lifec…

I went in expecting it to be about Rails and that I’d know most of it already. I was wrong on both counts! It’s mostly about Rack and it really helped me understand what happens in the gap between the web server and Rails!

Logster is a nice nice little gem from the fine folks at Discourse that lets you view your Rails logs in the browser in Development and Production

github.com/discourse…

I was curious how class​ Test < ActiveRecord::Migration[6.0] actually works under the covers. Three key discoveries were:

  • ActiveRecord::Migration[6.0] gets evaluated
  • Migration has a class method [](version)
  • Ruby has syntactic sugar where a[b] is the same as a.[](b)

Digging in to Ruby Method Definitions

Today I learned that parentheses are optional in Ruby, even when defining methods, not just when you’re calling them.

Somehow that got me digging into positional arguments, named arguments and blocks so here’s a comprehensive (but pathological) example of defining a method without parentheses but with all 7 possible argument types:

def arguments   required_positional,
                optional_positional=2,
                *other_positionals,
                another_required_positional,
                required_keyword:,
                optional_keyword: 7,
                another_required_keyword:,
                **other_keywords,
                &block
  puts "required_positional = #{required_positional}"
  puts "optional_positional = #{optional_positional}"
  puts "other_positionals = #{other_positionals}"
  puts "another_required_positional = #{another_required_positional}"
  puts "required_keyword = #{required_keyword}"
  puts "optional_keyword = #{optional_keyword}"
  puts "another_required_keyword = #{another_required_keyword}"
  puts "other_keywords = #{other_keywords}"
  3.times(&block)
end

arguments   1, 2.0, 3, 4, 5, other_b: 10, another_required_keyword: 8,
            other_a: 9, required_keyword: 6, other_c: 11 do
  print 'foo '
end

# required_positional = 1
# optional_positional = 2.0
# other_positionals = [3, 4]
# another_required_positional = 5
# required_keyword = 6
# optional_keyword = 7
# another_required_keyword = 8
# other_keywords = {:other_b=>10, :other_a=>9, :other_c=>11}
# foo foo foo

Observation 1

You may have noticed that I passed 2.0 (as a Float) to the optional_positional argument, which defaults to 2 (as an Integer). I did this because *other_positionals only gets left over arguments. If I omit the 2.0 (hoping for optional_positional to be set to it’s default value of 2) then optional_positional greedily grabs the first spare argument (3, in the example below), “stealing” it from the other_positionals array:

arguments   1, 3, 4, 5, other_b: 10, another_required_keyword: 8,
            other_a: 9, required_keyword: 6, other_c: 11 do
  print 'bar '
end

# required_positional = 1
# optional_positional = 3
# other_positionals = [4]
# another_required_positional = 5
# required_keyword = 6
# optional_keyword = 7
# another_required_keyword = 8
# other_keywords = {:other_b=>10, :other_a=>9, :other_c=>11}
# bar bar bar

Observation 2

The Ruby docs state that (emphasis mine):

Prefixing an argument with * causes any remaining arguments to be converted to an Array.
The array argument must be the last positional argument, it must appear before any keyword arguments.

As you can see in the examples above, that’s not the behaviour we’re seeing. I added an additional (required) positional argument after the array argument and Ruby happily accepted it. I’m not sure if that’s a language bug or a documentation bug but given that it’s very un-idiomatic Ruby you will hopefully never see (or write!) this in the wild. Just because you can, doesn’t mean you should! :)

Observation 3

This is basically just a variation of the previous two observations but I’ll point it out anyway… the second required positional argument (another_required_positional) takes precedence over optional_positional and *other_positionals. This makes sense (once you accept the undocumented ability to add additional required positional arguments) but I thought I’d mention it anyway. If we only provide two positional arguments they are consumed by the 1st and 4th required arguments, leaving the 2nd at its default value and the 3rd (array) empty:

arguments   1, 5, other_b: 10, another_required_keyword: 8,
            other_a: 9, required_keyword: 6, other_c: 11 do
  print 'baz '
end

# required_positional = 1
# optional_positional = 2
# other_positionals = []
# another_required_positional = 5
# required_keyword = 6
# optional_keyword = 7
# another_required_keyword = 8
# other_keywords = {:other_b=>10, :other_a=>9, :other_c=>11}
# baz baz baz

Observation 4

In case you were wondering, the undocumented behaviour for positional arguments does not hold true for keyword arguments. Adding an additional keyword argument after the keyword hash argument, **other_keywords, causes a syntax error:

def faulty_keyword_arguments(   required_keyword:, optional_keyword: 'b',
                                **other_keywords, another_required_keyword:)
# syntax error, unexpected tLABEL, expecting & or '&'
# ...ords, another_required_keyword:)
# ...                               ^

As the message suggests, the only thing allowed here is a block argument (starting with an ampersand).

That being said, other than the **other_keywords keyword hash needing to be last in the keyword arguments, there are no restrictions on the order of optional and required keyword arguments. As you can see in the intial example, another_required_keyword is defined after optional_keyword. They do not need to be grouped together like positional arguments do.

Observation 5

You may be wondering if the old idiom of passing a hash after positional arguments works. This continues to work, but only if you don’t use any keyword arguments in the method definition:

def final_hash a, b=2, *c, &block
  puts "a = #{a}"
  puts "b = #{b}"
  puts "c = #{c}"
end

final_hash(1, 2, 3, 4, 5, six: 6, seven: 7, eight: 8)

# a = 1
# b = 2
# c = [3, 4, 5, {:six=>6, :seven=>7, :eight=>8}]

In the example above there are only 6 arguments. The last three items are a single hash - there are implicit hash braces around them. This means all the elements of the hash must be grouped together at the end. If you try to include a positional argument amongst the hash items you’ll get an error about a missing hash rocket =>.

Observation 6a

If all you do with the block is call it with yield, you don’t need to explicity name the block in the method definition:

def inline_implicit_block a, b=2, *c
  puts "a = #{a}"
  puts "b = #{b}"
  puts "c = #{c}"
  yield self
  yield self
  yield self
end

Observation 6b

If you omit parentheses around the method arguments when calling the method, you can’t use the single line block syntax { print 'foo ' }. This is why the explicit &block examples above have the multiline do and end block syntax. Including the braces lets us use the single line syntax:

inline_implicit_block(1, 2, 3, 4, five: 5, six: 6) { print 'qux '}

# a = 1
# b = 2
# c = [3, 4, {:five=>5, :six=>6}]
# qux qux qux

Mind Blown 🤯

⌘E on a Mac copies the selected text to a special clipboard used by Find windows.

Instead of ⌘C, ⌘F, ⌘V you can just do ⌘E, ⌘F!!! Saves a keyboard shortcut AND the contents of your clipboard!

Relistening to a recent-ish episode that I had lots of thoughts on (as someone recently and still learning Ruby on Rails)

Today I watched a really helpful talk on burnout (https://youtu.be/71suekjBV9Y).

VERY slow start but the discussions of Maslach’s Mismatches that contribute to burnout (spoiler, it’s not just overwork) and the symptoms to watch out for were really helpful.

Delivering Value

You don’t have to be better than everyone else. Every doctor in America, save one, is not the best doctor. Every lawyer, save one, is not the best lawyer. Every Rails programmer, save one, is not the best Rails programmer. Being the best isn’t a prerequisite to running a successful business. You need to be capable, and to deliver fantastic value to your customers. That does not require being a ninjedi gurumensch. A lot of fantastic value can be delivered with capable, workmanlike coding.
–Patrick McKenzie - www.kalzumeus.com/2012/09/2…

I like this framing and the reminder that you don’t have to be the best to add value. I’m keen to start exploring ways I can best add value over the next month or two.

Sounds like RubyKaigi 2019 had some interesting talks and announcements!

Bundler isn’t something I think about often but I’d definitely welcome some speed gains when I do use it…

github.com/gel-rb might be just the ticket!

Enjoying a belated beachside birthday bash by Botany Bay before bed… then a big run and a buffet breakfast 🙂

Exceptionally Ordinary

I have surrounded myself, virtually, with successful creators, developers and entrepreneurs. The blogs I read and the podcasts I listen to are full of people who have found, or sometimes carved out, a niche.

Some deliberately chose an under-served area and went deep, gradually building up a reputation and an audience. Others have “laddered up” from one opportunity to another, refining their skills as they go.

I look at what nearly all of them do and think to myself, “I could do that”. As best I can tell I am smart, articulate and capable.

Capable of writing. Capable of developing. Capable of designing interfaces that make sense. Capable of thinking strategically, weighing risks and making decisions. Capable of learning just about anything. Capable of teaching others.

And yet there seems to be one big thing I am incapable of. I am incapable of staying focused on one thing for more than a few weeks. Incapable of coming up with a long term plan and sticking with it. Incapable of mastering a topic once I’ve got the basics (or at best the intermediates) down pat. Incapable of finishing projects without external motivation.

I’m a big believer in focusing on your strengths, building them up until you stand out. And yet I wonder, are there some weaknesses that are too big to ignore? Can a big enough weakness hamstring a person, negating any strengths. Should I focus on my strengths or try to overcome my weaknesses?

But maybe it’s a moot point. Perhaps both approaches require a level of determination and grit that I’m not capable of.

And so I drift. Accumulating shallow learnings. A jack of a great many IT trades. A master of none.

But that’s okay isn’t it? Not everyone can be a master. There are a lot of people happily existing at levels below master. There‘s hobbyists. Amateurs. Prosumers. Juniors. Apprentices. Professionals. The proficient. The competent. The ordinary.

Perhaps I need to stop surrounding myself with the exceptional. They are, after all, exceptions. Maybe I need to settle for being ordinary.

Or maybe I need to look for different opportunities. To look for people or places where versatility is what’s needed. Perhaps that’s on smaller teams that don’t have the headroom for exceptional experts? Or finding a partner with complimentary strengths and weaknesses?

I’m not sure what the answer is. I’m not even sure I’ve figured out what the question is.

Looks like the (unedited?) videos for Railsconf 2019 are up for the 1st and 2nd of May…

youtu.be/5QgzXg0gJ…

youtu.be/V9hD0SaGR…

Makes Rubyconf AU 2019 stand out even more for getting the edited videos up the same day as the talks!!!

Good article on delegating (technically forwarding) in Standard Ruby, in Rails or with a third party gem for more control:

blog.appsignal.com/2019/04/3…

Today I learned Transport NSW has been trialling on-demand public transport!!! Looks like a local but public Uber Pool to get people the last (or first) “mile” from/to existing public transport. Very cool!

transportnsw.info/travel-in…

I’ve never developed for Android, I‘ve never tried “Reactive” programming and I’ve only used Java in uni classes but I enjoyed reading thoughtbot.com/blog/demy…

The basic concepts reminded me a little of Key-Value Observing in Cocoa on Mac

Lesson on Learning

Just found some old notes I took about learning, although I’m not sure of the context. All of it looks useful but the things I highlighted are still things I need to learn.

Self-learning is not a Sprint

Pre-requisite concepts

  • prodigy myth (highlighted) vs growth mindset
  • grit: develop it like a muscle
  • be willing to look/be stupid
  • set deadlines

Strategies

  • small steps
  • don’t ignore mistakes

Self-study techniques

  • a few (15) minutes each day

Good post (as usual) about Microsoft, Zoom and Slack by Ben Thompson: stratechery.com/2019/micr…

If he is correct, I don’t think Microsoft will be winning any new customers with Teams. It’s clunky, painful and feels “enterprisey”

Campaign Monitor API

I was working with the Campaign Monitor API (https://www.campaignmonitor.com/api/) today.

It works but I don’t love that their gem (http://campaignmonitor.github.io/createsend-ruby/) keeps returning Hashie Mashes instead of “real” Ruby objects. I might be missing something but I kept having to use parts of the Mash to fetch additional Mashes which then contained the data I was after. It would be nice if I could retrieve the “parent” object and then lazily load child objects as I access them.

On that note, the property names were quite inconsistent. Sometimes they would be ruby-like but then other times I’d have to write things like lists.map(&:Name) or client_ref.ClientID.

Lastly, I didn’t like that the API offers almost nothing in the way of filtering or searching when retrieving results. It felt like every time I wanted to do anything I had to iterate through a whole list (possibly across multiple pages) until I found the result I was after.

Maybe I’m spoiled by how developer friendly most Ruby code is but it felt a little like this API and the gem were just slapped together so they could tick off “RESTful API” and “Ruby wrapper” on their feature list checkboxes but without really thinking about the end user of the software.

📚 I ended up enjoying the Fire Sermon so much I read the next two in quick succession!

Dark at times, but believable and compelling! Glad I read them!

www.goodreads.com/series/10…