Today I Learned

A Hashrocket project

172 posts about #rails

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"

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.

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

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

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.