Today I Learned

7 posts by bartoszmąka

Generating JS multi dimensional arrays edge cases

tl;dr

Don’t use Array#fill to generate multi dimensional arrays

Use other methods instead, like this for example:

Array.from(
  { length: 3 },
  () => Array.from({ length: 3 })
)
Array(3).fill().map(
  () => Array(3).fill()
)

Long version

One of many ways to generate an array is to use JS Array constructor:

> a = Array(5)
[ <5 empty items> ]
> a.length
5

Array can be filled with some value using Array#fill method

> a = Array(5).fill('value')
[ 'value', 'value', 'value', 'value', 'value' ]
> a = Array(5).fill(0)
[ 0, 0, 0, 0, 0 ]
> a = Array(5).fill()
[ undefined, undefined, undefined, undefined, undefined ]
> a[1] = 42
42
> a
[ undefined, 42, undefined, undefined, undefined ]

So far it works fine, but be careful with filling array with a reference type, like an Array or Object

> b = Array(3).fill(Array(3).fill())
[ [ undefined, undefined, undefined ],
  [ undefined, undefined, undefined ],
  [ undefined, undefined, undefined ] ]
> b[1][1] = 42
42
> b
[ [ undefined, 42, undefined ],
  [ undefined, 42, undefined ],
  [ undefined, 42, undefined ] ]
> b[1] === b[2]
true

Each row of such generated array is actually the same object
It’s literally what the documentations says:

arr.fill(value[, start[, end]])
value: Value to fill the array with. (Note all elements in the array will be this exact value.)

MDN web docs
But not everybody has to know that, right?
And using Array#fill without that knowledge might lead to a hard to track bug in your application

Solutions

Fill the array with some value, and then map through the values

> a = Array(3).fill().map(() => Array(3).fill())
[ [ undefined, undefined, undefined ],
  [ undefined, undefined, undefined ],
  [ undefined, undefined, undefined ] ]
> a[1][1] = 42
42
> a
[ [ undefined, undefined, undefined ],
  [ undefined, 42, undefined ],
  [ undefined, undefined, undefined ] ]

Or use other method, like Array.from

> a = Array.from({ length: 3 }, () => Array.from({ length: 3 }))
[ [ undefined, undefined, undefined ],
  [ undefined, undefined, undefined ],
  [ undefined, undefined, undefined ] ]
> a[1][1] = 42
42
> a
[ [ undefined, undefined, undefined ],
  [ undefined, 42, undefined ],
  [ undefined, undefined, undefined ] ]
>

How to inline specify gem version in the shell

The problem:

I’ve multiple versions of rails gem and I’d like to generate new app but in different version than the default one

> gem list | grep rails
rails (5.2.4, 5.2.3, 4.2.11.1)

Typing just rails new my_app would generate rails app with version 5.2.4

The solution:

It turns out, that it’s possible to specify gem version like this:

rails _4.2.11.1_ new my_app

It works with bundler, rspec and probably any other gem callable from shell as well

bundle _1.17.2_ install
rspec _3.8.0_

Tested on zsh + asdf and zsh + rvm

A way to make autofilled inputs background transparent

Consider the following form

form

There’s an issue with that form - If it’s filled with chrome-stored credentials - it looks like this

filled_ugly_form

Here’s the selector for autofilled input: input:-webkit-autofill

It’s possible to change color of this box, like this:

input:-webkit-autofill,
input:-webkit-autofill:hover, 
input:-webkit-autofill:focus, 
input:-webkit-autofill:active  {
  -webkit-box-shadow: 0 0 0 30px red inset;
}

But this method is not useful, if you want to make the background transparent.

However there’s a way (or rather workaround) to make it transparent, like this:

input:-webkit-autofill,
input:-webkit-autofill:hover, 
input:-webkit-autofill:focus, 
input:-webkit-autofill:active  {
  transition: background-color 5000s;
  -webkit-text-fill-color: #fff !important;
}

This is basically equivalent to transparent background, unless you’ll wait really long for the animation to complete ;)

group models by range of values

The task is to group calls with durations:

  • up to one minute
  • up to two minutes
  • up to five minutes
  • more than five minutes

Consider the following records with various durations

# duration is in seconds
2.times { Call.create(duration: rand(10..60)) }
3.times { Call.create(duration: rand(65..120)) }
4.times { Call.create(duration: rand(130..300)) }
5.times { Call.create(duration: rand(300..3000)) }

We can send such SQL to #group to get this done

def duration_in_minutes_ranges_sql
  <<~SQL
    CASE
      WHEN duration BETWEEN 0 AND 60 THEN 'up_to_one'
      WHEN duration BETWEEN 61 AND 120 THEN 'up_to_two'
      WHEN duration BETWEEN 121 AND 300 THEN 'up_to_five'
      WHEN duration > 300 THEN 'over_five'
    END
  SQL
end

Call.group(duration_in_minutes_ranges_sql).count
=> {
  "up_to_one"=>2,
  "up_to_two"=>3,
  "up_to_five"=>4
  "over_five"=>5,
}

versions: rails: 6.0.0.rc1 pg: 1.1.4 psql: 11.3

validate! can raise various classes of exceptions

class ActiveRecord::Base and module ActiveModel::Validations raise different exceptions on failed validate!

consider the following classes:

class Foo
  include ActiveModel::Validations

  validates :foo, presence: true

  def foo
    nil
  end
end

class Organisation < ActiveRecord::Base
  validates :name, presence: true
end

Take a look on classes of raised exceptions:

[2] pry(#<Api::V1::HooksController>)> Foo.new.validate!
ActiveModel::ValidationError: Validation failed: Foo can't be blank
from /Users/bartoszmaka/.asdf/installs/ruby/2.6.2/lib/ruby/gems/2.6.0/gems/activemodel-6.0.0.rc1/lib/active_model/validations.rb:412:in `raise_validation_error'
[3] pry(#<Api::V1::HooksController>)> Organisation.new.validate!
ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
from /Users/bartoszmaka/.asdf/installs/ruby/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0.rc1/lib/active_record/validations.rb:80:in `raise_validation_error'

The highest common ancestor of those classes is StandardError

[4] pry(#<Api::V1::HooksController>)> ActiveModel::ValidationError.ancestors
=> [ActiveModel::ValidationError,
 StandardError,
 Exception,
 ActiveSupport::Dependencies::Blamable,
 ActiveSupport::ToJsonWithActiveSupportEncoder,
 Object,
 ActiveSupport::Tryable,
 ActiveSupport::Dependencies::Loadable,
 JSON::Ext::Generator::GeneratorMethods::Object,
 PP::ObjectMixin,
 Kernel,
 BasicObject]

[5] pry(#<Api::V1::HooksController>)> ActiveRecord::RecordInvalid.ancestors
=> [ActiveRecord::RecordInvalid,
 ActiveRecord::ActiveRecordError,
 StandardError,
 Exception,
 ActiveSupport::Dependencies::Blamable,
 ActiveSupport::ToJsonWithActiveSupportEncoder,
 Object,
 ActiveSupport::Tryable,
 ActiveSupport::Dependencies::Loadable,
 JSON::Ext::Generator::GeneratorMethods::Object,
 PP::ObjectMixin,
 Kernel,
 BasicObject]

If you’re using form objects - chances are, that you’re importing ActiveModel::Validations. Make sure that you rescue_from the proper exceptions.

Used versions: ruby 2.6.2p47, Rails 6.0.0.rc1

Another way to use absolute paths in js 'require'

consider following files structure:

...
▾ helpers/
    timeHelper.js
    timeHelper.test.js
▾ services/
    handleSlackCommand.js
    handleSlackCommand.test.js
  index.js
  package.json
  ...

in the package.json add:

{
  ...
  "dependencies": {
    "dotenv": "^6.2.0",
    "express": "^4.16.4",
    ...
    "helpers": "file:helpers", <-- this line
  },
}

now in the services/handleSlackCommand.js I can use

const { whatever } = require('helpers/timeHelper');

instead of

const { whatever } = require('../helpers/timeHelper');

integer limit is adjustable in activerecord migration

create_table 'example' do |t|
  t.integer :int                 # int (4 bytes, max 2,147,483,647)
  t.integer :int1, :limit => 1   # tinyint (1 byte, -128 to 127)
  t.integer :int2, :limit => 2   # smallint (2 bytes, max 32,767)
  t.integer :int3, :limit => 3   # mediumint (3 bytes, max 8,388,607)
  t.integer :int4, :limit => 4   # int (4 bytes)
  t.integer :int5, :limit => 5   # bigint (8 bytes, max 9,223,372,036,854,775,807)
  t.integer :int8, :limit => 8   # bigint (8 bytes)
  t.integer :int11, :limit => 11 # int (4 bytes)
end