Today I Learned

A Hashrocket project

89 posts by viniciusnegrisolo @vfnegrisolo

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.

Advanced Search with Textacular

Textacular exposes full text search capabilities from PostgreSQL, extending ActiveRecord with scopes making search easy and fun!

We wanted an advanced search, but that should also work as autocomplete.

So we end up using the gem textacular and the PostgreSql tsquery with the :* for prefixed searches

Game.advanced_search(title: 'street fi:*')

Thanks @mrmicahcooper!

references

Tabularize in VIM - changing whitespaces count

In order to tabularize texts based on comma character use the tabular vim plugin:

:Tabularize /,

This will by default add at least 1 whitespace to the left and exactly 1 whitespace to the right.

You can change this adding /l<NUM>r<NUM> to the end of command.

For 0 to the left and 1 to the right:

:Tabularize /,/l0r1

example:

# original
resources :posts, path: 'blog_posts', only: [:index, :show, :create]
resources :comments, path: 'user_comments', only: [:index, :show]

# :Tabularize /,
resources :posts    , path: 'blog_posts'    , only: [:index , :show  , :create]
resources :comments , path: 'user_comments' , only: [:index , :show]

# :Tabularize /,/l0r1
resources :posts   , path: 'blog_posts'   , only: [:index, :show , :create]
resources :comments, path: 'user_comments', only: [:index, :show]

reference

Difference betweent `to_a` and `to_ary`

  • to_a => called for explicit conversions
  • to_ary => called for implicit conversions

example:

class Coordinates
  attr_reader :x, :y
  def initialize(x, y)
    @x, @y = x, y
  end

  def to_a
    puts "to_a called"
    [x, y]
  end

  def to_ary
    puts "to_ary called"
    [x, y]
  end
end

coordinates = Coordinates.new(5, 7)

puts "splat"
a = *coordinates
# => to_a called

puts "multiple assignment"
x, y = coordinates
# => to_ary called

puts "Implicit conversion into block params"
[coordinates].each { |(x, y)| puts x }
# => to_ary called

Go to file under the cursor in Vim

Let’s say in a file you reference another one, requiring that code. Vim has an very easy way to open the required (imported, loaded, etc) file. Great tip for adding external dependencies code.

Put the cursor in the file name you want to open and:

gf

If you want to go back: <Ctrl> o.

To find the file Vim uses an option called path, and you can check your current path with:

:set path

So if you expect a file is not being found you can check the path and add values to it.

:set path+=some_special_folder/

Finally, as Vim looks for a lot of paths, it may find more than one, and gf will bring the first one. To get the second match, type g2f, for the third g3f and so on.

Also, check this article to restrict Vim options by file extensions and to add file suffixes.

Vim macro is amazing!

If you want to repeat a bunch of vim commands for running multiple times, record them in a macro and run wherever you want.

To record a macro you need:

  1. type q followed by any char: [a-z]
  2. type your vim commands
  3. type q again to finish recording

Finally, to run the macro that you just created type:

@ followed by the same char you chose to store the macro.

Ruby String Mutability

Until Ruby 3 we need to explicitly call the method freeze on literal strings, so they become immutable. And, if you have a lot of literal strings in a file, this will be very repetitive and verbose. In order to let our code cleaner there is a magic comment that can be added in the top of each file.

the magic:

# frozen_string_literal: true

And it is done, all literal string are frozen now :)

example:

class Unfrozen
  def foo
    'bar'
  end
end
class StringFrozen
  def foo
    'bar'.freeze
  end
end
# frozen_string_literal: true
class ClassFrozen
  def foo
    'bar'
  end
end

To test that:

require 'spec_helper'

describe 'Ruby String Mutability' do
  it 'validates string mutability' do
    expect(Unfrozen.new.foo.frozen?). to be false
    expect(StringFrozen.new.foo.frozen?). to be true
    expect(ClassFrozen.new.foo.frozen?). to be true
  end
end
Randomized with seed 51265
.

Finished in 0.00179 seconds (files took 0.45396 seconds to load)
1 example, 0 failures

\o/