Working with WeatherKit from Ruby

The Dark Sky API was recently shutdown by Apple, who acquired the iOS app and API a couple of years ago.

Working with the new API from Apple, WeatherKit, may be easy in Swift, but is more complex due to the introduction of JWT for authentication and the use of Apple developer credentials.

Here is how I was able to get the REST API working for my Rails app, using the Tenkit gem

bundle add tenkit

Credentials

in config/initializers/tenkit.rb:

Tenkit.configure do |c|
  c.team_id = Rails.application.credentials.tenkit[:apple_developer_team_id]
  c.service_id = Rails.application.credentials.tenkit[:apple_developer_service_id]
  c.key_id = Rails.application.credentials.tenkit[:apple_developer_key_id]
  c.key =  Rails.application.credentials.tenkit[:apple_developer_private_key]
end

The credentials needed to construct a JWT (done for us by the Tenkit gem) are gathered from your Apple Developer account. I’d recommend putting them in the Rails app credentials (as used above), or ENV variables if that is more convenient for your project.

Below, we’ll gather the values needed, then display them in the credentials file.

Gathering

apple_developer_team_id - This is your Apple Team ID, taken from the top-right of your developer account page:

apple_developer_service_id - This is a Service ID that you’ll need to create: https://developer.apple.com/account/resources/identifiers/list/serviceId It will be in the same style as an App ID, as a reverse domain name, e.g. “uk.co.cowlibob.fancypantsweather”

apple_developer_key_id - Generate a new key: https://developer.apple.com/account/resources/authkeys/add Give it a name (not important but descriptive to you) and make sure to check “WeatherKit”

Make sure you download the key and keep it safe for the next credential. Use the generated Key ID as the value for apple_developer_key_id.

apple_developer_private_key - This is the freshly downloaded private key, a .p8 file. P8 is a format that presents the private key in plain text (.p12 is a binary format). You may have seen these before, they look something like this:

-----BEGIN PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA
-----END PRIVATE KEY-----

Configuring the credentials

You can now open the credentials file for edit and add these. I use vim to edit these, as specified by the env variable EDITOR. We can use the | symbol to start a block of text in the YAML file, much like a heredoc in Ruby.

> rails credentials:edit --environment development

# aws:
#   access_key_id: 123
#   secret_access_key: 345
tenkit:
  apple_developer_team_id: 'A11AA1AAA1'
  apple_developer_service_id: 'uk.co.cowlibob.fancypantsweather'
  apple_developer_private_key: |
    -----BEGIN PRIVATE KEY-----
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAA
    -----END PRIVATE KEY-----
  apple_developer_key_id: 'BBB11BBBBB'

Making a request

client = Tenkit::Client.new
lat = "53.4138684"
lon = "-1.5833708"

report = client.weather(
  lat,
  lon,
  data_sets: [:forecast_hourly]
)

ap report.weather.forecast_hourly.hours

That’s all there is to it once setup. You can also request multiple and different data sets. There are some undocumented features of the API such as date ranges that I’ll detail in a future post. For now, this is what the Tenkit gem supports.

Problems with SwiftUI Map View

The SwiftUI Map view is a great timesaver, but has some odd issues.

When two Map views are visible, (e.g. siblings in a vStack):

  • Both views will receive input applied to one, e.g. panning by sliding a finger across one of the views. Here, the views are in sync with each other.
  • Zoom by pinch gesture (in or out) does not work. In fact, coordinateRegion supplied to the Map() view has no effect either.

This extends to a scenario with a map on a presented view, where the parent view also displays a map.

Time to dig in and wrap MKMapView.

Using Screen to manage long-running server tasks

Occasionally I need to start a long running batch process on a server, such as a rake task to sync data from one system to another.

Leaving an SSH session open is not always possible, so here’s how to do it:

ssh server
screen -S peach
bundle exec rake long_running_task

Hitting ctrl-a then d will put the task in the background and you can then close the SSH session.

To reconnect to the long running task:

ssh server
screen -r peach

Note that “peach” is just a label for the screen session; it could be anything such as “long_running_task”, “elk123” or “404_cake_not_found”

I love how much fastlane.tools automates for you, but when you login with the incorrect credentials, it’s difficult to logout. github.com/fastlane/…

I really enjoyed the first episode of www.rooftopruby.com

On the theme of no magic, a colleague pointed out authentication-zero as an interesting alternative to devise for authentication. I’ve used and enjoyed clearance before, but am liking the simplicity of generated code.

Starting again

I’m starting a new rails project, and have been evaluating bullettrain.co to kick-start the project.

It looks really good, and has powerful generators and theming.

However, I’ve been bitten before by building an app using a 3rd party project (refinerycms anyone?) and limited by the dependencies, and lost in it’s magic.

As such, having built a lot with bullettrain in just one day, I’m jumping back to vanilla rails 7.0.4, with tailwindcss (another experiment for me).

PG Gem woes, not for the first time.

If you encounter errors installing pg gem on an Apple Silicon mac:

checking for libpq-fe.h... no
Can't find the 'libpq-fe.h header

Then try the below commands firstly; to ensure the homebrew version of libpg is used:

echo 'export PATH="/opt/homebrew/opt/libpq/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
gem install pg -- --with-pg-config=`which pg_config`

and follow up with bundle install once successful.

This worked for me with the Postgres.app (postgres 14), and having previously installed libpq:

brew install libpq@14.6

I thought I’d posted about this before, but I’m currently looking at replacing my Linode instance with a VPS with these guys: ecowebhosting

It looks really compelling, and I don’t see any green mainstream alternative; any recommendations out there?

Safer rails deployments

I’ve been using a small script to deploy the rails apps I maintain (both work and side projects) for a while now.

It’s helped cut the dud deployments due to two issues:

  • Leaving debugger statements on the code, where the debugging gems are not deployed to production.
  • Typos or cut text that leave invalid syntax, not caught during development as the codebase is loaded lazily.

I’ll expand the script at some point as I see other issues crippling my deployments.

#! /bin/sh
TARGET=${1:-production}
BRANCH=${2:-main}

test `ag byebug | grep "app\/.*\.rb" | wc -l` = 0
if (($? == 1)); then
  echo "Byebug command left in place:"
  echo `ag byebug | grep "app\/.*\.rb"`
  exit
fi

DEPLOY_CHECK=true bin/rails runner "puts %q{Loaded successfully.}" RAILS_ENV=production
if (($? == 1)); then
	echo "Syntax error, app could not execute."
	exit
fi

echo "deploying"
git pull origin $BRANCH && git push origin $BRANCH && bundle exec cap $TARGET deploy BRANCH=$BRANCH

Note, DEPLOY_CHECK is used to prevent eager loading in the development environment:

config/development.rb

Rails.application.configure do
    ...
    config.eager_load= ENV.fetch('DEPLOY_CHECK', false)
    ...
end

Another note, ag (https://github.com/ggreer/the_silver_searcher) is great.

Security changes in Psych gem

Upgrading the ruby instance for correctedtime.com uncovered this surprise; Psych (YAML library) won’t parse YAML specifying arbitrary ruby classes. As I use a YAML encoded column to store result data, the upgrade needed to specify allowed classes.

Specified in production.rb and it’s counterparts:

config.active_record.yaml_column_permitted_classes =  ['Symbol', 'Event::Row', 'Event::BoatRow']

Using Cloudinary with ActionText in Rails 6

I’ve been playing with Cloudinary for image hosting in a web app (direct upload to them). They have a great set of features, whereby a transformation can be specified as part of the image URL.

Pairing this with Trix/ActionText in Rails 6 was simple enough, but there’s a catch later on for embedded images.

Dependencies

First we’ll get ActionText and the Cloudinary gem up and running.

> rails action_text:install
> bundle add cloudinary

Configuration

Then add you cloudinary credentials to the environment. You can obtain the values from the cloudinary.yml and add them to your production (or development) encrypted credentials file:

> rails credentials:edit --environment=production

Here are the keys you need, with values extracted from the cloudinary.yml above:

  cloudinary:
      cloud_name: "example_app"
      api_key: "444444444444444"
      api_secret: "AAAAAAAA-BBBBBBBBBBBBBBBBBB"
      secure: true

With those values safely stored, we need to load them. In a new initializer:

# config/initializers/cloudinary.rb

Cloudinary.config do |config|
  config.cloud_name = Rails.application.credentials.cloudinary[:cloud_name]
  config.api_key = Rails.application.credentials.cloudinary[:api_key]
  config.api_secret = Rails.application.credentials.cloudinary[:api_secret]
  config.secure = true
  config.cdn_subdomain = true
end

Now specify that ActiveStorage should use Cloudinary, editing config/storage.yml. You can also specify other services and local disk:

# config/storage.yml

cloudinary:
    service: Cloudinary
    
local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

and config/environments/production.rb

# config/environments/production.rb

config.active_storage.service = :cloudinary

Javascript

To use direct uploading (uploaded images never touch your server), you will need to include the ActiveStorage JS in app/javascript/packs/application.js. We’re also going to include the Trix editor JS code and ActionText which supports it:

# app/javascript/packs/application.js

require("@rails/activestorage").start()
require("trix")
require("@rails/actiontext")

Ruby & Erb

So now we’re ready for the editor and display of edited content. In our edit .erb template, we will use the trix editor to manage html content. Let’s assume the age-old Post model has title and content attributes, both of type text.

Add the following to your model:

# app/models/post.rb

has_rich_text :content

Note, if you have been upgrading an older Rails project, ensure you have the following in the head section of your application layout <%= csrf_meta_tags %>. This ensures ActionText can send requests to your app without authentication token errors.

# new.html.erb

<%= form_for Post.new do |f| %>
    <%= f.text :title %>
    <%= f.rich_text_area :content %>
    <%= f.submit %>
<% end %>

The rich_area_text input type gives us the wonderful Trix editor, with drag and drop image support. To display the Post, we have the following:

# show.html.erb

<h1><%= @post.title %></h1>

<%= @post.content %>

Done yet?

Not quite. Remember I mentioned a catch? We need to ensure that the Cloudinary image is displayed properly. At the moment, a local URL is likely specified for any images embedded in content. Open up a file that the ActionText install generated for us and modify to use Cloudinary and replace image_tag calls:

# app/views/active_storage/blobs/_blob.html.erb
--- <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
+++ <% if local_assigns[:in_gallery] %>
+++     <%= cl_image_tag blob.key, width: 800, height: 600, c_limit: true %>
+++ <% else %>
+++     <%= cl_image_tag blob.key, width: 1024, height: 768, c_limit: true %>
+++ <% end %>

Note that I chose to remove the ternary operator conditional and replace it with two separate cl_image_tag calls. This allows clearer differences between in_gallery and other views. The additional parameters to cl_image_tag are passed to the cloudinary API as transforms. FYI, c_limit means that an image will not be resized above it’s original size, so the width and height are effectively max values.

How about now?

Yep, we’re done. Assuming you have no CSRF token issues, you should be good to go. Try dragging an image into the editor. It will be immediately uploaded to cloudinary and referenced when your post is submitted.

One thing I’d still like to resolve is the occasional error, which seems to be timing related; submitting the form before the image is uploaded and acknowledged by Cloudinary results in a missing image in the rendered content. We can probably disable the form submit button/action while the upload in in progress, but that will require implementing event hooks. I’m not sure if ActionText can handle that or not yet.

Text editors for Rails

I’ve been evaluating different text editors for Ruby on Rails work on macOS.

Sublime Text 4 is a paid upgrade, which is fine. However, it was automatically upgraded to and I now get the nagware prompts. Which prompted me to check if the upgrade from 3 to 4 was my best option.

So I’ve looked at Nova, which I loved, and would like to support. However, I’m now trialing RubyMine, which I really didn’t want to like, but it’s brilliantly thought out for what I do. Maybe an actual IDE is what I’m looking for…

I’ve worked before in Vim. It appeals to me greatly, but never got completely comfortable with the inter-file navigation, plug-ins and all. I’ll give it another shot later.

But for now, it looks like RubyMine could be getting my business.

Slow text paste into irb

I’ve struggled for a while with slow pasting of text into iterm2. Last week, I tried the stock macOS Terminal app, but found the same issue.

It turns out this is not related to the terminal client (iTerm 2 has options to slow down paste for compatibility), or event to zsh (ohmyzsh had a bug related to this a few years ago), but to irb.

To work around the slow pasting of multiline text into irb disable multiline

New iMac looks great but…

The new M1 iMacs look interesting. I wonder if they will be able to act as an external screen for another computer, as display mode did years ago.

I wish the chin had been removed, or at least coloured in the same vibrant hues as the backplane.

The new ultra wide camera on the iPad Pro with “center stage” would have been great for the iMac, but it just gets a 1080p camera.

I wonder how the next iMac (27in equivalent) will differ, and how the future revisions of the 24in model will evolve?

A new Posterous?

world.hey.com/jason/hey…

Hey.com are experimenting with blogging via email; something that feels very like Posterous, before they were acquired by Twitter and shutdown. It’s an idea I’ve kicked around with before, but never seen a way to make it pay as a service.

Handling attachments and signatures always seemed like a fun task, but I see they only accept posts from your hey.com account. This (I presume) reduces complexity over email client compatibility and spam handling.

I missed this news at the end of June. US Sailing is adopting the RYA Portsmouth Yardstick system for sailing dinghy handicaps.

That really means a tonne of work I had in progress is not required to release to the US AppStore!

www.correctedtime.com/vs

External display breaks the law!

I’ve been struggling with Fitts’ Law since I added an external monitor above my MacBook Pro.

Physically, the screens are arranged like this, with the MBP on a keyboard self of a table-top standing desk. The external monitor is on a height adjustable vesa mount, part of the desk.

The downside of this arrangement is that I’m unable to easily access the menubar for the bottom screen, as the mouse cursor just flies straight through it onto the top display.

I’ve tried a logical side-by-side arrangement in the displays preferences, but it breaks my head getting from top to bottom by going left to right.

My current work-around is to offset the displays logically like this, so that I get a “sticky” corner that my cursor can get trapped in. Then I just need to scroll right to the menu I need.

SwiftUI Keyboard Layout

As a n00b iOS developer (Learning to apply Swift/SwiftUI), I have been looking at how to adjust the layout while the software keyboard is open. This looks like a brilliant solution. stackoverflow.com/a/5840260…

Here for posterity: gist.github.com/cowlibob/…

Trying https://www.focusmate.com, with good results so far.

In this weeks Hacker Newsletter (448) I saw an article about letting others watch you work.

focusmate.com is a free service to pair with a stranger via video chat to get your work done, separately, together. Book a session on the calendar, join the video, communicate your goals verbally, then knuckle down.

The mind boggles, but it worked for my first session. I’ll be trying it again soon.

I’m using the Cactus layout, but have customised the CSS, with style inspiration from alexcican.com/blog The the colours though, are more Fruit Pastilles lolly than anything else.

blog.cowlibob.co.uk

Mastodon