Posts in "Rails"

Update to Rails 5.0.0.rc1 from 4.2

Just updated this blog to Rails 5.0.0.rc1 from 4.2, since this is a pretty small hobby blog app the update was quite simple and smooth.

1) Update your Gemfile, then bundle update rails

# core part of rails 5
gem 'rails', '~> 5.0.0.rc1'
gem 'turbolinks', '~> 5.x'

# use the same web server
gem 'puma', '~> 3.0'

# use latest master branch to make bundle resolve dependencies
gem 'kaminari', github: 'amatsuda/kaminari'
gem 'simple_form', github: 'plataformatec/simple_form'
gem 'rspec-rails', github: 'rspec/rspec-rails', tag: 'v3.5.0.beta3'

# Fix for https://github.com/jnicklas/capybara/issues/1592
gem 'capybara', github: 'jnicklas/capybara'

2) rails app:update to update the configuration files(config/*, bin/*)

Use d to check the diff and y to overwrite if it's ok.

3) Create app/models/application_record.rb like this

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end

4) Update for turbolinks

This is equal to bind on window.onload, DOMContentLoaded, or jQuery ready events

$(document).on "turbolinks:load", ->

Also check the official guide on upgrade your rails app

May 15, 2016 Rails

Run DelayedJob Manually in Test Env

TL;DR

# in config/initializers/delayed_job_config.rb
Delayed::Worker.delay_jobs = true

# in your spec
Delayed::Worker.new.work_off

Common Setup of DelayedJob

Assume you follow DelayedJob readme example to configure it like this: Delayed::Worker.delay_jobs = !Rails.env.test?, what it does is in test env it doesn’t delay the job, meaning DelayedJob is being transparent, the job you put will be executed in “real time”. In most cases you don’t even need to worry about it, your test should be just fine, but recently it caught me…

It Fails When…

To give some background, I’m working on a API centric rails project. In order to authenticate with API we pass in access token for every request, and that’s done in the middleware layer. Since access token is stored in cookie, and in middleware we can’t access browser cookie directly, so another tool called RequestStore is used. If in the same request, what you stored in RequestStore you can access it later no matter the context, a unrealistic example would be you store a cookie value to RequestStore then use it in model later. Don’t do that :).

The code below is a simplified version to illustrate the flow.

class ApplicationController < ActionController::Base
  before_action :set_api_access_token

  def set_api_access_token
    RequestStore.store[:access_token] = cookies.signed[:access_token]
  end
end

class Authentication < Faraday::Middleware
  def call(env)
    env[:request_headers]['Authorization'] = RequestStore[:access_token] if RequestStore[:access_token]
    @app.call(env)
  end
end

Every api request happened inside the rails ApplicationController stack should have the access token being set, but what would happen in a different context like rake task or DelayedJob where you need to send request to the API? The before_action is not gonna be executed there so RequestStore[:access_token] would be nil. This is an easy-to-spot issue if you try it once, but if you follow the TDD work flow and write test for it first, then it’ll fail you.

With Delayed::Worker.delay_jobs set to false in test env, the job will be executed immediately in the same request, so the RequestStore[:access_token] still contains the value and will pass to the Authorization header in the middleware, spec passed but but in real world env it failed. Typical false positive result.

To Run It Manually

# in config/initializers/delayed_job_config.rb
Delayed::Worker.delay_jobs = true

# in your spec
# here is the code to enqueue a job to DelayedJob queue
visit post_path(post)
# run it manually
Delayed::Worker.new.work_off
# expectation
expect(api_endpoint).to have_been_requested
end

Delayed::Worker.new.work_off returns an Array like [1, 0] indicating succeeded job counts and failed job counts. I’ve also seen some people testing against that like expect(Delayed::Worker.new.work_off).to eq([1, 0]), personally I don’t think it’s necessary.

  1. You have your own expectation right after that and that should be the main concern of the spec. If the job failed, your spec should be failed too.
  2. What if multiple jobs are enqueued while you’re only focusing one of them in the spec? Update the value to [2, 0]? That’s just noise.

I guess what I encountered is a rare case, but definitely an interesting case. I kinda prefer this way to mimic real world environment to prevent any possible regressions.

Commercial time: If you’re about to build a API centric rails app, be sure to check out the awesome gem called spyke made by @balvig, the slogan is “Interact with remote REST services in an ActiveRecord-like manner.”

December 13, 2014 Rails, test

Use ActiveJob in Rails 4.1

ActiveJob is the headline feature in Rails 4.2, the Active Job Basics on RailsGuides explains the philosophy and usage very well, make sure you’ve checked that first. However there’re some gotchas if you want to use it right now in your Rails 4.1 app. Here I’m gonna show you how to install it in 4.1, and things you need to take extra care of.

Install ActiveJob in Rails 4.1

Add activejob to your Gemfile then bundle install.

Create a active_job.rb file under config/initializers and paste code below.

require 'active_job'
# or any other supported backend such as :sidekiq or :delayed_job
ActiveJob::Base.queue_adapter = :inline

Now you should be abel to load ActiveJob in your rails app without error.

Note that the one you installed is not the one inside the rails repository, that has a version of 4.2.0.beta2 same as Rails at the time of writing, the one you installed is version 0. You can find the archived source code from its original repository.

Creating a Job

To create a job, you have to manually create the app/jobs folder first, then follow the same naming convention to create your job class file like app/jobs/guests_cleanup_job.rb.

class GuestsCleanupJob < ActiveJob::Base
  queue_as :default

  def perform(*args)
    # Do something later
  end
end

Enqueuing the Job

GuestsCleanupJob.enqueue(record)
GuestsCleanupJob.enqueue(record, options)

Differences between latest ActiveJob

  • no rails generator for jobs.
  • no callback mechanism like before_enqueue, before_perform etc.
  • doesn’t load itself to rails app by default, that’s why you need a initializer to load it manually.
  • enqueue syntax is slightly different, in Rails 4.2.beta2 enqueue has changed to perform_later.
  • internally it’s using activemodel-globalid instead of GlobalID(GlobalID is renamed from activemodel-globalid).
  • setting backend syntax is slightly different
# Rails 4.2.beta2
Rails.application.config.active_job.queue_adapter = :delayed_job

# Rails 4.1
ActiveJob::Base.queue_adapter = :delayed_job

p.s. I haven’t checked ActionMailer. I’m currently using it with DelayedJob and so far so good.

Summary

ActiveJob is very convenient, it provides a unified interface for the job infrastructure that allows you to switch the backend easily.

But as you can see there’re big diffs between the latest developed version and the one now we’re able to install in Rails 4.1.

Is it worth it to make the effort to try it now, and push these small upcoming changes when you upgrade to Rails 4.2 to your mental stack? My suggestion is if you’re just right about to implement a queue system and willing to adapt to it, then it’s OK, otherwise maybe better just leave the current app running as is and wait for a more mature timing.

October 22, 2014 Rails

Run rails server as production

Short note

  • rake assets:precompile
  • config.serve_static_assets = true # on production.rb
  • rails s -e production

That's all.

config.serve_static_assets

When you run rake middleware you'll see use ActionDispatch::Static in the development mode but not in production mode.

In normal case, you should not set that to true unless for trying production mode on local machine. As the comment on the source code suggested:

Disable Rails's static asset server (Apache or nginx will already do this)

October 09, 2012 Rails

Set Project Specific Environment Variables with Pow

In your rails app, when you need an envionment variable like ENV['TWITTER_CONSUMER_KEY'] for local deveopment, where do you put them? Simply set them before you start your rails server as a one time thing, or just put them under ~/.profile or ~/.zshrc?

Well it works but I'm not happy with that. Because first it belongs to a specific project and expose them to global env make me feel a little bit uncomfortable, second, what if you happend to have more than one twitter integrated app, how do you name the variables to solve naming collision?

If you're using Pow, there is a perfect solution for you.

.powrc and .powenv

Pow provides these 2 files for you to config pow and setup any environment variables.

Before an application boots, Pow attempts to execute two scripts — first .powrc, then .powenv — in the application's root. Any environment variables exported from these scripts are passed along to Rack.

Convention here is putting .powrc under git version control, and override or setup any project specific environment variables to .powenv.

Let's do it

```diff .gitignore

  • .powenv ```

```ruby .powenv
export TWITTER_CONSUMER_KEY=foo
export TWITTER_CONSUMER_SECRET=bar


BTW, you must run this command to restart pow manually so these scripts will be loaded.

$ touch ~/.pow/restart.txt


[Pow Document: Customizing Environment Variables](http://pow.cx/manual.html#section_2.2)
September 01, 2012 Rails, Pow

How to I get rid of "WARN Could not determine content-length of response body

If you get these warning messages in your rails log, just simply put thin to your Gemfile.

WARN  Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true

```ruby Gemfile
gem 'thin'


I found this from this stackoverflow [question](http://stackoverflow.com/questions/7082364/what-does-warn-could-not-determine-content-length-of-response-body-mean-and-h)
August 30, 2012 Rails

Upgrading to Rails 3.2 from 3.1

Yesterday I upgraded one of my old-pending-hobby projects to latest Rails version, which is 3.2.8 now. Since I have several other projects in old version, I'd like to keep a note for what I've done.

As you can see in my commit, all you have to do is update the version in Gemfile and hit bundle update in your terminal.

```ruby Gemfile
gem 'rails', '3.2.0'

group :assets do
gem 'sass-rails', " ~> 3.2.3"
gem 'coffee-rails', "~> 3.2.1"
gem 'uglifier', '>= 1.0.3'
end


Then add new configuration to `development.rb` and `test.rb` environment file. That's all :)

```ruby config/environments/development.rb
  # Log the query plan for queries taking more than this (works
  # with SQLite, MySQL, and PostgreSQL)
  config.active_record.auto_explain_threshold_in_seconds = 0.5

  # Do not compress assets
  config.assets.compress = false

```diff config/environments/test.rb

  • # Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets
  • config.assets.allow_debugging = true

  • # Raise exception on mass assignment protection for Active Record models

  • config.active_record.mass_assignment_sanitizer = :strict

August 23, 2012 Rails

Finding an array of ids while keeping the order with Rails

The order may different

Suppose you're retrieving records using array of ids.

ids = [100, 1, 6]
User.where(:id => ids).map(&:id)
# => [1, 6, 100]

The order of the data may different from ids array, what if you want to keep its order?

for MySQL

There is a function in mysql called FIELD()

FIELD(str,str1,str2,str3,...)
Returns the index (position) of str in the str1, str2, str3, ... list. Returns 0 if str is not found.

So by combing this FIELD() function, here is the code.

ids = [100, 1, 6]
User.where(:id => ids).order("field(id, #{ids.join(',')})").map(&:id)
# => [100, 1, 6]

This will generate SQL below:

SELECT `users`.* FROM `users` WHERE `users`.`id` IN (100, 1, 6) ORDER BY field(id, 100, 1, 6)

for other databases

I didn't dig too much but most of google results suggest sort it manually using ruby.
Here is an example:

ids = [100, 1, 6]
users = User.where(:id => ids)
users = ids.map {|id| users.detect {|user| user.id == id } }
users.map(&:id)
# => [100, 1, 6]
August 14, 2012 Rails

Run Ruby Script in Rails Environment

Overview

If you wanna execute some ruby code in rails environment, say manipulate w/ model or something, you can always try rake task first. But there're circumstances you don't want to use it, maybe it's just a one-time script that you don't want to save it to git, or whatever.

I've found out there're two simple ways to accomplish it.

require config/environment.rb

# test.rb
require "config/environment"
p User.first

# in terminal
ruby test.rb

use rails runner

Rails Guides about rails runner

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

rails runner "p User.first"
rails runner some_ruby_script.rb
rails runner -e staging "Model.long_running_method"
rails runner -h

You can also use the alias “r” to invoke the runner: rails r.

I failed to call rails r on Rails 3.0.10, maybe it's above 3.1+.

June 29, 2012 Rails, script

Explicitly specify table name in scope definition

I was wondering why people explicitly specify table name in such a scope definition...

class Answer < ActiveRecord::Base
  scope :recent, order('answers.created_at DESC')
end

Until I got this error today.

Mysql2::Error: Column 'created_at' in order clause is ambiguous: SELECT  COUNT(DISTINCT `answers`.`id`) AS count_id, question_id AS question_id FROM `answers` LEFT OUTER JOIN `questions` ON `questions`.`id` = `answers`.`question_id` WHERE (`answers`.user_id = 87) GROUP BY question_id ORDER BY created_at DESC LIMIT 10

I have a recent scope defined in both Question and Answer model. And when you chain those two models, rails don't know the recent is associated to which one.

@answers = @user.answers.recent.group(:question_id).includes(:question).limit(10)
June 29, 2012 Rails