Radoslav Stankov

http://rstankov.com 14 posts

Introducing KittyEvents

During the Christmas break me and Mike were discussing a new feature at Product Hunt. The feature required scheduling an ActiveJobs when a user signs up, votes or submits a comment.

There is SignUp object which handles user registration. So scheduling a new background job there is quite simple:

module SignUp  
  # ... handle user sign up

  def after_sign_up(user)
    WelcomeEmailWorker.perform_later(user)
    WelcomeTweetWorker.perform_later(user)
    SyncProfileImageWorker.perform_later(user)
    NewFancyFeatureWorker.perform_later(user) # <- new worker
  end
end  

Unfortunately after_sign_up method was becoming quite large ☚ī¸
Now imagine having to add NewFancyFeatureWorker to 10 other places đŸ˜Ģ

Those issues pushed us to create a simple wrapper around ActiveJobs, which we call KittyEvents.

Now in SignUp there is just one trigger for an "event":

module SignUp  
  # ... handle user sign up

  def after_sign_up(user)
    ApplicationEvents.trigger(:user_signup, user)
  end
end  

And there is a central place, where events are mapped to ActiveJobs Workers:

# config/initializers/application_events.rb
module ApplicationEvents  
  extend KittyEvents

  event :user_signup, [
    WelcomeEmailWorker,
    WelcomeTweetWorker,
    SyncProfileImageWorker,
    NewFancyFeatureWorker, # <- new worker
  ]

  # ... other events
end  

When an event is triggered, all ActiveJobs Workers for this events are scheduled and executed.

Another bonus, is when using KittyEvents, you only make a single Redis call to trigger any number of events. This shaves off precious milliseconds when using KittyEvents in request.

Feature flags in React

A month ago I gave a talk at js.talks() conference about React at Product Hunt.

One of the sections, which didn't make it in the talk, was how feature flags are handled in Product Hunt.

Almost every feature in Product Hunt starts with a feature flag in Flipper. The feature is only available for selected group of users. Initially only for the developers working on it. This allows for splitting big feature into smaller deployable chunks. This eliminates large list of problems and allows for very early feedback on features.

The usual feature timeline looks something like:

After a feature is completed and had run without issues for some time the feature flag is removed.

Working in such way, also helps with code structure. Since all features are isolated.

Usage

In the backend, there is a facade for Flipper:

Features.enabled?('unicorns', current_user)  

In the frontend, feature flags are stored in Redux reducer and exposed via the following utilities:

// `LinkToUnicorns` would be shown, only when user have access to unicorns feature
<EnabledFeature name="unicorns">  
   <LinkToUnicorns />
</EnabledFeature>  
// depending on user permission UnicornsPage or PageNotFound would be rendered
const UnicornsBranch = createFeatureFlaggedContainer({  
  featureFlag: 'unicorns',
  enabledComponent: UnicornsPage,
  disabledComponent: PageNotFound,
});

// ...
 <Route path="/unicorns" component={UnicornsBranch} />
// ...

Sample implementation

Here is a sample Redux implementations of those components:

// This is quite simple reducer, containing only an array of features.
// You can attach this data to a `currentUser` or similar reducer.

// `BOOTSTAP` is global action, which contains the initial data for a page
// Features access usually don't change during user usage of a page
const BOOTSTAP = 'features/receive';

export default featuresReducer(state, { type, payload }) {  
  if (type === BOOTSTAP) {
    return payload.features || [];
  }

  return state || [];
}

export function isFeatureEnabled(features, featureName) {  
  return features.indexOf(featureName) !== -1;
}
// This is your main reducer.js file
import { combineReducers } from 'redux';

export features, { isFeatureEnabled as isFeatureEnabledSelector } from './features';  
// ...other reducers

export default combineReducers({  
  features,
  // ...other reducers
});

// This is the important part, access to `features` reducer should only happens via this selector.
// Then you can always change where/how the features are stored.
export isFeatureEnabled({ features }, featureName) {  
  return isFeatureEnabledSelector(features, featureName);
}

Here are the components implementations:

import { connect } from 'react-redux';  
import { isFeatureEnabled } from './reducers'

function EnabledFeature({ isEnabled, children }) {  
  if (isEnabled) {
    return children;
  }

  return null;
}

export default connect((store, { name }) => { isEnabled: isFeatureEnabled(store, name) })(EnabledFeature);  
import { isFeatureEnabled } from './reducers'

export default function createFeatureFlaggedContainer({ featureName, enabledComponent, disabledComponent }) {  
  function FeatureFlaggedContainer({ isEnabled, ...props }) {
    const Component = isEnabled ? enabledComponent : disabledComponent;

    if (Component) {
      return <Component ..props />;
    }

    // `disabledComponent` is optional property
    return null;
  }

  // Having `displayName` is very usefull for debuging.
  FeatureFlaggedContainer.displayName = `FeatureFlaggedContainer(${ featureName })`;

  return connect((store) => { isEnabled: isFeatureEnabled(store, featureName) })(FeatureFlaggedContainer);
}

(code in gist)

Handling paths in React application

When using React Router, "a" components should be replaced by Link:

<Link to="/about">About</Link>  
<Link to={`/${post.categorySlug}/${post.slug}`}>{post.name}</Link>  

Passing a route to Link as string works, but doesn't protect us from mistyping. Also changing routes is not very easy. For example if we decide that /${post.categorySlug}/${post.slug} should become /posts/${post.slug}, there would be a lot of grepping.

Ruby on Rails solves those problems by generating a method for every route your application.

This concept works great with React Router:

<Link to={paths.about()}>About</Link>  
<Link to={paths.post(post)}>{post.name}</Link>  

All you have to do is define all your routes in file:

// routes.js
export default {  
   post(post) {
     return `/${post.categorySlug}/${post.slug}`;
   },

   about() {
    return '/about';
   },

   // not all routes are strings
   contacts() {
     return { pathname: 'contacts', state: { modal: true } }; 
   },

   // helper for image sources
   image(path) {
     return `https://product-hunt-cdn.com/images/${path}`
   }

   // ....
};

This technique works great with flow, giving you type safety in your links.

I have thought several times about generating this file from router component. But didn't have a chance to do so and with React Router v4 this won't be very easy.

Flow as replacement for PropType

A couple of months ago at Product Hunt, we decided to switch from React.PropTypes to Flow. Initially, we started using Flow just for function definitions. Then we start replacing propType.

Why we do did that? Let's say we have an UserImage component with two possible uses:

<UserImage user={user} width={50} height={30} />  
<UserImage user={user} variant="small" />  

Its definition would be something like this:

const UserShape = {  
  id: React.PropTypes.number.isRequired,
  name: React.PropTypes.name.isRequired,
};

class UserImage extends React.Component {  
  static propTypes = {
    user: React.PropTypes.shape(UserShape).isRequired,
    width: React.PropTypes.number,
    height: React.PropTypes.number,
    variant: React.PropTypes.oneOf(['big', 'medium', 'small')
  };

  render() { /* ... */ }
}

There are some problems with that definition:

  • UserShape can be used only with other React components
  • propTypes throws a warning during runtime
  • width and height should be passed together, but we can't enforce that
  • It is possible to pass all three props - width, height, variant

Here is how the Flow solves those problems:

type User = {  
  id: number,
  name: string,
};

class UserImage extends React.Component {  
  props: {
    user: User,
    width: number,
    height: number,
  } | {
    user: User,
    variant: 'big' | 'medium' | 'small',
  };

  render() { /* ... */ }
}
  • User is a generic type (can be used for any javascript function)
  • Flow types are used only during build
  • <UserImage user={user} width="10" /> breaks the build 😎

Unfortunately this is still possible:

<UserImage user={user} width={50} height={30} variant="small" />  

One way to solve that is the following:

  props: {
    user: User,
    width: number,
    height: number,
    // expects "variant" is not passed
    variant?: void,
  } | {
    user: User,
    variant: 'big' | 'medium' | 'small',
    // expects "width" and "height" are not passed
    width?: void,
    height?: void,
  };

It's a bit ugly, but it gets the job done:

<UserImage user={user} width={50} height={30} variant="small" />  
                       ^^^^^^^ number. This type is incompatible with void

<UserImage user={user} width={50} height={30} variant="small" />  
                                   ^^^^^^^ number. This type is incompatible with void

<UserImage user={user} width={50} height={30} variant="small" />  
                                                       ^^^^^^^ string. This type is incompatible with void

For more information check Flow documentation.

p.s. Gabriele Petronella and Vjeux told me on twitter about $Exact and {| |}:

  props: 
    {| user: User, width: number, height: number |} |
    {| user: User, variant: 'big' | 'medium' | 'small' |};

Rails 5 features, I'm excited about

Ruby on Rails 5 has arrived. Though, I don't care much about the big features. I'm more excited about the smaller features, which would not change my life, but will make it a bit easier.

API mode improvements

Most of my work in Rails projects, these days is just using Rails as an api endpoint. There are lots of improvements.

I'm really glad we finally got ActionController::API.

Easier conversion of errors to JSON

Returning consistent and usable error messages is vital when designing and API.

Previously getting usable validation errors from rails was quite painful. Since you can only have:

user.errors.messages # => {:email=>["can't be blank"]}  

This error message is it quite hard for clients. In several projects, I have defined a json locale where things like "can't be blank" is just "blank" and used that for the api.

But this is no more! Since errors.details exists now:

user.errors.details # => {:email=>[{:error=>:blank}]}  
Better exceptions in development

Speaking about errors. There is a new option debugexceptionresponse_format:

# config/environments/development.rb
config.debug_exception_response_format = :api  

You will get exception information in JSON format during development, instead of HTML.

ActiveJob improvements

In a previous post - Retrying ActiveJob, there was a glue code depending on a Rails 5 feature - ActiveJob::Base#deserialize. Now that can be removed.

Also having the ApplicationJob makes including the ActiveJobRetriesCount from the post easier:

class ApplicationJob < ActiveJob::Base  
  include ActiveJobRetriesCount
end  

ActiveRecord improvements

And even more

There is, even more, stuff like redirect_back or ActiveModel::AttributeAssignment. But for more information I encourage you to check:

Overall I think this is a solid release.

I have heard on The Bike Shed that there are discussions about changing the release process and to smaller and more frequent release cycle. I think this would be great. Especially since the features, I'm mostly excited usually are smaller and don't require a major version change.