Today I Learned

A Hashrocket project

128 posts about #rails

Storing recurring schedules in #Rails + #Postgres

If you have a scheduling component to your Rails application you may need to store the day of week and time of day in the database.

One way to store the day of week is to use an integer column with a check constraint that will check that the value is between 0 and 6.

create table schedules (
  id serial primary key,
  day_of_week integer not null check(day_of_week in (0,1,2,3,4,5,6)),
  beg_time time not null,
  end_time time not null
);

Then when you read it back from the database and need to convert it back to day name you can use Date::DAYNAMES. e.g.:

[2] pry(main)> require 'date'
=> true
[3] pry(main)> Date::DAYNAMES
=> ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
[4] pry(main)> Date::DAYNAMES[0]
=> "Sunday"
[5] pry(main)>

If you need to store time of day as entered (in a time without timezone column - as specified above) check out the wonderful Tod gem by Jack Christensen

Convert to BigDecimal with `to_d` w/ActiveSupport

Ruby provides the BigDecimal method to convert to BigDecimal.

> require 'bigdecimal'
> BigDecimal("123.45")
#<BigDecimal:56236cc3cab8,'0.12345E3',18(18)>

But you can’t convert a float without a precision

> BigDecimal(123.12)
ArgumentError: can't omit precision for a Float.
> BigDecimal(123.12, 5).to_s
"0.12312E3"

When using Rails, and specifically with ActiveSupport required, you can use the to_d method converts to BigDecimal.

> require 'active_support'
> 123.to_d
#<BigDecimal:55ebd7800ea8,'0.123E3',9(27)>
> "123".to_d
#<BigDecimal:55ebd7800ea8,'0.123E3',9(27)>

And for floats provides a default precision of Float::DIG+1 which for me is 16. DIG is described as

The number of decimal digits in a double-precision floating point.

> 123.45.to_d
#<BigDecimal:55ebd2d6cfb8,'0.12345E3',18(36)>
> 123.45.to_d.to_s
"123.45"

Note, to_s in ActiveSupport outputs a more human readable number. Also Note, nil is not convertable with to_d

> require 'active_support'
> nil.to_d
NoMethodError: undefined method `to_d' for nil:NilClass
> BigDecimal(nil)
TypeError: no implicit conversion of nil into String

What you had before you saved w/`previous_changes`

When you set values into the ActiveRecord object the previous values are still available with changes, but when you save, however you save, those changes are wiped out. You can access what those values were before saving with previous_changes.

> thing = Thing.create({color: 'blue', status: 'active'})
> thing.color = 'red'
> puts thing.changes
{"color" => ['blue', 'red']}
> puts thing.previous_changes
{}
> thing.save
> puts thing.changes
{}
> puts thing.previous_changes
{"color" => ['blue', 'red']}

Access record from ActiveRecord::RecordInvalid

You can pass an array of hashes to Thing.create! in ActiveRecord. If one of those records is invalid, then an ActiveRecord::RecordInvalid error is thrown. You might need to know which record threw the error, in which case you can get the record from the error with record_invalid_error.record

bad_record = nil

begin
  Things.create!([{value: 'bad'}, {value: 'good'}])
rescue ActiveRecord::RecordInvalid => record_invalid_error
  bad_record = record_invalid_error.record
end

if bad_record
  puts "got a bad record with value: #{bad_record.value}"
end

Array of hashes `create` many ActiveRecord objects

Generally, you use the create method of ActiveRecord objects to create an object by passing a hash of attributes as the argument.

Thing.create(color: 'green', status: 'active')

You can also pass an array of hashes to create:

things = [
  {
    color: 'blue',
    status: 'pending'
  },
  {
    color: 'green',
    status: 'active'
]

created_things = Thing.create(things)

One disappointing thing is that this does not batch the insert statements. It is still just one insert statement per object, but it might make your code simpler in some cases.

Getting BetterErrors in Rails While Using Ngrok

By default, the BetterErrors gem only works for localhost. If you’re using ngrok to access your rails server and you want to have access to BetterErrors, you’ll need to whitelist the IP of the machine that ngrok is running on.

Add the following to development.rb:

BetterErrors::Middleware.allow_ip!(NGROK_MACHINE_PUBLIC_IP)
# - or -
# Use an IP finding service to grab your public IP each time you start the server:
# (ipecho.net, api.ipify.org, etc.)
BetterErrors::Middleware.allow_ip!(open('http://api.ipify.org').read)

Rails Ignore Pending Migrations

Ruby on Rails gives us a nice warning in development when we haven’t run a pending database migration. But what if we’re iterating on a migration, and the results can best be viewed in the browser? We might want to temporarily disable this warning.

Here’s how to do it:

# config/environments/development.rb
config.active_record.migration_error = false

Don’t forget to turn it back on when you’re finished.

Better Rails SQL Migrations

Some people on our team, including me, write raw SQL in Ruby on Rails migrations. But they can get unwieldy when iterating on a complex migration that does multiple things.

Mitigate this is to break up your statements into separate HERDOCs, like so:

def up
  execute <<-SQL
    update pixaxes set metal = 'diamond' where metal = 'iron';
    -- many things...
  SQL

  execute <<-SQL
    update swords set metal = 'diamond' where metal = 'iron';
    -- many more things...
  SQL
end

If the migration fails, we’ll get an error pointing to the specific problematic HEREDOC, instead of essentially ‘the entire statement is invalid’. You then put a debugger between any HEREDOC to iterate on the issue.

h/t Jack Christensen

Set the Domain or Host in Rails URL Helpers

You can explicitly set the domain with Rails URL Helpers:

app.root_url
=> "http://www.example.com/"

app.root_url(domain: 'www.customdomain.org')
=> "http://www.customdomain.org"

 

If we include the protocol in our domain argument, this brings up an interesting gotcha. Check it out:

app.root_url(domain: 'http://www.customdomain.org')
=> "http://www.http://www.customdomain.org/"

 

Thankfully, Rails gives us the host option. Host will use a Regex to remove the protocol from the argument:

app.root_url(host: 'https://www.customdomain.org')
=> "http://www.customdomain.org/"

 

Notice the output in the last example; our protocol was replaced removed and replaced with http. You can use the protocol argument to enforce your protocol on a given host:

app.root_url(host: 'http://www.customdomain.org', protocol: 'https')
=> "https://www.customdomain.org/"

Alias A Model Method Or Field in a GraphQL Type

If you need to override the name of a model method or field in a GraphQL type, you can use the property argument.

Let’s say we have a model called Message:

class Message < ApplicationRecord
  def bar
    "bar"
  end
end

We can then make an alias to the bar method by specifying a property in our MessageType:

Types::MessageType = GraphQL::ObjectType.define do 
  field :foo, types.String, property: :bar
end

Has ActiveRecord::Relation already been grouped?

I’ve recently run into a situation where I needed to apply a group statement to an ActiveRecord scope if a group statement had already been applied. In this case we need to examine the current scope to see if in fact a group statement has already been applied. ActiveRecord::Relation fortunately has a group_values method that returns an array of all the columns that the query has been grouped by as symbols.

if current_scope.group_values.length > 1
  current_scope.group(:another_column)
else
  current_scope
end

Authenticate With Username or Email

Today I learned a way to implement a login form that accepts email or username as the login. I’ve been on the other side of a form like this many times, but had never written one myself.

Here’s one solution, with ActiveRecord:

User.where('username = ? or email = ?', "jwworth", "jake@example.com")

title or slug are represented by the same parameter, and either can be nil.

login = params.fetch('username_or_email')
User.where('username = ? or email = ?', login, login)

Convert nested JSON object to nested OpenStructs

If you are parsing a nested JSON string such as this:

{
   "vendor": {
       "company_name": "Basket Clowns Inc",
       "website": "www.basketthecloon.com"
 }

And want to access it with dot notation, simply doing:

OpenStruct.new(JSON.parse(json_str))

will not do!

Turns out there is a cool option on JSON.parse called object_class:

JSON.parse(json_str, object_class: OpenStruct)

Now you can access the resulting object with dot notation all the way down:

obj.vendor.website #=> "www.basketthecloon.com"

Change The Nullability Of A Column

Do you have an existing table with a column that is exactly as you want it except that it needs to be changed to either null: false or null: true?

One option is to use ActiveRecord’s change_column_null method in your migration.

For example to change a nullable column to null: false, you’ll want a migration like the following:

def change
  change_column_null :posts, :title, false
end

Note, if you have existing records with null values in the title column, then you’ll need to deal with those before migrating.

If you want to make an existing column nullable, change that false to true:

def change
  change_column_null :posts, :title, true
end

Find host and port in development

Rails 5.1 is a little different the pre 5.1

require 'rails/commands/server/server_command'

In rails 5.0 or below

require 'rails/commands/server'
# lib/host_and_port.rb

def __host_and_port__
  options = Rails::Server::Options.new.parse!(ARGV)
  options.values_at(:Host, :Port)
end

You can then find the host and port for various configuration files.

# config/initializers/carrier_wave.rb
require Rails.root.join('lib/host_and_port').to_s

CarrierWave.configure do |config|
  config.asset_host = "http://" + __host_and_port__.join(":")
end

or

# config/environments/development.rb
require Rails.root.join('lib/host_and_port').to_s

host, port = __host_and_port__
config.action_mailer.default_url_options = { host: host, port: port }

Generating And Executing SQL

Rails’ ActiveRecord can easily support 90% of the querying we do against the tables in our database. However, there is the occasional exceptional query that is more easily written in SQL — perhaps that query cannot even be written with the ActiveRecord DSL. For these instances, we need a way to generate and execute SQL safely. The sanitize_sql_array method is invaluable for this.

First, let’s get a connection and some variables that we can use downstream in our query.

> conn = ActiveRecord::Base.connection
=> #<ActiveRecord::ConnectionAdapters::PostgreSQLAdapter ...>
> one, ten = 1, 10
=> [1, 10]

Now, we are ready to safely generate our SQL query as a string. We have to use send because it is not publicly available. Generally, this is frowned upon, but in my opinion it is worth breaking the private interface to ensure our SQL is sanitized.

> sql = ActiveRecord::Base.send(:sanitize_sql_array, ["select generate_series(?, ?);", one, ten])
=> "select generate_series(1, 10);"

Lastly, we can execute the query with our connection and inspect the results.

> result = conn.execute(sql)
   (0.4ms)  select generate_series(1, 10);
=> #<PG::Result:0x007facd93128a0 status=PGRES_TUPLES_OK ntuples=10 nfields=1 cmd_tuples=10>
> result.to_a
=> [{"generate_series"=>1},
 {"generate_series"=>2},
 {"generate_series"=>3},
 {"generate_series"=>4},
 {"generate_series"=>5},
 {"generate_series"=>6},
 {"generate_series"=>7},
 {"generate_series"=>8},
 {"generate_series"=>9},
 {"generate_series"=>10}]

Time travelling in rspec/rails

When basing logic on the current time its helpful for testing to have a stable time. A time that does not change. Rails has a module ActiveSupport::Testing::TimeHelpers that was added in Rails 4.2 to provide methods that manipulate the time during testing.

travel_to(Time.parse("2017-01-19")) do
  puts Time.now.strftime(:date)
end

puts Time.now.strftime(:date)

The above code outputs 2017-01-19 and 2017-05-02 (the current date). A fun way to time travel in modern ruby.

Chaining expectations in Rspec

Generally, we think about expectations in RSpec one at a time. If the first expectation fails, then don’t go any further. Expectations in RSpec however are chainable, meaning, I can attach one expectation to another for the same subject and then know about the failures or successes for both expections, that looks like this.

expect(1).to eq(2).and eq(3)

Which produces output like this:

 Failure/Error: expect(1).to eq(2).and eq(3)

          expected: 2
               got: 1

          (compared using ==)

       ...and:

          expected: 3
               got: 1

          (compared using ==)

The same result can be got from the below code which may appeal to you a bit more:

def chain_exp(*expects)
  expects.inject {|exps, exp| exps.and(exp)}
end

expect(1).to chain_exp(eq(2), eq(3))

Login for feature test with warden test helpers

Warden provides a way to login as a user without having to go through the web interface that a user generally sees for sign in.

user = FactoryGirl.create(:user)
login_as user, scope: :user

In the config block for Rspec you would include this statement:

config.include Warden::Test::Helpers, type: :feature

If you have different models for different types of users in your system you can sign in with different scopes. Lets say you have a student user concept, you can sign in with:

student = FactoryGirl.create(:student)
login_as student, scope: :student

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`. ruby :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!

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 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

Rails Enum with prefix/suffix

[enum]:http://api.rubyonrails.org/classes/ActiveRecord/Enum.html

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

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

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.