multimodel_transactions plugin: Saving and creating multiple model objects in a single controller action
In Ruby on Rails, it’s common to have a resource have a one-to-one correspondence with a model. Often you will have a create action that looks something like this:
class UsersController < ApplicationController
# ... other actions, etc
def create
User.transaction do
@user = User.new(params[:user])
if @user.save
flash[:notice] = 'User was successfully created.'
redirect_to(@user)
else
render :action => "new"
end
end
end
# ... other actions
endNow what if we have another model object that is also managed by this action, perhaps a profile object? Perhaps using the fields_for helper in the view?
Then we might change our code so that it looks something like this:
class UsersController < ApplicationController
# ... other actions, etc
def create
User.transaction do
@user = User.new(params[:user])
@profile = Profile.new(params[:profile])
user.profile = @profile
if @user.save && @profile.save
flash[:notice] = 'User was successfully created.'
redirect_to(@user)
else
User.connection.rollback_db_transaction
render :action => "new"
end
end
end
# ... other actions
endAnd our new.html.erb view will have a form that looks something like this:
<% form_for(@user) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<%# Other fields using the "f" builder %>
<% fields_for(@profile) do |builder| -%>
<%= builder.error_messages %>
<p>
<%= builder.label :favorite_movie %><br />
<%= builder.text_field :favorite_movie %>
</p>
<%# Other fields using the "builder" builder %>
<% end %>
<p>
<%= f.submit "Create" %>
</p>
<% end %>There should be no problems here right? Wrong. The problem is that in edit.html.erb, we are going to also have a form_for(@user) and fields_for(@profile). So how does new.html.erb and edit.html.erb know to create a form action of create_user_url and update_user_url, respectively? It does this by checking the new_record? method of the passed in model.
Here’s where a problem is created. Lets revisit part of our controller code for the create action:
if @user.save && @profile.save
flash[:notice] = 'User was successfully created.'
redirect_to(@user)
else
User.connection.rollback_db_transaction
render :action => "new"
endIf @user.save fails, @user.new_record? will still be true. This is what we want since we will be calling render :action => 'new' and we want the form generated there to call create_user_url, not update_user_url.
But what happens when @user.save works and @profile.save fails? @profile.new_record? will still be true, which is good, but @user.new_record? will be false, because it successfully saved! The user row will not exist in the database, because we rolled back our transaction, but only @profile knew to revert to an unsaved active record state. @user has no idea what’s going on.
So then, when new.html.erb renders form_for(@user), it will create a form with the update_user_url instead of create_user_url! This will result in an exception like this:
ActiveRecord::RecordNotFound in UsersController#update Couldn't find User with ID=2
To understand how to fix this, we need to understand why it works with only one model. It works because @user.save calls rollback_active_record_state! which records the object’s state, with respect to things like new_recodr?, and takes a block that it executes in a begin/rescue block. If it catches an exception, it reverts to the state it was in before it yielded to the block, and reraises the exception. The exception is usually ActiveRecord::Rollback, which is gobbled up by a transaction that @user.save starts before calling rollback_active_record_state!
We can fix the problem by using this method on both objects, and throwing an exception if any of the models fail to save.
The above if statement becomes something like this:
#an anonymous Exception class, it's best to create a real class for use
#in multiple locations
ec = Class.new(:Exception)
begin
@user.rollback_active_record_state! do
@profile.rollback_active_record_state! do
if @user.save && @profile.save
flash[:notice] = 'User was successfully created.'
redirect_to(@user)
else
User.connection.rollback_db_transaction
raise ec.new
end
end
end
rescue ec
render :action => "new"
endI have created a plugin that helps replace this type of code. It works with any number of model objects across any number of database connections.
It is at git://github.com/azimux/multimodel_transactions.git
With it installed, the above code becomes:
ax_multimodel_if([@user, @profile],
:if => proc {
@user.save && @profile
},
:is_true => proc {
flash[:notice] = 'User was successfully created.'
redirect_to(@user)
},
:is_false => proc {
render :action => "new"
}
)Note to Smalltalk users: if you need an example of ruby’s block passing syntax making certain method calls look a little ugly, there you go :P
There is also another method in the plugin that helps with models being in multiple databases (or the future possibility of such a thing happening.): ax_multimodel_transaction
To apply it to the above call to ax_multimodel_if, it would look somewhat like this:
ax_multimodel_transaction [@profile], :already_in => @user do
ax_multimodel_if([@user, @profile],
:if => proc {
@user.save && @profile
},
:is_true => proc {
flash[:notice] = 'User was successfully created.'
redirect_to(@user)
},
:is_false => proc {
render :action => "new"
}
)
endh1. Gotchas
1) If @profile has the user_id foreign key, it’s important to do:
@user.profile = @profileand not:
@profile.user = @userWhen neither of the objects have been saved yet. I’m not exactly sure why this is, I just know from experience that if you do it the other way around, @profile will be saved with a nil user_id
2) Like always when doing things sensitive to transactions and rollbacks, you might wish to add a “self.use_transactional_fixtures = false” to the relevant functional test. This is really only necessary if your test runs commands that execute queries that are not expecting to be inside of a rolledback transaction after the call to “post :create” or “post :update” or whatever. It is only very rarely necessary to do, but it’s good to keep in mind when writing more complex functional tests.
Posted in Ruby, Ruby on Rails | no comments |
Missing ExceptionNotifiable module with exception_notification plugin
It looks like the exception_notification plugin is undergoing a pretty big overhaul to get it working well with rails3. The official git repository for that plugin only has one branch, master. What should you do for your existing rails2 applications that are not ready for migration to rails3?
If you are getting something somewhat like this:
rake aborted! uninitialized constant ApplicationController::ExceptionNotifiable .../rails/activesupport/lib/active_support/dependencies.rb:102:in `const_missing'
You can fix it by using commit e8b603e523c14f145da7b3a1729f5cc06eba2dd1 of that plugin.
Something like this should do the trick:
cd vendor/plugins/exception_notification git checkout -b rails2 e8b603e523c14f145da7b3a1729f5cc06eba2dd1
That particular commit is from November 13, 2008. It is the last commit not having anything to do with rails3, and it is a very stable commit and unlikely to need modification for existing projects.
if you are using externals to manage your subprojects, you can quickly fix it like this (from the main project directory):
ext freeze exception_notification e8b603e523c14f145da7b3a1729f5cc06eba2dd1
Posted in externals, Ruby, Ruby on Rails | 1 comment |
Unable to login after upgrade from typo 5.1.3 to 5.3 via git
Upgrading via a git merge might not be the best way of upgrading typo. The safest way is probably to follow the instructions in the UPGRADE file. However, I have a few modifications I've made to Typo that I'd like to preserve.
So I went ahead and merged 5_3_0 with my 5.1.3 branch and ran a db:migrate
I couldn't log in anymore. It turns out a "status" column is added to the users table in a migration, and in the same migration, each existing user's status is set to "active"
The problem is that after a db:migrate, the status column was still NULL. What happens is that it works if you run 1 migration at a time. But if you run the migration that adds and sets the status column to "active" in the same rake invocation that has already accessed the User class, it will already have the old columns loaded into it and will not think the "status" column even exists.
To fix it, you need to issue a User.reset_column_information after adding the column.
This column is not the only problem column, the "text_filter_id" and "editor" column also failed to update.
Below is a quick patch I made from my typo repository. I only went back to the migrations I was missing between 5.1.3 and 5.3 and made sure any model classes that were used had "reset_column_information" called before using. If you are going to attempt upgrading your typo blog via a git merge/pull, this patch might be useful.
0001-Changed-some-recent-migrations-to-call.patch
A note about an oddity I ran into when running these migrations in a production environment: You'll need to set config.cache_classes in config/environments/production.rb to false while running the migrations, otherwise it might try to preload information about classes whose tables don't yet exist. I'm not exactly sure why this happens, I've never had this problem in the past.
Posted in Ruby on Rails, typo | no comments |
Happy Birthday Gentoo, and all hail the world file!
Gentoo turns 10 today, and so I figured I’d make a post about one of my favorite aspects of Gentoo: the world file.
Such a simple idea and yet most package management systems seem to lack it.
When you install a package, it gets recorded in the world file, but its dependencies do not. This means you can always figure out which packages you need/don’t need.
Contrast this with many other package management systems, who do not distinguish between packages specifically installed by name by the user and those pulled in as dependencies.
After some time passes and you install and remove packages, you wind up with a bunch of packages that you are not sure if they’re necessary or not.
With gentoo, you can simply do a
emerge --pretend --depclean
On an up-to-date system to see which packages the system thinks are no longer needed. You can then rerun without the pretend option if you don’t see anything necessary in the list.
With other package management systems this can be a pain in the neck. For instance, on debian, I used to have to go through dselect and look at each package and use intuition to determine if anything installed was no longer necessary. And with gem, the package management system for rubygems, I wind up maintaining my own world-file-like file list in a file I call required_gems by hand, then to remove unneeded packages, deleting every single gem, and then reinstalling the ones I have recorded in my required_gems file.
So all hail the world file and happy 10th anniversary Gentoo!
Posted in Linux, Gentoo | no comments |
How to rip the Spanish audio track from a dvd
I’m in the process of learning Spanish and wanted to have something in Spanish to listen to.
I figured I’d try listening to the Spanish tracks from some of my DVDs for this purpose. The only one I have that actually has a Spanish audio track is Pulp Fiction and my Simpson’s DVDs.
To rip Pulp Fiction, I used the following command:
mplayer -alang es -vc dummy -vo null -ao pcm:fast -af resample=44100:0:0 -ao pcm:file=pf.18.wav -chapter 18-18 dvd://
This rips chapter 18. I repeated this for chapter 01 through 27. Note that you have to replace that number in 3 places in the command each time. I prefer to have it broken up by chapter for skipping-around convenience, but if you want it all in one file, leave off -chapter.
If you want to rip from a different language, change what you pass to -alang, or remove -alang for the default language.
You can then encode them with LAME if you’d like.
By the way, my chapter 16 is screwed up on my Pulp Fiction disc and won’t rip. If you have this DVD and don’t mind ripping the Spanish audio from that chapter for me, let me know. It’s the “Where’s my watch?” chapter.
Posted in Linux | no comments |
bacterial colony mutation simulation
Below is a quick ruby program I threw together to try and make a very rough simulation of the impact of rare and barely beneficial mutations in bacteria colonies.
The way this code is here, there’s a strong type of bacteria (with the beneficial mutation) and a weak kind of bacteria (without the beneficial mutation)
The way these numbers are currently set, the bacterial colony starts at 1 weak bacteria and grows from there. There’s a 1 in 10 million chance that a weak bacteria will gain the strong mutation or that a strong bacteria will lose it’s strong mutation. The benefit to the bacteria is a 0.1% improvement in survival rate. Anytime the colony grows above capacity, a bunch are killed off (the current survival rate is 50%, so half die every time it grows above 4 million bacteria.)
Feel free to play with the numbers and see the various outcomes.
A quick explanation of the algorithm: I don’t test every single bacteria to see if it survives. Instead, if 49.9% of them are supposed to die, and let’s say that there’s 5 bacteria. 5 * 49.9 = 2.495. So I kill 2 bacteria and then generate a random number between 0 and 1 to test against 0.495 to see if the 3rd bacteria dies or not. Not testing every single bacteria directly makes it less like nature of course, but it also makes the algorithm much faster.
MUTATION_RATE = 1.0 / 10_000_000
CAPACITY = 4_000_000
BENEFIT = 0.001
SURVIVAL_RATE = 0.5
class Colony
attr_accessor :survival_rate, :count
def initialize rate, count = 0
self.survival_rate = rate
self.count = count
end
def double
self.count *= 2
end
def kill
self.count = self.count.cut(survival_rate)
end
end
class Experiment
attr_accessor :colony_w, :colony_s, :milestones, :capacity, :mutation_rate,
:generations, :min_w, :min_s, :max_w, :max_s, :first_mutation
def initialize survival_rate, benefit, capacity, mutation_rate
self.capacity = capacity
self.mutation_rate = mutation_rate
self.colony_w = Colony.new(survival_rate, 1)
self.colony_s = Colony.new(survival_rate + benefit)
self.milestones = (0..10).map {|i| i * 0.1}
self.generations = 0
self.min_w = colony_w.count
self.min_s = colony_s.count
self.max_w = 0
self.max_s = 0
update_min_max
end
def update_min_max
self.min_w = [min_w, colony_w.count].min
self.min_s = [min_s, colony_s.count].min
self.max_w = [max_w, colony_w.count].max
self.max_s = [max_s, colony_s.count].max
end
def total
colony_w.count + colony_s.count
end
def percent_w
colony_w.count.to_f / total
end
def percent_s
colony_s.count.to_f / total
end
def double
[colony_w, colony_s].each {|c| c.double}
end
def above_capacity?
total > capacity
end
def tick
run
update_min_max
check_milestone
if generations % 10_000 == 0
print_status
end
end
def check_milestone
if percent_s > milestones[0]
stone = milestones.shift
puts "colony_s has reached #{stone * 100}% of the population at generation #{generations}.
total population: #{total}
"
end
end
def print_status
puts "gen #{generations}: weak: #{colony_w.count} strong: #{colony_s.count}"
end
def mutate
cols = [colony_s,colony_w]
[cols, cols.reverse].each do |cs|
mutated = cs[0].count.cut(mutation_rate)
if mutated > 0
cs[1].count += mutated
cs[0].count -= mutated
unless first_mutation
puts "first mutation arises at gen #{generations}"
print_status
self.first_mutation = true
end
end
end
end
def run
double
mutate
while above_capacity?
colony_w.kill
colony_s.kill
end
self.generations += 1
end
end
Integer.class_eval do
def cut prob
full = self * prob
whole = full.to_i
partial = full - whole
rand < partial ? whole + 1 : whole
end
end
e = Experiment.new SURVIVAL_RATE, BENEFIT, CAPACITY, MUTATION_RATE
e.print_status
while e.percent_w > 0.001 && e.generations <= 500_000_000
e.tick
end
puts "took #{e.generations} generations to get population_strong from min of #{e.min_s}
to #{e.colony_s.count} (#{e.percent_s * 100}%)
and populations_weak from #{e.min_w} to a max of #{e.max_w} down
to #{e.colony_w.count} (#{e.percent_w * 100})%"Here’s some of the results I get when running it with different values:
Using the current values
gen 0: weak: 1 strong: 0
first mutation arises at gen 23
gen 23: weak: 4194303 strong: 1
colony_s has reached 0.0% of the population at generation 29.
total population: 2097152
colony_s has reached 10.0% of the population at generation 3911.
total population: 2329272
colony_s has reached 20.0% of the population at generation 4317.
total population: 2620602
colony_s has reached 30.0% of the population at generation 4587.
total population: 2995564
colony_s has reached 40.0% of the population at generation 4808.
total population: 3494804
colony_s has reached 50.0% of the population at generation 5010.
total population: 2097183
colony_s has reached 60.0% of the population at generation 5213.
total population: 2621926
colony_s has reached 70.0% of the population at generation 5434.
total population: 3495609
colony_s has reached 80.0% of the population at generation 5703.
total population: 2622864
colony_s has reached 90.0% of the population at generation 6108.
total population: 2623759
took 7307 generations to get population_strong from min of 0
to 3258961 (99.0003848879678%)
and populations_weak from 1 to a max of 2097152 down
to 32906 (0.999615112032169)%So with a benefit of 0.001, it took 29 generations for the mutation to surface, 5010 generations for it to match the frequency of the weaker bacteria, and 7307 generations for the strong bacteria to make up over 99% of the colony.
With a 0.0001 benefit (strong bacteria has a %0.01 better chance of survival over the weak bacteria)
gen 0: weak: 1 strong: 0
first mutation arises at gen 20
gen 20: weak: 2097151 strong: 1
colony_s has reached 0.0% of the population at generation 21.
total population: 2097152
gen 10000: weak: 2095041 strong: 6691
gen 20000: weak: 2093070 strong: 56031
colony_s has reached 10.0% of the population at generation 27047.
total population: 2324111
gen 30000: weak: 2091153 strong: 420262
colony_s has reached 20.0% of the population at generation 31090.
total population: 2613776
colony_s has reached 30.0% of the population at generation 33781.
total population: 2986635
colony_s has reached 40.0% of the population at generation 35989.
total population: 3484132
colony_s has reached 50.0% of the population at generation 38015.
total population: 2090424
gen 40000: weak: 1045189 strong: 1554583
colony_s has reached 60.0% of the population at generation 40043.
total population: 2613202
colony_s has reached 70.0% of the population at generation 42254.
total population: 3485008
colony_s has reached 80.0% of the population at generation 44952.
total population: 2615286
colony_s has reached 90.0% of the population at generation 49018.
total population: 2620706
gen 50000: weak: 262319 strong: 2870166
gen 60000: weak: 33896 strong: 2649526
gen 70000: weak: 5297 strong: 2445672
took 75938 generations to get population_strong from min of 0
to 2004344 (99.9000174446134%)
and populations_weak from 1 to a max of 2097151 down
to 2006 (0.0999825553866474)%It took 38015 generations for the strong and weak bacteria to be equal in numbers, 75938 generations for the strong bacteria to go from 0 to over 99% of the population. Almost 10 times longer, which is interesting because the benefit was decreased by 10.
With 0 benefit
Note: I changed the code to only print out the status every 50 million generations for this one, and only to run a maximum of 500 million generations
gen 0: weak: 1 strong: 0
first mutation arises at gen 25
gen 25: weak: 4194303 strong: 1
colony_s has reached 0.0% of the population at generation 27.
total population: 2097151
colony_s has reached 10.0% of the population at generation 1117956.
total population: 2097329
colony_s has reached 20.0% of the population at generation 2550113.
total population: 2097589
colony_s has reached 30.0% of the population at generation 4577301.
total population: 2097995
colony_s has reached 40.0% of the population at generation 8054126.
total population: 2097269
colony_s has reached 50.0% of the population at generation 35885018.
total population: 2101332
gen 50000000: weak: 1052661 strong: 1052518
gen 100000000: weak: 1051453 strong: 1052153
gen 150000000: weak: 1051255 strong: 1050806
gen 200000000: weak: 1052545 strong: 1051996
gen 250000000: weak: 1057002 strong: 1055415
gen 300000000: weak: 1053651 strong: 1055580
gen 350000000: weak: 1057720 strong: 1056790
gen 400000000: weak: 1059992 strong: 1059863
gen 450000000: weak: 1060187 strong: 1059305
gen 500000000: weak: 1058117 strong: 1059018
took 500000001 generations to get population_strong from min of 0
to 1059018 (50.0212787564326%)
and populations_weak from 1 to a max of 2097152 down
to 1058117 (49.9787212435674)%Looks like it works it’s way to 50% very slowly, and then stays there. This is interesting and isn’t what I expected. I had expected that it would stay at whatever frequency it was at when it reached capacity. I guess what is happening is that whichever type there is more of is going to have more bacteria mutate into the other type than it gets back in return.
Posted in Ruby, science | no comments |
uninstall feature added to ext
I’ve added an uninstall command to for removing subprojects from being managed by externals.
Use:
ext uninstall some_project
To stop tracking some_project. This will not remove the files in some_project’s directory. If you want to do that, use the -f/–force_removal option:
ext uninstall -f some_project
This will remove all of the files and the some_project directory itself.
To switch a project to a new repository, you can do something like the following:
ext uninstall -f some_project ext install new/repository/url/some_project
This is the type of thing I added the feature for. I moved a couple plugins from subversion to git recently, and didn’t feel like manually changing the .externals and ignore features.
Enjoy!
Posted in externals | no comments |
How to migrate typo from mysql to postgresql
I almost always use postgresql when working on a rails application. I won’t list all the little reasons why, but a major reason is for transactional DDL statements, which means when I run a migration that fails, I don’t have to then go run a bunch of cleanup queries to get my database back to how it was before the migration was ran.
When I was setting up this instance of typo, I decided I’d go ahead and go with mysql since I didn’t plan to hack on typo very much. Long story short: I decided to migrate from mysql to postgresql. This howto was done with mysql 5.0.70, postgresql 8.3.5, and typo 5.1.3 It probably will work with any mysql 5+ and postgresql 8+.
In case anybody else out there might be interested in doing likewise, here’s how I did it. These steps will be for a production database, but the changes required for doing it to a development database should be obvious.
Step 0: Backup your data
You don’t really need to be told this, do you?
Step 1: Dump the data from mysql
run the following to dump the data.
mysqldump --compatible=postgresql --no-create-info -u root -p --skip-extended-insert --complete-insert --skip-opt typo > typo.dump
We are only dumping the data, hence the –no-create-info option.
Step 2: Create your postgresql database
You can do this however you see fit. I’ve included how I do it in case it’s useful:
CREATE USER typo_prod;
CREATE DATABASE typo_prod OWNER typo_prod ENCODING 'utf8';
\password typo_prodand enter the password you wish to use.
Step 3: Change your database.yml to use your new postgresql database
Again, do this however you want. Here’s my database.yml with passwords omitted:
defaults: &defaults
database: typo
adapter: postgresql
encoding: utf8
host: localhost
password:
development:
username: typo_dev
database: typo_dev
<<: *defaults
test:
username: typo_test
database: typo_test
<<: *defaults
production:
username: typo_prod
database: typo_prod
password:
host: salmon
<<: *defaults
Step 4: Create the schema in your new database
To do this we’ll run the db:migrate rake task
RAILS_ENV="production" rake db:migrate
Step 5: Fire up a rails console to fix stuff
Now we need to fire up a rails console to do a lot of necessary cleanup work before we can import our data
ruby script/console production
Once it’s ready to go, type (or more practically, copy pasta)
conn = ActiveRecord::Base.connectionWe’ll need this for a lot of the commands we have yet to run. You’ll keep this console open for the remainder of this howto. Any ruby code you see in this document will go into this console.
Step 6: Remove data created during the migrations
The typo migrations automatically add some default data, like some default pages/articles/blog. All of the data we want is in the dump we created earlier. Let’s delete all this stuff that’s in the way
conn.tables.each do |table|
conn.execute "delete from #{table}"
endStep 7: Temporarily change boolean columns to integers
mysqldump dumps it’s booleans as 0/1. These are interpreted by postgres as integers. It will not automatically cast these into booleans just because the column is boolean (I’m not sure why.) It’s too time consuming to go add casts to all of these 0/1’s, and a regular expression to use with sed would be far too complex to bother with since not all 1’s and 0’s in the dump correspond to boolean data.
So, we will temporarily change the boolean columns in our shiny new database to integers. Before we do this, we need to temporarily drop the defaults for these boolean columns because there won’t be an implicit cast from false/true to 0/1.
This code will build a couple of hashes to store which columns are booleans and what the defaults are.
bools = {}
defaults = {}
conn.tables.each do |table|
conn.columns(table).each do |col|
if col.type.to_s == "boolean"
(bools[table] ||= []) << col.name
(defaults[table] ||= {})[col.name] = col.default if !col.default.nil?
end
end
endhere’s the value of bools and defaults in my console after the above code:
#bools
{"resources"=>["itunes_metadata", "itunes_explicit"], "contents"=>["published",
"allow_pings", "allow_comments"], "users"=>["notify_via_email",
"notify_on_new_articles", "notify_on_comments", "notify_watch_my_articles",
"notify_via_jabber"], "feedback"=>["published", "status_confirmed"],
"categorizations"=>["is_primary"]}
#defaults
{"contents"=>{"published"=>false}, "feedback"=>{"published"=>false}}Let’s now temporarily drop the defaults
defaults.each_pair do |table,cols|
cols.each_key do |col|
conn.execute "alter table #{table} alter column #{col} DROP DEFAULT"
end
endNow let’s alter the column types for the columns in bools.
We’ll use a closure to run the alter statements, so that we can use it again later to alter them back to booleans.
change_to_type = proc {|to_type|
bools.each_pair do |table, cols|
cols.each do |col|
conn.execute "alter table #{table} alter column #{col} type #{to_type}
USING (#{col}::#{to_type});"
end
end
}
change_to_type.call :integer
Step 8: Load the data dump into the new database
Ah, finally. Let’s load the data. Back to a shell in a directory with the dump, run:
sed "s/\\\'/\'\'/g" typo.dump | sed "s/\\\r/\r/g" | sed "s/\\\n/\n/g" | psql -1 typo_prod
Pass whatever options you need to connect to psql as you normally would. The first sed converts all of the \’ to two consecutive ‘s, which is what psql expects. The next two calls to sed in the pipeline replace the escaped carriage returns and newlines with actual carriage returns and newlines, which is again what psql expects.
You may get a couple warnings, but hopefully no errors. The few warnings I received were inconsequential.
Step 9: Change the boolean columns back to boolean and restore the default columns
Back to our rails console. We now have the data in place and can change the columns back using our closure from earlier:
change_to_type.call :booleanAnd then restore the defaults we dropped:
defaults.each_pair do |table, cols|
cols.each_pair do |col, default|
conn.execute "alter table #{table} alter column #{col} SET DEFAULT #{default}"
end
endStep 10: Repair the sequences.
Another annoying aspect of postgresql is that inserting a value into a serial column doesn’t automatically advance the sequence to be ready to serve up an unused value. There will be a sequence called “#{table}_id_seq” for each table with an id column in the database.
We manually have to advance all of the sequences:
conn.tables.each do |table|
if conn.columns(table).detect{|i|i.name == "id"}
conn.execute "SELECT setval('#{table}_id_seq', (SELECT max(id) FROM #{table}))"
end
endConclusion
So that should do it. Restart your mongrel cluster (or whatever you are using to manage your rails server processes) and you should now be using your blog with a postgresql backend!
Posted in Ruby, Ruby on Rails, typo | 2 comments |
how to debug a Ruby on Rails script in NetBeans
So you’ve written some helper script or possibly a script that’s ran by cron to do some background work on your site (updating full text indexes, sending out notification emails, generating reports, etc) but you can’t find a way to debug it in rails so that it hits your breakpoints. Annoying.
What I did to solve this was first to create a rake task that creates other rake tasks based on script name. This way you can debug it the way you would debug any rake task (and it’s also convenient to be able to execute scripts from the rake context menu anyways.) This allows you to run any script by right clicking on the project in Netbeans, and going to “Run/Debug Rake Task->script->your_killer_script.rb”
Place this in a file called scripts.rake and place it in your lib/tasks folder
require 'find'
namespace :scripts do
Find.find("#{RAILS_ROOT}/script/") do |p|
if File.file?(p) && p !~ /(\.svn-base|\.netbeans-base)$/
desc "Run #{File.basename(p)}"
task File.basename(p, "*") => :environment do
load p
end
end
end
endThen, right click on your project and hit “Run Rake Task->Refresh List”
You should now be able to right click on your project and hit “Debug Task Rake Task->script->your_killer_script.rb”
It should hit any breakpoints you have set. Happy debugging!
Posted in Ruby on Rails, Netbeans | no comments |
How to debug an individual rails test in Netbeans
In Netbeans, to test a Ruby application normally I right click on the project and go to “Run Rake Task -> test”
A problem arises when I try to debug a test. None of the breakpoints get hit. I think this is because a new process is spawned off to actually run the tests and the debugger is attached to the parent process. So you can really only hit breakpoints involved in spawning the tests but none in the tests or in any of your application code called by the tests.
Opening an individual test and right clicking in the buffer and hitting “Debug your_mom_test.rb” seems to fail for me with rails 2.1. I was able to correct this by changing the line at the top of the test from
require 'test_helper'to
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if $0 == __FILE__
require File.dirname(__FILE__) + '/../test_helper'Enjoy hitting your breakpoints while testing!
Posted in Ruby on Rails, Netbeans | no comments |