Today I Learned

A Hashrocket project

105 posts about #rails

Revealing Rails Scopes

I've been working on some Rails code that brings in ActiveRecord models from multiple gems. Often these models have default scopes, that bane of a legacy Rails codebase, and figuring that out requires source diving one or more gems. Today I hacked my way to a faster solution: just read the SQL Rails generates.

Here's a post without a default scope, and then one with a default scope:

pry(main)> Post.all.to_sql
=> "SELECT \"posts\".* FROM \"posts\""
pry(main)> Developer.all.to_sql
=> "SELECT \"developers\".* FROM \"developers\" ORDER BY \"developers\".\"username\" ASC"

I see you, ORDER BY.

Debug the `--exclude-pattern` option in rspec.

You can exclude certain files from being run by rspec with the --exclude-pattern option like so:

rspec --exclude-pattern run_me_not_spec.rb

You can place this option into your .rspec file.

When doing this and then committing the .rspec file its helpful to make sure the exclude pattern is correct. Try this command and pipe it into grep.

rspec --dry-run -fdoc | grep 'excluded test name'

If no results are returned, then you are successfully excluding the test! The --dry-run option is important because actually running the entire test suite would be too time consuming.

How Rails Responds to `*/*`

Yesterday I fixed a bug in TIL. This application has a Twittercard, but it's never worked. Twitter's card validator confusingly claims the site lacks Twitter meta tags.

After experimenting, I realized that when cURL-ing our site, the response type is application/json. Why is Rails giving me JSON?

When an HTTP request's accept headers equal */*, any MIME type is accepted. And, when a respond_to block is present:

Rails determines the desired response format from the HTTP Accept header submitted by the client.

Determined how? I learned that the first MIME type declared is used for */* requests.

Notice the difference (HTML first):

# app/controllers/posts_controller.rb
 def index
    @posts = Post.all
    respond_to do |format|
      format.html
      format.json { render json: @posts }
    end
  end

Request/response:

$ curl -vI localhost:3000/
...
> Accept: */*
>
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
...

JSON first:

# app/controllers/posts_controller.rb
 def index
    @posts = Post.all
    respond_to do |format|
      format.json { render json: @posts }
      format.html
    end
  end

Request/response:

$ curl -vI localhost:3000/
...
> Accept: */*
>
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
...

Reversing the order (HTML first) solved the issue.

Start rails with a different web server

The webserver for my current project is puma, which is a multi-threaded ruby server. This multithreaded nature makes it hard to but a pry statement in and break in specific places. There are multiple threads that will listen to the input the user provides at the REPL.

Using webrick would allow us to debug and step through our code but changing the applications configuration in order to enable that seems unreasonable.

Fortunately, rails provides an easy way to change servers at the command line.

> rails server webrick

Just pass the name of the server you would rather use as a command line argument.

When typing rails server --help you'll see this option available on the first line:

Usage: rails server [mongrel, thin etc] [options]

Deprecated Dynamic Actions

Rails routes have supported dynamic actions in the past, like this:

# config/routes.rb

 get 'ui(/:action)', controller: 'ui'

The 'Today I Learned' application uses code like this to create design templates.

This will be deprecated in Rails 5.1. If you want to address the change now, while still supporting a variety of routes, one solution is to name them explicitly:

# config/routes.rb

namespace 'ui' do
  %w(index edit show).each do |action|
    get action, action: action
  end
end

Polymorphic Path Helpers

Underlying many of the path helpers that we use day to day when building out the views in our Rails apps are a set of methods in the ActionDispatch::Routing::PolymorphicRoutes module.

The #polymorphic_path method given an instance of a model will produce the relevant show path.

> app.polymorphic_path(Article.first)
  Article Load (0.5ms)  SELECT  "articles".* FROM "articles"  ORDER BY "articles"."id" ASC LIMIT 1
=> "/articles/2"

Given just the model's constant, it will produce the index path.

> app.polymorphic_path(Article)
=> "/articles"

Additionally, there are variants with edit_ and new_ prefixed for generating the edit and new paths respectively.

> app.edit_polymorphic_path(Article.first)
  Article Load (0.6ms)  SELECT  "articles".* FROM "articles"  ORDER BY "articles"."id" ASC LIMIT 1
=> "/articles/2/edit"
> app.new_polymorphic_path(Article)
=> "/articles/new"

Mark For Destruction

Do you have some complicated logic or criteria for deleting associated records? ActiveRecord's #mark_for_destruction may come in handy.

Let's say we have users who author articles. We want to delete some of the user's articles based on some criteria -- those articles that have odd ids.

> user = User.first
#=> #<User...>
> user.articles.each { |a| a.mark_for_destruction if a.id.odd? }
#=> [#<Article...>, ...]
> user.articles.find(1).marked_for_destruction?
#=> true
> user.articles.find(2).marked_for_destruction?
#=> false

We've marked our articles for destruction and confirmed as much with the #marked_for_destruction? method. Now, to go through with the destruction, we just have to save the parent record -- the user.

> user.save
   (0.2ms)  BEGIN
  User Exists (0.8ms)  SELECT  1 AS one FROM "users" WHERE ("users"."email" = 'person1@example.com' AND "users"."id" != 1) LIMIT 1
  SQL (3.0ms)  DELETE FROM "articles" WHERE "articles"."id" = $1  [["id", 1]]
  SQL (0.2ms)  DELETE FROM "articles" WHERE "articles"."id" = $1  [["id", 3]]
   (2.1ms)  COMMIT
=> true

Note: the parent record must have autosave: true declared on the association.

class User < ActiveRecord::Base
  has_many :articles, autosave: true
end

Convert A Symbol To A Constant

If you have a symbol and need to convert it to a constant, perhaps because of some metaprogramming induced by a polymorphic solution, then you may start off on an approach like the following. In fact, I've seen a number of StackOverflow solutions like this.

:module.to_s.capitalize.constantize
#=> Module

That is great for one-word constant names, but what about multi-word constants like OpenStruct. This approach will not work for the symbol :open_struct. We need a more general solution.

The key is to ditch #capitalize and instead use another ActiveSupport method,#classify`.

:open_struct.to_s.classify.constantize
#=> OpenStruct

List The Enqueued Jobs

Many Rails apps need to delegate work to jobs that can be performed at a later time. Both unit and integration testing can benefit from asserting about the jobs that get enqueued as part of certain methods and workflows. Rails provides a handy helper method for checking out the set of enqueued jobs at any given time.

The enqueued_jobs method will provide a store of all the currently enqueued jobs.

It provides a number of pieces of information about each job. One way to use the information is like so:

describe '#do_thing' do
  it 'enqueues a job to do a thing later' do
    Processor.do_thing(arg1, arg2)
    expect(enqueued_jobs.map { |job| job[:job] }).to match_array([
      LongProcessJob,
      SendEmailsJob
    ])
  end
end

To use this in your Rails project, just enable the adapter in your test configuration file:

Rails.application.config.active_job.queue_adapter = :test

Rails Database Migration Status

Wondering if you've run the latest database migration? Wonder no more. There are better ways to find out this information than blindly running the migrations or waiting for your app to crash.

A handy command built into Rails is rake db:migrate:status. Here's some sample output from my blog's development PostgreSQL database:

% rake db:migrate:status

database: worth-chicago_co_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20150422174456  Create developers
   up     20150422174509  Create authem sessions
   up     20150422200749  Create posts
   up     20150423152139  Add url slugs to posts
   up     20150628171109  Add constraints to posts and developers

Knowledge is power!

Rails' `Hash#except`

A cool feature provided by Rails is the #except method on Hash. This function removes a key-value pair from a hash.

Let's remove a Rails parameter, the most obvious use case I can think of:

[1] pry(#<SessionsController>)> params
=> {"utf8"=>"✓",
 "user_login"=>{"email"=>"", "password"=>"general"},
 "controller"=>"sessions",
 "action"=>"create"}
[2] pry(#<SessionsController>)> params.except(:utf8)
=> {"user_login"=>{"email"=>"", "password"=>"general"},
 "controller"=>"sessions",
 "action"=>"create"}

And the source code:

[1] pry(#<SessionsController>)> show-source Hash#except
From: /Users/dev/.rvm/gems/ruby-2.2.5/gems/activesupport-4.2.7.1/lib/active_support/core_ext/hash/except.rb @ line 9:
Owner: Hash
Visibility: public
Number of lines: 3

def except(*keys)
  dup.except!(*keys)
end
[2] pry(#<SessionsController>)> show-source Hash#except!

From: /Users/dev/.rvm/gems/ruby-2.2.5/gems/activesupport-4.2.7.1/lib/active_support/core_ext/hash/except.rb @ line 17:
Owner: Hash
Visibility: public
Number of lines: 4

def except!(*keys)
  keys.each { |key| delete(key) }
  self
end

Thanks Rails!

Use UUID as primary key on Rails with Postgres

Just enable the uuid-ossp extension and set the id field with uuid:

class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    enable_extension 'uuid-ossp'
    create_table :users, id: :uuid do |t|
      t.string :name
      t.timestamps
    end
  end
end

Rails Runner Shebang Line

I've known about the Rails Runner command for a long time - all it does is execute some Ruby in the context of your app. I've rarely used it, but had a situation today where I wanted to. I couldn't quite remember how it worked, so I ran it without any arguments and discovered something new (to me, anyway):

$ rails runner
...blah, blah, blah...
You can also use runner as a shebang line for your executables:
-------------------------------------------------------------
#!/usr/bin/env /Users/jon/project/bin/rails runner

Product.all.each { |p| p.price *= 2 ; p.save! }
-------------------------------------------------------------

Whoa, Rails gives you the runner as a shebang line too!!

Use PostgreSQL socket in database.yml 🐘🔌

When using the host: configuration option in the database.yml set to localhost or 127.0.0.1, I would need to add an entry to PostgreSQL's pg_hba.conf file to allow my ip address access. But, if you give the host: option the directory of your PostgreSQL sockets, rails will be able to use the socket, without needing to add an entry to the PostgreSQL configuration file.

production:
  database: fancy_things
  host: '/var/run/postgres'
  ...

Rails Enum with prefix/suffix

Today I learned that Rails 5 released new options for enum definition: _prefix and _suffix.

class Conversation < ActiveRecord::Base
  enum status:          [:active, :archived], _suffix: true
  enum comments_status: [:active, :inactive], _prefix: :comments
end

conversation.active_status!
conversation.archived_status? # => false

conversation.comments_inactive!
conversation.comments_active? # => false

h/t @joshuadavey

Rails Enums and PostgreSQL Enums

Today I learned that Rails Enum works pretty well with PostgreSQL Enum.

Enum in PostgreSQL work as a new type with restricted values and if you need to guarantee data integrity there's no best way to do that than in your database. Here is how we create a new enum in PostgreSQL and use in a new column:

class AddEnumToTrafficLights < ActiveRecord::Migration
  def up
    ActiveRecord::Base.connection.execute <<~SQL
      CREATE TYPE color AS ENUM ('red', 'green', 'blue');
    SQL

    add_column :traffic_lights, :color, :color, index: true
  end

  def down
    remove_column :traffic_lights, :color

    ActiveRecord::Base.connection.execute <<~SQL
      DROP TYPE color;
    SQL
  end
end

In Rails models I prefer to use Hash syntax when mapping Ruby symbols to database values.

class TrafficLight < ApplicationRecord
  enum color: {
    red: 'red',
    green: 'green',
    blue: 'blue',
  }
end

And now Enum in action:

TrafficLight.colors
#=> {
#=>   "red"=>"red",
#=>   "green"=>"green",
#=>   "blue"=>"blue"
#=> }
TrafficLight.last.blue!
TrafficLight.last.color
#=> "blue"

h/t @ joshuadavey

Advance The Date

In Rails land, you can advance a date forward and backward with the #advance method:

> Date.today
=> Wed, 31 Aug 2016
> Date.today.advance(days: 1)
=> Thu, 01 Sep 2016
> Date.today.advance(months: 1)
=> Fri, 30 Sep 2016
> Date.today.advance(months: -2)
=> Thu, 30 Jun 2016

h/t Dillon Hafer

Combine Multiple Rake Commands Without &&

Ordinarily, to successively run commands whose execution depends on the previous command's success, you would separate the commands with two &'s. This includes rake commands:

rake db:migrate && rake db:redo && rake db:test:prepare

The downside to this, however, is that the Rails environment is loaded with each rake invocation. Thus, in this example the Rails environment is loaded three times.

This is slow. To speed up the process, we can load the Rails environment just once as follows:

rake db:migrate db:redo db:test:prepare

We still run all the tasks but don't have to wait an eternity for Rails to get its act together and load rake. Hooray!

Hat-tip @mrmicahcooper

One query with Select In

Today I learned that if you pass an ActiveRecord::Relation as param to where Rails will create a single query using IN and an inner SELECT:

Repository.where(user_id: User.active.select(:id))
=>  Repository Load (1.1ms)
        SELECT "repositories".*
        FROM "repositories"
        WHERE "repositories"."user_id" IN (
          SELECT "users"."id"
          FROM "users"
          WHERE "users"."status" == 1
       )

h/t @mattpolito

Rails Generate, No Tests

Rails generators do a lot of things, such as adding boilerplate (assert true) tests.

Exclude these files with --no-test-framework:

$ rails generate model foobar --no-test-framework

An alternative would be setting this in your app's configuration; this is the more granular approach.

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

Does template exist?

An instance of the class ActionView::LookupContext::ViewPaths is available as lookup_context in a rails view.

If you need to dynamically construct partial paths and know that sometimes the partial may not exist, you can use the lookup_context method exists? to determine if the partial exists.

lookup_context.exists?(dynamic_path, [], true)

The third argument is partial, the second argument is prefixes.

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.

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"'
  }
}

Get a Random Record from an ActiveRecord Model

Let's say you have an events table with a model name Event. If you want to get a random event from the table, you could run

Event.find_by_sql(
  <<-SQL
    SELECT * FROM events ORDER BY random() LIMIT 1
  SQL
).first

The functional part of this query is the ORDER BY random() bit. For every row that postgres is sorting, it generates a random number (between 0.0 and 1.0 by default). Then it sorts the rows by their randomly generated number. Read more about the postgres random() function at the documentation page.

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.

Read-Only Models

Are you in the midst of a big refactoring that is phasing out an ActiveRecord model? You may not be ready to wipe it from the project, but you don't want it accidentally used to create any database records. You essentially want your model to be read-only until it is time to actually delete it.

This can be achieved by adding a readonly? method to that model that always returns true.

def readonly?
  true
end

ActiveRecord's underlying persistence methods always check readonly? before creating or updating any records.

source

h/t Josh Davey

Where Am I In The Partial Iteration?

Let's say I am going to render a collection of posts with a post partial.

<%= render collection: @posts, partial: "post" %>

The ActionView::PartialIteration module provides a couple handy methods when rendering collections. I'll have access in the partial template to #{template_name}_iteration (e.g. post_iteration) which will, in turn, give me access to #index, #first?, and #last?.

This is great if I need to do something special with the first or last item in the collection or if I'd like to do some sort of numbering based on the index of each item.

source

h/t Josh Davey

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

Reflect on Association

Integration tests should expose flaws in our Rails domain models and associations pretty well. Any remaining uncertainty can be remedied by familiarity with the methods (has_many, belongs_to), entity relationship diagrams, and good old trial and error.

But what if we want to explicitly check our associations in a unit test?

Here's one way, with reflect_on_association:

# app/models/hike.rb
class Hike < ActiveRecord::Base
  has_many :trips
end
# spec/models/hike_spec.rb
RSpec.describe Hike, type: :model do
  describe 'associations' do
    it 'has many trips' do
      ar = described_class.reflect_on_association(:trips)
      expect(ar.macro) == :has_many
    end
  end
end

Overkill? Probably. But it's nice to have.

Source code:

https://github.com/rails/rails/blob/master/activerecord/lib/active_record/reflection.rb#L109

Enable commands history on rails console and irb

In order to enable commands history on rails console or irb you can just add the following to your ~/.irbrc file:

IRB.conf[:SAVE_HISTORY] = 200
IRB.conf[:HISTORY_FILE] = '~/.irb-history'

So if you close your rails console or irb the history is persisted. So next time just use the arrows UP/DOWN to get your history back.

reference

IRB Context documentation

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

HTTP Request Methods Limiting Arguments

Check out this RSpec test from a Rails application:

# spec/controllers/chapters_controller_spec.rb
describe ChaptersController do
  describe '#create' do
    it 'creates a chapter' do
      expect do
        post :create, chapter: FactoryGirl.attributes_for(:chapter)
      end.to change(Chapter, :count).by(1)
    end
  end
end

The code after :create will soon be deprecated.

5.0+ versions of Rails will only accept keyword arguments for ActionController::TestCase HTTP request methods.

Here's that same test, minus the deprecation warnings I encountered upgrading to the first Rails 5 release candidate:

# spec/controllers/chapters_controller_spec.rb
describe ChaptersController do
  describe '#create' do
    it 'creates a chapter' do
      expect do
        post :create, params: { chapter: { title: 'Change', body: 'Change is coming!' } }
      end.to change(Chapter, :count).by(1)
    end
  end
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"

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.

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.

Perform SQL Explain With ActiveRecord

Want to check out the performance characteristics of some SQL query from within a Pry session? ActiveRecord allows you to perform a SQL explain on any ActiveRecord::Relation object. After chaining some Arel functions together, add an #explain.

Here is an example:

Recipe.all.joins(:ingredient_amounts).explain
  Recipe Load (0.9ms)  SELECT "recipes".* FROM "recipes" INNER JOIN "ingredient_amounts" ON "ingredient_amounts"."recipe_id" = "recipes"."id"
=> EXPLAIN for: SELECT "recipes".* FROM "recipes" INNER JOIN "ingredient_amounts" ON "ingredient_amounts"."recipe_id" = "recipes"."id"
                                 QUERY PLAN
----------------------------------------------------------------------------
 Hash Join  (cost=1.09..26.43 rows=22 width=148)
   Hash Cond: (ingredient_amounts.recipe_id = recipes.id)
   ->  Seq Scan on ingredient_amounts  (cost=0.00..21.00 rows=1100 width=4)
   ->  Hash  (cost=1.04..1.04 rows=4 width=148)
         ->  Seq Scan on recipes  (cost=0.00..1.04 rows=4 width=148)
(5 rows)

source

Rails: Start Resque Worker for Async Mail Delivery

QUEUE=mailers rake resque:work

Assuming you have properly configured Resque to work with ActiveJob, you will now have a worker waiting to execute mail delivery requests. Read more about how to execute these deliveries to take full advantage of the feature.

If you want to run the worker as a background process simply set the BACKGROUND environment variable before running the rake task:

BACKGROUND=1 QUEUE=mailers rake resque:work

Create Migrations Faster with Vim-Rails

Not only can we create database migrations with vim-rails, it's faster than using a generator. I counted the steps today and realized I was wasting precious keystrokes by underutilizing this great plugin.

Here's how I was setting up a migration:

  • Generate the migration:
$ rails g migration add_diameter_to_wheels
  • Load the migration in Vim with :Emigration (vim-rails)
  • Replace change method with up and down methods

Here's my improved method:

  • Generate a migration (in Vim) with :Emigration AddDiameterToWheels!
  • Create up and down methods

Less steps and characters, and best of all, we never have to leave Vim.

h/t Chris Erin

Demodulize A Class Name

If you call .class.name on an instance of some class, the fully qualified name will be returned, module names and all. Consider the following example class:

module One
  module Two
    class Three
      ...
    end
  end
end
> One::Two::Three.new.class.name
#=> "One::Two::Three"

If you just want the unqualified class name; modules not included, you can use the #demodulize method provided by ActiveSupport.

> One::Two::Three.new.class.name.demodulize
#=> "Three"

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.

Write and Read Rails Attributes with Aliases

Lately I've been using a nice alias for writing record attributes in Rails, []=. Behold:

[1] pry(main)> d = Developer.new
=> #<Developer:0x007ff7130f8ef0
[2] pry(main)> d[:username] = 'isaacemard'
=> "isaacemard"
[3] pry(main)> d.username
=> "isaacemard"

That's all it takes. There's a getter variant, too:

[4] pry(main)> d[:username]
=> "isaacemard"

Here's how I used it today:

# app/models/developer.rb
before_create :generate_slug

def generate_slug
   self[:slug] ||= SecureRandom.hex(5)
end

To me this code reads a lot more nicely than write_attribute(attr_name, value). These aliases have been a part of Rails since at least 2012.

Source

h/t Mike Chau and Chris Erin

Attach A File With Capybara

There are two ways to attach a file with Capybara. The more conventional way is with the attach_file method.

Assuming there is a form with a file input similar to the following:

<label for='data-file'>Data File</label>
<input type='file' name='data-file' />
attach_file('data-file', 'path/to/file.csv')

The first argument to attach_file is a locator which refers to the name attribute on the input tag.

If for some reason there is no name attribute on the input tag, the file can be attached with a standard find and set.

find('form input[type="file"]').set('path/to/file.csv')

Custom Validation Message

When using Rails validations, a standard error message will be provided whenever there is a violation. Consider the scenario when there is a uniqueness validation on the email attribute and it is violated:

# User model
validates_uniqueness_of :email

# Users controller
new_user.errors.full_messages
#=> ["Email has already been taken"]

Sometimes you don't want the default validation message. The validation declaration can be given a message option to specify an alternate validation message.

# User model
validates_uniqueness_of :email, message: 'is not available'

# Users controller
new_user.errors.full_messages
#=> ["Email is not available"]

Keep in mind that full_messages will prepend the model name to the front of the message. You'll want to ensure that the resulting message is coherent.

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

Truncate Almost All Tables

The database_cleaner gem is a handy way to make sure you have a consistent database context for each test example or suite. One database_cleaner strategy that can be used is the truncation strategy. This truncates the data from all the tables by default. This is not ideal for fixed tables that contain domain-specific data because you end up having to do way more test setup than should be necessary. Fortunately, specific tables can be excepted by the truncation strategy using the except option.

For instance, if we have a standard set of roles for users of our application, we can except that table from truncation with a line like the following in our rails_helper.rb file:

DatabaseCleaner.strategy = :truncation, {:except => %w[roles]}

Interact with Rails via Runner

The rails runner feature of the Ruby on Rails command line interface is pretty awesome.

The documentation states:

runner runs Ruby code in the context of Rails non-interactively.

Use it like the ruby command to execute Ruby code in the context of your Rails environment. Take this file:

# rails_runner_in_action.rb
puts Developer.count # 5
puts Post.count # 40
puts Channel.count # 17

And run it with:

$ rails runner rails_runner_in_action.rb
5
40
17

It also runs Ruby code right in the terminal, so this works (rails r is an alias):

$ rails r "puts Developer.count"
5

http://guides.rubyonrails.org/command_line.html#rails-runner

after_commit callback in active_record

We use ActiveRecord callbacks at times to sync data between systems but if you're using transactions you don't want to have called a callback like after_create and then have the whole transaction roll back, then you'd have out of sync systems! Rails conveniently includes an after_commit hook for such a case.

class Fruit < ActiveRecord::Base
  after_commit :sync_to_fruitstand
end

ActiveRecord::Base.transaction do
  Fruit.create(name: 'Banana')
  Fruit.create(name: 'Kiwi')
  Fruit.create(name: 'Rasberry')
end

Now, the callback is called (for all 3 times) only when the final Fruit creation for a Rasberry succeeds.

Set Schema Search Path

By default the schema search path for a PostgreSQL database is going to be "$user", public. Tables created by a Rails migration are going to end up on the public schema by default. If your application has other schemas in play, then you may want to ensure that those schemas are also on the schema search path. This can be accomplished by adding the schema_search_path setting to your database.yml file. For instance, to include both the legacy and public schema in the Postgres search path, add the following line:

schema_search_path: "legacy,public"

h/t Jack Christensen

Change Rails validation message completely

Rails always appends field name to validation messages, and when setting message: on a validation statement it gets appended to the humanized name of the field.

If you want to change the validation message completely use the locales file:

# config/locales/en.yml
en:
  activerecord:
    attributes:
      user:
        email: "E-mail address"
    errors:
      models:
        user:
          attributes:
            email:
              blank: "is required"

This was particularly useful with the ValidatesTimeliness gem which does not support a lambda.


🎉Happy 500 post to TIL 🎉

Rails Sandbox 🏖

When you are working with a complicated database structure, and find yourself needing to debug a complex or dangerous (delete) action, you might be hesitant to experiment. Keep experimenting! Don't want to setup all that data again? No worries. You can use a sandbox $ rails c -s

Usage: rails console [environment] [options]
  -s, --sandbox      Rollback database modifications on exit.

The sandbox flag will keep all database changes in a database transaction when you start the rails console and automatically issue a rollback when you quit the console.

Preserve params between omniauth req & callbacks

If you need to pass a param to omniauth callbacks so that you can maintain a certain state about the user signing in you can do the following:

In the url call to omniauth provider strategy add a querystring:

<a href="/users/auth/facebook?mobile=true">Sign In with Facebook</a>

Then to retrieve it on the callback url handler:

request.env['omniauth.params']['mobile']

Generate a Rails Secret Key

Have you ever wondered about those secret keys found in config/secrets.yml of your Rails app? The comments generated in that file describe the keys as such:

'Your secret key is used for verifying the integrity of signed cookies.'

Great... but what if they become compromised? Or we need to change them? We can generate new ones.

Rails provides rake secret for just this purpose.

The source code is here. It's pretty simple; the code simply requires SecureRandom and spits out a string. If you want to be really clever, you can pipe the string directly into your Vim buffer for the config file, with :.! rake secret.

Check out rake -T | grep secret inside a Rails root directory for more information.

Show Rails Routes With Pry

In Show Rails Models With Pry, I showed that pry-rails comes with some handy console commands. In addition to being able to list all your Rails models, you can list all the routes for your application using show-routes.

I get the following output by using that command in a small blog project:

> show-routes
              Prefix Verb   URI Pattern                     Controller#Action
                root GET    /                               application#index
markdownify_articles POST   /articles/markdownify(.:format) articles#markdownify
            articles POST   /articles(.:format)             articles#create
         new_article GET    /articles/new(.:format)         articles#new
        edit_article GET    /articles/:id/edit(.:format)    articles#edit
             article GET    /articles/:id(.:format)         articles#show
                     PATCH  /articles/:id(.:format)         articles#update
                     PUT    /articles/:id(.:format)         articles#update
               users POST   /users(.:format)                users#create
            new_user GET    /users/new(.:format)            users#new
                user GET    /users/:id(.:format)            users#show
            sessions POST   /sessions(.:format)             sessions#create
         new_session GET    /sessions/new(.:format)         sessions#new
             session DELETE /sessions/:id(.:format)         sessions#destroy
              signin GET    /signin(.:format)               sessions#new
                     POST   /signin(.:format)               sessions#create
              signup GET    /signup(.:format)               users#new

Show Model and Grep with Pry-rails

This week Josh Branchaud wrote about show-models in Pry-rails:

http://til.hashrocket.com/posts/62f61ee06f-show-rails-models-with-pry

This method has two nice variants. show-model User shows you that model, useful in a large database.

show-models also accepts --grep for searching:

[1] pry(main)> show-models --grep published
Post
  id: integer
  title: string
  published_at: datetime # 'published' is highlighted

Set a Default Scope

ActiveRecord provides default_scope, which you can use to control how records are returned. An example:

# app/model/chapter.rb
class Chapter < ActiveRecord::Base
  default_scope { order(:title) }
end

Now, instead of this (ordered by ID):

Chapter.all.pluck(:title).take(5)
=> ["BALINESE CAUSE",
"BUILDING RAT",
"TAIL",
"LILAC MERCURY",
"PUMP SQUARE"]

We get this:

Chapter.all.pluck(:title).take(5)
=> ["AFGHANISTAN COAT",
"ALCOHOL GONG",
"ANTARCTICA BULL",
"ASPARAGUS",
"BALINESE CAUSE"]

Beware, this decision can lead to inexplicable behavior down the road. Use with caution.

One use case I've heard is soft deletes. We might not want soft-deleted records appearing in .all:

# app/model/chapter.rb
class Chapter < ActiveRecord::Base
  default_scope { where('soft_deleted is false') }
end

h/t Brian Dunn

Strong Parameters with arrays

Need to permit a strong parameter that can either be an array or a string? Just permit it twice!

# This is just for shortened syntax for this TIL.
class Param < ActionController::Paramaeters
end 

Param.new({foo: "bar"}).permit(:foo) 
#=> { "foo" => "bar" }
Param.new({foo: "bar"}).permit(foo: []) 
#=> { }

Param.new({foo: ["bar"]}).permit(foo: []) 
#=> { "foo" => ["bar"] }
Param.new({foo: ["bar"]}).permit(:foo)
 #=> { }

Param.new({foo: ["bar"]}).permit(:foo, foo: [])
 #=> { "foo" => ["bar"] }
Param.new({foo: "bar"}).permit(:foo, foo: [])
 #=> { "foo" => "bar"}

Show Rails Models With Pry

With the pry-rails gem, you get some extra goodies in the Rails console for your project. One of those goodies is show-models, a command for printing out a list of all models in the rails project. Add and bundle the pry-rails gem, run rails c, and then run show-models to give it a go.

> show-models
Pokemon
  id: integer
  name: string
  level: integer
  pokemon_type: varchar
  belongs_to Trainer
  created_at: datetime
  updated_at: datetime
Trainer
  id: integer
  name: string
  has_many Pokemons

Grep Rails Routes

This week I found a treasure inside the Hashrocket Dotmatrix; behold:

# .zshrc
alias groutes='rake routes | grep $@'

This aliases rake routes | grep [foo], which I frequently use to search through big Rails routes files.

It looks like the Rails core team is responding to this pattern in Rails 5:

https://github.com/rails/rails/issues/18902

In the meantime, patch it yourself and reap the benefits.

h/t Dorian Karter & Jon Allured

Params Includes Submission Button Info

When a form is submitted for a Rails app, the respective controller action will have access to a variety of information in the params hash. Included is an entry with the name and value of the button that submitted the form. By default, Rails will give the name commit to a submission button.

<%= f.submit %>
# results in:
<input type="submit" name="commit" value="Submit">Submit</input>

The corresponding create action will have parameters that include that submission button's info:

# in create action
> params['commit']
=> 'Submit'

This is useful when you have multiple buttons that submit the same form, but should have slightly different results in the corresponding action. Differentiating becomes easy when you can easily check which was used to submit the form. No javascript required.

Inspect Your Previous Changes

Today I found a nice ActiveModel method for inspecting previous changes, previous_changes (SQL has been removed):

[1] pry(main)> k = Kit.first
=> #<Kit:0x007fb487cec988 id: 1, name: "Foo", ... >
[2] pry(main)> k.name = 'Bar'
=> "Bar"
[3] pry(main)> k.save
=> true
[4] pry(main)> k.previous_changes
=> {"name"=>["Foo", "Bar"], "updated_at"=>[Fri, 20 Nov 2015 17:57:50 UTC +00:00, Fri, 20 Nov 2015 18:00:51 UTC +00:00]}

If nothing was updated, the method returns an empty hash.

[1] pry(main)> k = Kit.first
=> #<Kit:0x007fb487dc47e8 id: 1, name: "Foo", ... >
[2] pry(main)> k.name = 'Foo'
=> "Foo"
[3] pry(main)> k.save
=> true
[4] pry(main)> k.previous_changes
=> {}

I can imagine many practical and potentially foolish applications for this information... planning to work it into a test at some point. ActiveModel::Dirty is full of interesting methods like this.

http://apidock.com/rails/ActiveModel/Dirty/previous_changes

Test Your Migrations

Today I discovered rake db:migrate:redo. This command rolls back a migration, and then runs the migration again. It's a convenience method for checking your work and iterating through complex migrations.

The Hashrocket Dotmatrix leverages this via the twiki method:

twiki () {
  rake db:migrate && rake db:migrate:redo && rake db:test:prepare
}

Migrate the database, then roll it back, then migrate it again, then rebuild the test database. If anything is wrong going up or coming down, this will catch it.

h/t Josh Branchaud

Rails restore_attributes

Today I found a useful method in ActiveRecord, restore_attributes.

This method restores attributes on a dirty model. Use it when you are hacking in the Rails console and want to quickly return to a clean slate.

Dirty the record:

pry> p = Post.first
=> #<Post:0x007f8462b09eb8
 id: 106,
 created_at: Sun, 09 Feb 2014 17:15:01 CST -06:00,
 url_slug: "hello-world">

pry> p.url_slug = 'foobar'
=> "foobar"

pry> p.created_at = Time.now
=> 2015-10-13 11:41:37 -0500

pry> p
=> #<Post:0x007f8462b09eb8
 id: 106,
 created_at: Tue, 13 Oct 2015 11:41:37 CDT -05:00,
 url_slug: "foobar">

And restore:

pry> p.restore_attributes
=> ["url_slug", "created_at"]

pry> p
=> #<Post:0x007f8462b09eb8
 id: 106,
 created_at: Sun, 09 Feb 2014 17:15:01 CST -06:00,
 url_slug: "hello-world">

https://github.com/rails/rails/blob/master/activemodel/lib/active_model/dirty.rb#L191

ActiveRecord Dirty module

Rails provides a way to check if your model's attributes have changed since initially loading it from database. It also provides convenient methods for checking what the values were before the change using #{attribute_name}_was as in the example below.

# A newly instantiated object is unchanged:
person = Person.find_by_name('Uncle Bob')
person.changed?       # => false

# Change the name:
person.name = 'Bob'
person.changed?       # => true
person.name_changed?  # => true
person.name_was       # => 'Uncle Bob'
person.name_change    # => ['Uncle Bob', 'Bob']
person.name = 'Bill'
person.name_change    # => ['Uncle Bob', 'Bill']

# Save the changes:
person.save
person.changed?       # => false
person.name_changed?  # => false

# Assigning the same value leaves the attribute unchanged:
person.name = 'Bill'
person.name_changed?  # => false
person.name_change    # => nil

# Which attributes have changed?
person.name = 'Bob'
person.changed        # => ['name']
person.changes        # => { 'name' => ['Bill', 'Bob'] }

Rails destroy

The rails generate (g) command is useful for quickly creating the building blocks of a Rails app. Until you misspell or otherwise mess up a command:

% rails g model blurg_post
      invoke  active_record
      create    db/migrate/20151006162244_create_blurg_posts.rb
      create    app/models/blurg_post.rb
      invoke    rspec
      create      spec/models/blurg_post_spec.rb

'Blurg post'? I meant 'blog post'. Luckily, we can remove those auto-generated files as fast as we created them, with destroy (d).

% rails d model blurg_post
      invoke  active_record
      remove    db/migrate/20151006162244_create_blurg_posts.rb
      remove    app/models/blurg_post.rb
      invoke    rspec
      remove      spec/models/blurg_post_spec.rb

http://edgeguides.rubyonrails.org/command_line.html#rails-destroy

deep_munge, I hardly knew ye.

Rails 4.1.8 has a method called deep_munge which is intended to massage params to convert empty arrays into nils in response to security issues like http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-2660.

This can be tricky when sending in a json string as post data that may have some values that should be empty arrays. Those empty arrays will get converted to nil.

To get around that rails 4.1.8 added a configuration option

config.action_dispatch.perform_deep_munge = false

Rails 5.0 intends to not have the same sql injection vulnerabilities and so have removed the deep_munge method that would change an empty array value to nil but have left in the configuration option which produces behavior best described by looking at the tests.

Rails source

All Rails http verbs are UPPERCASE

Rails supports many http verbs, and in the rails source code they're handily organized by ietf rfc.

    RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
    RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
    RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY)
    RFC3648 = %w(ORDERPATCH)
    RFC3744 = %w(ACL)
    RFC5323 = %w(SEARCH)
    RFC4791 = %w(MKCALENDAR)
    RFC5789 = %w(PATCH)

source

Notice though that they're all upper case, so if you try to make an http request with lowercase patch, then you'll get a crazy UnknownHttpMethod error.

Yo dawg, I heard you like Hash#merge

Yo dawg...I heard you like Hash#merge...

With #reverse_merge, you can have a new hash in your old hash, and keep the keys from your old hash in your new hash.

hash_one = { a: 1, b:2 }
hash_one.merge({ a:2, b:3 }) # => { a:2, b:3 } 

So the hash supplied as a parameter will override hash_one's keys and values.

If you're in Rails and you don't want your existing hash keys to get overwritten, use your new friend #reverse_merge

Now, you can merge the other way around!

hash_one = { a: 1, b:2 }
hash_one.reverse_merge({ a:2, b:3, c:3 }) # => { a:1, b:2, c:3 } 

Boom, keep your keys.

Autosave False On ActiveRecord Associations

A relationship between two ActiveRecord models can be established with a has_one or has_many association. This relationship has some implications. By default, saving a record will also save the associated records that have since been built. Consider this example of users that have many posts.

> u = User.first
#=> #<User ...>
> u.posts
#=> []
> u.posts.build(title: "Some Title", content: "This is a post")
#=> #<Post ...>
> u.save
#=> true
> u.posts(reload: true)
#=> [#<Post ...>]

When the user is saved, the associated post that was built for that user also gets saved to the database.

If the association is instead defined with the autosave option set to false, then saving a record will not cause associated records to also be saved. The associated records will need to be saved explicitly. Consider the same example from above, but with has_many posts, autosave: false.

> u = User.first
#=> #<User ...>
> u.posts
#=> []
> u.posts.build(title: "Some Title", content: "This is a post")
#=> #<Post ...>
> u.save
#=> true
> u.posts(reload: true)
#=> []

The post wasn't saved with the user and it wasn't saved explicitly, so it isn't persisted to the database.

Transaction within a transaction

When using transactional fixtures in rspec, you're likely to always be in a transaction. When that's the case, transactions in you're code don't necessarily behave as you'd expect.

def update
  ActiveRecord::Base.transaction do
    Thing.find(params[:id]).update_attributes(color: 'red')
    raise ActiveRecord::Rollback
  end
end

In this case the transaction won't rollback the changes to Thing, and if you're testing that rollback in rspec, you'll be very confused.

While not the perfect solution, it's possible to force this transaction to behave how you'd like it to with the requires_new: true named argument which makes the transaction a 'sub-transaction'.

  ActiveRecord::Base.transaction(requires_new: true) do
  end

documentation

within_window capybara

Capybara lets you test things in multiple windows. Which blows my mind for some reason.

window = page.drivers.window_handles.first
within_window(window) do
  test_the_form
end

ActiveRecord::Relation size vs count

An array in ruby has 3 methods that do the same thing. size, count, and length all return the number of items in an array.

An ActiveRecord::Relation however uses them a bit differently. count is always going to run a query in the database while size will return the number of items in the collection based on the objects currently in the object graph.

> songs  = Songs.all
> songs.size
10
> songs.count
SELECT count(*) FROM songs;
10

Creating an object from an ActiveRecord::Relation

Rails allows us devs to create a record from an ActiveRecord::Association like so:

> wheel = Car.find_by(type: 'Mazda').wheels.build
> wheel.car_id
1

And in this way we can create a wheel associated with a specific car.

But we can also create an object from an ActiveRecord::Relation that allows us to predefine attributes.

> spare_wheel = car.wheels.where(spare: true).build
> spare_wheel.spare
true

Hash Slicing

Rails' ActiveSupport adds #slice and #slice! to the Hash class. The interface of these two methods seems a little inconsistent though.

> {a: 1, b: 2, c: 3}.slice(:a)
=> {:a=>1}

The #slice method returns what is being sliced.

> {a: 1, b: 2, c: 3}.slice!(:a)
=> {:b=>2, :c=>3}

The #slice! method, on the other hand, returns what is being excluded.

Explicitly Set Table Name

ActiveRecord includes a table_name method that infers your database table name based on a class name. For an empty model called AlternativePost, here's what it comes up with:

[0] > AlternativePost.table_name
=> "alternative_posts"

If this isn't right, you're in trouble (Postgres example):

[1] > AlternativePost.new
PG::UndefinedTable: ERROR:  relation "alternative_posts" does not exist

Luckily, you can set the table name explicitly with the table_name= method.

class AlternativePost < ActiveRecord::Base
  self.table_name = 'posts'
end

Now Rails knows which database table to refer to:

[2] > AlternativePost.table_name
=> "posts"
[3] > AlternativePost.new
=> #<AlternativePost:0x007f8554dcbd98 id: nil, title: nil...

A practical application would be a model Post that corresponds to a table organized inside a Postgres schema admin.posts.

Return an Empty Active Record Collection

You can use .none in a scope to short circuit the query in the event you don't have all the data.

Imagine this query but the project_type on a Project is nil


class User

  scope :active -> { where(archived: nil }

  scope :by_project, -> (project) do
    return none unless project.type.present?
    where(project_guid: project.guid, role: project.type)
  end

end

Just return none.

The cool thing about this is it's chainable. So you can still do something like:


project = Project.new(project_type: nil)

User.by_project(project).active

Ignore Poltergeist JavaScript Errors

Poltergeist with PhantomJS (<2.0) does not support JavaScript's bind() method. This means that when executing an integration test that exercises JavaScript with the bind() method, an error will occur. If you cannot simply upgrade to a version of PhantomJS that supports bind(), then what can you do?

Ignore the error!

This can be achieved by placing the following rescue block in the appropriate place.

rescue Capybara::Poltergeist::JavascriptError

Use this in moderation. You want to make sure you don't ignore actual JavaScript errors.

source

Select Value For SQL Counts

If you are like me and prefer writing raw SQL over the Arel DSL for counting stuff in your database, then the select_value method will come in handy. Write a command similar to the following with a type cast to get the count of whatever.

> sql = 'select count(*) from posts where published_at is not null'
=> "select count(*) from posts where published_at is not null"
> ActiveRecord::Base.connection.select_value(sql).to_i
   (0.6ms)  select count(*) from posts where published_at is not null
=> 42

Writing raw SQL for a simple query like this hardly seems like a win. However when a count query starts to involve joins or other fanciness, I find it much clearer to reason about the raw SQL.

Make fonts work with asset pipeline on production

If you encounter a problem where fonts don't work on production when using Bourbon CSS toolset, you need to replace the font declaration from:

font-face("great-font","/assets/greatfont")

to

@include font-face("greatfont", "greatfont", $asset-pipeline: true)

This will use asset pipeline fingerprinted URL in the font declaration and should work on all environments.

P.S. With rails 4.0+ you only need to put font files in app/assets/fonts and they will be fingerprinted and zipped.

Change Column Null

The not null constraint is a great way to ensure data integrity. If a Rails model validates_presence_of an attribute, that column should be not null in the database.

Rails has a special migration method for setting this constraint.

change_column_null :users, :mandatory_attribute, false

You could also use the change_column method. The reason change_column_null is a better choice is that change_column requires you to state the type of the column; change_column_null does not.

change_column(table_name, column_name, type, options)
change_column_null(table_name, column_name, null, default = nil)

Inline your helper_method declaration

Remember, def is just a special function in ruby and as such it has a return value that is a symbol that is the name of the function being defined.

That allows us to do some cool (weird?) looking things with class methods that take a symbol that refers to a function, like the helper_method call in a controller.

  class DataController < BaseController

  #actions and whatever

  private

  helper_method def format_that_data(data)
    "#{data[0]} X #{data[1]}"
  end

  end

Rails offset method

Rails has a method for skipping records in a query, 'offset'. It is available through the ActiveRecord::QueryMethods library.

User.offset(15) # generated SQL has "OFFSET 15"

The docs recommend using it with 'order'. I used the two methods today to return all blog posts, excluding the ten most recent:

Post.order('created_at desc').offset(10)

ActiveRecord subselects

So you want to find all the rocketeers who wrote blog posts in a date range.

Blog::Post.where(published_at: 15.years.ago..4.years.ago).includes(:rocketeer).map(&:rocketeer)
  # Blog::Post Load (0.6ms)  SELECT "blog_posts".* FROM "blog_posts"
  #   WHERE ("blog_posts"."published_at" BETWEEN '2000-06-12 14:40:06.429288' AND '2011-06-12 14:40:06.429498')
  # Rocketeer Load (0.7ms)  SELECT "rocketeers".* FROM "rocketeers"
  #   WHERE "rocketeers"."id" IN (12, 13, 14, 16)  ORDER BY "rocketeers"."name"

But you want to do it in one query!

Rocketeer.where(
  id: Blog::Post.where(published_at: 15.years.ago..4.years.ago).select(:rocketeer_id)
)
  # Rocketeer Load (0.9ms)  SELECT "rocketeers".* FROM "rocketeers"
  #   WHERE "rocketeers"."id" IN (
  #     SELECT "blog_posts"."rocketeer_id" FROM "blog_posts"
  #       WHERE ("blog_posts"."published_at" BETWEEN '2000-06-12 14:42:20.005077' AND '2011-06-12 14:42:20.005317'))  ORDER BY "rocketeers"."name"

Count Records by Type with ActiveRecord

Account.select(:account_type).group(:account_type).count

#=> {"Employee"=>35, "Manager"=>41, "SuperAdmin"=>1, "Admin"=>8}

Conditional Callbacks

Rails has a method for triggering a callback conditionally: <column>_changed?. In the case of my current project, I want to trigger a Slack web hook only if a specific attribute, 'likes', has changed.

# app/models/post.rb
after_update :notify_slack, if: :likes_changed?

Of course, this logic could be moved into the callback method definition, but I like that it's stated upfront at the top of the class.

Retrieve An Object If It Exists

Rails' Active Support provides the blank? and present? convenience methods as extensions to many objects. It also extends the Object class by providing the presence method. This method returns the receiver if it is not blank, otherwise it returns nil.

Instead of doing

User.nickname.present? ? User.nickname : User.firstname

I can simply do

User.nickname.presence || User.firstname

To Sentence

Check out the Rails to_sentence method, which takes an array and returns a stringified sentence.

pry(main)> %w(one two three).to_sentence
=> "one, two, and three"

Really handy in the view layer. This method also accepts some interesting options like :words_connector and :locale.

Classify and Constantize

Metaprogramming! Today I created a variable event that calls a serializer unique to the value of event. Along the way I learned about the Rails methods classify and constantize.

classify takes a plural table name and returns a class name.

> 'some_things'.classify
=> "SomeThing"

constantize tries to find a constant with the name specified in the argument string.

> "Module".constantize
=> Module

> "Nothing".constantize
NameError: uninitialized constant Nothing

Here's a sample of how I used these methods today (on the 'Today I Learned' app itself):

# app/models/post.rb
event = 'some_important_event'

# app/workers/event_notifier.rb
"#{event.classify}Serializer".constantize.new

This will new up an instance of SomeImportantEventSerializer, if such a class exists.

Remove Belongs To Association

To remove a belongs_to association using the Rails migration DSL, use the remove_references method. This method is aliased to remove_reference and remove_belongs_to.

class RemoveTagReferences < ActiveRecord::Migration
  def change
    change_table :posts do |t|
      t.remove_references :tag
    end
  end
end

Find Or Create By With Block

The Rails find_or_create_by method is great for bringing something into existence only once. I like to use it for database seeds, so you get the basic objects you need to make your development site useable, but don't keep creating them over and over again.

One feature I recently learned is that this method takes a block.

User.find_or_create_by(first_name: 'Jake') do |user|
  user.last_name = 'Worth'
end

This lets you find the object, and if it doesn't exist, create it with whatever attributes you need.

Add a UUID Datatype to Your Rails App

Create a migration to add the extension


class CreateUuidExtension < ActiveRecord::Migration
  def change
    create_table :uuid_extensions do |t|
      enable_extension 'uuid-ossp'
    end
  end
end

Add a column to your table with a data type of :uuid. Don't forget to add a default of uuid_generate_v4()

class AddUuidToAccounts < ActiveRecord::Migration
  def change
    add_column :accounts, :uuid, :uuid, default: 'uuid_generate_v4()'
  end
end

Multipurpose Environmental Variables

I use HTTP Basic auth in non-production environments because it ships with Rails and is easy to implement. The first time I used it, I set it up like this:

# app/controllers/application_controller.rb
if ENV['http_basic_auth']
  http_basic_authenticate_with name: ENV['username'], password: ENV['password']
end

I used environmental variables so that no credentials were hard-coded, and so I could toggle it without deploying.

Today I learned you can also implement it like this:

# app/controllers/application_controller.rb
if creds = ENV['basic_auth_credentials']
  username, password = creds.split(':', 2)
  http_basic_authenticate_with name: username, password: password
end

This requires an environmental variable called basic_auth_credentials, set to <username>:<password>. I prefer this because it allows one variable to serve two purposes: it toggles the feature and also contains the information the feature needs to work.

It's a tradeoff; slightly less explicit, but simpler to set and unset (one variable versus three).

HashWithIndifferentAccess for keyword arguments

HashWithIndifferentAccess is a cool hash-like object that allows you to access values with either a symbol OR a string. But you can't pass it to a method that expects keyword arguments.

def foo(a:, b:); return a, b; end
h = HashWithIndifferentAccess.new({a: 1, b: 2})
foo(h)
=> ArgumentError: wrong number of arguments (1 for 0)

Fear not!

foo(h.symbolize_keys)
=> 1, 2

Calling symbolize_keys on a HashWithIndifferentAccess object will create a hash that can be used for keyword arguments.

http://apidock.com/rails/v4.2.1/Hash/symbolize_keys

Pretend Generations

To get an idea of what a rails generate command is going to to generate, you can do a dry run with the -p flag or the --pretend flag. If you run

$ rails generate model post -p

then you will see the following output

    invoke  active_record
    create    db/migrate/20150513132556_create_posts.rb
    create    app/models/post.rb
    invoke    rspec
    create      spec/models/post_spec.rb
    invoke      factory_girl
    create        spec/factories/posts.rb

though those files will not have actually been created. You now know precisely what rails will generate for you.

source

Conditional Class Selectors in Haml

You can assign a class selector to a tag in HAML like so:

%div.active

You can conditionally assign a class selector in a concise manner like so:

%div{ class: ( "active" if @thing.active? ) }

You can do multiple conditional class selectors with array syntax:

%div{ class: [ ("active" if @thing.active?), ("highlight" if @thing.important?) ] }

source

Change column default

class AddDefaultFalseToDeveloperAdminFlag < ActiveRecord::Migration
  def change
    change_column_default :developers, :admin, false
  end
end

Modify attribute on set

In this example, we want to allow users to enter a twitter handle with or without an at-sign ('@'), just like Twitter, but save it without the at-sign. twitter_handle is already an attribute on the class.

class User < ActiveRecord::Base
  def twitter_handle=(handle)
    write_attribute(:twitter_handle, handle.to_s.gsub(/^@/, ''))
  end
end

The third argument of radio_button_tag is checked!

Of course I should know this but I didn't, 3rd value of radio_button_tag is whether the input is checked or not.

radio_button_tag("name", "value", isChecked)

#stilllearningrails

Set ActiveRecord conn. pool size via DATABASE_URL

Need to turn up the connection pool size via environment variables? Append a query param like

"postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"

Check out the source on github

Start rails server in production mode

rails s -e production

Parameters Filtering

Rails logs your server's activity, which is useful for development and debugging. However, often the server handles sensitive information that should not be logged.

A few examples are authentication credentials, personal data, and financial information.

To prevent Rails from logging such data, add this to your application configuration:

config.filter_parameters << :param_a, :param_b

When the named parameters are handled by the server, they will be logged as [FILTERED] instead of their actual value. Add this configuration by environment if you want to keep the parameters unfiltered in development.