Skip to content

Instantly share code, notes, and snippets.

@gvarela
Created November 23, 2009 22:10
Show Gist options
  • Save gvarela/241424 to your computer and use it in GitHub Desktop.
Save gvarela/241424 to your computer and use it in GitHub Desktop.

Instructions

Below is an example problem that necessitates research of Rails code. An appropriate answer should provide any resources you used to discover the answer as well as a written explanation of the cause and a possible patch.

Setup a test app with the basic dependencies: rails 2.3.4 -m http://gist.github.com/raw/241424/e5a2c6750ea5f2f8e798acfcdb5ba76d77f99c41/template.rb fdl_question

Story

You have a rails 2.3.4 application where you have an expensive web service call. You decide to use write-through caching using memcached and a job queue (in this case Delayed::Job) to speed up the responsiveness for the user. Assume you have an update action that calls save on an ActiveResource object. In the save action you write the current object into the cache then send a job into the queue to actually perform the save (i.e. send_later :save). Below might be an example of how you would execute this write through pattern.

def save_with_write_caching
  if valid?
    Rails.cache.write(self.cache_key, self)
    self.send_later :save_without_write_caching
  end
end
alias_method_chain :save, :write_caching

But before you add the code to throw the save into the queue you begin testing a simpler method to be able to test the write through aspect of your code.

def save_with_write_caching
  if valid?
    Rails.cache.write(self.cache_key, self)
    self.save_without_write_caching
  end
end
alias_method_chain :save, :write_caching

However,you quickly run into a problem where after you write the object to the cache you are unable to modify/save the object. You receive an error that states "Can't modify a frozen object" at the point of the save call.

This is an actual problem we ran into on a production site. Please research and answer the questions below.

Questions

  1. Pinpoint the exact line in the rails code that causes this issue.
  2. Explain the call stack from Rails.cache.write to the point at which the object is frozen
  3. Why was this decision made by the core team.
  4. How would you patch this and why?
  5. There will be follow up questions about the Rails caching code so ensure you understand how it works.
git :init
run "echo 'TODO add readme content' > README"
run "touch tmp/.gitignore log/.gitignore vendor/.gitignore"
run "cp config/database.yml config/database.example.yml"
run "rm public/index.html"
file ".gitignore", <<-END
log/*.log
tmp/**/*
config/database.yml
END
# gem 'mislav-will_paginate'
# rake("gems:install", :sudo => true)
run "mkdir lib/extensions"
initializer 'extensions.rb', <<-CODE
# require 'extensions/#'
CODE
run "rm config/environments/development.rb"
file 'config/environments/development.rb',
%q{
# Settings specified here will take precedence over those in config/environment.rb
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the webserver when you make code changes.
config.cache_classes = true
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true
# Show full error reports and disable caching
config.action_controller.consider_all_requests_local = true
config.action_view.debug_rjs = true
config.action_controller.perform_caching = true
config.action_controller.session_store = :mem_cache_store
config.action_controller.cache_store = :mem_cache_store
config.cache_store = :mem_cache_store
# Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = false
}
file 'lib/tasks/memcached.rake',
%q{
require 'yaml'
require 'erb'
namespace :memcached do
desc "Start memcached locally"
task :start do
if memcached_installed?
puts "starting memcached"
cmd = "/usr/bin/env memcached #{config_args} > log/memcached-#{Rails.env}.log"
puts cmd
`#{cmd}`
end
end
desc "Restart memcached locally"
task :restart do
Rake::Task['memcached:stop'].invoke
Rake::Task['memcached:start'].invoke
end
desc "Stop memcached locally"
task :stop do
pidfile = File.join(RAILS_ROOT,'tmp','pids','memcached-' + Rails.env + '.pid')
`kill \`cat #{pidfile}\``
`rm #{pidfile}`
puts "memcached killed"
end
desc "flush memcached store locally"
task :flush_all do
`echo 'flush_all' | nc localhost 11211`
puts "memcached flushed"
end
end
def memcached_installed?
location = `which memcached`
if location.strip.empty?
puts 'memcached is not installed'
puts 'sudo port install memcached'
false
else
true
end
end
def config
return @config if @config
if File.exists?(File.join(Rails.root, 'config','memcached.yml'))
config = YAML.load(ERB.new(IO.read(File.join(Rails.root, 'config','memcached.yml'))).result)
@config = config['defaults'].merge(config[Rails.env])
else
@config = {
'veryverbose' => true
}
end
end
def config_args
args = { }
args['-p'] = config['port'] if config['port']
args['-c'] = config['c_threshold'] if config['c_threshold']
args['-m'] = config['memory'] if config['memory']
args['-d'] = ''
args['-P'] = "#{File.join(RAILS_ROOT,'tmp','pids','memcached-' + Rails.env + '.pid')}"
args['-v'] = '' if config['verbose'] == true
args['-vv'] = '' if config['veryverbose'] == true
args.to_a * ' '
end
}
git :add => ".", :commit => "-m 'initial commit'"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment