Warning: The magic method SFML_Singleton::__wakeup() must have public visibility in /home/public/wp-content/plugins/sf-move-login/inc/classes/class-sfml-singleton.php on line 72

Warning: Cannot modify header information - headers already sent by (output started at /home/public/wp-content/plugins/sf-move-login/inc/classes/class-sfml-singleton.php:72) in /home/public/wp-includes/feed-rss2.php on line 8
Thoughts, etc. https://www.munderwood.ca Tracking the occasional random walk Wed, 10 Feb 2021 21:26:34 +0000 en-CA hourly 1 https://wordpress.org/?v=5.7.2 https://www.munderwood.ca/wp-content/uploads/2016/03/photo-150x150.jpg Thoughts, etc. https://www.munderwood.ca 32 32 JSON responses as PHP arrays in Laravel 8 tests https://www.munderwood.ca/index.php/2021/02/10/json-responses-as-php-arrays-in-laravel-8-tests/ https://www.munderwood.ca/index.php/2021/02/10/json-responses-as-php-arrays-in-laravel-8-tests/#respond Wed, 10 Feb 2021 21:26:33 +0000 https://www.munderwood.ca/?p=305 [Read more...]]]> I’m in the process of upgrading an API code base that has been in production since Laravel 5, and continues to evolve. The bulk of the update to version 8 went smoothly, but one breaking change that I couldn’t find documented anywhere held me up for some time this afternoon.

A great many of the tests use a pattern of getting a response from an endpoint, running $data = $response->decodeResponseJson(), and then making assertions against the resulting PHP array in $data. Unfortunately, in Laravel 8 the return type is now \Illuminate\Testing\AssertableJsonString, and I kept running into errors such as:

Argument #2 of PHPUnit\Framework\Assert::assertArrayHasKey() must be an array or ArrayAccess

I made various attempts to convert the output of decodeResponseJson into an array (and being confused as to why it didn’t work, since AssertableJsonString implements ArrayAccess), but in the end the solution was simple.

Instead of refactoring every existing test to use Laravel’s new JSON-based assertions, I merely had to change each call to decodeResponseJson to just json. That is, switching to

$data = $response->json();

was all it took.

]]>
https://www.munderwood.ca/index.php/2021/02/10/json-responses-as-php-arrays-in-laravel-8-tests/feed/ 0
MMSI parsing library: mmsi.js https://www.munderwood.ca/index.php/2020/02/11/mmsi-parsing-library-mmsi-js/ https://www.munderwood.ca/index.php/2020/02/11/mmsi-parsing-library-mmsi-js/#respond Wed, 12 Feb 2020 03:53:40 +0000 http://www.munderwood.ca/?p=289 [Read more...]]]> A Maritime Mobile Service Identity is a nine-digit number that can be broadcast by a ship or other vessel to provide some information about itself and its current location, to anyone within radio range. I recently published on NPM a small JavaScript library for validating MMSIs and parsing them to extract details about the country or jurisdiction to which they are assigned.

In the initial version, the primary usefulness of the library lies in two parts. First is its knowledge of which three digits of a given MMSI represent a MID code, since the location of the Marine Identification Digits is not the same in every MMSI. Second is its ability to provide the ISO 3166 code for the jurisdiction,1 along with its name and (if different) full name.

For example, given the MMSI “316123456”, with mmsi.js you can find out that the MID code is “316”, which corresponds to a code of “CA” and a country of “Canada”. With a different format, “986681234” has a MID code of “668”, corresponding to São Tomé and Príncipe, with a code of “ST” and a full name of “São Tomé and Príncipe (Democratic Republic of)”.

Additional information encoded in MMSIs includes details about the type of transceiver to which the code is assigned, such as vessel, search-and-rescue aircraft, or shore station. I plan to add more functionality to extract these sorts of details, as well as to improve the detection of valid values beyond simply checking for nine numeric digits.

  1. There are a very small number of jurisdictions assigned MID codes by the ITU that do not have ISO codes, such as the Crozet Archipelago, Kerguelen Islands, and Saint Paul and Amsterdam Islands, all part of the French Southern Territories. In these few cases, reasonable adaptations have been assigned.
]]>
https://www.munderwood.ca/index.php/2020/02/11/mmsi-parsing-library-mmsi-js/feed/ 0
Measuring distances on Leaflet maps in Vue applications https://www.munderwood.ca/index.php/2019/04/25/measuring-distances-on-leaflet-maps-in-vue/ https://www.munderwood.ca/index.php/2019/04/25/measuring-distances-on-leaflet-maps-in-vue/#comments Thu, 25 Apr 2019 18:08:05 +0000 http://www.munderwood.ca/?p=279 [Read more...]]]> Vue2Leaflet is a great library that provides a very straightforward approach to using Leaflet maps in Vue applications. Leaflet.PolylineMeasure is a great Leaflet plugin that adds a tool to allow the user to measure distances along (poly)lines by drawing them on top of the map.

Today I released the initial version of a Vue2Leaflet plugin to provide a simple wrapper of the polyline measurement tool as a Vue2Leaflet plugin, via the vue2-leaflet-polyline-measure component. Usage is simple:

<l-map>
  <l-polyline-measure :options="{ showUnitControl: true }" position="bottomright"/>
  <!-- other map components -->
</l-map>

The options  object is passed directly to the underlying library, so can be any of the polyline measurement options. The position  parameter places the control in a corner of the map based on the available Leaflet control options.

]]>
https://www.munderwood.ca/index.php/2019/04/25/measuring-distances-on-leaflet-maps-in-vue/feed/ 2
Side effects of js-data-http and async operations https://www.munderwood.ca/index.php/2018/06/15/side-effects-of-js-data-http-and-async-operations/ https://www.munderwood.ca/index.php/2018/06/15/side-effects-of-js-data-http-and-async-operations/#respond Fri, 15 Jun 2018 13:45:30 +0000 http://www.munderwood.ca/?p=264 [Read more...]]]> Recently I encountered a rather strange scenario, wherein I created two different  findAll operations using the js-data-http adapter for a js-data query, only to see two identical network requests emitted. The root of the problem turned out to be that I was creating a single object to hold the query options and reusing it across requests, in combination with the fact that js-data-http mutates its parameters in place during asynchronous operation.

// This will find posts from user 4 twice!
const opts = { force: true };
store.findAll('post', { userId: 2 }, opts);
store.findAll('post', { userId: 4 }, opts);

Here is a brief demonstration of the behaviour. In the fiddle two requests are made to the JSONPlaceholder test API, for posts from different users. When the responses are received, they both contain the posts for the user specified in the second query.

The simple solution is to not use the same object for multiple requests.

// This works as expected.
store.findAll('post', { userId: 2 }, { force: true });
store.findAll('post', { userId: 4 }, { force: true });

Why does this work?

Or more interestingly, why doesn’t the first method work?

Part of the process js-data-http uses is to move the query object passed as the second argument to  findAll into a new property of the third argument,  options.params. This is because the function that actually makes the network request expects any query parameters to be specified in  options.params, at least in part because the query parameter format used in GET requests is not the same as the JSON-formatted query syntax used by js-data. So some amount of transformation is required between what we pass in to js-data and what it sends to the server.

However, because the transformation is done in place, js-data mutates the object that is passed into it. The chain of events goes roughly as follows, due to the asynchronous promise-based nature of the findAll  operation.

  • An  options object is created in your code.
  • The first  findAll process, Process A, begins, with Query A and Options A.
  • The second  findAll, Process B, begins, with Query B and Options B.
  • Process A moves its query parameters onto the options object, so that  options.params contains (a transformed version of) Query A.
  • Process B moves its query parameters onto the same options object. Now  options.params contains Query B, but it is still also being referenced from Process A!
  • Process A makes its network request, based on Query B.
  • Process B makes its network request as well, also based on Query B.
  • Two responses to the second query are received.

Moral of the story

This turned out to be a very simple solution to a problem that required a very lengthy debugging process, all because I made the incorrect assumption that a third-party library would not have undocumented side effects on the variables that I passed into it. It was complicated by the fact that the side effects were themselves dependent on a different parameter, and of course by the async nature of the operations, but at its heart that was the issue.

]]>
https://www.munderwood.ca/index.php/2018/06/15/side-effects-of-js-data-http-and-async-operations/feed/ 0
Adding Vue reactivity to js-data relationships https://www.munderwood.ca/index.php/2018/05/16/adding-vue-reactivity-to-js-data-relationships/ https://www.munderwood.ca/index.php/2018/05/16/adding-vue-reactivity-to-js-data-relationships/#comments Wed, 16 May 2018 18:30:25 +0000 http://www.munderwood.ca/?p=256 [Read more...]]]> While attempting to have Vue components react to changes in my js-data model instances and their relationships, I found a great post from Caleb Roseland with an accompanying code example. They detail how to make model properties reactive in Vue, without overriding js-data’s own reactivity. What they don’t discuss is how to make Vue react to changes in the set of models associated with another through a relationship.

For example, if you have a Post that hasMany Comments, and a component on your page that iterates over post.comments , then the comments list still won’t change in Vue when it does in js-data.

Summary / tl;dr

By adding a boolean option to specify that some js-data relations should be made reactive, and defining a  VueReactiveRecord class that enables Vue reactivity on those relations, we can use related models in a Vue template and have them update when the data store changes. Check out this gist to see an example of the final result.

Making relationships reactive

My first attempt was to hew as closely to Caleb’s example as possible, automatically discovering every relationship and making them all reactive. Unfortunately the resulted in neither side of the relationship working correctly. Even though a post  and some associated comment  records were in the store, post.comments  was empty as was each comment.post . At first I wondered if there would be an infinite loop problem, of A reacting to B reacting to A reacting to B reacting … . Instead it appeared as if neither side was able to react even once, and the models were never connected to each other. Either way, I didn’t debug that issue and instead set up a method by which relationships would only become reactive if explicitly told to do so.

Opting in

When defining the js-data mappers, I included a new optional property named vueReactive  to the relationships that I explicitly wanted to make reactive within Vue components. For example, it’s much more likely that a new comment will be added to an existing post than that the post a comment was for will become a different one, so we can specify that the hasMany from posts to comments should be reactive, but leave the belongsTo  from comments to posts alone:

defineMapper('post', ...
  ...
  relations: {
    hasMany: {
      comment: {
        foreignKey: 'post_id',
        localField: 'comments',
        vueReactive: true,
      },
    },
  },
);

With that in place, I updated Caleb’s  ViewReactiveRecord class:

class ViewReactiveRecord extends Record {
  ...
  // Add Vue reactivity to relationships as well, when their definitions say to.
  const relationsByType = this._mapper().relations;
  // e.g. relationsByType = { hasMany: {...}, belongsTo: {...} }
  for (const relType in relationsByType) {
    const relations = relationsByType[relType];
    // e.g. relations is all hasMany relationships, or all belongsTo ones.
    for (const relName in relations) {
      const relation = relations[relName];
      // Now relation is the actual definition of a single relationship on the mapper
      if (!relation.vueReactive) { continue; }
      const key = relation.localField;
      Vue.util.defineReactive(this, key, this[key]);
    }
  }
  ...
}

Using the reactive relationships

Now we can have a Vue template that does something such as this:

<div>
  <h1>{{ user.name }}</h1>
  <h2>Post titles</h2>
  <p v-for="post in user.posts" :key="post.id">
    {{ post.title }}
  </p>
</div>

and know that if the set of associated posts in the js-data store changes, the list of titles displayed will reactively update accordingly.

 

]]>
https://www.munderwood.ca/index.php/2018/05/16/adding-vue-reactivity-to-js-data-relationships/feed/ 3
Getting a Faker instance in Laravel’s tinker environment https://www.munderwood.ca/index.php/2018/05/03/getting-a-faker-instance-in-laravels-tinker-environment/ https://www.munderwood.ca/index.php/2018/05/03/getting-a-faker-instance-in-laravels-tinker-environment/#comments Thu, 03 May 2018 14:47:51 +0000 http://www.munderwood.ca/?p=250 I sometimes find that it would be useful to use  artisan tinker to play with the sorts of data that Faker will generate in a Laravel factory. Unfortunately I don’t do it often enough to always remember the correct way to do it, since the obvious  new Faker\Generator doesn’t work. So for future reference, what you need is:

>>> $faker = Faker\Factory::create();

 

]]>
https://www.munderwood.ca/index.php/2018/05/03/getting-a-faker-instance-in-laravels-tinker-environment/feed/ 3
What to do if js-data serializes array elements as empty objects https://www.munderwood.ca/index.php/2018/04/30/what-to-do-if-js-data-serializes-array-elements-as-empty-objects/ https://www.munderwood.ca/index.php/2018/04/30/what-to-do-if-js-data-serializes-array-elements-as-empty-objects/#respond Mon, 30 Apr 2018 15:20:23 +0000 http://www.munderwood.ca/?p=239 [Read more...]]]> TL;DR: Use the items validation keyword to specify the data type of the elements of arrays in your js-data schema to avoid unexpected results when serializing records, e.g. for saving.

The setup

I was using js-data v3 with a schema containing an array field recently, and came across some initially baffling behaviour. I had a fooSchema that included a field definition along these lines:

import { Schema } from 'js-data';

const FooSchema = new Schema({
  // ...
  barIds: {
    type: 'array',
  },
  // ...
});

Foo object could have many associated Bar objects, and the API supplying the data specified them by including an array of their integer ids in the barIds field. Simple enough, and the above worked perfectly while consuming the output of the API. The unexpected behaviour didn’t start until I tried to send the array back to the API to update the record.

Serializing the array

When calling the save method on a Record , the defined  Mapper serializes it with the toJSON method. Given the above schema, a record with  barIds === [2, 3, 5]  was getting converted to the JSON field  barIds: [{}, {}, {}]. The length of the JSON array was always equal to the length of the underlying data array, but it never contained anything except empty objects instead of the desired relationship information.

A simple solution

When an array field in a record with a schema is serialized by js-data, each element is mapped to an empty object unless a valid JSON Schema for the element is specified. This choice of defaulting to  {} happens in the Schema#pick method, when the type  property of the schema is  'array'. To prevent this, simply specify the expected schema for the array elements in the schema for the records. In my case since the ids are numbers, this meant:

import { Schema } from 'js-data';

const FooSchema = new Schema({
  // ...
  barIds: {
    type: 'array',
    items: { type: 'number' },
  },
  // ...
});

The items validation keyword specifies how the elements of an array-type property should be treated. If not specified, the default is to treat them as objects. If the array contains actual related elements, then a js-data schema could be given, for example  bars: BarSchema. Otherwise, any valid JSON Schema definition should provide the desired results.

]]>
https://www.munderwood.ca/index.php/2018/04/30/what-to-do-if-js-data-serializes-array-elements-as-empty-objects/feed/ 0
Ember pagination links https://www.munderwood.ca/index.php/2018/01/16/ember-pagination-links/ https://www.munderwood.ca/index.php/2018/01/16/ember-pagination-links/#respond Wed, 17 Jan 2018 00:20:25 +0000 http://www.munderwood.ca/?p=233 [Read more...]]]> An Ember project I’m working on needed a simple set of links to move through a paginated JSON API. There are a few pagination-related addons out there, but they tend to want to deal with your data and model collections, as well as the display of pagination links. I was looking for something that would simply update the page parameter sent to the back end, and display links to other pages accordingly. The task seemed like the perfect excuse to build my first fully fledged Ember addon, ember-pagination-links.

By default, the addon creates a set of basic links with a configuration maximum number of pages to display at once, and arrows for navigating page by page and to the start and end of the list:

Each number except the current page is a link that will invoke an action, passing it the clicked value. Everything is classed for easy styling, and the icons to use for arrows and ellipses are configurable strings:

The documentation in the readme goes into more detail, and the issue tracker is open for business.

]]>
https://www.munderwood.ca/index.php/2018/01/16/ember-pagination-links/feed/ 0
Clearing an Ember Paper select component https://www.munderwood.ca/index.php/2017/09/01/clearing-an-ember-paper-select-component/ https://www.munderwood.ca/index.php/2017/09/01/clearing-an-ember-paper-select-component/#respond Fri, 01 Sep 2017 18:21:52 +0000 http://www.munderwood.ca/?p=215 To allow users to clear the selection of an Ember Paper {{paper-select}} component, simply pass it the  allowClear=true option:

{{#paper-select
    placeholder="Select a thing..."
    required=false
    allowClear=true
    ...
}}

This option is passed along to the ember-power-select component that ember-paper uses under the hood, so the resulting clear button can be styled accordingly. For example,

.ember-power-select-clear-btn {
  position: absolute;
  right: 25px;
}

Thanks to @miguelcobain for this tip on the community Slack channel.

]]>
https://www.munderwood.ca/index.php/2017/09/01/clearing-an-ember-paper-select-component/feed/ 0
Primary keys other than “id” in lumen-jwt https://www.munderwood.ca/index.php/2017/06/20/primary-keys-other-than-id-in-lumen-jwt/ https://www.munderwood.ca/index.php/2017/06/20/primary-keys-other-than-id-in-lumen-jwt/#respond Tue, 20 Jun 2017 14:39:18 +0000 http://www.munderwood.ca/?p=195 [Read more...]]]> Lumen-jwt is a great library that easily adds JavaScript Web Token (JWT) authentication to any Lumen application. I recently had the opportunity to submit my second pull request to the project, allowing user accounts to be uniquely identified via fields other than id, which was previously the only option. (As of this writing the PR has been approved, but not yet merged.)

There is now a key called JWT_ID_FIELD that can optionally be added to a project’s .env file to specify the name of the field that should be used to look up and uniquely identify users. The default is of course id, so any existing applications that don’t specify a value will continue to work without any changes.

So if the primary key field in your user table is user_idusername, or indeed anything else, then using lumen-jwt is straightforward:

# Specify accounts should be looked up by username
JWT_ID_FIELD=username

My original use case for this feature was to allow using hashids to identify users without exposing database ids publicly. If an alternative unique id field such as this is persisted in your user model then you’re already done.

Another option is for the hashids to be computed properties, calculated via Eloquent mutators whenever a user instance is loaded. The down side to this approach is that there isn’t a straightforward method for retrieving the model corresponding to a given hashid. Instead, you can write a custom user provider to decode the hashid before retrieving the user―that will be the subject of a future post.

]]>
https://www.munderwood.ca/index.php/2017/06/20/primary-keys-other-than-id-in-lumen-jwt/feed/ 0