How to Create a Simple CRUD App With Rails and Vue

This tutorial was inspired by James Hibbard’s post but instead of using React, I will use Vue. This is not meant to compare React and Vue nor to conclude which one is better though I find Vue more approachable. This is just my second time to use Vue so please don’t look at me as an expert. This is also a learning experience for me and I hope you learn something, too :)

While most of the content are based on James' post, I decided to treat Rails and Vue as two separate applications. The Rails application is API-only that serves JSON, i.e. you will not see any mention of Vue included in the codebase. On the other hand, the Vue application is also a pure web client and does everything via API calls. I will also use Vuex to centrally managed the API calls and the state used by the Vue components.

In this post, I decided not to do a typical tutorial where you go about building the application from start to finish. Instead, I will just highlight the major code changes.

Setup

The tutorial was developed using the following:

The code is available at https://github.com/gregmoreno/rails-vue-tutorial.

Quick Guide

# Get the code
git checkout git@github.com:gregmoreno/rails-vue-tutorial.git
cd rails-vue-tutorial

# Run Rails app
# To view JSON localhost:3000/events
cd rails-api
bundle install
rails db:setup
rails db:seed
rails s

# Run Vue app
# To view app localhost:8080/events
cd vue-ui
npm install
npm run serve

# Or using foreman to run the 2 apps
foreman start -f Procfile.dev

Start here:

http://localhost:8080/events

Architecture

The project is composed of 2 applications - a Rails application that returns events in JSON and a Vue application that handles UI interaction. The applications are in their respective directories.

./rails-api
./vue-ui

API Endpoints

GET     /events      # list of events
POST    /events      # create a new event
PUT     /events/:id  # update an event
DELETE  /events/:id  # delete an event

Each event has the following structure:

{
  "id": 1,
  "event_type": "Symposium",
  "event_date": "2018-05-01",
  "title": "A Social-Neuroscience Perspective on Empathy",
  "speaker": "Albert von Bezold, Jules Cotard, Marian Diamond",
  "host": "Alcmaeon of Croton",
  "published": true,
  "created_at": "2019-04-13T22:41:55.867Z",
  "updated_at": "2019-04-13T22:41:55.867Z"
},

Rails Components

Controller

The EventsController is a typical Rails controller that returns a JSON using Jbuilder. However, I would love to share a pattern that I often use with my Rails controller.

class EventsController < ApplicationController
  def index
    load_events
  end

  def create
    build_event

    # more code follows
  end

  def update
    load_event
    build_event

    # more code follows
  end

  def destroy
    load_event
    @event.destroy

    # more code follows
  end

private

  def event_scope
    Event.all
  end

  def load_events
    @events = event_scope.order(event_date: :DESC)
  end

  def load_event
    @event ||= event_scope.find(params[:id])
  end

  def build_event
    @event ||= event_scope.build
    @event.attributes = event_params
  end

  def event_params
    @event_params = params[:event]
    @event_params ? @event_params.permit(permitted_params) : {}
  end

  def permitted_params
    %i[
      event_type
      event_date
      title
      speaker
      host
      published
    ]
  end
end

Did you notice it?

I like separating the actions into disctinct methods even though it often just results to one-liners. However, this clean separation makes the controller easier to understand and evolve. For example, event_scope may eventually evolve to scope with current_user.

Our Rail application is boring but I love it that way. It is all Ruby and there is no Javascript framework to worry about

Since we have built an API on top of Rails, we need to tell our Rails application that requests would come from a diferent source. Otherwise, our Vue application will just receive errors every time it calls the API. If you’re planning on serving your Vue application other than from localhost, update the origins parameter and restart your Rails application.

# config/initializers/cors.rb

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins /localhost:*/

    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head],
      credentials: true
  end
end

Here is good introduction about CORS in the context of Rails if you like to dig deeper.

Vue Components

Components are building blocks of a Vue-based application. You might represent the page header as a component, or the sidebar, the list of events, or even just the event itself. A single page can also be a component composed of several components, and so on.

Components under src/views, by convention, maps to URLs and represent the top-level page. The mapping is listed in src/router.js.

src/views/
  About.vue
  EventsView.vue

src/components/
  Event.vue
  EventForm.vue
  EventList.vue

Lifecycle

  1. User action is captured by a Vue component.
  2. Vue component calls a Vuex action that calls an API (via axios library).
  3. Rails handles the API and returns a JSON response.
  4. Vuex mutates a state based on the response.
  5. The Vue component updates the page based on the state changes.

In this sequence, the Vue component has no idea about the API calls - it simply calls a Vuex action. The Vuex action has no idea about about the Vue component - it only cares about the API request/response and the state to be modified. The Vue component reacts to changes to the state.

Vuex and State Management

When building an application, you often how to deal with these three things:

  • state - the source of truth
  • view (or views) - mapping the state for the components
  • actions - ways the state could change

In our sample application, the state is the list of events, the view is the component the displays each event, and the actions are loading the events via the API, adding new event, etc.

The beauty of Vuex (and other state management libraries like Redux) is it enables a clean separation between state, views, and actions and at the same, they work together in a harmonious way. It was an aha! moment when I finally I understood what it does.

Listing Events

Let’s see how the Vue pieces fit together in the context of listing events.

First, initialize list of events.

// src/main.js#9
const store = initStore({
  [states.EVENTS]: []
});

Visiting the URL /events loads the EventsView component. The mapping is specified using the Vue Router.

// src/router.js#11
{
  path: "/events",
  name: "events",
  component: EventsView
}

After the component is loaded, invoke the action GET_EVENTS

// src/views/EventsView.vue#63
created() {
  this[GET_EVENTS]();
}

The GET_EVENTS action is just another method that calls the API to fetch the list of events. The state is then mutated with the response data.

// src/store/actions/index.js#6
[actions.GET_EVENTS]({ state, commit }) {
  return api
    .getEvents()
    .then(response => {
      commit(mutations.SET_EVENTS, response.data);
    })
}

// src/api.js#12
getEvents() {
  return axios.get("/events");
}

The list events from the API call is then saved. Or, using the right technical term, the state is mutated.

// src/store/mutations/index.js#8
[mutations.SET_EVENTS](state, events) {
  state[states.EVENTS] = events;
}

The component EventsView listens to any changes to the list of events. It then passes the list to another component EventList.

// src/views/EventsView.vue#12
<template>
  ...
  <EventList :events="events"/>
</template

The EventList component takes care of actually displaying the events.

// src/components/EventList.vue
<template>
  <div>
    <div v-for="event in events" :key="event.id">
      <a>
        <span></span>
        &mdash;
        <span></span>
      </a>
    </div>
  </div>
</template>

Adding and Updating Events

Of course, our application will be useless if it can only list events. So, let’s include a way to add events to our application.

First, add an event handler when a link (or button) is clicked.

// src/views/EventsView.vue#7
<a @click="newEvent">New Event</a>

// src/views/EventsView.vue#90
newEvent() {
  this.$router.push({ name: "newEvent" });
},

The newEvent() method pushes the path /events/new via the Vue router which we used to determine when to show the EventForm component.

// src/views/EventsView.vue#19
<div v-if="showEvent">
  <Event :event="event"/>
</div>
<div v-else-if="showEventForm">
  <EventForm :event="event"/>
</div>

The conditionals are just methods that checks the current path.

// src/views/EventsView.vue#73
showEvent() {
  return (this.$route.name == "event") && (this.event !== null);
},
showEventForm() {
  return (this.$route.name == "newEvent") && (this.event !== null);
}

The EventForm component receives the event selected from the EventsList component. In Vue, passing an object from a parent component to a child component is done via props.

// src/components/EventForm.vue#99
props: {
  event: {
    type: Object,
    required: true
  }
},

The EventForm component handles the saving of events whether it is part of adding a new event or updating an existing one sequence. The component also follows the state-view-actions pattern. First, we create a handler when the form is submitted.

// src/components/EventForm.vue#6
<form v-on:submit.prevent="saveEvent()" className="eventForm">
  ...
</form>


// src/components/EventForm.vue#109
saveEvent() {
  this[SAVE_EVENT](this.aEvent)
    .then(() => {
      this.$emit("eventSelected", this.aEvent);
    });
}

The SAVE_EVENT action is just a method that accepts an event object and takes care of calling the API.

// src/store/actions/index.js#13
[actions.SAVE_EVENT]({ state, commit }, event) {
  if (event.id) {
    return api
      .updateEvent(event)
      .then(response => {
        commit(mutations.SET_EVENT, response.data);
      })
  } else {
    return api
      .createEvent(event)
      .then(response => {
        commit(mutations.SET_EVENT, response.data);
      })
  }
},

After receiving the response from the API, the list of events is updated.

// src/store/mutations/index.js#8
[mutations.SET_EVENT](state, event) {
  const i = state[states.EVENTS].findIndex(e => e.id == event.id);

  if (i >= 0) {
    // DOM will not be updated if you modify via index.
    state[states.EVENTS].splice(i, 1, event);
  } else {
    state[states.EVENTS].push(event);
  }
},

Since the EventList component is watching the events list object, it automatically updates the list when an event is added or updated.

Deleting an event

There is nothing special with deleting an event - it just follows the state-view-action pattern like the other scenarios.

// src/views/EventsView.vue#98
deleteEvent(event) {
  this[DELETE_EVENT](event)
    .then(() => {
      this.$router.push({ name: "events" });
    })
}


// src/store/actions/index.js#28
[actions.DELETE_EVENT]({ state, commit }, event) {
  if (event.id) {
    return api
      .deleteEvent(event)
      .then(response => {
        commit(mutations.DELETE_EVENT, event);
      })
  }
}

// src/store/mutations/index.js#18
[mutations.DELETE_EVENT](state, event) {
  const i = state[states.EVENTS].findIndex(e => e.id == event.id);

  if (i >= 0) {
    state[states.EVENTS].splice(i, 1);
  }
}

Testing

I was curious how to test Vue applications so I added a simple unit test using Jest. If you like to use a different framework, the vue-cli create command will allow you to choose one.

To help in our testing, we use a factory method to prepare the component to be tested.

// tests/unit/Event.spec.js
import { shallowMount } from "@vue/test-utils";
import Event from "@/components/Event";

const factory = (values = {}) => {
  return shallowMount(Event, {
    propsData: {
      event: { ...values }
    }
  });
};

shallowMount loads a component and stubs the child components which is perfect for unit testing. It also allows you to set the data or props attributes of the component, for example:

describe("Event", () => {
  it("renders the event details", () => {
    const wrapper = factory({
      title: "event title",
      event_type: "symposium"
    });

    expect(wrapper.find(".test-event-type").text()).toEqual("Type: symposium");
    expect(wrapper.find(".test-title").text()).toEqual("Title: event title");
  });
});

To run the tests:

npm run test:unit

Just follow the Vue testing guide if you want to dive deeper into Vue testing.

Conclusion

At this point, I hope you have a good idea how the different parts of Vue works together. Like I said in the beginning, I’m not an expert in Vue nor have I extensive experience on other Javascript frameworks like React. Though personally, I find Vue more approachable and I will definitely use this in my personal projects.

If you are new to Vue (like I am), I highly recommend you read their list of beginner gotchas. I guarantee you in will save you lots of frustrations :)

If you are interested in comparing Vue with other frameworks, the article by Yogev Ahuvia is a good read.

Schedule Delays Should Not Surprise You

When do you plan to talk about that crucial albeit uncomfortable topic? You know, that you are not going to hit your release date.

Often, awareness about delays surfaces only when we are near our release date and our boss asked the dreaded question “Are we going to make it?”. In reality, delays don’t happen near the end - it happens numerous times in the middle.

You got pulled on an unscheduled meeting. Your project is now delayed by an hour. You left the office early to buy that toy for your daughter’s 7th birthday. Add 4 more hours. You worked on that nasty bug from the previous release. Make that 3 days and 5 hours. You got sick. Now, you are delayed by a week. You procrastinated. etc. etc.

These seemingly little things happen all the time yet we don’t usually consider them as contributors to our schedule delays. Often, we attribute “major” things like a developer quitting in the middle of the project or big requirement change midway as causes of delays. Yet these little slips accumulated over time can become your major cause of delay.

One of the first things you can do is to have enough granularity with your deliverables that progress is obvious over a period of just couple of days as much as possible. When your commitments span weeks and there is no clear progress over a long period, you won’t have any idea if you are near or far to your target. And if your job is to manage delivery, that feeling of not knowing sucks. But if you make commitments over short intervals and you failed to deliver, you know if you need to make up for a day or two, which is more manageable. If you let a week go by without knowing what is happening, that is too long already and you could be facing a delay that is a challenge to compensate for.

The second step is to make sure your team understands who depends on those deliverables. If Alice is working on that API that Bob needs, and Bob is working on the shopping cart that Charlies needs to test, and Charlie needs to give the test results to Alice, every one should understand the interdependency among the individuals. This dynamic is repeated hundred times prior to hitting a milestone. When you think about it, hitting your deadline is all about making and keeping hundreds of small vital promises.

If you keep tab of those hundred promises, delays shouldn’t surprise you. When your team failed to deliver on this week’s promises and then four weeks later you learned your project is now late, then that is a problem. Most likely, people are not talking to teach other, not aware of the impact of a broken promise, or worse, have no sense of mutual accountability.

Every delay will cost your team some credibility points, and each one can costs more than the previous. On the bright side, there are positive things to be gained out of it. Delays are uncomfortable topic but these often trigger new insights, alternative views, and previously unimagined possibilities. As new information comes in, you are now closer to reality and your team can course correct towards the best results instead of blindly sticking to plans and schedules that were set when you still have a long list of unknowns.

Flexibility is software’s greatest asset and quickly adapting to changing environments is characteristic of excellence in software development. You can add time, subtract features, add resources, or do some combination of the three. You have heard this a million times - the famous triangle of features, resources, and time. At the end of day you only have those 3 things to work with.

There will be pressure to give a new end date but don’t give in. While it is uncomfortable knowing you already have a bad date, wait until the cause of the delay and extent are totally understood. This is not the time to estimate how much you’ve slipped and tack it on to your current schedule which is already proven to be wrong. This is the time to think through all the new information and possible remedies.

Delays are commonly viewed negatively and much of it stems from our desire for things to be predictable. Software development is an experiment of putting together a diverse group of people to perform a creative endeavour. Even after decades of experimenting with different practices, is still not an exact science — it’s more of an art form sprinkled with technical fairy dusts

Every software project, no matter how successful they were, started with a large number of important things that were unknown. It is undesirable to have many unknowns but not unusual. Given each software development project is unique with its own personality and combine that with new technologies and new approaches, you end up with high degree of uncertainty — the odds are against you.

However, attempting to eliminate all the unknowns at the start is not only unrealistic but spending more effort on planning has diminishing returns. Instead, one of your primary job is to make every one embrace uncertainty and guide your organization so it thrives in an uncertain environment. Your team’s mindset is not to follow a plan to perfection but to make the right decisions every day as the unknowns become knowns.

5-bullet Sunday Morning"

I’m currently reading Principles by Ray Dalio

When your name is Ray Dalio, you dont need a thought-provoking title nor a fancy cover to have a bestseller. It is a thick book and with a hardbound black cover, it can pass as the Bible. Just like the Bible, this is not a book you read in one sitting nor something to pass the time. I had to pause, think about what he said, question it, and see if I can apply it to my life.

Here is Ray Dalio’s popular TED talk.

Bitcoin is so hot I need to mention it

A friend messaged me complaining how complicated and tedious it is to trade Bitcoin and other cryptocurrencies. Worse, he is just halfway to all the things he need to do. We haven’t fully discussed what a wallet is for, why a piece of paper can be so important in the Internet age, and why he needs a water-tight fire-proof vault :) He made the leap even though he still doesn’t fully understand what all these currencies are and I can only imagine how these steps can feel like just a waste of time.

He is not alone and this is good thing.

Yes, I am channeling my inner Stoicism here so here me out. Massive financial gains happen when you got in an opportunity before the general public does. The Bitcoin ecosystem right now is not much different from the early days of the Internet — with all the possibilities, confusion, as well as danger of another bubble.

If you feel the pain, look at it as your ticket to an exciting game. Let us enjoy the game and I wish you good luck.

On watching Cirque du Soleil

I first heard of Cirque du Soleil about 4 years ago. Whenever they are in Vancouver, they would have a big tent setup in the parking lot near a train station. Around this time, I would see that tent on way to and from my work. It is like one mysterious event. You don’t see much from the outside but people are lining up and you hear conversations in the train on how breathtaking the show is.

When I heard they are coming again, we made sure we will not let it pass this time, damn the cost. I am glad we did not. My kids love it. I love it. Seeing people fly and do stunts in a 3D theatre is fun though we know it is all computer magic and stunt doubles. But watching people somersault in the air with only a rope on one arm is way different fun. You can actually feel the danger with it.

I am grateful I get to watch this with my family.

This thing in JavaScript

Finished some modules from Wes Bos JavaScript course, so why not apply it right away. So I did and I thought this was no a brainer.

$('#filters').on('change', () => {
  console.log($(this).val());
});

Except that it is not and I paid some scarce hair for it. The right solution is to use regular function.

$('#filters').on('change', function () {
  console.log($(this).val());
});

Ah the binding. When using arrow function, this is not bound to the on scope. Instead, it just inherited whatever the parent scope is. Lesson learned.

Now I get it. So I thought.

$('#filters').on('change', function () {
  console.log($(this).val());
  setTimeout(function () {
    console.log($(this).val());
  });
});

Damnit, same error again. Because inside the function passed to setTimeout, this is bound to setTimeout. Hence, the same error. To fix it, just use arrow function.

$('#filters').on('change', function () {
  console.log($(this).val());
  setTimeout(() => {
    console.log($(this).val());
  });
});

Confusing, eh? Just remember to go back to the binding rules for the arrow function and now it makes sense because we want to have this refer to the parent scope.

Quote for you

“A side effect of doing challenging work is that you’re pulled by excitement and pushed by confusion at the same time.” - James Clear

This post also appeared at Medium.

5-bullet Sunday Morning

On Javascript, 1–1s, scholarships, bitcoin, and evil things.

As a developer:

Modern JavaScript Explained For Dinosaurs

Even though I’m already familiar with the tools mentioned in the article, this provided me a better understanding of the front-end ecosystem. It also helped that the headline resonated with me :) The article started with doing it old school — just like many of us dinosaurs did decades ago and moved on to modern techniques. It’s like resetting your brain and learning things again but only this time you come out with a better understanding.

On related note, I’m currently taking the ES6 course by Wes Bos. Just finished the Promises module and I say it’s totally worth it.

As a manager:

I’m thinking about 1–1s, why we do it and how to make it worthwhile for both parties. 1–1s that turn into status updates that can easily (and more efficiently) be done via email is a waste of everyone ’s time. But if you view 1–1s as an opportunity to create a connection between you and your manager, it is a very effective tool to build great working relationships that will go beyond your current workplace.

Don’t think of it as a meeting but rather a coffee with someone you are eager to know better.

As a parent:

My wife and I, together with our Grade 11 son, attended a college scholarship seminar sponsored by my son’s school. The school’s theatre is packed but according to my son, it is just a fraction of the student population that would benefit from it. I wonder why there aren’t that much interest on such an opportunity. My hypothesis is many students (and even parents) think applying for college scholarships is done when you are close to graduation time.

My takeaway from the seminar is that going after the smaller grants (e.g. $500 — $5000) will give you a better chance of hitting your scholarship goals. Why? Because there are so many of them (at least in Canada) and the chance of getting accepted to at least 10 is not unrealistic. Add those amounts and now your college is free. On the other hand, if you aim for the lottery, not only you are competing against the best and the brightest students who found a cure to cancer or ended hunger in Africa, there is just a handful of slots available.

If you’re a teacher or member of the parent council, and if you happen to be in BC Canada, I recommend you book Brittany Palmer.

On Bitcoin and cryptocurrencies:

There is a lot of hype going around and we shouldn’t be shocked if 99% of the cryptocurrencies out there turn out to be scams. Many people don’t know, including those who now call themselves “investors”, that these cryptocurrencies have 2 sides — the money and the technology. What you will often hear is the money side. After all, a headline that says some “kid became a millionaire after investing $100” will always grab our attention.

Of course, as an “investor”, I am happy with the success of the cryptocurrencies but even if the prices go down to zero, the technology (hint: it is not just blockchain) will stay and evolve in the coming years. And that is where the real benefits lie.

Cryptocurrencies are here to stay. But don’t be stupid.

I’m pondering:

What if evil is part of life? That the person doing bad things is just doing his job? Like a normal job, some are bad at it, some are doing it really well, and some are simply looking to change job? On that note, Mindhunter looks to be another interesting series from Netflix. But that has to wait because it is Stranger Things Season 2 baby.

This post was also published at Medium.

Am I Too Old to Be a Senior Developer?

Or, aren’t you too young to be a Senior developer?

I never had the title of Senior Developer until about 3 years ago. By that time, I already have 15 years of experience in creating, deploying and fixing software. This is not counting the years I spent freelancing for various tech companies while I was in college. I have worked in startups and big companies, led teams, launched several products on my own and failed, and numerous times I had to learn stuff not related to coding just to get that freaking software out the door. Suddenly, I feel so old and it feels so jurassic now to remember my Visual Basic program running on Windows 95 - ah the good old days!

I imagine these are more than enough to earn a senior title. On the other hand, I’ve worked with developers with a senior title who produced amateurish work. It made me wonder when should companies give the “senior” title to someone. More importantly, when can a developer, call oneself a senior in this field?

In the early stages of a project, the team usually has a number of technical options. A senior developer can articulate the pros and cons of a technical decision - the why and when, not just the how and what. This could be as simple as avoiding early optimizations, highlighting roadblocks the team will face, or avoiding unnecessary work.

An effective signal to know someone is truly a senior developer (or in that mindset regardless of title) is when the developer looks at context when applying a theory. What problem are we solving? What is important and what is not? When to start and when to stop. How often have you seen over-engineered systems that are flexible in ways that don’t matter and inflexible in ones that do? This is simply because developers want to try the latest cool thing they’ve read.

There is also, looking at the big picture. Not every moment in software development is spent working on change-the-world tasks. It’s quite the contrary. Majority of your time is spent on boring tasks that must be completed to the same quality as any other potentially more interesting tasks. Have you met a developer who planned on resigning if he get to work on a legacy project, regardless if that project generates millions of dollars of revenue? I did. It made me think now, if he was more interested in showing off in his resume rather than adding value to the company.

If my thoughts above would be considered, does this mean a developer with 3 years of coding experience and brings tremendous value to the team, can now be called a senior developer? How about someone with 10 years experience but in reality is just experienced the same year 10 times?

Realization. It’s never about time - it’s the maturity.

On the other hand, how realistically can you gain maturity in a short amount of time? Can you compress 10 years worth of experience into 1?

Success in software development goes beyond technical skills. If you want to learn how to manage a crisis, you have to experience a crisis. If you want to have a high performing team, you have to learn to work together, which takes time. Hey, even when superstar athletes play together they rarely get success in their first two years.

Can we now agree that years of experience is misleading? Worthwhile experiences simply do not happen every day. You can try compressing it similar to what schools do, or read books that’s ideally based on the author’s years of experience. Still, while these are helpful, they are not good enough to deal with messy problems. In actual work, there are lots of compromises and very few assumptions. And you have to rely on your previous success and failures when dealing with new problems.

#til Elixir's Anonymous Function

fn is a keyword that creates an anonymous function.

sum = fn(a,b) ->
  a+b
end

IO.puts sum.(1,2)

Invoke using . (dot) similar to Ruby’s #call. Note the dot is not used for named function calls.

fb = fn
  {0, 0, c} -> "FizzBuzz"
  {0, b, c} -> "Fizz"
  {a, 0, c} -> "Buzz"
  {a, b, c} -> c
end

A single function with multiple implementations depending on the arguments passed. Elixir does not have assignments - yes you heard it right. Instead, it uses pattern matching then it binds the variable to the value. Note each implementation should have the same number of arguments.

IO.puts fb.({0,0,3})
# FizzBuzz
IO.puts fb.({0,2,3})
# Fizz
IO.puts fb.({1,0,3})
# Buzz
IO.puts fb.({1,2,3})
# 3

Since it is just a function, why not define it inside another function.

fizzbuzz = fn(n) ->
  fb = fn
    {0, 0, c} -> "FizzBuzz"
    {0, b, c} -> "Fizz"
    {a, 0, c} -> "Buzz"
    {a, b, c} -> c
  end

  fb.({rem(n,3), rem(n,5), n})
end

IO.puts fizzbuzz.(10)
IO.puts fizzbuzz.(11)
IO.puts fizzbuzz.(12)
IO.puts fizzbuzz.(13)
IO.puts fizzbuzz.(14)
IO.puts fizzbuzz.(15)
IO.puts fizzbuzz.(16)

We now have a baseline FizzBuzz solution with no conditional logic. Note rem(a,b) is an operator that returns the remainder after dividing a by b. The final solution uses the Enum module.

Enum.each(1..100, fn(n) -> IO.puts fizzbuzz.(n) end)

I’ve read the |> pipe operator before, so why not use it. The pipe operator simply takes the result of one expression and passes it as the 1st parameter to the next expression.

(1..100) |> Enum.each(fn(n) -> IO.puts fizzbuzz.(n) end)

Joy Ride in Ruby's Spaceship

Ruby’s <=> operator is commonly referred to as the spaceship in the community. You may not have played with it directly but I bet you have relied on it a lot of times. Because, every time you use sort an array, you are tapping in to the spaceship operator.

Now, why would you care? Because sometimes we need to sort things which does not have a natural ordering computers are accustomed to. Take for example sorting clothes that use S, M, and L to refer to their sizes. And to add more fun, how about putting XS, XL, and XXL into the mix.

Let’s start with a bare class and see what happens.

require 'minitest/autorun'

class Size
  attr_reader :size

  def initialize(size)
    @size = size.to_s.upcase
  end

  def to_s
    @size
  end
end

describe Size do
  let(:sizes) { %w[L S M].map { |s| Size.new(s) } }
  it { sizes.sort.map(&:to_s).must_equal %w[S M L] }

  let(:a) { Size.new('S') }
  let(:b) { Size.new('M') }

  it { (a > b).must_equal false }
  it { (a < b).must_equal true }
end

$> ruby size.rb                                                                                                     [2.1.2]
Run options: --seed 37770

# Running:

EEE

Finished in 0.001207s, 2486.1356 runs/s, 0.0000 assertions/s.

  1) Error:
Size#test_0001_anonymous:
ArgumentError: comparison of Size with Size failed
    size.rb:24:in `sort'
    size.rb:24:in `block (2 levels) in <main>'


  2) Error:
Size#test_0002_anonymous:
NoMethodError: undefined method `>' for #<Size:0x007f8a7bae6c50 @size="S">
    size.rb:29:in `block (2 levels) in <main>'


  3) Error:
Size#test_0003_anonymous:
NoMethodError: undefined method `<' for #<Size:0x007f8a7bae6390 @size="S">
    size.rb:30:in `block (2 levels) in <main>'

3 runs, 0 assertions, 0 failures, 3 errors, 0 skips

Our test failed and that’s good news, isn’t it? The first failure is because the default implementation of <=> doesn’t do all the comparison required to make sort work. To make this work, we need to implement a <=> that returns the following:

  • nil if the comparison does not makes sense
  • -1 if left side is less than right side
  • 1 if left side is greater than right side
  • 0 if left and right are the same

Let’s update our code to use the spaceship.

class Size
  attr_reader :size

  SIZES = %w[S M L].freeze

  def initialize(size)
    @size = size.to_s.upcase
  end

  def to_s
    @size
  end

  def <=>(other)
    position <=> other.position
  end

  protected

  def position
    SIZES.index(size)
  end
end

Some things to ponder in our implementation.

  • In Ruby, operator calls are just method calls where the left side is the receiver and right side is the argument. In other words, this is a <=> b is the same as a.<=>(b)
  • It is common practice to call the argument as other as you know the other object :)
  • We leverage an existing implementation of <=>. The method #index returns the position of an element in the array, which is a Fixnum. The Fixnum class already knows how to compare numbers.
  • We use protected to hide an implementation detail but at the same time allow us to use it within instance methods of objects of the same class.

Now, how about the other test failures? Do we need to implement the < and > operators as well? Fortunately, Ruby got our back. We just need to include the module Comparable and we’re good. But wait, there’s more! By including the Comparable module, we also get <=, >=, and == for free.

Here’s the full implementation with additional test scenarios, including a reverse sort.

require 'minitest/autorun'

class Size
  include Comparable

  SIZES = %w[S M L].freeze

  attr_reader :size

  def initialize(size)
    @size = size.to_s.upcase
  end

  def to_s
    @size
  end

  def <=>(other)
    position <=> other.position
  end

  protected

  def position
    SIZES.index(size)
  end
end

describe Size do
  let(:sizes) { %w[L S M].map { |s| Size.new(s) } }

  it { sizes.sort.map(&:to_s).must_equal %w[S M L] }
  it { sizes.sort { |a,b| b <=> a }.map(&:to_s).must_equal %w[L M S] }

  let(:a) { Size.new('S') }
  let(:b) { Size.new('M') }

  it { (a > b).must_equal false }
  it { (a < b).must_equal true }
  it { (a >= b).must_equal false }
  it { (a <= b).must_equal true }
  it { (a == b).must_equal false }
end

$> ruby size.rb                                                                                                     [2.1.2]
Run options: --seed 44807

# Running:

.......

Finished in 0.001490s, 4698.8003 runs/s, 4698.8003 assertions/s.

7 runs, 7 assertions, 0 failures, 0 errors, 0 skips

Exercise: Software versions often follow the convention major.minor.patch. Create a Version class that takes a string version, e.g. “10.10.3” and implement the <=> operator.

I’m currently reading Effective Ruby by Peter Jones and this post is based on Item 13: Implement Comparison via “<=>” and the Comparable Module.

You Can't Handle the Truth

Quick question. What’s the output of this code in Ruby?

amount = 0
if amount
  puts 'hey'
else
  puts ‘nah'
end

If you answered nah, you’re wrong. But it’s fine because this is one of the biggest gotchas for developers who are new to Ruby. Heck, even seasoned developers like myself sometimes forget this. I blame my college CS professors for putting too much C syntax in my brain.

Ruby has a simple rule for dealing with boolean values: everything is true except false and nil. This also means that every expression and object in Ruby can be evaluated against true or false. For example, you can have a method find that returns an object when it finds one or nil otherwise.

if  o = Customer.find_by(email: ‘stevej@rip.com’)
  puts o.name
else
  puts ‘not found it'
end

But it’s a different story when returning a numeric value because 0 evaluates to true.

false and nil can also be a common source of confusion because you have 2 values that can be false. Consider the default behaviour of Hash, which returns nil if the key does not exist. If you only factor in the nil scenario, you will have a problem when a key returns a false value - a common scenario with code that handles configuration or settings. In the case below, this will output missing key

h = {'a' => 1, 'b' => false}
key = ‘b'
if h[key]
  puts 'found a value'
else
  puts 'missing key'
end

If that’s enough confusion for you, consider this: true, false, and nil are just instances of a class.

irb> true.class
=> TrueClass
irb> false.class
=> FalseClass
irb> nil.class
=> NilClass

They are global variables but you can’t set any value to it which is fine. Otherwise, there will be chaos!

irb> true = 1
SyntaxError: (irb):18: Can't assign to true
true = 1

But, this is Ruby and we can always introduce chaos. Matz, the creator of Ruby, has given us this much power because he trusts that we know what we are doing.

irb> class Bad
irb>   def ==(other)
irb>     true
irb>   end
irb> end

irb> false == Bad.new
=> false
irb> Bad.new == false
=> true

What the heck just happened? Well, == is just another method call - the first is for the FalseClass instance while the second is for the Bad instance.

If you have been using Ruby for a while and wants to become better at it, I suggest you get a copy of Effective Ruby by Peter Jones.

Rails #permitted

I recently upgraded a personal app I use for learning new things in Rails. But when I upgraded from 4.1.4 to 4.1.12 I encountered this familiar error.

Customer.where(auth.slice(:provider, :uid)).first_or_initialize
ActiveModel::ForbiddenAttributesError: ActiveModel::ForbiddenAttributesError
from /Users/greg/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/activemodel-4.1.12/lib/active_model/forbidden_attributes_protection.rb:21:in `sanitize_for_mass_assignment`

Now I remember. It’s one of those mass assignments where you have to specify the ‘permitted’ values before you can continue. In Rails 4, it’s good practice to whitelist the attributes you receive in the controller and it goes something like this:

def user_params
  params.require(:user).permit(:username, :email, :password)
end

# somewhere in the controller
Customer.create(user_params)

Now, let’s use this idiom. It should be easy, right?

> auth.permit(:provider, :uid)
=> nil

Wait, that didn’t go as expected. How about just simply composing the hash?

> Customer.where(provider: auth[:provider], uid: auth[:uid]).first_or_initialize
=> #<Customer:0x007fd9168ffa88>

Interesting. #permit returns nil, using plain hash works, and #slice doesn’t.

> auth.slice(:provider, :uid).class
=> OmniAuth::AuthHash < Hashie::Mash

It shouldn’t matter what auth is as long as it behaves like what the Customer model expects. But what does the Customer model expects? Actually, the error message is telling us what it expects. In Rails, there is this module for mass assignment protection:

# https://github.com/rails/rails/blob/master/activemodel/lib/active_model/forbidden_attributes_protection.rb
module ActiveModel
  # Raised when forbidden attributes are used for mass assignment.
  #
  #   class Person < ActiveRecord::Base
  #   end
  #
  #   params = ActionController::Parameters.new(name: 'Bob')
  #   Person.new(params)
  #   # => ActiveModel::ForbiddenAttributesError
  #
  #   params.permit!
  #   Person.new(params)
  #   # => #<Person id: nil, name: "Bob">
  class ForbiddenAttributesError < StandardError
  end

  module ForbiddenAttributesProtection # :nodoc:
    protected
      def sanitize_for_mass_assignment(attributes)
        if attributes.respond_to?(:permitted?) && !attributes.permitted?
          raise ActiveModel::ForbiddenAttributesError
        else
          attributes
        end
      end
      alias :sanitize_forbidden_attributes :sanitize_for_mass_assignment
  end
end

Nothing fancy here. Rails does a simple check whether to allow mass assignment or not.

> auth.slice(:provider, :uid).permitted?
=> false

> { provider: auth[:provider], uid: auth[:uid] }.permitted?
NoMethodError: undefined method `permitted?' for {:provider=>"facebook", :uid=>"123"}:Hash

OmniAuth::AuthHash does not even allow it. Plain Hash works because it doesn’t even respond to #permitted.

Rebuild Rails Part 4

Now, it is time to build real pages in our super duper Tracks framework. We will support ERB and to do that we need the erubis gem.

# app/controllers/posts_controller.rb
class PostsController < Tracks::Controller
  def index
    locals = { title: "/posts/index" }
    render template: "posts/index", locals: locals
  end
end

# app/views/posts/index.html.erb
hello from tracks <%= title %>

# lib/tracks/controller.rb
def render(options)
  template_name = options.fetch(:template)
  locals = options.fetch(:locals)

  filename = File.join "app/views", "#{template_name}.html.erb"
  template = File.read(filename)
  erb = Erubis::Eruby.new(template)
  erb.result locals
end

$ ruby spec/application_spec.rb
Run options: --seed 12593

# Running:

..

Finished in 0.020498s, 97.5705 runs/s, 195.1410 assertions/s.

2 runs, 4 assertions, 0 failures, 0 errors, 0 skips

Our tests show we are good but it’s not even close to Rails' “magic”. First, let’s use the controller/action convention, i.e. if no template is passed to the render method, we should use the app/views/posts/index.html.erb template. We also modify our Controller to save the env data passed by Rack because we need to check the path.

# lib/tracks/controller.rb
module Tracks
  class Controller
    attr_reader :env, :controller_name, :action_name

    def initialize(env)
      @env = env
      extract_env_info
    end

    def extract_env_info
      _, @controller_name, @action_name, after = path_info.split("/")
    end

    def path_info
      env["PATH_INFO"]
    end

    def extract_template_name
      "#{controller_name}/#{action_name}"
    end
  end
end

We then update our render method to check for the template if it is not passed.

# lib/tracks/controller.rb
     def render(options)
-      template_name = options.fetch(:template)
+      template_name = options.fetch(:template) { extract_template_name }
       locals = options.fetch(:locals)

       filename = File.join "app/views", "#{template_name}.html.erb"
@@ -29,6 +43,5 @@ module Tracks
       erb.result locals
     end

Our next modification involves using the @instance_variables to pass values from the controller to the view files. To do that, we just need to pass the current binding to the eruby instance and it should pickup the instance variables we have in the controller.

In Rails, it is a bit more involved. There is a concept of view context. Rails collects the instance variables from the controller, then duplicates the values into the view context. The Ruby methods #instance_variables, #instance_variable_get, #instance_variable_set allow Rails to accomplish that.

def render(options={})
  template_name = options.fetch(:template) { extract_template_name }
  filename = File.join "app/views", "#{template_name}.html.erb"
  template = File.read(filename)
  erb = Erubis::Eruby.new(template)
  erb.result(binding)
end

We also update our render method and controller because we do not need the locals parameter.

# app/controllers/posts_controller.rb
class PostsController < Tracks::Controller
  def index
    @title = "/posts/index"
    render
  end
end

# app/views/posts/index.html.erb
hello from tracks <%= @title %>

We still have the extra render call in our controller. To remove it, we keep track of the call to render and if there’s no rendered result yet, we call render.

diff --git i/app/controllers/posts_controller.rb w/app/controllers/posts_controller.rb
index f7883e3..d7aa012 100644
--- i/app/controllers/posts_controller.rb
+++ w/app/controllers/posts_controller.rb
@@ -1,6 +1,5 @@
 class PostsController < Tracks::Controller
   def index
     @title = "/posts/index"
-    render
   end
 end
diff --git i/lib/tracks/controller.rb w/lib/tracks/controller.rb
index 0119c1b..3d5d75a 100644
--- i/lib/tracks/controller.rb
+++ w/lib/tracks/controller.rb
@@ -30,7 +30,12 @@ module Tracks

       controller_class_name = controller.capitalize + "Controller"
       controller_class = Object.const_get(controller_class_name)
-      controller_class.new(env).send(action)
+      controller_context = controller_class.new(env)
+      controller_context.send(action)
+
+      if controller_context.rendered_string.nil?
+        controller_context.render
+      end
+
+      controller_context.rendered_string
     end

     def render(options={})
@@ -38,7 +43,7 @@ module Tracks
       filename = File.join "app/views", "#{template_name}.html.erb"
       template = File.read(filename)
       erb = Erubis::Eruby.new(template)
-      erb.result(binding)
+      @rendered_string = erb.result(binding)
     end

We also update our controller and tests to cover the case of using render explicitly.

# app/controllers/posts_controller.rb
class PostsController < Tracks::Controller
  def index
    @title = "/posts/index"
  end

  def show
    @title = "/posts/index"
    render template: "posts/index"
  end
end

# spec/application_spec.rb
require_relative "spec_helper"

describe CrazyApp::Application do
  include Rack::Test::Methods

  def app
    CrazyApp::Application
  end

  it "should respond with /" do
    get "/"
    last_response.ok?.must_equal true
    last_response.body.strip.must_equal "hello from index.html"
  end

  it "should respond with different path" do
    get "/posts/index"
    last_response.ok?.must_equal true
    last_response.body.strip.must_equal "hello from tracks /posts/index"
  end

  it "should respond with different template" do
    get "/posts/show"
    last_response.ok?.must_equal true
    last_response.body.strip.must_equal "hello from tracks /posts/index"
  end
end