Ylan Segal

Structs With Keyword Arguments in Ruby 2.5

Ruby 2.5 was released a few days ago. Among the new features, Structs gained the ability to be instantiated with using keyword arguments.

Ruby has traditionally had the ability to create a classes that bundle data attributes together, provide accessors for those attributes and other methods like converting into a hash:

1
2
3
4
5
Point = Struct.new(:x, :y)
p = Point.new(2, 3) # => #<struct Point x=2, y=3>
p.x # => 2
p.y # => 3
p.to_h # => {:x=>2, :y=>3}

Notice, that the newly created class is initialized with positional arguments. Often when using Ruby – especially when using Rails, data is passed around in hashes. For example, let’s assume that we are instantiating an instance of a Point inside a controller action using Rails. The instantiation would look something similar to:

1
point = Point.new(params[:x], params[:y])

As the number of positional arguments grow, this can become tedious. Ruby 2.5 ships with a new feature that allows creating Structs that accept keyword arguments, much like ActiveRecord models do, as described in this feature request.

1
2
3
Point = Struct.new(:x, :y, keyword_init: true)
Point.new(x: 1, y: 2) # => #<struct Point x=1, y=2>
Point.new(y: 2, x: 1) # => #<struct Point x=1, y=2>

There are a few things to note. When using keyword arguments, if a value is missing, it will be set to nil. Additionally, if extra arguments are supplied, an ArgumentError will be raised:

1
2
3
Point = Struct.new(:x, :y, keyword_init: true)
Point.new(y: 2) # => #<struct Point x=nil, y=2>
Point.new(x: 1, y: 2, z: 3) # => ArgumentError: unknown keywords: z

Stuck in an older ruby? You can easily build similar support on your own, which I often do in projects I work on:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module StructKeywordInitialization
  def initialize(args)
    members.each do |field|
      self.public_send("#{field}=", args[field])
    end
  end
end

Point = Struct.new(:x, :y) do
  include StructKeywordInitialization
end

Point.new(x: 1, y: 2)       # => #<struct Point x=1, y=2>
Point.new(y: 2)             # => #<struct Point x=nil, y=2>
Point.new(x: 1, y: 2, z: 3) # => #<struct Point x=1, y=2>

We’ve created a new module that takes advantage of the #members method in Struct to define a dynamic initializer. Note that in this version, extra arguments will not raise an ArgumentError. Depending on your application, this might be a better fit or not. It’s left to the reader to make a version that does raise an error with extra arguments.

The REPL: Issue 41 - December 2017

Trying to Represent a Tree Structure Using Postgres

Pat Shaughnessy writes a great 5 part series on using tree structures inside Postgres to store hierarchical data. In this first post he covers on why using a tree structure makes sense. In later parts he will cover the basics of the LTREE extension, how to install and use it and how it hooks into the Postgres internals.

Building a Distributed Log from Scratch

Brave New Geek writes the first part of a promised series on building a distributed log from scratch. in this post he focuses on storage mechanics. If you interested in why using a log is a good abstraction for distributed systems, see the referenced article The Log: What every software engineer should know about real-time data’s unifying abstraction.

Distributed systems for fun and profit

Mikito Takada writes a short e-book about distributed systems at a high level, covering scalability, availability, performance, latency and fault tolerance. The implications of different levels of abstractions, time and ordering and different modes of replication are part of the fun. Warning: After reading you might find yourself going down the rabbit hole resaearching Vector Clocks and CRDTs (convergent replicated data types). See you there.

The REPL: Issue 40 - November 2017

Redis Streams and the Unified Log

In this article, @brandur writes about the unified log concept and how to use Redis streams (coming soon) to build a foundation for a unified log. He covers what a unified log is good for, compares it to Kafka and provides code examples that tie everything together. This is great quality writing. I highly recommend you read the other articles on his blog. They are worth it.

CLIs are reified UIs

This articles provides some perspective about CLIs vs GUIs. The author makes a convincing argument, that CLIs make the interaction with the computer clearer, because they are more visible. This brings easier interaction because of the ability of copy, pasting, editing and so on.

Brilliant Jerks in Engineering

Brendan Gregg breaks that the implications of having a brilliant at engineering team member that is also a jerk. He breaks down the jerkiness into selfless and selfish. The post is thorough and found myself nodding along to many of the described behaviors and the problems that they cause.

The REPL: Issue 39 - October 2017

Floating Point Visually Explained

Sooner or later every software engineer runs into issues with floating point arithmetic precision. Fabien Sanglard explains how floating point numbers are stored and how the approximate real numbers. The post talks specifically about numbers in C, but the lesson is applicable generally.

API design: Choosing between names and identifiers in URLs

Martin Nally covers the ins and outs of using human-readable names or ids in URLs. Both have their place, even in the same systems.

10 new features in Ruby 2.5

Ruby 2.5 is expected to be released this Christmas, like it always does. Here are a few new features that will be included. There are no major changes. The language is relatively mature now and the core teams seems to be focused on performance improvements.

Pipe Atom Text Into Any Command

On my day-to-day software engineering tasks, I sometimes have the need to pass the file or selection through another program and replace it with the output. The uncomfortable workflow on my Mac is:

  • Select text and copy (command-c)
  • Open a terminal and type:

    $ pbpaste | some_command | pbcopy
    
  • Go back to my editor and paste (command-v)

Lately, I’ve been doing that workflow more often. It was starting to become annoying. Vim and Emacs have long had support for manipulating the current buffer in this way. There is a package that does just that for Atom, my editor choice: The aptly-named pipe.

Select text to pipe into external commands and replace it with the output. Sort of like ! in vim. Commands are sent to your $SHELL.

Examples

Formatting SQL

Assuming I have a selection like:

1
2
3
4
5
6
7
8
9
10
11
SELECT
    `account_usr`.`account_uid`,
    GREATEST (`account`.`created_at`, IFNULL (`oauthorization`.`created_at`, 0),IFNULL (`usr`.`last_login`, 0)) AS last_used_at
FROM
    `account_usr` LEFT OUTER JOIN `account` ON `account_usr`.`account_uid` = `account`.`uid`
    LEFT OUTER JOIN `usr` ON `account_usr`.`user_uid` = `usr`.`uid`
    LEFT OUTER JOIN `oauthorization` ON `usr`.`uid` = `oauthorization`.`user_uid`
WHERE (`account`.`end_date` > '2017-10-25')
AND `account`.`product_uid` IN (1, 10)
GROUP BY `account`.`uid`
HAVING last_used_at < '2016-10-10 17:26:57.301147'

I can select it, run pipe (command-;) and type pg_format at the prompt. The selection now becomes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SELECT
    `account_usr`.`account_uid`,
    GREATEST (`account`.`created_at`,
        IFNULL (`oauthorization`.`created_at`,
            0),
        IFNULL (`usr`.`last_login`,
            0)) AS last_used_at
FROM
    `account_usr`
    LEFT OUTER JOIN `account` ON `account_usr`.`account_uid` = `account`.`uid`
    LEFT OUTER JOIN `usr` ON `account_usr`.`user_uid` = `usr`.`uid`
    LEFT OUTER JOIN `oauthorization` ON `usr`.`uid` = `oauthorization`.`user_uid`
WHERE (`account`.`end_date` > '2017-10-25')
AND `account`.`product_uid` IN (1, 10)
GROUP BY
    `account`.`uid`
HAVING
    last_used_at < '2016-10-10 17:26:57.301147'

Writing Ruby Documentation or Examples

When creating blog posts, pull request or other code examples, I often use the fantastic xmpfilter – part of the rcodetools gem.

Starting selection:

1
2
3
4
5
6
require "ostruct"

person = OpenStruct.new(name: "Ylan", last_name: "Segal")

person.name # =>
person.last_name # =>

After piping to xmpfilter

1
2
3
4
5
6
require "ostruct"

person = OpenStruct.new(name: "Ylan", last_name: "Segal")

person.name # => "Ylan"
person.last_name # => "Segal"

Conclusions

The pipe package opens up a world of possibility for working with your current buffer and all the CLI tools that already exist. Give it a try.