Ruby on Rails
HTTP 417 error when POSTing to Lighttpd
Submitted by Duncan Bayne on Sat, 2009-12-19 04:18We just shifted from using HTTP GET to HTTP POST for some of our web services; in this case, we are POSTing from a .NET 2.0 application to a Ruby on Rails server running on Lighttpd (a.k.a. lighty).
This resulted in an HTTP 417 error.
This is caused by the Microsoft .NET WebClient class adding an expect header into the POST, which in turn causes Lighttpd 1.4 to fail. The fix (as explained by Phil Haack) is to make the following call before POSTing:
System.Net.ServicePointManager.Expect100Continue = false;Reciprocal relationships in ActiveRecord
Submitted by Duncan Bayne on Sun, 2009-11-15 00:50Imagine you had a domain model involving a Company and a Person.
A Person can be an employee of a single Company, or the CEO of a single Company. If a Person is a CEO of a Company, that Company is known as the Person's empire. If a Person is employed by a Company, that Company is known as the Person's employer.
If a Person is employed by a Company, that Person is known to the Company as an employee. If a Person is a CEO of a company, that Person is known as the Company's ceo.
The UML for this particular relationship (excuse the crude layout; I hacked it up in a few minutes with Dia and I don't know how to resize elements) looks like this:

It is possible (although not entirely intuitive) to express this type of relationship in Ruby on Rails, using ActiveRecord. Note that the foreign_key calls are to the Red Hill on Rails Core plugin; if you're not using it or something like it to enforce referential integrity in your database, then you should probably look into it as a matter of urgency.
Anyhow, to the code:
class Company < ActiveRecord::Base
has_many :employees, :class_name => 'Person', :foreign_key => 'employer_company_id'
belongs_to :ceo, :class_name => 'Person', :foreign_key => 'ceo_person_id'
end
class Person < ActiveRecord::Base
belongs_to :employer, :class_name => 'Company', :foreign_key => 'employer_company_id'
has_one :empire, :class_name => 'Company', :foreign_key => 'ceo_person_id'
end
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.column :employer_company_id, :integer
t.timestamps
t.foreign_key :employer_company_id, :companies, :id
end
end
def self.down
drop_table :people
end
end
class CreateCompanies < ActiveRecord::Migration
def self.up
create_table :companies do |t|
t.column :ceo_person_id, :integer
t.timestamps
t.foreign_key :ceo_person_id, :people, :id
end
end
def self.down
drop_table :companies
end
end
To prove this works, we can fire up a console and create an imaginary company, with employees and a CEO:
>> megacorp = Company.create
=> #<Company id: 1, ceo_person_id: nil, created_at: "2009-11-15 00:34:07", updated_at: "2009-11-15 00:34:07">
>> bob = megacorp.employees.create
=> #<Person id: 1, employer_company_id: 1, created_at: "2009-11-15 00:34:15", updated_at: "2009-11-15 00:34:15">
>> joe = megacorp.employees.create
=> #<Person id: 2, employer_company_id: 1, created_at: "2009-11-15 00:34:22", updated_at: "2009-11-15 00:34:22">
>> megacorp.ceo = bob
=> #<Person id: 1, employer_company_id: 1, created_at: "2009-11-15 00:34:15", updated_at: "2009-11-15 00:34:15">
>> megacorp.save!
=> true
>> megacorp.employees
=> [#<Person id: 1, employer_company_id: 1, created_at: "2009-11-15 00:34:15", updated_at: "2009-11-15 00:34:15">, #<Person id: 2, employer_company_id: 1, created_at: "2009-11-15 00:34:22", updated_at: "2009-11-15 00:34:22">]
>> megacorp.ceo
=> #<Person id: 1, employer_company_id: 1, created_at: "2009-11-15 00:34:15", updated_at: "2009-11-15 00:34:15">
>> bob.employer
=> #<Company id: 1, ceo_person_id: 1, created_at: "2009-11-15 00:34:07", updated_at: "2009-11-15 00:34:33">
>> bob.empire
=> #<Company id: 1, ceo_person_id: 1, created_at: "2009-11-15 00:34:07", updated_at: "2009-11-15 00:34:33">
>> joe.employer
=> #<Company id: 1, ceo_person_id: 1, created_at: "2009-11-15 00:34:07", updated_at: "2009-11-15 00:34:33">
>> joe.empire
=> nil
As you can see it's possible to build up just the kind of relationships we want. In practice you'd want some constraints and observers - so for example, making a Person the CEO of a company would automatically add that Person to the employees collection. But you can see in principle how it'd all work from the above example.
This issue has been nagging at me since I ran into it while coding yesterday evening. Now I've put it to rest, I'm off to enjoy the fabulous weather we've been experiencing recently. At some point, I've got to sort out some decent code formatting on this blog (in particular, Ruby syntax highlighting) but that's a concern for a later (perhaps rainier) day :-)

Fixing UUID generation on DreamHost
Submitted by Duncan Bayne on Tue, 2009-11-10 05:37Dreamhost recently changed their configuration (at least on our server) to prevent users from running ifconfig. This causes UUID generation to fail, with an error like this showing up in the Rails log:
An exception was thrown while generating the temp path. all of /sbin/ifconfig /bin/ifconfig ifconfig ipconfig /all failed.
Long story, short form: the uuid Gem relies upon the macaddr Gem, which in turn uses one of the above binaries to determine the MAC address of the machine it's running on. So if you can't execute ifconfig (or ipconfig if you're on Windows) then UUID.generate will fail.
There are two possible fixes. Either you can change your code to use the uuidtools Gem, which seems to work nicely without being able to run ifconfig. Or, you can tell the macaddr Gem what the MAC address is in your environment.rb file:
require 'uuid' # Specify a MAC address because Dreamhost doesn't allow us to run ifconfig. Mac.mac_address = '00:1f:e0:8c:0f:23'
Either works - I think the former is a better way of doing it (or you'll be modifying your environment.rb file for every machine to which you deploy) but the latter is certainly faster if you're in a hurry.
Hack to reset auto increment on MySQL tables during RSelenese tests
Submitted by Duncan Bayne on Mon, 2009-08-24 07:30If you have an RSelenese test (i.e. a test for Selenium on Rails) that creates an entity, it helps to know what the ID of the entity is. Unfortunately the auto-increment functionality in MySQL will prevent you from figuring it out; imagine the following test:
setup :fixtures => :all
delete_cookie '_dogserver_session', '/'
open '/selenium/setup'
login_as('quentin', 'monkey')
create_dog()
click_and_wait 'edit_dog_12'Provided you know that your fixtures load 11 Dog objects, you're okay. However, what if you ran that test a second time? The fixture would be reloaded, but the auto-increment value for the dogs table would remain at 13 ... meaning that the test would fail as the Dog it creates would have an ID of 13.
A quick and dirty way of doing this is to reset the auto increment values for the tables we care about in the setup action on the Selenium controller. Adding the following code:
config = Rails::Configuration.new
str_database = config.database_configuration[RAILS_ENV]["database"]
str_username = config.database_configuration[RAILS_ENV]["username"]
str_password = config.database_configuration[RAILS_ENV]["password"]
conn = Mysql.real_connect("localhost", str_username, str_password, str_database)
# replace [ Dog, Owner, User] with the models whose tables you want to reset
for class_ar_each in [ Dog, Owner, User ]
id = class_ar_each.find(:first, :order => "id DESC").id + 1
conn.query("ALTER TABLE #{class_ar_each.table_name} AUTO_INCREMENT = #{id}")
end
conn.close()
... to the setup method in vendor/plugins/selenium_on_rails/lib/controllers/selenium_controller.rb and you're good to go.
To be perfectly clear: this is a hack, it only works on MySQL, there is no error handling, and it assumes there is at least one record loaded into each of the tables by fixtures. When / if time permits I'll look at submitting a patch for this and cleaning it up somewhat, but until then, you're on your own. YMMV, etc.
Helping out over at RailsForum
Submitted by Duncan Bayne on Sat, 2009-06-13 23:53Every day I'm at work, I'm going to try to answer one unanswered thread over at RailsForum. The idea is to keep my skills sharp across all areas of RoR, to help to keep the Rails as helpful and friendly as it's currently known for being, and of course to boost my own & my company's profile on the 'net.
Silent failure is evil
Submitted by Duncan Bayne on Fri, 2009-06-12 04:51Here's a gotcha: SQLite3 doesn't do referential integrity. That'll be a problem if you're relying on the RedHillOnRails plugin to enforce referential integrity on the database.
[Segue: if you're writing a green-fields application that doesn't have to deal with a legacy database with known referential integrity problems, there is no excuse not to enforce referential integrity in the database. Even if you're using Rails, which has no support out-of-the-box for referential integrity in the database, it's easy enough to install and use the aforementioned RedHillOnRails plugin.]
It's quite reasonable for the SQLite3 team to not handle referential integrity out of the box. After all, SQLite3 is designed to be tiny. To meet this requirement requires a lean feature set.
What is a right pain in the ass is the fact that SQLite3 silently fails when a consumer requests referential integrity. If my database creation script runs without errors, I pretty much expect that it worked. I don't expect that the database noticed failures, but didn't think those were sufficiently important to mention. Especially when the failure in question relates to something as important as referential integrity!
In my opinion (although others disagree) failure should be noisy, and success should be silent.
Ruby on Rails ... on my iPhone
Submitted by Duncan Bayne on Thu, 2008-12-04 11:27Awwwwwwwwwwwwwwwwwwwwwwww, yeah:
That is a screenshot of Ruby on Rails 2.2.2 ... running on my iPhone. I didn't think that anything could be cooler than the iPhone light saber app, but it turns out I was wrong ![]()
It's actually pretty easy to do:
- Jailbreak your iPhone (this allows you to run apps that haven't been signed by Apple; it is easy using the QuickPwn tool but of questionable legality in the USA thanks to the DMCA).
- Using Cydia, install Ruby, WGet, and MobileTerminal.
- As root, use WGet to download the latest RubyGems source, and install it.
- Again as root, install Rails with
gem install rails.
And that's it - create your Rails app, switch to Safari and browse to http://127.0.0.1:3000/.
Ruby vs.System.Reflection.Emit / Extension Methods
Submitted by Duncan Bayne on Tue, 2008-11-11 10:40Okay, so I'm trying to tweak SubSonic to emit nested domain objects that can be serialized by the Microsoft SOAP serializers. My particular gripe is with the fact that child records are exposed as methods:
public OBZ.Database.MyChildCollection MyChildRecords
{
return new OBZ.Database.MyChildCollection().Where(MyChild.Columns.ThingId, ThingId).Load();
}
... whereas I need them exposed as properties:
[XmlArray]
public OBZ.Database.MyChildCollection MyChildRecords
{
get
{
return new OBZ.Database.MyChildCollection().Where(MyChild.Columns.ThingId, ThingId).Load();
}
}Now, I'm pretty sure that this is just a matter of editing some templates somewhere and regenerating my database classes. In other words, this post isn't intended to be a criticism of SubSonic. Quite the contrary in fact. I love SubSonic to bits and will be contributing a patch or two to it when (if?) I have the time.
However, this got me to thinking of how easy it'd be to add a couple of get accessors to these classes if I was using Ruby (or really any dynamic language with strong metaprogramming).
Here's a simple example, where I use the interactive Ruby tool (irb) to extend the String class:
irb(main):001:0> class String irb(main):002:1> def barf irb(main):003:2> "Bananas" irb(main):004:2> end irb(main):005:1> end => nil irb(main):006:0> a = "Hello" => "Hello" irb(main):007:0> a.barf => "Bananas"
As you can see, once I've added barf to the String class, any subsequent Strings I instantiate expose barf for me to use. It doesn't take much imagination to see that I could use this to easily extract myself from the class of problem I'm having with SubSonic. (I'll grant you that I wouldn't be having that problem in the first place if I was using Ruby, as you might have noticed from my example that a get accessor is just a method, but that's a topic for another day).
How about C#? Well, it offers extension methods but note they're methods not properties, so that's no help. I could use the System.Reflection.Emit namespace to create a new class that inherits from the SubSonic-generated class, or I could simply create a new class that inherits from the SubSonic-generated classes and contains the properties I need. The problem is that System.Reflection.Emit is just a little bit more verbose than the Ruby alternative, and both that and inheritance are fixing the problem by creating a new class, rather than just extending an existing one.
The more I use Ruby, the more I'm coming to value dynamic languages in general, and Ruby in particular.
Rails on Cygwin on Vista
Submitted by Duncan Bayne on Sun, 2008-11-09 12:06This is, frankly,amazingly cool. With a bit (alright, a lot) of fiddling around I've got Cygwin running sufficiently well on Vista that I can use said operating system to test out an idea (specifically: emitting WSDL from Ruby based on ActiveRecord objects, and having Visual Studio build a .NET 2.0 - based web service client from that).
Anyway, here's what it looks like:
Personally I'm quite chuffed. I'll be posting details - along with an archive of my emacs setup - shortly.
Aaaaaaaargh - 2CO INS pain
Submitted by Duncan Bayne on Mon, 2008-10-13 11:24The following is a support request to 2CO regarding their INS functionality (with names removed to protect the ... I would say guilty, but in fact the poor long-suffering support engineers who are stuck with supporting a quirky, buggy system). Incredibly, this support request is still open, and their bug in the vendor_order_id callback has already cost us a fair bit of manual fiddling with the database to correct corrupt records.





Recent comments
1 week 1 day ago
3 weeks 3 days ago
3 weeks 6 days ago
11 weeks 1 day ago
15 weeks 5 days ago
31 weeks 14 hours ago
31 weeks 19 hours ago
37 weeks 3 days ago
37 weeks 3 days ago
37 weeks 4 days ago