Today I Learned

A Hashrocket project

109 posts by viniciusnegrisolo @vfnegrisolo

Ember nested routes and it's active class

Ember adds automagically a css class active to all link-to elements under the current route.

So let’s say you have this in your router:

// app/router.js
Router.map(function() {
  this.route('blog', function() {
    this.route('post', { path: ':slug' });
  });
});

If you access a page like http://localhost:4200/blog/last-post, all the following links will have the active css class.

{{#link-to 'blog'}}Blog{{/link-to}}
{{#link-to 'blog' post}}{{post.name}}{{/link-to}}
<a id="ember661" href="/blog" class="ember-view active">Blog</a>
<a id="ember663" href="/blog/last-post" class="ember-view active">Last Post</a>

So far so good, but now, if by any reason you just want either child link or parent link with the active class you can change the parent link-to to use .index.

{{#link-to 'blog.index'}}Blog{{/link-to}}

So both blog and blog.index links to the same url, but they act differently with active class.

ActiveJobs callbacks with conditionals

Today I learned that ActiveJobs callbacks accept filters, so it’s easy to do conditionals like:

class SyncUserJob < ApplicationJob
  queue_as :default

  after_perform(if: :recursive) do
    SyncOrganizationJob.perform_later(user, recursive: true)
    SyncRepositoryJob.perform_later(user)
  end

  attr_accessor :user, :recursive

  def perform(access_token, recursive: false)
    @access_token = access_token
    @recursive    = recursive
    @user         = sync_user
  end

  def sync_user
    ...
  end

More context with git diff

So if you want to see more (or less) context when running a git diff command you can use -U<number_of_lines>.

Here it is a regular git diff (3 lines as default):

vnegrisolo@home:~/my-app(master)% git diff
index modified: app/jobs/github/sync_organization_job.rb
@ sync_organization_job.rb:24 @ module Github
      organization.login       = data['login']
      organization.description = data['description']
      organization.avatar_url  = data['avatar_url']
-      organization.user        = @user
+      organization.user        = user

      organization.save
      recursive_sync(organization) if @recursive

Now just showing the changed lines:

vnegrisolo@home:~/my-app(master)% git diff -U0
index modified: app/jobs/github/sync_organization_job.rb
@ sync_organization_job.rb:27 @ module Github
-      organization.user        = @user
+      organization.user        = user

Finally with 5 lines of context instead of 3:

vnegrisolo@home:~/my-app(master)% git diff -U5
index modified: app/jobs/github/sync_organization_job.rb
@ sync_organization_job.rb:22 @ module Github
      organization = Organization.find_or_initialize_by(github_id: data['id'])

      organization.login       = data['login']
      organization.description = data['description']
      organization.avatar_url  = data['avatar_url']
-      organization.user        = @user
+      organization.user        = user

      organization.save
      recursive_sync(organization) if @recursive
      organization
    end

Ruby Method arity

Today I learned that Method#arity does not consider blocks.

Also it returns negative with splat arguments.

Finally, all keyword arguments counts 1.

class Foo
  def one; end
  def two(a, b); end
  def three(a, *b); end
  def four(a, &b); end
  def five(a, b:, c:); end
end

puts Foo.instance_method(:one).arity
# => 0
puts Foo.instance_method(:two).arity
# => 2
puts Foo.instance_method(:three).arity
# => -2
puts Foo.instance_method(:four).arity
# => 1
puts Foo.instance_method(:five).arity
# => 2

h/t @higgaion

Rails reset counter caches

Today I learned that Rails has an easy way to reset counter caches

You just need to call reset_counters(id, *counters) method

irb> Organization.reset_counters(4, :repositories)
  Organization Load (0.3ms)  SELECT  "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2  [["id", 4], ["LIMIT", 1]]
   (0.5ms)  SELECT COUNT(*) FROM "repositories" WHERE "repositories"."organization_id" = $1  [["organization_id", 4]]
  SQL (2.1ms)  UPDATE "organizations" SET "repositories_count" = 6 WHERE "organizations"."id" = $1  [["id", 4]]
=> true

Rails autoload paths

Today I learned that I can see Rails autoload_paths running:

puts ActiveSupport::Dependencies.autoload_paths
/Users/vnegrisolo/projects/my-rails-app/app/business
/Users/vnegrisolo/projects/my-rails-app/app/channels
/Users/vnegrisolo/projects/my-rails-app/app/controllers
/Users/vnegrisolo/projects/my-rails-app/app/controllers/concerns
/Users/vnegrisolo/projects/my-rails-app/app/jobs
/Users/vnegrisolo/projects/my-rails-app/app/mailers
/Users/vnegrisolo/projects/my-rails-app/app/models
/Users/vnegrisolo/projects/my-rails-app/app/models/concerns
/Users/vnegrisolo/projects/my-rails-app/app/serializers
/Users/vnegrisolo/projects/my-rails-app/app/services
/Users/vnegrisolo/projects/my-rails-app/test/mailers/previews

You can always add more paths with:

# config/application.rb
config.autoload_paths << Rails.root.join('lib')

By the way, that’s why concerns (model/controller) do not have Concern as namespace.

Ember queryParams default value has to be static

Today I learned that Ember queryParams do not work with computed properties. They intend to be static values and here is an issue discussion about that.

So this code will not work properly:

export default Ember.Controller.extend({
  queryParams: ['start'],
  start: new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-' + new Date().getDate();
});
export default Ember.Route.extend({
  queryParams: {
    start: { refreshModel: true }
  },
  model(params) {
    console.log('start', params.start);
  }
});

In this example the default value is cached and if you try to change it manually, or use a computed property with volatile, you’ll see a delegate is not a function error message.

Date javascript

Date javascript function on chrome is weird:

new Date('2016-10-3')
Mon Oct 03 2016 00:00:00 GMT-0400 (EDT)
new Date('2016-10-10')
Sun Oct 09 2016 20:00:00 GMT-0400 (EDT)

Why? Anyway, this is my “alternative” approach for that:

new Date('2016-10-10 0:0')
Mon Oct 10 2016 00:00:00 GMT-0400 (EDT)
new Date('2016-10-3 0:0')
Mon Oct 03 2016 00:00:00 GMT-0400 (EDT)

Custom QUnit assertions

QUnit is the test framework on Ember land. It’s really good, but there are just 14 assertions to be used. We have ok() and notOk() so it seems that we can do whatever we want, but when some test fails the message are not good enough.

A simple string includes example:

// tests/acceptance/home-test.js
assert.ok(homePage.title.includes('Welcome to TIL'));
// => failed, expected argument to be truthy, was: false

To improve that I created this new custom assertion:

// tests/helpers/custom-asserts.js
import QUnit from 'qunit';

QUnit.assert.includes = function(text, expected) {
  let message = 'expected: "' + expected + '" to be included on: "' + text + '"';
  this.ok(text.includes(expected), message);
};

And imported in:

// tests/helpers/module-for-acceptance.js
import '../helpers/custom-asserts';

And here it is the new message on error:

// tests/acceptance/home-test.js
assert.includes(homePage.title, 'Welcome to TIL')
// => expected: "Welcome to TIL" to be included on: "Welcome to Today I Learned!"

Rails and HttpAuthentication Token

Rails has some controller helper modules for authentication:

So you can have on your controller like that:

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  include UserAuthentication

  before_action :authenticate, only: %i(show)

  def show
    render json: current_user, status: :ok
  end
end

And a controller concern like this:

# app/controllers/concerns/user_authentication.rb
module UserAuthentication
  # you might need to include:
  # include ActionController::HttpAuthentication::Token::ControllerMethods

  def authenticate
    head :forbidden unless current_user
  end

  def current_user
    @current_user ||= authenticate_or_request_with_http_token do |token|
      Session.find_by(token: token).try(:user)
    end
  end
end

Then your controller will read and parse the token from the header:

{
  headers: {
    "HTTP_AUTHORIZATION"=>'Token token="82553421c8f4e5e34436"'
  }
}

Rails 5 token ActiveModel type

Rails 5 has a new ActiveModel type token. To define it you can use: has_scure_token.

So I had this class:

class Session < ApplicationRecord
  belongs_to :user

  before_validation(on: :create) do
    self.auth_token = SecureRandom.urlsafe_base64(24)
  end
end

And then I started to use the new has_secure_token and the code is way simpler now:

class Session < ApplicationRecord
  belongs_to :user
  has_secure_token :auth_token
end

If you use generators for models you can run:

rails generate model session auth_token:token

By the way the current implementation uses SecureRandom.base58(24), so it is url safe.

Print call stack on Javascript

If you want to print the javascript call stack you can use:

console.log(new Error().stack);

Here it is an example:

function firstFunction() {
    secondFunction();
}
function secondFunction() {
    thridFunction();
}
function thridFunction() {
    console.log(new Error().stack);
}
firstFunction();

//=> Error
//    at thridFunction (<anonymous>:2:17)
//    at secondFunction (<anonymous>:5:5)
//    at firstFunction (<anonymous>:8:5)
//    at <anonymous>:10:1

h/t @rondale_sc

Elixir with macro `<-` and `=`

So Elixir with macro accepts Matching clauses <- and Bare expressions =. Both match patterns and do not leak variables assigned inside these structures.

with {:ok, width} <- Map.fetch(%{width: 10}, :width),
  do: {:ok, 2 * width}
#=> {:ok, 20}

with {:ok, width} = Map.fetch(%{width: 10}, :width),
  do: {:ok, 2 * width}
#=> {:ok, 20}

So what is the difference between these?

When a match fails, matching clauses <- returns failed result but bare expressions = raises a MatchError:

with {:ok, width} <- Map.fetch(%{height: 10}, :width),
  do: {:ok, 2 * width}
#=> :error

with {:ok, width} = Map.fetch(%{height: 10}, :width),
  do: {:ok, 2 * width}
#=> ** (MatchError) no match of right hand side value: :error

Another difference is that when guard is only available for matching clause.

with {:ok, width} when is_number(width) <- Map.fetch(%{width: 10}, :width),
  do: {:ok, 2 * width}
#=> {:ok, 20}

Rails 5 - Array Inquirer

Rails 5 has a new wrapper class for Array called ArrayInquirer the same way StringInquirer does, but with arrays.

irb> colors = [:blue, 'green'].inquiry
=> [:blue, "green"]
irb> colors.class
=> ActiveSupport::ArrayInquirer

This will create nice question like methods such as:

irb> colors = [:blue, 'green'].inquiry
=> [:blue, "green"]
irb> colors.blue?
=> true
irb> colors.green?
=> true
irb> colors.red?
=> false

It also creates the method any? for you:

irb> colors = [:blue, 'green'].inquiry
=> [:blue, "green"]
irb> colors.any?('blue', :yellow)
=> true
irb> colors.any?('red', :yellow)
=> false

By the way we use StringInquirer already:

irb> Rails.env
=> "development"
irb> Rails.env.class
=> ActiveSupport::StringInquirer
irb> Rails.env.development?
=> true

references

Ember prevent nested actions to be called

If you have an element that is affected by multiple actions you can use bubbles=false to prevent that action to bubble up to the next on the hierarchy.

<div class="container-fluid" {{action 'hideNav'}}>
  <div class="navbar-header">
    <button {{action 'toggleNav' bubbles=false}} type="button" class="navbar-toggle collapsed" aria-expanded="false">
      <span class="sr-only">Toggle navigation</span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
    </button>
    {{link-to name 'index' class="navbar-brand"}}
  </div>

  <div class="collapse navbar-collapse">
    {{yield}}
  </div>
</div>

Rails 5 db schema and its indexes

Rails 5 changed the way they register db indexes on db/schema.rb file. Now it’s nested to create_table method.

rails 5

create_table "users", force: :cascade do |t|
  ...
  t.index ["username"],
    name: "index_users_on_username", using: :btree
end

rails 4

add_index "users", ["username"],
  name: "index_users_on_username", using: :btree

Foreign keys are registered the same way as before:

ActiveRecord::Schema.define(version: 20160524224020) do
  add_foreign_key "sessions", "users"
end

Rails 5 - Use limit on ActiveRecords with OR

Rails 5 comes released a new ActiveRecord or but it still have some constraints you should know.

limit, offset, distinct should be on both queries, otherwise:

irb(main):001:0> Session.where(user_id: 1).or(
                   Session.where(provider: 'github').limit(10)
                 ).to_sql
=> ArgumentError: Relation passed to #or must be structurally compatible.
   Incompatible values: [:limit]

You need to add limit to both sides of the query, like:

irb(main):002:0> puts Session.where(user_id: 1).limit(10).or(
                   Session.where(provider: 'github').limit(10)
                 ).to_sql
=> "SELECT  "sessions".*
    FROM "sessions"
    WHERE ("sessions"."user_id" = 1 OR "sessions"."provider" = 'github')
    LIMIT 10"

Does CoffeeScript has a ternary operator?

Javascript has a ternary operator condition ? expr1 : expr2 like:

var x = true;
x ? 'yes' : 'no'
//=> "yes"
x = false;
x ? 'yes' : 'no'
//=> "no"

What about CoffeeScript?

#is-it-ternary.coffee
x ? 'yes' : 'no'

will be compiled to the following:

//is-it-ternary.js
if (typeof x !== 'undefined' && x !== null) {
  x;
} else {
  ({ 'yes': 'no' });
}

Why? Because of The Existential Operator

So there is no ternary operator on CoffeeScript. The best you can do is to have an one-line if-else statement:

if x then 'yes' else 'no'

h/t @mwoods79

Vim tree view mode

In folder/file structure if you type i you’ll change the way you see the folders and files.

There are 4 different view modes and one of them is something like that:

../
my-app/
| app/
| bin/
| config/
| | environments/
| | | development.rb
| | | production.rb
| | | test.rb
| | initializers/
| | locales/
| | application.rb
| | boot.rb
| | database.yml
| | environment.rb
| | routes.rb
| db/

So yes, We don’t need nerdtree

Rails 5 API and CORS

So if you have your frontend and backend on different domains you’ll need to allow CORS (cross-origin HTTP request).

Rails 5 with --api mode will prepare the setup for you. You just need to uncomment the following:

# Gemfile
gem 'rack-cors'
# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'localhost:4200'
    resource '*',
      headers: :any,
      methods: %i(get post put patch delete options head)
  end
end

Time Travel by Rails

Rails have travel, travel_to, travel_back Testing Helper Methods to travel in time and then test your app with exact dates and times.

include ActiveSupport::Testing::TimeHelpers

Time.current
=> Tue, 17 May 2016 12:31:52 UTC +00:00

travel_to Time.new(2004, 11, 24, 01, 04, 44)
Time.current
=> Wed, 24 Nov 2004 06:04:44 UTC +00:00

travel_back
Time.current
=> Tue, 17 May 2016 12:32:17 UTC +00:00

Another nice alternative is to use gems like:

Rails 5 API mode

You can build a rails API with the --api mode on Rails 5.

rails _5.0.0.rc1_ new rails5-api --api

Gems removed with api mode:

  • coffee-rails
  • jquery-rails
  • sass-rails
  • uglifier
  • turbolinks
  • jbuilder

Files removed with api mode:

  • app/assets
  • lib/assets
  • vendor/assets
  • app/helpers
  • app/views/layouts/application.hmtl.erb

ApplicationController

app/controller/application_controller.rb has changed to inherit from ActionController::API

Rack middleware list changed with api mode:

5 middlewares were removed, including Cookies and Flash.

use Rack::MethodOverride
use WebConsole::Middleware
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash

Check their changelog

Rails 5 changed belongs_to default validation

New Rails 5 applications comes with this initializer:

# config/initializers/active_record_belongs_to_required_by_default.rb
Rails.application.config.active_record.belongs_to_required_by_default = true

So now, belongs_to associations require presence by default.

In order to make this relation optional you can change the initializer value for the whole app, or add optional: true to your relation:

belongs_to :user, optional: true

That’s the code change.

How to take next elements with Enumerator

So we had this logic to get the next 10 Tuesdays and Thursdays:

date = Time.current
(1..10).map { date = next_available_day(date) }

def next_available_day(date)
  loop { return date if tuesday_or_thursday?(date += 1.day) }
end

def tuesday_or_thursday?(date)
  date.wday == 2 || date.wday == 4
end

With Enumerator it seems cleaner:

next_available_dates(Time.current).take(10)

def next_available_dates(date)
  Enumerator.new do |enumerator|
    loop { enumerator.yield date if tuesday_or_thursday?(date += 1.day) }
  end
end

def tuesday_or_thursday?(date)
  date.wday == 2 || date.wday == 4
end

h/t by @mwoods79

Rails 5 deprecation warning for Controller tests

Rails 5 has changed its ActionController::TestCase::Behavior to methods we use in controller tests and now you need to specify params key to the requests.

So if you had this in you tests:

get :show, id: 1

You’ll need to change to:

get :show, params: { id: 1 }

There is a warning message like this:

ActionController::TestCase HTTP request methods will accept only
keyword arguments in future Rails versions.
Examples:
  get :show, params: { id: 1 }, session: { user_id: 1 }
  process :update, method: :post, params: { id: 1 }

Even if you use Rpsec you will need to change because rspec-rails uses this module to create the requests to the controllers.

Auto Complete with vim snippets

There is a vim plugin called vim-snipmate that lets to auto complete a bunch of code for you. So you type some snippet trigger and hit <tab> and that’s it.

Also there is another vim plugin vim-snippets with a lot of snippets for a lot of languages or file types.

For example this snippet for if in shell files:

snippet if
    if [[ ${1:condition} ]]; then
        ${0:#statements}
    fi

reference

Global git hooks

There are no global git hooks.

But you can create global hook templates and for every project you can use them.

Create a folder for your global templates:

mkdir -p ~/.git-templates/hooks

Configure git to use this templates folder:

git config --global init.templatedir '~/.git-templates'

Create an executable hook, for example `~/.git-templates/hooks/pre-commit bash #!/bin/sh echo "This happens before the commit" For every project you want to use these templates restart git project running: bash git init These will create symbol links like: bash lrwxr-xr-x pre-commit -> /Users/vnegrisolo/.git-templates/hooks/pre-commit

Vim arglist => replace text to multiple files

  • :arg => list files in arglist
  • :argdelete * => clean arglist
  • :argadd **/*.rb => add files to arglist
  • :argdo %s/foo/bar/gc => replace foo by bar in arglist
  • :argdo update => save changes to arglist
  • :argdo undo => undo changes to arglist

Navigation in arglist

  • [a => go to the previous file in arglist
  • ]a => go to the next file in arglist
  • [A => go to the first file in arglist
  • ]A => go to the last file in arglist

Github Reactions and Merge Strategy

Recently Github has launched 2 new features:

Reactions to comments

Now users can add few reactions to Issues, Pull Requests or Comments. This will reduce a lot of noise on issues like the 👍 (:+1:) for new feature. Really cool.

Squash and rebase as a new merge strategy

Github always create a new commit merging a branch into another one (usually master).

So now we can choose to squash commits and merge. So all commits in a branch will become just one and this one will be rebased into the branch. Sounds cool for a few situations.

Check it out!

Rails nested forms

You can use rails Nested Forms even with plain form objects.

You just need to use form.fields_for and then declare the params name and the object for validation errors.

view

= form_for @foo_form, as: :foo, url: foo_path do |form|
  = form.text_field :name
  = form.fields_for :bar_params, @foo.bar do |nested_form|
    = nested_form.text_field :description

form

class FooForm
  include ActiveModel::Model

  attr_accessor :name, :bar_params, :bar
  validates :name, presence: true

  def initialize(attributes = {})
    super attributes

    self.bar  = BarForm.new(bar_params)
  end

  def save
    valid? && Foo.create(name: name)&& bar.save
  end
end
class BarForm
  include ActiveModel::Model

  attr_accessor :description
  validates :description, presence: true

  def save
    valid? && Bar.create(description: description)
  end
end

controller

class FooController < ApplicationController
  def edit
    @foo_form = FooForm.new
  end

  def create
    @foo_form = FooForm.new(params[:foo])
    if @foo_form.save
      redirect_to root_path
    else
      render :edit
    end
end

So the bar params will be scoped into bar_params node, and the validations will work.

Capybara tests on a Rails Engine

If you have a Rails Engine you probably have a dummy app attached in test/dummy or spec/dummy to run and manual test the engine, right?

So you can use this dummy app to have some feature tests with capybara.

The trick is to change the RAILS_ROOT to point to the correct file.

For cucumber tests add the following into features/support/env.rb:

ENV["RAILS_ROOT"] ||= File.expand_path(File.dirname(__FILE__) + '/../../spec/dummy')

For rspec tests add the following into spec/rails_helper.rb:

ENV["RAILS_ROOT"] ||= File.expand_path(File.dirname(__FILE__) + '/dummy')

Ruby Keyword Arguments - mandatory params

If you do not set a default value for a keyword argument it will become mandatory and will raise an ArgumentError.

an example:

def mandatory_keyword(bar:)
  "foo=#{bar}"
end

def default_keyword(bar: nil)
  "foo=#{bar}"
end

and its test:

describe 'keyword_arguments' do
  it { expect{mandatory_keyword()}.to raise_error(ArgumentError) }
  it { expect(mandatory_keyword(bar: 'bar')).to eq('foo=bar') }

  it { expect(default_keyword()).to eq('foo=') }
  it { expect(default_keyword(bar: 'bar')).to eq('foo=bar') }
end

In case you don’t want this behavior you can always set a default to nil.

Cucumber step inception

I learned that you can call a cucumber step inside another one. Super cool!

Image a simple step like this:

Then /^I should see "(.*)"$/ do |text|
  expect(page).to have_content(text)
end

Now you have modals on your web app and you need to restrict this assertion to inside the modal.

So you can start to duplicate the step above to match the within modal and all the ones that could be reusable with modals.

Or you can create a new generic step that scope into the modal and calls your original step, such as:

And /^(.*?) within the modal$/ do |step_definition|
  within ".modal", visible: true do
    step step_definition
  end
end

Now you can call both steps in your gherkin files:

Then I should see "Hello World!"

or

Then I should see "Hello World!" within the modal

Just be careful with generic and reusable steps. The idea is to help developers and not confuse them.

reference: Cucumber wiki - call step from step definition

h/t Taylor Mock

Capybara's RackTest driver limitation

By default, if you click a link through Capybara and it takes you into an external site, the current_url will work fine, but the content on the page does not change.

RackTest is the Capybara’s default driver and it has its limitations.

you cannot use the RackTest driver to test a remote application, or to access remote URLs

If you really need to do that know other drivers features and try them.

h/t by Nick Palaniuk

Ruby array shortcuts - "&:" and "&method"

Call a method on every items with &:

So this:

[:foo, :bar].each do |item|
  item.to_s
end

Can be reduced to:

[:foo, :bar].each(&:to_s)

But, what if you want to call a method for each item in an array, and this item should be a parameter for this method?

Call a method with every items as a parameter with &method

So this:

[:foo, :bar].each do |item|
  puts(item)
end

Can be reduced to:

[:foo, :bar].each(&method(:puts))

Fixing a past commit

To add some code changes to a past commit you can commit with the fixup option and then rebase it.

# add the code changes you want to add to the past commit
git add .

# get the git commit you want to add the changes and copy its reference
git log

# commit the changes with the options `fixup`
git commit --fixup <COMMIT_REF>

# rebase the commits to apply the fixup from one commit before the starter commit
git rebase -i --autosquash <COMMIT_REF>~1

This will open your editor (vim) with the rebase plan, so you can apply it.