Code tagged with activerecord
# So, you wrote a Rails application to track Marathon events. In a sudden
# change of heart, you pick up your company and move it from the United
# States to Germany. Why rewrite your app to handle metric distances?
# Rails provides a solution: write_attribute and read_attribute.
class Marathon < ActiveRecord::Base
def distance=(meters)
write_attribute(:distance, meters * 1609.344)
end
def distance
read_attribute(:distance) / 0.000621371192
end
end
# I was working on a migration that had a decent chance of messing up my database
# and wanted assurance it would work with production data, not just my fixtures.
# Building on Bojan Mihelac's code to download files, I wrote a Capistrano task
# to load the production database into my local database. And then capistrano
# 1.4.0 was released with a built-in get method for downloading files. So if
# you have 1.4.0, you can just throw away that whole function.
# Get file remote_path from FIRST server targetted by
# the current task and transfer it to local machine as path, SFTP required
# http://source.mihelac.org/articles/2007/01/11/capistrano-get-method-download-files-from-server
def actor.get(remote_path, path, options = {})
execute_on_servers(options) do |servers|
self.sessions[servers.first].sftp.connect do |tsftp|
logger.info "Get #{remote_path} to #{path}"
tsftp.get_file remote_path, path
end
end
end
desc "Load production data into development database"
task :load_production_data, :roles => :db, :only => { :primary => true } do
require 'yaml'
database = YAML::load_file('config/database.yml')
filename = "dump.#{Time.now.strftime '%Y-%m-%d_%H:%M:%S'}.sql"
on_rollback { delete "/tmp/#{filename}" }
run "mysqldump -u #{database['production']['username']} –password=#{database['production']['password']} #{database['production']['database']} > /tmp/#{filename}" do |channel, stream, data|
puts data
end
get "/tmp/#{filename}", filename
exec "/tmp/#{filename}"
exec "mysql -u #{database['development']['username']} –password=#{database['development']['password']} #{database['development']['database']} < #{filename}; rm -f #{filename}"
end
# After adding the code to the tail of my config/deploy.rb, I ran cap
# load_production_data. Then I had a full copy of the production database
# to tinker with until I was content my migration was flawless.
# Production data is also useful for other things. I can run complicated
# stats queries without worrying about bogging down the site, and see layout
# bugs that don't happen unless there's a few dozen tags on the page
Nothing special, just a simple helper I threw together in a few minutes. It is used in model tests to verify that a column is required. A few usages follow.
def should_require_attribute(model, attrib_name, error_messages=[], invalid_value=nil)
# Collect the valid value
valid_value = model.send(attrib_name)
error_messages = error_messages.to_a.sort
# Set it to nil
model.send("#{attrib_name.to_s}=", invalid_value)
model.should_not_be_valid
model.should_have(error_messages.size).errors
model.errors.on(attrib_name).to_a.sort.should == error_messages
# Put things back where you found them
model.send("#{attrib_name.to_s}=", valid_value)
model.should_be_valid
end
# Simple case
specify "should be invalid without state" do
should_require_attribute(your_model, :state, "is required")
end
# More than one error expected
specify "should require a valid email address" do
should_require_attribute(your_model, :email_address, ["is required", "should be a valid format"])
end
# Invalid value other than nil
specify "should require a non .gov email address" do
should_require_attribute(your_model, :email_address, "can't contain .gov domains", "bigbrother@government.gov")
end
Need to clean up your old sessions?
I picked up on the idea of using a shell ActiveRecord class to sweep old sessions by someone on the mailing list a long time ago. I simply tweaked the idea to accept flexible time parameters.
class Session < ActiveRecord::Base
# time_ago examples:
# 30m => 30 minutes
# 1h => 1 hour
# 3d => 3 days
def self.sweep(time_ago = nil)
time = case time_ago
when /^(\d+)m$/ then Time.now.utc - $1.to_i.minute
when /^(\d+)h$/ then Time.now.utc - $1.to_i.hour
when /^(\d+)d$/ then Time.now.utc - $1.to_i.day
else Time.now.utc - 1.hour
end
self.delete_all "updated_on < '#{time.to_s(:db)}'"
end
end
Now just schedule with cron...
$ crontab -e
# Delete old session after 3 hours of no use. Run every 15 minutes.
*/15 * * * * /usr/local/bin/ruby /var/www/apps/cool_app/current/script/runner -e production "Session.sweep('3h')"
namespace :db do
namespace :backup do
def interesting_tables
ActiveRecord::Base.connection.tables.sort.reject! do |tbl|
['schema_info', 'sessions', 'public_exceptions'].include?(tbl)
end
end
desc "Dump entire db."
task :write => :environment do
dir = RAILS_ROOT + '/db/backup'
FileUtils.mkdir_p(dir)
FileUtils.chdir(dir)
interesting_tables.each do |tbl|
klass = tbl.classify.constantize
puts "Writing #{tbl}..."
File.open("#{tbl}.yml", 'w+') { |f| YAML.dump klass.find(:all).collect(&:attributes), f }
end
end
task :read => [:environment, 'db:schema:load'] do
dir = RAILS_ROOT + '/db/backup'
FileUtils.mkdir_p(dir)
FileUtils.chdir(dir)
interesting_tables.each do |tbl|
klass = tbl.classify.constantize
ActiveRecord::Base.transaction do
puts "Loading #{tbl}..."
YAML.load_file("#{tbl}.yml").each do |fixture|
ActiveRecord::Base.connection.execute "INSERT INTO #{tbl} (#{fixture.keys.join(",")}) VALUES (#{fixture.values.collect { |value| ActiveRecord::Base.connection.quote(value) }.join(",")})", 'Fixture Insert'
end
end
end
end
end
end
I love that super calls method_missing if the method is not defined on the superclass.
Consider this case. You have some ActiveRecord named Account, which has an associated email_address. However, an account owner may optionally give a special “notification” email address, which will be used for things like newsletter emails and security issues and such. If no notification address has been explicitly given, it should fall back to the account’s primary email address. It’s as simple as this:
class Account < ActiveRecord::Base
def notification_address
super || email_address
end
end
Calling super forces the superclass, ActiveRecord::Base, to be sent the notification_address message, which it won’t understand. This causes method_missing to be called on AR::Base, which looks for the notification_address attribute in the record’s attribute set. If that has not been set, it will be nil, in which case we then default to the email_address value.
Just as you’d expect.
Some people are upset that database.yml can expose passwords in plaintext. However, there is a pretty simple way to get encryption into database.yml. Because the database.yml file is actually run through an ERB interpreter by Rails, we can put code into our file:
# database.yml
production:
adapter: mysql
username: db_user
password: <%= custom_method_to_obtain_password %>
host: your_db_host
This snippet will validate all of your fixtures all at once.
# file: validate_models.rake
# task: rake db:validate_models
namespace :db do
desc "Run model validations on all model records in database"
task :validate_models => :environment do
puts "-- records - model --"
Dir.glob(RAILS_ROOT + '/app/models/**/*.rb').each { |file| require file }
Object.subclasses_of(ActiveRecord::Base).select { |c|
c.base_class == c}.sort_by(&:name).each do |klass|
total = klass.count
printf "%10d - %s\n", total, klass.name
chunk_size = 1000
(total / chunk_size + 1).times do |i|
chunk = klass.find(:all, :offset => (i * chunk_size), :limit => chunk_size)
chunk.reject(&:valid?).each do |record|
puts "#{record.class}: id=#{record.id}"
p record.errors.full_messages
puts
end rescue nil
end
end
end
end
In the world of web applications, eventually you�??ve seen it all, and you start to see the same relational patterns occur over and over. To help out the newbies, here�??s a list of various relationships (in Ruby on Rails syntax) that we see all the time.
Can you think of any more common relationship patterns?
Wrap a set of inserts in a transaction to speed up inserts in Rails.
We recently put in a tag system that allows users of our database to tag people or organizations and then send out emails or create address labels from those tags. A problem came up when we implemented functionality to �??Tag All�?? listings on a certain page. Usually this meant 50-100 inserts like so:
ids.each do |i|
Tag.create :user_id = uid, :entity_id = i
end
This is all well and good, except for the fact that Rails wraps each insert in a transaction, which slows things down a bit. The solution was to wrap the entire loop in a transaction, that way the inserts are committed all at once, assuming they all are successful.
Tag.transaction do
ids.each do |i|
Tag.create :user_id = uid, :entity_id = i
end
end
Here are some numbers. In development mode, with ~40 inserts:
- Without wrapping it in a transaction, it takes 3.66 seconds to complete the 40 inserts
- When wrapped in a transaction, it takes 0.534 seconds for the same set of inserts
Another approach:
Tag.transaction do
ids.each do |i|
t = Tag.new :user_id = uid, :entity_id = i
t.save!
end
end
This rolls back the entire operation if any inserts are invalid. Depending on the situation, this may be more desirable then having those rows silently fail.