Today I Learned

14 posts about #rails

A More Succinct Way to Update Model Attributes

Instead of model.update_attributes! you can use this shorter form: model.update!

Speed Up Bundler

It turns out that you can speed up Bundler if you increase parallelization.

$ bundle config --global jobs 8

On OS X you can use the sysctl command to determine the jobs setting appropriate for your hardware:

$ sysctl hw.ncpu
hw.ncpu: 4

I'm using number of cores times two. Works for me. YMMV.

Assignment on associations will bypass validation

Important gotcha that assignment to has_many associations will cause an immediate save even if callback validation fails.

u = User.last

u.accounts = []

u.save # returns false because this user cannot have blank accounts

u.reload.accounts # returns empty array

The gotcha here is that save is a no-op really. As soon as u.accounts=[] is called, the data is saved immediately, bypassing validation.

Speeding Up Rake Command Completion

The speed of rake command completion is determined by the startup time of your Rails app.

If your Rails app is slow to startup, the oh-my-zsh rake-fast plugin will make rake command completion tolerable again.

Edit your zsh configuration file:

plugins=(... rake-fast ...)

After refreshing your shell instance issue the following command:

$ rake_refresh

Take a deep breath. Enjoy fast rake command completion.

Serializing many value objects to database columns

While reading the IDDD book on serialization of value objects there is this description of an approach called ORM and Many Values Serialized into a Single Column. It's good to note that some of the main objections to this approach are technology related and barely applicable in a world of Rails' ActiveRecord + PostgreSQL.

The objections presented by the book are:

  • Column width: It mentions that serializing to varchar fields will meet some limitations imposed by Oracle and MySQL implementations. In PostgreSQL, besides having composite types (e.g. json or array), the limit on any column is much higher (1GB).
  • Must query: The book states that if the values must be queried this approach cannot be used. This is another limitation imposed by the underlying technology. Using PostgreSQL one can easily query composite values and even created indexes over them.
  • Requires custom user type: This is not related to the database technology but is heavily biased towards hibernate. In Rails' ActiveRecord the custom serializers require very little boilerplate and it offers out of the box support for json, array and range types.

Configuring a Rails app to redirect http to https

Problem

I have a Rails app on Heroku that is serving up a site on http and https. Google oAuth's callback URL is for https, so attempting to log into the site from the http URL fails.

Solution

The intention was to serve up the site just from the https url, so the solution is to configure Rails to redirect all http traffic to https.

In config/production.rb:

  config.force_ssl = true

Resource: http://stackoverflow.com/questions/27377386/force-ssl-for-heroku-apps-running-in-eu-region

Rolling back Rails migrations

There are a bunch of ways to roll back migrations, so I figured I'd capture them in Q & A format.

Let's say the following migration files exist:

> ls db/migrate

20160613172644_migration_1
20160614173819_migration_2
20160615142814_migration_3
20160615160123_migration_4
20160615174549_migration_5

Q: How do I roll back the last migration.
A: rake db:rollback

Q: How do I roll back the last 3 migrations?
A: rake db:rollback STEP=3

Q: How do I roll back a specific migration?
A: rake db:migrate:down VERSION=20160615142814
Details:
The timestamp comes from the filename: 20160615142814_migration_3

and... the one I learned today:

Q: How do I roll back all the migration past a certain version?
A: rake db:migrate VERSION=20160615142814.
Details:
The above will keep the following:

20160613172644_migration_1
20160614173819_migration_2
20160615142814_migration_3

and roll back the following:

20160615160123_migration_4
20160615174549_migration_5

In other words, it will keep all the migrations upto and including the version you specified.

Retrieving the IDs for a Model

Prefer the ids method to map(&:id) when you want to retrieve an array of model IDs.

Example

receipt.receive_orders.ids
receipt.receive_orders.map(&:id)

In the example above, using ids avoids fetching the receive orders from the database.

Rails Source

def ids
  pluck primary_key
end

Rails assignment operation implicitly updates DB

In Rails, you may fall into a trap where simply assigning a value to an ActiveRecord object's properties may cause a DB write immediately.

This will occur implicitly without callingobject.save()!

What's vulnerable? It appears that ActiveRecord objects that expose a property via association are vulnerable to this quirk. This won't happen for properties that have no association.

For example, given the following:

class MyClass < ActiveRecord::Base

has_many :children

attr_accessor :property_name

end

....

obj = MyClass.find(1)

obj.children = [child_one, child_two, child_three] <=== this will write to the DB immediately!

obj.property_name = 'value' <=== this is in memory only, the DB has not been updated

obj.save <=== property_name is updated in the DB now

This may cause issues if your save implies validations and the validations fail. In this case, the associated property was updated in the DB but the other properties were not because of the validation failure.

Yikes!

validates_presence_of won't work with booleans

If you want to validate that a value for a particular boolean field exists (using ActiveRecord instead of null constraints from you DB), then you cannot use validates_presence_of. This is due to the way that the blank? works on an object with the value false. Instead, you will need to use this for your validation:

 validates_inclusion_of :field_name, :in => [true, false]

SEE:

http://stackoverflow.com/questions/2883823/why-does-false-invalidate-validates-presence-of http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates_presence_of

Database Column "type" is reserved in Rails

I made a database column called type. It contained a string. It was bound to Object. Everything was cool.

I wanted to create my object. But I kept getting this error.

object = Object.create(food: "P&L Burger", name: "The Matty", type: "Burgers")

**************************************
Invalid single-table inheritance type: 1 is not a subclass of Object

TIL that Rails has some reserved keywords for database column names. It just so happens that the column name type is reserved. There are also other reserved column names.

See: Active Record Basics: 2.2 Schema Conventions

The type column is reserved for single-table-inheritance where the Ruby Object name would be stored in the type column and would be accessible through object.type #=> Object

See: Single Table Inheritance

Rails console options to make your life better

Often when learning or testing out an implementation in planning, entering into a REPL to get some feedback and play with something more physical is desirable.

Here are some tips to improve your rails console workflow:

Specify the environment you wish to use

Maybe you want to try and reproduce an error in production during your test runs. Maybe you want to seed the database for your development server before having created a seed file. Maybe you're just feeling wild for the day and want to switch things up.

Rails gives you the option to select the environment for the console session:

>rails c --environment=test [test/development/production/...other...]
>rails c test  # as a shorthand

Note: The default environment is set to development

Sandbox so your DB entries do not persist

Often I find myself wanting to just play around with the current build, knowingly wanting to throw away the changes after. Sandboxing is a great way to do this while reducing your cleanup workflow.

>rails c --sandbox
>rails c -s

This saves you from having to write out the following to return to a seeded state:

>rake db:reset RAILS_ENV=[environment]

Rails 4 Strong Parameters Gotcha: Array in Params

When using strong parameters in Rails 4, any non-scalar parameters must be permitted using a special syntax. The following example is for when you are passing in an array of scalar values in a request parameter (and you are using Rails 4 Strong Parameters to whitelist the parameter).

Firstly, note that the following will not work (i.e. the spec will fail):

# routes.rb
post '/thing/create' => 'thing#create'

# thing_controller_spec.rb
RSpec.describe ThingController, type: :controller do
  example do
    post :create, things: [1, 2, 3]

    expect(response.body).to eq('1,2,3')
  end
end

# thing_controller.rb
class ThingController < ApplicationController
  def create
    permitted_params = params.permit(:things)
    render inline: permitted_params[:things].join(',')
  end
end

Why does it fail? Rails is expecting a scalar value to be passed in the :things parameter. However, we pass in an array, so Rails silently removes the parameter, even though it is included in the call to params.permit.

In order to make the spec pass, we update the implementation as follows:

# thing_controller.rb
class ThingController < ApplicationController
  def create
    permitted_params = params.permit(things: [])
    render inline: permitted_params[:things].join(',')
  end
end

For more info about strong parameters, especially in regards to how to permit non-scalar and nested parameters, please refer to: https://github.com/rails/strong_parameters#permitted-scalar-values.

ActiveRecord #count versus #length

Let's say you have an ActiveRecord model with a has_many association. Ever wonder why you receive different results from #count and #length after you append to that has_many collection? Consider this rough example where Parent and Kid are ActiveRecords and Parent will has_many :kids:

parent = Parent.create!
parent.kids.create! name: 'Evan'
puts "Current length is #{parent.kids.length}"
puts "Current count is #{parent.kids.count}"

The output will be:

Current length is 0
Current count is 1

The difference in results appear to be happening because of how length() and count() compute their results. length() is only considering what is loaded into heap memory (cached), while count() will actually check what is loaded into the DB.

Here's a great blog post about the difference between the two methods, as well as size().