How To React Mixin Your ES6

...or, how we made all our content related classes "likeable" with one file.

Background

I am currently working on a Meteor app that uses Webpack and React. The app contains multiple feeds that contain multiple different types of content (articles, stories, videos, albums, etc.). In order to properly display the unique characteristics of each type of content, the app is currently making use of about 6 different React classes for displaying individual pieces of content.

However, each of these 6 classes are not completely unique. We make use of helper classes to try to reduce the amount of duplicate code. This has been great for the different aspects of displaying content, but is not as effective when classes need to share functionality. That's where mixins come in.

React Mixins with ES6 Classes

The original React.createClass syntax made including mixins very easy:

const Article = React.createClass({
  mixins: [Likeable],
  // everything else
});

But, React is moving towards the ES6 Class syntax for creating classes:

class Article extends React.Component {
  // everything else
}

Unfortunately, this newer syntax does not include a way for including mixins. So, we are using the react-mixin module, along with ES7 decorators.

Now, including our mixin looks something like this:

import { Component } from "react"
import ReactMixin from "react-mixin"
import { Likeable } from "app/client/mixins"

@ReactMixin.decorate(Likeable)
export default class Article extends Component {
  // everything else
}

The decorate function magically includes our mixin, and all of its functionality in to our Article class. We can do the same thing in all of our other content related classes.

Likeable

Now, on to the functionality. I wanted the ability to make all of our different content classes "likeable" without the need to duplicate that code in all 6 classes, and now we have the setup for that. So, lets create our mixin:

import { Likes } from "apollos/core/lib/collections"
import { nav as navActions, liked as likedActions } from "apollos/core/client/actions"

import Helpers from "app/client/helpers"

const Likeable = {

  componentWillMount: function() {
    this.likeableAction = this.onClickAction.bind(this)
  },

  onClickAction: function() {
    const entry = this.getEntry();

    this.updateRedux(entry);
    this.updateDatabase(entry);

    return {
      type: "FALSY",
      payload: {}
    }
  },

  getEntry: function() {
    const data = this.data;
    if (data.devotion) return data.devotion
    if (data.article) return data.article
    if (data.story) return data.story
    if (data.currentSermon) return data.currentSermon
    if (data.series) return data.series
    if (data.album) return data.album
  },

  updateRedux: function(entry) {
    this.props.dispatch(likedActions.toggle({
      entryId: entry.entryId
    }));
  },

  updateDatabase: function(entry) {
    // find existing like
    const foundLike = Likes.findOne({
      userId: Meteor.user()._id,
      entryId: entry.entryId
    });

    // update database
    if (foundLike) {
      Likes.remove(foundLike._id);
    } else {
      Likes.insert({
        userId: Meteor.user()._id,
        entryId: entry.entryId,
        title: entry.title,
        image: entry.channelName === "sermons" ?
          Helpers.backgrounds.image(this.data.series) :
          Helpers.backgrounds.image(entry),
        link: Helpers.content.links(entry),
        icon: Helpers.categories.icon(entry),
        category: Helpers.categories.name(entry),
        date: entry.meta.date,
        status: entry.status,
        dateLiked: new Date()
      });
    }
  }

}

export default Likeable

This looks a lot like a normal React Class, and can use many of the same lifecycle functions that a React Class can (componentWillMount, componentWillUnmount, etc). Lets walk through this.

First, we import anything we need for our mixin. In our case, we are including the Likes collection so that we can store it in the database. We are also including some redux actions so that we can update the state of the app. Finally, we include our Helpers to massage our content.

The componentWillMount function will be called just like a normal React Class, except that it is called prior to the class which is including this mixin. Here, we set our action to take upon the user "liking" something, and bind this so we can use it throughout our mixin. this.likeableAction is used in the parent class.

The onClickAction first handles getting our entry, in this case an article, but as you can see in getEntry this could be any number of different content types. Then, it updates our redux store to toggle the "likeness" of the content. Finally, it updates the database, either adding or removing the "Like", respectively.

Conclusion

I was able to drop this mixin in to all 6 of our content related classes, and all of our content became "likeable". This is a huge win for maintainability, and adding future "likeable" content will be trivial and clean. I plan on refactoring some other parts of our app to use this same approach (infinite scrolling, sharing, etc).

Have you a mixin.

Testing Meteor with Gagarin on Circleci Osx Environment

CircleCI has been rolling out a new OSX environment for use in the automated building, testing, and deploying of iOS apps. The team I work with is currently building a Meteor app that we are going to deploy to iOS, Android, and other places, and we have been using CircleCI for a while, so we were excited at the chance to automate iOS builds to staging and production.

We had been running Gagarin tests on the CircleCI Linux containers without issue, but when switching to the OSX containers there were a few gotchas. I didn’t find many others trying to do the same, so I thought I’d share.

circle.yml

An example project with example tests is available here, but here is the meat of it:

machine:
  pre:
    - brew update; brew cleanup; brew cask cleanup
    - brew uninstall --force brew-cask; brew update
    - brew install brew-cask
    - brew install homebrew/versions/node010
    - curl https://install.meteor.com | sh
    - brew cask install google-chrome
    - brew install chromedriver

dependencies:
  pre:
    - npm install -g gagarin
    - chromedriver --port=9515:
       background: true

test:
  override:
    - gagarin -v -t 10000

gotchas

You can stop reading now, but if you don’t then here is an explanation of the above yaml:

brew and brew-cask and pre-installed on the OSX containers, but brew always needs to be updated, and the version of brew-cask preinstalled is out of date. So, we update, clean, uninstall, and reinstall.

brew update; brew cleanup; brew cask cleanup 
brew uninstall --force brew-cask; brew update
brew install brew-cask

node is not pre-installed, so we install the version of node that Meteor likes, 0.10.41.

brew install homebrew/versions/node010

Install Meteor, obviously.

curl https://install.meteor.com | sh

Then, the one that stumped me for a while. chromedriver is pre-installed on the linux containers, but it’s not enough to just install chromedriver and run it. You actually need to install Google Chrome, as well (duh) (which is why we updated brew-cask).

brew cask install google-chrome
brew install chromedriver

Now you should be good to run your tests. Have fun!

Android Emulator Panic

For the past couple weeks, I have been researching ways to automate the building and deploying of Meteor applications. I have a couple posts forthcoming about that, but what I want to talk about today is some trouble I had with the vanilla Android SDK install for Mac.

So....

After downloading Android Studio, and setting my $ANDROID_HOME path:

export ANDROID_HOME=~/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools

...and installing the Android 5.1.1 SDK (this is what Meteor uses):

android

...I added the Android platform to my Meteor app:

meteor add-platform android

Finally, I tried firing up the Meteor application in the Android emulator:

meteor run android

ERRORRRORORORORORORORORR

PANIC: Missing emulator engine program for 'x86' CPUS.

wut

It turns out, the Android SDK is looking for an emulator that does not exist after the install:

emulators

It's looking for emulator-x86 instead of emulator64-x86. Very interesting. So, I did the dumbest thing possible and copied it over:

cp ~/Library/Android/sdk/tools/emulator64-x86 ~/Library/Android/sdk/tools/emulator-x86

And that fixed it. Great. Anyone have any ideas what's going here?

Barrel - Simple Key Value Store

I made a really simple gem called Barrel. You can use Barrel with Rails to store things. It will keep them and give them back later. Especially good for long running calculations.

Installation

Add this line to your application's Gemfile:

gem 'barrel'

And then execute:

$ bundle
$ rails generate barrel:install

Usage

Barrel is so simple:

Barrel.store 'Total Monkeys', '42'
Barrel.find 'Total Monkeys'
# => '42'

AWS S3 Copy File Size Limit

Last year, I rolled together a custom video platform for WorshipU using AWS. It includes an admin for uploading videos to S3, transcoding to multiple formats using Elastic Transcoder, delivery using Cloudfront, and video.js to play the videos in the browser.

The admin allows content managers to upload a bunch of videos at the same time to a designated upload folder in the S3 bucket, and then associate the videos with lessons at a later time. Upon association, the video is moved to a folder specific to the lesson, based on the lesson's id, quality, and encoding:

310/720.webm

WorshipU is a Rails application, so naturally I used the official AWS ruby gem for accessing their API. To move the video from the uploads folder to its respective lesson folder, I used the move_to method for S3Objects. Behind the scenes, move_to performs a copy and then deletes the original.

# get video from S3 uploads directory
video = bucket.objects[lesson.tmp_url]

# move the file to its new location
video.move_to("#{lesson.id}/original.mp4")

I've been really happy with this stack, and it worked great for about a year. Then recently, the content manager started uploading videos with much larger file sizes, and the background task that processes videos started failing. That's how I discovered that S3 has a 5gb file size limit on copying.

It looks like newer versions of the gem may address the file size limitations automatically. I couldn't find any information on it, but if you are using aws-sdk-v1, like me, then there is a sparsely documented way around this using the copy_from method. I could only find this pull request discussing it, but it allows you to do a multipart copy by passing respective parameter. It also request you to pass the length of the item as well.

# get video from S3 uploads directory
video = bucket.objects[lesson.tmp_url]

# create temporary object in new location
copy = bucket.objects.create("#{lesson.id}/original.mp4", "tmp")

# multipart copy from the uploads directory
# to the new object
copy.copy_from(
  video, 
  content_length: video.content_length, 
  use_multipart_copy: true
)

# delete the file in the uploads directory
video.delete

This fixed the problem perfectly for me, although with a few more steps. It would have been nice if I could have just sent the multipart copy option to the move_to function.