Today I Learned

Passing multiple exceptions to rescue

To rescue multiple classes of exceptions, you have to pass them as a list to rescue method. Passing as an array won’t work, unless you prefix it with * (effectively breaking it into a list)

# Works
begin
  # some code here    
rescue ExceptionA, ExceptionB
  puts "uff, thanks!"
end

# Doesn't work
begin
  # some code here    
rescue [ExceptionA, ExceptionB]
  puts "uff, thanks!"
end

# Works
EXCEPTIONS = [ExceptionA, ExceptionB]
begin
  # some code here    
rescue *EXCEPTIONS
  puts "uff, thanks!"
end

How to run build on CircleCI recurrently

To run CircleCI build nightly (or at any interval we want), we need a couple of things. First we need to get CircleCI token that will allow us to access CircleCI API.

Next we need a script, that will trigger the build. In example below we also provide an extra build parameter RUN_ALL_TESTS that effectively allows us to conditionally run some specs we do not want to run during regular build.

namespace :ci do
  desc 'Runs build on CircleCI'
  task build: :environment do
    command = 'curl -X POST --header "Content-Type: application/json" ' \
      "--data '{\"build_parameters\": {\"RUN_ALL_TESTS\": \"true\"}}' " \
      'https://circleci.com/api/v1.1/project/github/SomeOwner/' \
      "some_project/tree/master\?circle-token\=#{ENV.fetch('CIRCLE_CI_TOKEN')}"

    `#{command}`
  end
end

Last thing is to schedule the build. Simplest way is to use Heroku Scheduler and just configure it to run rake ci:build command.

Edit: No longer valid! For Circle 2.0 we can use workflows for the same effect!

Making factory_bot work with read-only models

In one of our apps we need to ensure that all models are in read-only mode. Still, for testing purposes we need to be able to create instances of such models. Following code makes it possible.

# spec/models/application_record_spec.rb
require 'rails_helper'

RSpec.describe ApplicationRecord do
  it 'ensures all descendants are read-only' do
    Unit = Class.new(ApplicationRecord)

    expect(Unit.new.readonly?).to eq true
    expect { Unit.create! }.to raise_exception(ActiveRecord::ReadOnlyRecord)
  end

  it 'allows creating objects using factories' do
    Unit = Class.new(ApplicationRecord)

    expect { read_only(:unit) }.to change { Unit.count }.by(1)
  end
  
  it 'disallows updating objects' do
    Unit = Class.new(ApplicationRecord)
    unit = read_only(:unit)

    expect { unit.update_attributes!(name: 'New name') }.to \
      raise_exception(ActiveRecord::ReadOnlyRecord)
  end
end

# spec/support/factory_bot.rb
module FactoryBot
  module Strategy
    class ReadOnly <  Create
      def result(evaluation)
        is_readonly = evaluation.object.readonly?
        evaluation.object.define_singleton_method(:readonly?, -> { false })

        super.tap do |object|
          object.define_singleton_method(:readonly?, -> { is_readonly })
        end
      end
    end
  end
end

FactoryBot.register_strategy(:read_only, FactoryBot::Strategy::ReadOnly)

RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end

# spec/factories/units_factory.rb
FactoryBot.define do
  factory :unit do
    sequence(:name) { |i| "Unit #{i}" }
  end
end

Configuring capybara-screenshot with Heroku

Due to many problems with our capybara-based automations we execute in Sidekiq on Heroku, we did need some visual feedback of what is going wrong. Unfortunately due to read-only nature of Heroku’s file system we did need to customize capybara-screenshot a bit to achieve this functionality.

# initializers/capybara_screenshot.rb

Capybara::Screenshot.s3_configuration = {
  s3_client_credentials: {
    access_key_id: ENV['DEBUG_BUCKET_S3_KEY'],
    secret_access_key: ENV['DEBUG_BUCKET_S3_SECRET'],
    region: ENV['DEBUG_BUCKET_S3_REGION']
  },
  bucket_name: ENV['DEBUG_BUCKET_S3_BUCKET']
}

# Default available methods use lunchy gem. 
# We do neither need nor want that. 
# Hence introducing simplified version.
Capybara::Screenshot.class_eval do
  def self.save_screenshot
    new_saver(Capybara, Capybara.page, false).save
  end
end

Capybara.save_path = '/tmp' # Writeable directory on heroku

Then, we have decided to rescue and re-raise all exceptions, but also save a screenshot in the process…

#...
  automation.perform
rescue => exception # rubocop:disable Style/RescueStandardError
  Capybara::Screenshot.save_screenshot
  raise exception

Selenium::WebDriver::Error::NoSuchDriverError

We’ve recently experienced some peculiar errors when processing capybara-based automation scripts on Heroku. Most of the time, the error returned did not show anything useful…

Selenium::WebDriver::Error::NoSuchDriverError: no such session

yet for a brief period of time, following error was reported when attempting to access capybara session

Selenium::WebDriver::Error::UnknownError: unknown error: session deleted because of page crash
from tab crashed

Finally, after spotting this comment we’ve reduced chrome window size from 1920,1200 to 1440,900 and the problem is no longer present.

The root reason is unknown, but most likely it is at least partially related to running out of memory (reference). Most of recommendations when using docker in this scenario, was to increase shm-size, by providing --shm-size=2g to docker run. That was not an option for us though…

Hope it helps in case you run into similar situation.

Fetching single file from private repository (+ CI)

We had a situation in which we did need to write an API for an app, but decided to keep it in separate repository and deploy as separate app. This API would use original app’s database in read-only mode. The problem was how to prepare database structure for testing purpose. We’ve decided to use structure.sql from original app, but we did want to keep it in sync somehow.

First thing was to get Github’s personal access token

Then, locally we’ve just altered bin/setup to include following code

require 'dotenv/load'
#...
puts "\n== Importing database structure =="
  Dotenv.load
  system! %{curl -H 'Authorization: token #{ENV.fetch('DEVELOPER_ACCESS_TOKEN')}' -H 'Accept: application/vnd.github.v3.raw' -O -L https://api.github.com/repos/OtherApp/other_app/contents/db/structure.sql}
  system! 'mv structure.sql db/structure.sql'

  puts "\n== Preparing database =="
  system! 'RAILS_ENV=test bin/rails db:drop db:create db:structure:load'

while for CircleCi we did need to add following entry to .circleci/config.yml

      - run:
          name: setup-db
          command: |
            curl \
              --header "Authorization: token ${DEVELOPER_ACCESS_TOKEN}" \
              --header "Accept: application/vnd.github.v3.raw" \
              --remote-name \
              --location https://api.github.com/repos/OtherApp/other_app/contents/db/structure.sql
            mv structure.sql db/structure.sql
            bundle exec rake db:create db:structure:load --trace

Obviously do not forget to ensure correct value for DEVELOPER_ACCESS_TOKEN in your .env and on CircleCI.

Two ways of initiating team building during a meetup

The first way of encouraging people to get to know each other during a meetup is to begin the event with the networking part. You can start a discussion about the topics of the presentations from the agenda. By asking the crowd about their suggestions about the aforementioned matter you give people some space to articulate their thoughts and share opinions with one another.

In my opinion, this approach may not always work so well. Why? Because a lot of people will not engage in a conversation as for some of them expressing their thoughts in front of people they do not know so well could be stressful. They might also feel like they do not know the topic well enough to give any constructive comments or they are simply not interested in a given subject. We also should take into consideration that some people are simply shy and do not like to be the center of attention. In the end, people form groups by themselves, always made up of those who are more or less acquainted with, that is why the problem of pushing people out of their comfort zones to mingle appears.

So how to help them in talking to one another and connect more people during the meetup? Let’s take a closer look at the second way of initiating team building during an event like this.

You can create groups at random, which gives you a higher chance of having small groups of people who have never met before. The participants will find interesting topics to discuss by themselves, especially if you help them a bit by printing out and distributing some typical IT topic ideas. Never strictly dictate the topic, though. They should feel like in a normal, friendly meeting. From my experience, this is a much better solution. This way of organizing meetups gives people more opportunities to talk. We can find a subject which we are interested in. So for shy, not confident people, we are lowering the bar for joining the conversation, speaking up and sharing their opinions.

Summary/comment: To sum everything up, I had the opportunity to participate in both of the above-mentioned types of meetings (these were meetings of the Ruby Users Group) and in my humble opinion, the latter was in many ways superior when it came to making people talk to each other and enabling them to get to know each other better.

Simplifying Circle CI setup for Crystal

Rather than setting up test environment for Crystal by yourself you can use official docker image:

version: 2
jobs:
  build:
    docker:
      - image: crystallang/crystal:0.25.0
    steps:
      - checkout

      - restore_cache:
          keys:
            - shards-{{ checksum "shard.lock" }}
      - run:
          name: Install shards
          command: |
            shards check || shards
      - save_cache:
          key: shards-{{ checksum "shard.lock" }}
          paths:
            - lib/
            - .shards/

      - run:
          name: Run specs
          command: crystal spec

What to look for when your factory is missing id

So yesterday evening I’ve spotted a factory that was missing its _factory suffix. Easy peasy, quick rename and off we go… yes.. no… suddenly factories linting failed with following error

FactoryGirl::InvalidFactoryError:
  The following factories are invalid:

  * lockoff - PG::NotNullViolation: ERROR:  null value in column "id" violates not-null constraint
  DETAIL:  Failing row contains (null, null, null, {}, {}, null, null, 2018-06-13 20:20:02.606616, 2018-06-13 20:20:02.606616).
  : INSERT INTO "lockoffs" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" (ActiveRecord::StatementInvalid)

null value in column "id" ? What? How? After checking absolutely everything reasonable, I’ve checked column definitions. It turned out, that during selecting changes for structure.sql, somebody forgot to add one important piece to commit, that was effectively preventing automatic generation of consecutive primary key values.

--
-- Name: lockoffs id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY lockoffs ALTER COLUMN id SET DEFAULT nextval('lockoffs_id_seq'::regclass);

If you ever face similar issue, do not forget to look for something like that as well…

Dealing with TabBar scenes

When we are using react-navigation for navigation in our React-Native app, often we use TabBar Navigator, because of the simplicity. But unfortunately sometimes it brings restrictions. Main one: Every Tab is mounted from begining and will never unmount by default.

To handle Tab changes we can use less known listeners which helps very much in building smooth and user-friendly apps:

componentDidMount() {
  this._navListener = this.props.navigation.addListener('willBlur', () =>
    this.scrollView.scrollTo({ x: 0, animated: true }),
  )
}

componentWillUnmount() {
  this._navListener.remove()
}

Docs reference

P.S. Always remember to remove listeners incase of memory leak :)

Joining multiple change expectations in rspec

To look for multiple changes on multiple objects, we could aggregate those changes and verify them en-masse, i.e.

expect { SyncReservations.call }.to \
  change { [property_1, property_2].map{ |p| p.reload.synced_at }.
    to([Time.new(2018, 1, 1, 16, 35), Time.new(2018, 1, 1, 16, 35)])

This will pick partial changes though (if only one value changes), so it is not recommended. Another way is nesting expectations, i.e.

expect {
  expect { SyncReservations.call }.to \
    change { property_1.reload.synced_at }.
      to(Time.new(2018, 1, 1, 16, 35))
}.to change { property_2.reload.synced_at }.
  to(Time.new(2018, 1, 1, 16, 35))

This will work, but you will get only one failure at a time. Still it is useful for joining conflicting expectations (i.e. to with not_to). For non conflicting ones, following syntax is recommended:

# using .and
expect { SyncReservations.call }.to \
  change { property_1.reload.synced_at }.
    to(Time.new(2018, 1, 1, 16, 35)).and \
  change { property_2.reload.synced_at }.
    to(Time.new(2018, 1, 1, 16, 35))

or

# using &
expect { SyncReservations.call }.to \
  change { property_1.reload.synced_at }.
  to(Time.new(2018, 1, 1, 16, 35)) &
  change { property_2.reload.synced_at }.
  to(Time.new(2018, 1, 1, 16, 35))

Properly yielding responses in Rspec

We had some problem with configuring response that should be yielded to block… after a short investigation, here are outcomes

allow(collection).to receive(:each).and_yield([1, 2])

will yield array ([1, 2]) to one block variable

allow(collection).to receive(:each).and_yield(1, 2)

will yield two values (1, 2) to two block variables

allow(collection).to receive(:each).and_yield(1).and_yield(2)

will yield two consecutive values (1, 2) to one block variable, twice

Clicked link and still on the same page (capybara)

When writing some automation i’ve tried to navigate to next page using a link…

click_on '6366'

…but saving and showing screenshot resulted in the same page being displayed. The reason was simple, link’s target was a new window (or tab). One way to handle this, is to switch context of window. It is as easy as

switch_to_window(window_opened_by { click_on '6366' })

but if you do not need/want to play around with multiple window contexts, consider following solution

visit find_link('6366')['href']

This way you will stay with current window context :)

Using mocks library in Crystal

Mocks cannot be defined in describe/it body so this won’t work:

describe Bot::Slack do
  describe "#post_response" do
    it "sends formatted message" do
      Mocks.create_mock JsonClient do
        mock self.post(url, body)
      end
      # testing here

and you will get: can't declare class dynamically.

Correct way:

Mocks.create_mock JsonClient do
  mock self.post(url, body)
end

describe Bot::Slack do
  describe "#post_response" do
    it "sends formatted message" do
      # testing here

Difference between <<~ and <<-

If you use heredoc in ruby you can choose one of two syntaxes the first <<- it takes all whitespace before stared of text so

def custom_html  
  <<-HTML
    <html>
      <body>
      </body>
    </html>
  HTML
end    

so if you will get string with all withspaces " <html>\n <body>\n </body>\n </html>\n" second <<~ will strip it so

def custom_html  
  <<~HTML
    <html>
      <body>
      </body>
    </html>
  HTML
end

will give you will get text "<html>\n <body>\n </body>\n</html>\n"

Always remember to remove listeners with MobileApp

It’s good practice to remember about EventListeners, especially in mobile apps. Just a few additional lines give you several times less probability to crash the app.

const onLogOut = () => {
  if (Platform.OS === 'ios') {
    PushNotificationIOS.removeEventListener('notification')
    PushNotificationIOS.removeEventListener('register')
  } else {
    androidNotificationListeners.map(listener => listener.remove())
  }
}

Custom validation error messages when using forms

Normally for rails model (i.e. Brand) you would do:

en:
  activerecord:
    errors:
      models:
        brand:
          attributes:
            subdomain:
              invalid: "should contain only 'a-z', '0-9' or '-' but not as starting or ending character"

When using validations in forms you need a small tweak:

  • activerecord => activemodel
  • brand => brand_form (assuming your form is BrandForm)

Final translation:

en:
  activemodel:
    errors:
      models:
        brand_form:
          attributes:
            subdomain:
              invalid: "should contain only 'a-z', '0-9' or '-' but not as starting or ending character"

Note: invalid is the key for validation type, full list here

Capybara::Selenium

When you not only use Capybara for rspec, you would like to change the wait time to visit the page, so I think it can be useful.

   Capybara.register_driver :selenium do |app|
      client = Selenium::WebDriver::Remote::Http::Default.new
      client.read_timeout = 20
      Capybara::Selenium::Driver.new(app, browser: :chrome,  http_client: client)
    end

Problems matching body params using stub_request?

Surprised that

HTTParty.post(
  'http://example.com/something',
  headers: { content_type: 'application/x-www-form-urlencoded' },
  body: { name: 'Tony', surname: 'Stark'}
)

is not matched by

stub_request(:post, 'http://example.com/something').
  with(
    headers: { 'Content-Type' => 'application/x-www-form-urlencoded' },
    body: { name: 'Tony', surname: 'Stark' }
  ).to_return(body: 'OK')

?

Remember, that HTTP Client does not send actually a hash, and stub_request does not know automagically which encoding you intended to use for your body params. You need to make it explicit, i.e.

stub_request(:post, 'http://example.com/something').
  with(
    headers: { 'Content-Type' => 'application/x-www-form-urlencoded' },
    body: URI.encode_www_form({ name: 'Tony', surname: 'Stark' })
  ).to_return(body: 'OK')

Missing your POST data in controller ?

So you can see JSON payload in inspector…

{days: [{date: "2018-05-18", unitPricing: {1: 285, 2: 285}},…]}

…but cannot see it in controller?

pry(#<Apis::V1::PricingBulkUpdatesController>)> params
=> <ActionController::Parameters {"controller"=>"apis/v1/pricing_bulk_updates", "action"=>"create"} permitted: false>

Remember to POST it with 'Content-Type': 'application/json' header and sell yourself a nice, juicy facepalm :)

Using have_selector with css and exact: option

Using have_selector with css selector and exact: option basically does not work.

I.e.

expect(page).to_not have_content('a.hidden', exact: 'Push pricing to CB')

yields

The :exact option only has an effect on queries using the XPath#is method. Using it with the query "div.hidden" has no effect.

We can use :contains("") css selector though!

expect(page).to_not have_content('a.hidden:contains("Push pricing to CB")')