10 Jul 2018
rbenv rehash
I recently had an issue when running the rbenv command rehash
. This command is useful to, after installing a new version of ruby or a new gem that adds some commands, refresh the rbenv executable shims.
Sometimes, though, you’ll come across an error where it cannot rehash, i.e., rbenv: cannot rehash: /some/path/to/.rbenv/shims/.rbenv-shim exists
.
The solution is simple: Just remove the offending file.
Why this happens is, also, thankfully, simple: Sometimes things go wrong and the temporary files are just not cleaned up by the previous rehash
command.
10 Jul 2018
Rails session stores
At work, we have a rails application which is using redis (AWS ElastiCache in PRODUCTION) for a few different things. General caching, sidekiq jobs, and we also use it for our session store.
Work on this application is ramping down and resources will be moved away from it shortly. We have had instances where AWS ElastiCache wasn’t great for the session store (network timeouts, unavailability, and automatic maintenance windows). Because of the lowered resources on the project, instead of architecting a more robust solution backed by redis, we opted to move the session store to ActiveRecord::SessionStore.
There’s not a lot to this, but I thought it would be useful to document what I ended up implementing.
Because we didn’t want this to impact users when it goes live, I ended up writing some code to manage translating and migrating any active sessions from redis to ActiveRecord::SessionStore.
Redis store
The redis session store we originally used was pretty straight forward. We used the redis-rails
gem. Sessions went in to redis under a “session” namespace. They were essentially just ruby hashes (not really but for arguments sakes this is accurate enough).
ActiveRecord::SessionStore
ActiveRecord::SessionStore treats session data a bit differently, though, and hashes the data values. Because of the discrepancy between the two session stores, I had to find a way to translate the session data from redis to ActiveRecord::SessionStore.
Thankfully Rails and ActiveRecord::SessionStore have a couple of nice features to help with this.
Piecing things together
First things first, I added the active_record_session_store
gem to the Gemfile
, replacing redis-rails
and created a new database migration to create the session table.
The database migration
The first pass at this is exactly what you’d expect. Create the table and add some indexes. We’ll come back to this later.
class AddSessionsTable < ActiveRecord::Migration[5.1]
def change
create_table :sessions do |t|
t.string :session_id, :null => false
t.text :data
t.timestamps
end
add_index :sessions, :session_id, :unique => true
add_index :sessions, :updated_at
end
end
The session migration service
This is the more interesting piece.
class SessionMigrationService
def initialize
url = "redis://#{Rails.application.secrets.redis[:host]}:#{Rails.application.secrets.redis[:port]}"
@redis = Redis.new(url: url)
@cache = ActiveSupport::Cache.lookup_store(:redis_store)
end
def execute
redis_session_data.each do |hash|
ActiveRecord::SessionStore::Session.new(
session_id: hash.fetch('session_id'),
data: hash.reject { |key| key == 'session_id' }
).save!
end
private
attr_reader :cache, :redis
def redis_session_data
keys.map do |key|
cache
.read(key)
.merge('session_id' => key.gsub(/session:/, ''))
end
end
def keys
redis.keys('session:*')
end
end
There’s not much to it. We instantiate a redis client and a cache client. The cache client here will allow us to very easily pull our session data out of redis as a ruby hash.
We pull the session key names out of redis, fetch the data that pairs to those keys out of the cache, and return them as an array of hashes. We loop over the array and use the session data in each hash to create and persist new ActiveRecord::SessionStore::Session objects.
The final bit
We need to execute the service inside of our database migration.
class AddSessionsTable < ActiveRecord::Migration[5.1]
def change
create_table :sessions do |t|
t.string :session_id, :null => false
t.text :data
t.timestamps
end
add_index :sessions, :session_id, :unique => true
add_index :sessions, :updated_at
SessionMigrationService.new.execute
end
end
And there you have it. Not much to it.
09 Jul 2018
Ants
I have faulty wetware memory. As a way to keep myself historically accurate around various things that happen in our home, I’m going to make a post about the thing and just update it regularly.
Ants are one of these things. Living in Southern California (really, living anywhere in coastal California between the southern border with Mexico and north of San Francisco) comes with the cost of being on top of one of the largest known ant colonies in the world (the Very Large Colony).
I’m always curious to know when they show up in our home, but I always forget.
2018-07-09
I saw a scout when I woke up this morning, in the master bathroom.
When we went downstairs to get our day started, they were all over the food cabinet near the refrigerator. They were going after a months old open box of girl scout cookies that we never finished that were buried in the back of the cabinet.
Removed the offending girl scout cookies, about 2 hours later 95% of the ants are gone on their own. I’ve cleaned the scent trails that I’m aware of.
2018-07-10
Woke up to find a number of ants in the upstairs master bathroom. They’re hanging around in one of the sinks. ??? No clue…
Still a few stragglers hanging around and scouting the downstairs of our place.
2018-10-22
Ants are back. Scouting around.
2018-10-24
They found some crumbs under the couch in the living room. Moved the couch, cleaned up, they’re starting to disappear.
2018-10-29
What the hell. They’re trying to move in to the house again. There must be over a thousand of them, out of no where, coming in from a crack at the front door and straight in to the office behind a display cabinet.
Moved the display cabinet, cleaned everything up. Put some bait traps outside of the house by the front door. There’s only a handful of confused ants in the house at the moment. We’ll see what happens.
2018-10-30
Success. No ants in the house.
08 Sep 2017
At my employer, my team recently made a decision to integrate VueJS in to a Rails application (thankfully, we had kept it upgraded and were on Rails 5.1) that we’re developing.
We landed on this decision because we discovered that we had some fairly complex/complicated UI/UX requirements for a few section of the application and wanted to avoid hand-writing lots of jQuery and DOM manipulation code to facilitate those UI pieces.
So far, it’s been a great success. There was one thing that I wanted to document, though, that I spent quite a bit of time working on: integrating VueJS/JavaScript specs in to the application.
Set up
First things first, I chose mocha and chai as our JavaScript testing framework. I wanted something that would be familiar to the other developers on my team, who have years of experience writing RSpec tests, so that they could jump right in and start being immediately productive.
With that out of the way, I set out to determine how to actually test a VueJS app. I quickly came across an excellent blog post (unit testing VueJS components for beginners) by eddyerburgh. From here, I was able to scoop up avoriaz, which is eddyerburgh’s VueJS unit testing utility library, and jsdom, which gives us a minimal browser environment (sort of, you can read more about it on the GitHub page) to load the VueJS app in to.
Dependencies
$ yarn add mocha chai mocha-webpack avoriaz jsdom jsdom-global --save-dev
Specs
Runner
One of the first things you’ll run up against in this scenario is integrating the testing framework in to the applications ecosystem. In our case, we’re using yarn
to manage our JavaScript dependencies and webpacker
to manage our JavaScript assets. We still use sprockets
, too, for some JavaScript in the “older” style. But, that’s besides the point. In order to be able to execute the test suite, we need to set a test runner up. This sets the environment (very important – it took me a bit of time and googling to sort this piece out) and gets all of the required bits passed in to mocha-webpack
.
// add the following to your package.json
"scripts": {
"test": "NODE_ENV=test mocha-webpack --webpack-config config/webpack/test.js spec/javascript/**/*.spec.js --recursive --require spec/javascript/setup.js"
}
This makes a couple of assumptions:
- You used the Rails webpacker generators to install VueJS (which gives you the configuration chain in
config/webpacker.yml
and config/webpack/**/*
- Your mocha/chai specs live in
spec/javascript
You’ll also need to deal with initializing jsdom
, add the following to a file in spec/javascript/setup.js
:
require('jsdom-global')()
With this configured, you can execute the test suite with the command: $ yarn test
.
avoriaz
The last thing I want to mention is how amazing avoriaz is for helping with VueJS tests. First off, the documentation is high quality. Secondly, it let’s you very easily and quickly access a VueJS and it’s components in a clear way with it’s mount()
method:
import { mount } from 'avoriaz';
import Foo from './Foo.vue';
const wrapper = mount(Foo);
expect(wrapper.text()).to.equal('some text');
We are using object literal stores in our VueJS applications at the moment (we haven’t yet needed to reach for VueX) and avoriaz makes accessing it as easy as:
import { expect } from 'chai'
import { mount } from 'avoriaz'
import SomeVueJsApp from '../../app/javascript/some_vue_js/app.vue'
describe('SomeVueJsApp', () => {
const wrapper = mount(SomeVueJsApp)
it('exposes the applictions store', () => {
expect(wrapper.data().store.state.users.length).to.equal(2)
})
})
It’s also easy to pass props in to the mount() method like:
const characters = ['a', 'b', 'c']
const numbers = [1, 2, 3, 4]
const wrapper = mount(SomeVueJsApp, { propsData: { characters, numbers }})
These are obviously convoluted examples but the point remains, this is actually very easy to use and quite powerful. I think that’s all that I had to say about this for now. I mostly wanted to document it for my own posterity, since it took me an hour or so to piece these things together. It might save me time again in the future or someone else.