How to write complex migrations in Rails

Rails gives you migrations to change your database schema with simple commands like add_column or update.
Unfortunately these commands are simply not expressive enough to handle complex cases.

This card outlines three different techniques you can use to describe nontrivial migrations in Rails / ActiveRecord.

Note that the techniques below should serve you well for tables with many thousand rows. Once your database tables grows to millions of rows, migration performance becomes an iss...

ActiveRecord: Passing an empty array into NOT IN will return no records

Caution when using .where to exclude records from a scope like this:

# Fragile - avoid
User.where("id NOT IN (?)", excluded_ids)

When the exclusion list is empty, you would expect this to return all records. However, this is not what happens:

# Broken example
User.where("id NOT IN (?)", []).to_sql
=>  SELECT `users`.* FROM `users` WHERE (id NOT IN (NULL))

Passing an empty exclusion list returns no records at all! See below for better implementations.

Rails 4+

Use the .not method to let Rails do the logic

`...

How to access your Rails session ID

This only works when you actually have a session ID (not the case for Rails' CookieStore, for example):

request.session_options[:id]
# => "142b17ab075e71f2a2e2543c6ae34b94"

Note that it's a bad idea to expose your session ID, so be careful what you use this for.

Controller specs do not persist the Rails session across requests of the same spec

In specs, the session never persists but is always a new object for each request. Data put into the session in a previous request is lost. Here is how to circumvent that.

What's going on?

You are making ActionController::TestRequests in your specs, and their #initialize method does this:

self.session = TestSession.new

This means that each time you say something like "get :index", the session in your controller will just be a new one, and you won't see ...

Rails: Overwriting default accessors

All columns of a model's database table are automagically available through accessors on the Active Record object.

When you need to specialize this behavior, you may override the default accessors (using the same name as the attribute) and simply call the original implementation with a modified value. Example:

class Poet < ApplicationRecord

  def name=(value)
    super(value.strip)
  end

end

Note that you can also avoid the original setter and directly read/write from/to the instance's attribute storage. However this is dis...

parallel_tests: Disable parallel run for tagged scenarios

Note: This technique is confusing and slows down your test suite.


Copy the attached code to features/support. This gets you a new Cucumber tag @no_parallel which ensures that the tagged scenario does not run in parallel with other scenarios that are tagged with @no_parallel. Other scenarios not tagged will @no_parallel can still run in parallel with the tagged test. Please read the previous sentence again.

This can help when multiple test processes that access a single resource that is hard to shar...

How to rotate log files explicitly

Usually, the logrotate service takes care of renaming log files each night or so to avoid logs becoming huge. That will rename your.log to your.log.1, the next time to your.log.2.gz, etc. Here is how to make that happen out of band (you should rarely need to do that).

Logrotate won't touch all your logs automagically. There is a config file for each service which you can tell logrotate to use.

So if you need logs to be rotated right now, do this (as root):

logrotate --force PATH_TO_CONFIG_FILE

For example, to rotate all y...

ApacheBench may return "Failed requests" for successful requests

When you use ab to do some performance benchmarking, you might run into output like this:

Complete requests:      200
Failed requests:        5
   (Connect: 0, Receive: 0, Length: 5, Exceptions: 0)

Note that in our example these "Failed requests" actually never failed.\
For some requests, the application just returned a response with a different content length than the first response. This is indicated by the "Length: 5" bit in the example above.

If you see requests that failed with other kinds of errors, they probably fail...

How to horizontally center absolute positioned container with CSS

Find out in this short guide, how to horizontally center a absolute positioned container with CSS.

Note: We have a card with all CSS centering options. You probably want to head over there and get an overview over what techniques are available for your use case and browser requirements.


Horizontally centering a static element in CSS is normally handled by setting the left and right margins to auto, for example:

// SA...

Rails: How to use a n:m association as 1:n association

Sometimes you might want to limit the number of associated records in a has_many association, but cannot add a foreign key to the other model (using belongs_to).

There are many takes on limiting the number of associated records in has_many associations, but none feels smooth.

However, when your...

How to set the user agent in tests

The User-Agent HTTP header identifies the client and is sent by "regular" browsers, search engine crawlers, or other web client software.

Cucumber

In Rack::Test, you can set your user agent like this on Capybara:

Given /^my user agent is "(.+)"$/ do |agent|
  page.driver.browser.header('User-Agent', agent)
  # Or, for older Capybaras:
  # page.driver.header('User-Agent', agent)
end

For Selenium tests with Firefox, it seems you can set the general.useragent.override profile setting to your preferred value. [See StackOver...

Spreewald: When using `patiently do`, don't reuse existing variable names

Spreewald's patiently repeats the given block again and again until it either passes or times out.

Be careful to give patiently a block that can actually be repeated. E.g. the following block can not be repeated:

Given /^the field "(.*?)" is empty$/ do |field|
  patiently do
    field = find_field(field)
    field.text.should be_blank
  end
end

The reason the above code will fa...

How to make Rational#to_s return strings without denominator 1 again

The way Rational#to_s works on Ruby has changed from Ruby 1.9 on. Here is how to get the old behavior back.

You may want this for things where Rationals are being used, like when subtracting Date objects from one another.

What's happening?

Converting a Rational to a String usually does something like this:

1.8.7 > Rational(2, 3).to_s
=> "2/3"
1.9.3 > Rational(2, 3).to_s
=> "2/3"
2.0.0 > Rational(2, 3).to_s
=> "2/3"

However, when you have a Rational that simplifies to an integer, you will only get a St...

Test redirects to an external URL with Cucumber/Capybara

When a controller action redirects to an external URL (like http://somehost.com/some/path) you will find that this is hard to test with Cucumber and Capybara:

  • A non-Javascript Rack::Test scenario will just ignore the host and try to open /some/path in your local application
  • A Selenium test will actually follow the redirect, which you probably don't want either

There are two workarounds for this. You can use either, or a combination of both.

  1. Write a controller spec

Controller specs can test if a resp...

How to disable cookies in cucumber tests

Unfortunately, Capybara does not offer a switch to disable cookies in your test browser. However, you can work around that by using a tiny Rack middleware -- it works for both Selenium and non-Selenium tests.


Wouldn't it be nice to say something like this?

Given cookies are disabled
When I try to sign in
Then I should see "Can't sign you in. Please enable cookies."

You can! Put the code below into some place like lib/rack/cookie_stripper.rb.

module Rack
  class CookieStripper
    
    ENABLED = false

...

Cucumber step to set cookies in your Capybara session

To set a cookie in your test browser for cucumber tests, you need to know which driver you are using. Use the step below according to your driver.

Rack::Test

Given /^I have a "([^\"]+)" cookie set to "([^\"]+)"$/ do |key, value|
  headers = {}
  Rack::Utils.set_cookie_header!(headers, key, value)
  cookie_string = headers['Set-Cookie']

  Capybara.current_session.driver.browser.set_cookie(cookie_string)
end

Note that Rack::Utils is only used to find out the correct cookie header string (you don't want to generate it yours...

The Plight of Pinocchio: JavaScript's quest to become a real language - opensoul.org

Great presentation about writing Javascript like you write everything else: Well-structured and tested.

JavaScript is no longer a toy language. Many of our applications can’t function without it. If we are going to use JavaScript to do real things, we need to treat it like a real language, adopting the same practices we use with real languages.

This framework agnostic talk takes a serious look at how we develop JavaScript applications. Despite its prototypical nature, good object-oriented programming principles are still relevant. The...

Ruby: Debugging a method's source location and code

Access the Method object

Dead simple: Get the method object and ask for its owner:

"foo".method(:upcase)
# =>  #<Method: String#upcase> 

"foo".method(:upcase).owner
# => String

Look up a method's source location

Ruby 1.9 adds a method Method#source_location that returns file and line number where that method is defined.

class Example; def method() end; end
# => nil

Example.new.method(:method).source_location
# => ["(irb)", 11] 
 
"foo".method(:upcase).source_location
# => nil # String#upcase is a native method...

Fix „rvm no such file to load -- openssl“ or "rvm no such file to load -- zlib"

For example if you use rvm and get this message:

ERROR:  Loading command: install (LoadError)
    no such file to load -- zlib
ERROR:  While executing gem ... (NameError)
    uninitialized constant Gem::Commands::InstallCommand

You've installed your ruby without having all required libraries.

I don't know why there isn't a Warning message if you install a ruby with rvm and didn't have libraries like openssl and zlib.

To fix this you can execute this:

#to show the requirements for your system
rvm requireme...

Ruby 1.9 or Ruby 2.0 do not allow using shortcut blocks for private methods

Consider this class:

class Foo

  private
  
  def test
    puts "Hello"
  end
  
end

While you can say create a block to call that method (using ampersand and colon) on Ruby 1.8, ...

1.8.7 > Foo.new.tap(&:test)
Hello
=> #<Foo:0x1e253c8> 

... you cannot do that on Ruby 1.9 or 2.0:

1.9.3 > Foo.new.tap(&:test)
NoMethodError: private method `test' called for #<Foo:0x00000001e8c258>

^
2.0.0 > Foo.new.tap(&:test)
NoMethodError: private method `test' called for #<Foo:0x000000027bc738...

MySQL: How to create columns like "bigint" or "longtext" in Rails migrations, and what :limit means for column migrations

Rails understands a :limit options when you create columns in a migration. Its meaning depends on the column type, and sometimes the supplied value.

The documentation states that :limit sets the column length to the number of characters for string and text columns, and to the number of bytes for binary and integer columns.

Using it

This is nice since you may want a bigint column to store really long numbers in it. You can just create it by ...

How to copy your „Google Chrome“ or „Chromium“ profile without creating an online account

Google Chrome saves your profile data in ~/.config/google-chrome.
To transfer the profile to for example a system you have setup freshly do following steps:

  • make a copy of ~/.config/google-chrome
  • install google-chrome
  • restore your backuped profile to ~/.config/google-chrome
  • launch google-chrome

(Replace google-chrome by chromium-browser if you use chromium-browser)

ActiveRecord: count vs size vs length on associations

TL;DR: You should generally use #size to count associated records.

size

  • Counts already loaded elements
  • If the association is not loaded, falls back to a COUNT query

count

  • If a counter cache is set up, returns the cached value
  • Issues a COUNT query else

Caveats

  • If you trigger a COUNT query for an association of an an unsaved record, Rails will try to load all children where the foreign key IS NULL. This is not what you want. To prevent this behavior, you can use unsaved_record.association.to_a.size.
  • `c...

Loading dumps via SSH, unpacking and sourcing them, all with a progress bar

Here is a hacky way to load dumps directly from the source server, without fully copying them over and extracting them first.

It may break horribly for you. This is the dark side of the force.

  1. Install pipe viewer, if you don't have it already: sudo apt-get install pv
  2. Know the location of the dump file on the remote server. We'll use /mnt/dumps/my_project.dump.bz2 in the example below.
  3. Find out the size of the (bzipped) file in by...