Read my latest article: 8 things I look for in a Ruby on Rails app (posted Thu, 06 Jul 2017 16:59:00 GMT)

Active Record, I <3 U but I still trust my database server (a tiny bit more)

Posted by Fri, 19 Aug 2005 01:20:00 GMT

While working on a portion of my book, I found myself in ./script/console and was seeing some weird issues when I would use has_many and belongs_to.

Let’s take two simple models.

<pre>
class Order < ActiveRecord::Base
  belongs_to :customer
end

class Customer < ActiveRecord::Base
  has_many :orders
end
</pre>
</code>

After a few test records...

<code>
<pre>
test_dev=# SELECT * FROM customers;  
 id |      name      
----+----------------
  1 | Robby
  2 | Nigel
  3 | Linus
(3 rows)

test_dev=# SELECT * FROM orders;
 id | customer_id | amount 
----+-------------+--------
  1 |           1 |  12.00
  2 |           3 |  12.00
(2 rows)
</pre>
</code>

Nothing completely crazy going on, right?

<typo:code lang="ruby">
Loading development environment.
>> Customer.destroy(3)
=> {"name"=>"Linus", "id"=>"3"}
>>     

=# SELECT * FROM orders;
 id | customer_id | amount 
----+-------------+--------
  1 |           1 |  12.00
  3 |           3 |  12.00
(2 rows)

Wait a minute! I just deleted a customer with an id of 3!

So, what is wrong with this scenario? Can you think of any potential problems that could occur from data like this? The record has a customer_id for a customer that does not exist. This is why we have relational databases in the first place, right? :-)

Here is something that I learned today that I was unaware of. Active Record allows you to pass the has_method declaration the option :dependent.

class Customer < ActiveRecord::Base
  has_many :orders, :dependent => true
end

What is this option? Well, according to the AR documentation, “:dependent – if set to true all the associated object are destroyed alongside this object. May not be set if :exclusively_dependent is also set.”

In a nutshell, this works like ON DELETE CASCADE does in PostgreSQL. So, it will through and delete the orders associated with the customer that I was attempting to destroy.

Up until today, I hadn’t broken myself out of the habit of using the built-in constraints/triggers of PostgreSQL. So, as soon as I did, this issue came up and I learned about :dependent.

test_dev# \d orders
                                Table "public.orders" 
   Column    |     Type      |                       Modifiers                        
-------------+---------------+--------------------------------------------------------
 id          | integer       | not null default nextval('public.orders_id_seq'::text)
 customer_id | integer       | 
 amount      | numeric(10,2) | 
Indexes:
    "orders_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "orders_customer_id_fkey" FOREIGN KEY (customer_id) REFERENCES customers(id)

test_dev=# ALTER TABLE orders DROP CONSTRAINT orders_customer_id_fkey;
ALTER TABLE 

RobbyOnRails:~/Programming/footest robbyrussell$ ./script/console 
Loading development environment.
>> cust = Customer.create(:name => 'Jim')
=> #<Customer:0x275373c @new_record_before_save=true, @new_record=false, @attributes={"name"=>"Jim", "id"=>5}, @errors=#<ActiveRecord::Errors:0x274fa88 @base=#<Customer:0x275373c ...>, @errors={}>>
>> cust.orders.create(:amount => '25.00')
=> #<Order:0x274991c @new_record=false, @attributes={"id"=>4, "amount"=>"25.00", "customer_id"=>5}, @errors=#<ActiveRecord::Errors:0x2746dfc @base=#<Order:0x274991c ...>, @errors={}>>
>>                

test_dev=# SELECT * FROM orders;
 id | customer_id | amount 
----+-------------+--------
  1 |           1 |  12.00
  3 |           4 |  29.00
  4 |           5 |  25.00
(3 rows)

As you can see, I put myself into the hands of Active Record when I ran the DROP CONSTRAINT. Then I tried running the code at the top of the post… and it didn’t work.

According to the docs, if you use :dependent => true, it should delete the foreign table records. If not, it should set the value of the foriegn key field to NULL in the foreign table.

Basically, perform these two SQL queries:
UPDATE orders SET customer_id = NULL WHERE customer_id = 17; 

DELETE FROM customers  WHERE id = 17;

Then, the records are still in the database for those orders, but the customer is deleted. There are arguments for and against doing this sort of thing… but the ability to have the option is always nice. In any event, Active Record would not run the first query,it was just deleting from the customers table. Without my constraint, no error would be returned from PostgreSQL and I started to get some bad data.

Imagine showing a list of orders and trying to display the customer name associated with an order that has no linking customer. Doh! If Active Record sets the customer_id to NULL we can at least have some logic to work with this without having to run some fun SQL queries to figure out which orders do and dont have customers. (we want our applications to have clean data!)

Anyhow… Was this a bug? Should Active Record know to update the records to NULL in this case? I figured that is should be handling this task, especially since it was handling cascading deletes when you passed :dependent => true.

However, I didn’t want to prematurely post a bug report, so I began asking around on #rubyonrails (irc.freenode.net). People made a bunch of suggestions as to how to work around it. I could add a before_destroy method in my model, track the bug down, (re)add an ON DELETE trigger to my table (hah), etc. So, I decided that I would see if I could track down what happens when has_many is used for a model upon #destroy.

After a while of digging and making some tests, I posted a patch and a bug report. (please disregard my first patch… it did not work! heh)

Now that I figured this out, I am going to happy add my constraints back to my tables and go back to playing around. This reminded me of a post I had a few months ago when I mentioned that I thought it was best to put some constraints and logic in the database. I also agree that constraints should be put in the abstraction layer, but we cannot always put all faith in our code either. A few levels of checks doesn’t hurt. :-)

This was a fun little riddle that I took on today. The moral of the story? If you have the ability to use the builtin referential integrity features of PostgreSQL and those other databases, it might be a good idea to do so. Things get overlooked, people login to the database in many ways, and from different programs.

UPDATE: DHH responded to this post and provided a link which discusses Application Database versus Integrated Database

It should be noted that there is an important distinction between the two methods. When I said, “Things get overlooked, people login to the database in many ways, and from different programs” I was basically describing Integration Database. However, I was also thinking of the possibility of someone opening up their MySQL or PostgreSQL GUI and manually removing a record in plain SQL. According to Application Database, the moment that you do that, you basically break this model and cannot expect your application to be fully responsible for the problems that may or may not occur. At this point, you would need to look at your application in terms of Integration Database. Please do correct me if I am wrong on this. :-)

However, with this scenario, my first attempt to move to relying on AR had a minor hiccup, but it was an easy enough fix.

By performing the following command, I am moving towards an Integration Database pattern and that should be recognized when taking this into consideration.

ALTER TABLE orders ADD CONSTRAINT orders_customers_id_fkey 
    FOREIGN KEY (customer_id) REFERENCES customers (id) MATCH FULL; 

Okay, back to work!

Once again. Use constraints! (if you can)

... and thanks to DHH for providing the link and motivating me to make a note of this in my entry.

Foscon Flickr Photos

Posted by Fri, 05 Aug 2005 05:09:00 GMT

Scott Laird uploaded more FOSCON pictures. :-)

(you can even find a picture of me)

Why

My evening at FOSCON

Posted by Thu, 04 Aug 2005 03:08:00 GMT

I will be honest and say that at 6:28 PM, I decided that I was just not going to be able to make it to FOSCON. I was wedding clothes shopping which is taken place in SoCal later this week. When I got into my car I noticed that my friend, Cliff, had left me a few voicemails saying that he would be there. I ran home, dropped off my girlfriend and headed to Free Geek.

I got there just in time to see them introduce DHH. He gave his keynote, which is the same one that he will be delivering tomorrow. Short, sweet and to the point. He took a few questions, like, “Couldn’t this be redone in Perl and wha t would the difference be?” (not word-for-word… but close).. David responded, “Clean code.”

Afterwards, I managed to sneak in a , “Hi, I am Robby” but never managed to get to talk with hime anymore than that. Perhaps I will be able to make it to OSCON tomorrow and get a chance to introduce myself again. :-)

During the pizza eating portion of the evening, I got to talk with the man behind Ruby himself, Matz. We talked for a few minutes. I told him that I was the one who did RubyURL and how I got excited and told my friends when he mentioned my site on his blog.

The next speaker was…umm.. I forgot his name. It’s not on the site. He gave a quick overview of some really cool things that are coming to Ruby on Rails… and that is Rails-powered….FLASH!

Yes, Flash applications on top of Rails. It’s a port of NextStep or something. I didn’t keep up with it all, but what I did gather was quite cool sounding. What was his name…?

UPDATE: His name is… Rich Kilmer.

Glenn Vanderburg then gave a 42.minute talk on MetaProgramming with Ruby. That happened about 3.hours.from_now.ago. It was a nice talk and learned a few things about method_missing that I didn’t know before. I am using it in a few examples in my book, so it was nice to see some gotchas.

The last speaker was why the lucky stiff. WOW. He and thirsty cups (his backup band) gave a 30 minute concert, video, interactive,... I don’t know what the hell to call it. It was the most creative programming tutorials/talks that I have ever seen. He said that he would post the videos and information after tomorrow (when he gives the same tutorial at OSCON).

If you are going to see this… it would be wise to take pictures, video tape it… and bring your laptop for interactive participation. He gave a quick intro into drb and the audience (you) gets to be a part of that. I cannot wait to show my friends those videos.

My friend Cliff, who is a Python fan, seemed to enjoy himself. Maybe… just maybe… he might give Rails a real shot. He has this weird hangup about using tal for views, so I pointed him to Armita.

It was great to meet all those who came up and introduced themselves to me. I was the red head who looked like he hadn’t eaten anything all day. That slice of veggie pizza was well needed. :-)

Thanks to the folks in PDX.rb and Free Geek for putting this event on. I am glad that I made it out.

It’s time for some sleep…

UPDATE You can see some pictures from FOSCON on Flickr, under the tag… foscon

http://flickr.com/photos/tags/foscon/

The legacy of databases with Rails

Posted by Tue, 26 Jul 2005 03:20:00 GMT

As I have had way too much experience with working with horribly ugly and outdated database schemas, I find myself wanting to add new interfaces, but can’t just drop their old schema as other applications rely upon it.

Let’s say that you have an old table that looks like this:

mysql> DESCRIBE client_comment;
+---------------------+--------+------+-----+---------+----------------+
| Field               | Type   | Null | Key | Default | Extra          |
+---------------------+--------+------+-----+---------+----------------+
| client_comment_id   | int(8) |      | PRI | NULL    | auto_increment |
| client_comment_body | text   | YES  |     | NULL    |                |
+---------------------+--------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

In this example, I took the privilege of making the example only two fields, now imagine that this table has over 20-30 fields and they all have client_comment_ in front of the actual string that is of important. Who wants to type all that out over and over? Also, we all refer to this table as the table that holds customer notes, which doesn’t really match what they originally named it. No, we can’t just renmae the table, stuff relies on this structure. But for a new Rails interface, we’re going to tweak our model quickly to interact with the same outdated table in a cleaner fashion.

The first obvious thing is that we need to create a model called customer_note.rb.

class CustomerNote < ActiveRecord::Base
end
Next, we’ll tell the model that it’s table name is client_comment, rather than customer_notes like it is going to expect it to be. While we are at it, let’s take a quick moment to define the primary key for this table, because id is not what they picked when they first made the table.
class CustomerNote < ActiveRecord::Base

  set_primary_key "client_comment_id" 

  def self.table_name() "client_comment" end

end
With this, we can already start interacting with our table as if it were called customer_notes.
$ ./script/console
Loading development environment.
>> CustomerNote.create(:client_comment_body => "hello world")
=> #<CustomerNote:0xb7a02cfc @new_record=false, @attributes={"client_comment_body"=>"hello world", "client_comment_id"=>39}, @errors=#<ActiveRecord::Errors:0xb79a4508 @base=#<CustomerNote:0xb7a02cfc ...>, @errors={}>>

Okay, now we want to interact with the field client_comment_body as just body. We want to be able to get and set this value, so we’ll add the following methods to our model.

  def body
    read_attribute "client_comment_body" 
  end

  def body=(value)
    write_attribute "client_comment_body", value
  end

The first method allows you to access (get) the value of client_comment_body with CustomerNote.find(1).body. The second method handles when you (set) the value for body, it then provides the client_comment_body attribute with the value. Technoweenie on IRC helped me get this trimmed down from my original version.

In the end, you have this:

class CustomerNote < ActiveRecord::Base

  set_primary_key "client_comment_id" 

  def self.table_name() "client_comment" end

  def body
    read_attribute "client_comment_body" 
  end

  def body=(value)
    write_attribute "client_comment_body", value
  end

end

Then, you can do save yourself some typing by accessing the field with the alias that you provided. :-)

$ ./script/console
Loading development environment.
>> a = CustomerNote.new(:body => "Hello World!")
=> #<CustomerNote:0xb78c6ffc @new_record=true, @attributes={"client_comment_body"=>"Hello World!"}>
>> a.save
=> true
>> a
=> #<CustomerNote:0xb78c6ffc @new_record=false, @attributes={"client_comment_body"=>"Hello World!", "client_comment_id"=>40}, @errors=#<ActiveRecord::Errors:0xb7890d6c @base=#<CustomerNote:0xb78c6ffc ...>, @errors={}>>
>> b = CustomerNote.find(40)
=> #<CustomerNote:0xb788ccd0 @attributes={"client_comment_body"=>"Hello World!", "client_comment_id"=>"40"}>
>> b.body = "Goodbye...cruel... world." 
=> "Goodbye...cruel... world." 
>> b.save
=> true
>> CustomerNote.find(40)
=> #<CustomerNote:0xb7886330 @attributes={"client_comment_body"=>"Goodbye...cruel... world.", "client_comment_id"=>"40"}>
>> CustomerNote.find(40).body
=> "Goodbye...cruel... world." 
>>

Feel free to send me tips if there is an even better way of doing this. :-)

Cheers… and enjoy!

Rails versus J2EE lollipops

Posted by Tue, 12 Jul 2005 22:28:00 GMT

I admit. I never really enjoyed the bitter sweet flavor of the Java lollipop and had the urge to taste it again. I’ve read through a few Struts books and such to get familiar enough with it so that I can assist hosting customers with their configuration issues. Otherwise, I never found myself going out of my way to have the desire to learn it. (I took a Java class about 6 years ago in college… but that was when I decided that I didn’t want to be a programmer).

IBM Developerworks has published this article that compares J2EE and Rails. Is there room for both?

Having been paid to program to work in ASP, ASP.NET (uggh..), Perl, PHP, Python, and now Ruby… I always wonder if I missed out on something by not giving Java another try. I’m not feeling it… I like Ruby for now. Next language? Maybe I will follow in the steps of Lucas and pick up Lisp next… or maybe try Haskell?

What language do you recommend that I dive into next? I want something as sweet as Ruby… is that possible? :-)

Rails API in KDevelop

Posted by Thu, 07 Jul 2005 12:32:00 GMT

It dawned on me the other day that I could use the Documentation feature in Kdevelop and embed the Rails API in the windows when I am working in Kdevelop.

Rails API in Kdevelop

(click to view full screen)

You’ll also notice that I use script/console in Kdevelop.

Just thought that I’d share. :-)

Older posts: 1 ... 28 29 30 31 32