Today I Learned

A Hashrocket project

9 posts by ryanmessner

Send an event to a Channel from outside Phoenix

So this one was non-obvious to me.

In the following example, any module on any process can call TopicChannel.send_to_channel/1and that will be handled by the handle_info call below and sent to the socket.

defmodule MyAppWeb.TopicChannel do
  use Phoenix.Channel

  def send_to_channel(data) do
    Phoenix.PubSub.broadcast(
      MyApp.PubSub, 
      "topic:subtopic",
      %{type: "action", payload: %{data: data}
    )
  end

  def join("topic:subtopic", message, socket) do
    {:ok, socket}
  end
  
  def handle_info(%{type: "action"}=info, socket) do
    push socket, "action", info

    {:noreply, socket}
  end
end

Speed up webpacker by excluding dev dependencies

In our latest project, we were experiencing some long build times after accreting features for a few monhts.

By default webpacker pulls in all of your node_modules for parsing and optimization by babel, which can be quite unneccessary. In order to exclude your devDependencies from that process, you can add the folling code to your development.js config:

...

var package = require('../../package.json');
var excluded = Object.keys(package.devDependencies).map(function(dep){
  return new RegExp("node_modules/" + dep);
});

module.exports = merge(environment.toWebpackConfig(), customConfig, {
  module: {
    noParse: excluded
  }
});

The noParse option can possibly lead to errors when some packages are excluded from parsing (notably, css-loader), you can tweak which dependencies reside in dependencies vs devDependencies in your package.json in order to avoid these issues.

Share SCSS Variables with Javascript

When working with React and Sass, sometimes you want to be able to share variables defined in the stylesheets with your React javascript code. This is actually pretty simple with Webpack.

First make sure webpack is set up to import scss:

// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [{
      test: /\.scss$/,
      use: [{
        loader: "style-loader" // creates style nodes from JS strings
      }, {
        loader: "css-loader" // translates CSS into CommonJS
      }, {
        loader: "sass-loader" // compiles Sass to CSS
      }]
    }]
  }
};

Then define your variables export file:

// variables.scss
$white-color: #fcf5ed;
$dark-color: #402f2b;
$light-color: #e6d5c3;
$medium-color: #977978;
$alert-color: #cb492a;
$light-black-color: #706e72;
$black-color: #414042;

// the :export directive is the magic sauce for webpack
:export {
  whitecolor: $white-color;
  darkcolor: $dark-color;
  lightcolor: $light-color;
  mediumcolor: $medium-color;
  alertcolor: $alert-color;
  lightblackcolor: $light-black-color;
  blackcolor: $black-color;
}

Now you’re ready to import your variables to use with your React components:

import variables from 'variables.scss';

const CSS = {
  backgroudColor: variables.blackcolor
}

export default ({}) => {
  return <div style={CSS}>Content</div>
}

Delayed Job Queue Adapter in RSpec with Rails 5.1

In old versions of Rails, you were able to override the ActiveJob queue in a test like this:

describe MyJob do
  it 'works' do
    ActiveJob::Base.queue_adapter = :delayed_job
    expect {
      MyJob.perform_later(some_params)
    }.to change(Delayed::Job.count).by(1)
  end
end

With Rails 5.1, we have the ActiveJob::TestHelper class which you will need to employ in your tests. In order to override the queue a different strategy is needed.

describe MyJob do
  def queue_adapter_for_test
    ActiveJob::QueueAdapters::DelayedJobAdapter.new
  end
  
  it 'works' do
    expect {
      MyJob.perform_later(some_params)
    }.to change(Delayed::Job.count).by(1)
  end
end

You will need to add the following to your rspec config or a support file:

RSpec.configure do |config|
  config.include(ActiveJob::TestHelper)
end

# you will also need the code below for the test
# to clear out the jobs between test runs
class ActiveJob::QueueAdapters::DelayedJobAdapter
  class EnqueuedJobs
    def clear
      Delayed::Job.where(failed_at:nil).map &:destroy
    end
  end
  
  class PerformedJobs
    def clear
      Delayed::Job.where.not(failed_at:nil).map &:destroy
    end
  end
  
  def enqueued_jobs
    EnqueuedJobs.new
  end
  
  def performed_jobs
    PerformedJobs.new
  end
end

Firefox DevTools Vim Mode

I’ve been using Firefox more and more lately, and keep discovering new features with the devtools. One of the most handy when trying to edit css or html is vim mode.

To enable vim mode in the devtools, first to to your about:config page:

Change the devtools.editor.keymap setting to vim and you get all your familiar editing functionality.

Use Decorators for React Higher Order Components

When working with React libraries that make use of higher order components, you wrap your component in a function:

import { withRouter } from 'react-router-dom';

class MyComponent extends Component {
  ...
}

export default withRouter(MyComponent)

When chaining through multiple HOC’s, this can become unwieldly:

import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';

class MyComponent extends Component {
  ...
}

function mapStateToProps(state) { ... };

export default withRouter(connect(mapStateToProps)(MyComponent));

But there is hope, with the decorator syntax in ES6/Babel you can make this look a bit cleaner:

import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';

function mapStateToProps(state) { ... };

@withRouter
@connect(mapStateToProps)
export default class MyComponent extends Component {
  ...
}

Full documentation here.

Alternate names in destructuring assignment

Want to use destructuring assignment, but don’t like the names of the variables as they exist in the object?

If you have data like this:

const optimus = { name: 'Optimus', type: 'Semi-Truck' };
const bumblebee = { name: 'Bumblebee', type: 'Small Car' };

Instead of creating customized variables like this:

const { name, type } = bumblebee;
const bumblebeeName = name;
const bumblebeeType = type;

You can express this type of destructuring in one line like:

const { name: optimusName, type: optimusType } = optimus;