CrudVision - Lisa Seelye

August 20, 2007

Speeding up Beast Forum

Filed under: rails, sql, work — Lisa Seelye @ 11:30 am

(n.b., the version of Beast we use may not be the most current. This post may not reflect what's in trunk.)

One of our clients from work uses Beast forum. Unfortunately last week it kind of died. Mongrel's logs were complaining that it couldn't allocate memory, the index (ForumsController#index) was taking upwards of 12 seconds to load...

The problem was SQL. Beast is very greedy when it displays the front page; it does a forums.posts.last.created_at to get the timestamp for when the last post was made. This is done for every top-level forum and it will destroy your site.

At about 21:30ish the previous night I disabled the site because my brain was mush and I couldn't focus enough to diagnose the problem to say nothing of finding a solution. The next day when I got to the office I spent the first 90 minutes of the day diagnosing and fixing the problem. I found that I could find the relevant data with a GROUP BY and ORDER BY with one query for all forums... Except the times were the FIRST post and not the LAST post.

So I spent the next two hours figuring out why MySQL didn't want to ORDER BY and formulating a query that was both quick, returned data for every forum and worked. This is the ugly thing that resulted:

SQL:
  1. -- moderator_only is assigned from clean data in the controller; it is safe.
  2. SELECT posts.id, posts.created_at, posts.forum_id, posts.topic_id, users.login
  3. FROM posts
  4. INNER JOIN (SELECT MAX(id) AS id FROM posts GROUP BY forum_id) ids ON
  5. posts.id = ids.id
  6. LEFT OUTER JOIN users
  7. ON users.id = posts.user_id
  8. LEFT OUTER JOIN forums
  9. ON forums.id = posts.forum_id
  10. WHERE
  11. (forums.moderator_only = 0 OR forums.moderator_only = #{moderator_only}) AND forums.parent_id is null
  12. ORDER BY posts.created_at

This is fed into:

RUBY:
  1. last_posts = Post.find_by_sql "...."
  2. @last_posts = last_posts.group_by &:forum_id

In the view we just substitute forum.posts.last with @last_posts[forum.id].first in all instances. This may have to bet tweaked for your setup because I'm not sure where Beast ends and client-specific stuff begins.

You see there's some parent_id things going on in the SQL. At work we made Beast forum nested. We do plan to make these changes available for others to use but it's just a matter of finding time to get rid of the client-specific modifications and merging to trunk.

This is one of the things that I'm proud about: the SQL query was a pain!

August 14, 2007

Conquered Oracle

Filed under: oracle, rails, testing, work — Lisa Seelye @ 10:30 am

Yesterday at work I deployed the 'Oracle Project' to the customer's staging server. The only hiccup was (I think) a problem with the renumbering of migrations that was coming from two branch merges and a merge into trunk. I apparently misnumbered one and a critical migration wasn't run. Oops.

I've noticed that at the end of rake db:migrate that Rails rebuilds the db/scema.rb file (nothing new there) except with Oracle each table takes about 45 seconds to process! We've got about 35 models! That's almost 25 minutes! What gives?

Anyways, Oracle is conquered and it is my bitch.

August 7, 2007

Rails, Fixtures, Oracle, and Insert Errors

Filed under: oracle, rails, sql, work — Lisa Seelye @ 11:43 pm

At work I've been struggling with making one of our projects play nice with Oracle. I got Oracle XE installed through Windows XP in Parallels and just couldn't make my tests work. I kept getting errors that Rails couln't insert a nil value into a non-nil field (id).

What this eventually boiled down to is Rails fixtures MUST have an id attribute on them or you may run into errors! This was hidden behind the scenes since heretofore we were using auto_incrementing MySQL fields. To get this behaviour in Oracle one must do

CODE:
  1. select #{table_name}_seq.nextval id from dual

(done by Rails automatically, normally) or set up a Trigger (not done by rails, and would have to be done in a create_table migration).

However, fixtures bypass the normal ActiveRecord::Base#create call and just does a raw execute insert into... Which means there's no value for id, unless it's specified in the fixture. This kept me at it for hours today and I only found it by hacking the fixtures source to make it print in the logs what it was doing!

Let this be a lesson! Always put in ids even if you don't care about their values.

July 20, 2007

Railsconf Europe

Filed under: germany, rails, railsconf, travel, work — Lisa Seelye @ 12:00 pm

Just finished registering for RailsConf Europe this morning. Travel is booked. Officially I am going on behalf of my work, unofficially I'm going to fangirl next to Rails's niftiest people.

More later!

July 17, 2007

How NOT to write a HTTP socket library

Filed under: REST, actionscript, crud, work — Lisa Seelye @ 7:38 am

Yesterday at work we discovered that Actionscript's HTTPService library is extremely crippled in its functionality. How bad is it? Well dear reader it's common practice when writing a webserver or webservice that one should return 404 Not Found if a resource can't be found or 201 Created when a resource has been created. The other status codes also have their usefulness. In my API project for work I believe I use (only for the non-HTML access): 200, 201, 400, 401, 403, 404, 409, and 500. Quite a rich set. I also use the POST, GET, PUT, DELETE verbs.

HTTPService, however, was built in such a way that anything other than 200 OK means "Raise an exception because a fault happened" - including 201 Created (a successful response code!). HTTPService does not permit anything other than GET and POST. Good thing that Rails permits the _method hidden field, eh?

What I am most likely going to have to do is to do the same sort of thing I would have had to do if the API was done with SOAP: Always return 200 OK and maintain a set of "response objects" to cope for shortcomings in other technoloties. Instead of a standard:

RUBY:
  1. render :json => @product.to_json, :status => :created

I'll probably have to do:

RUBY:
  1. render :json => { :product => @product, :status => ErrorObject.new(201) }.to_json, :status => :ok

And I'll have to do that in every place. REST? What's that. Argh. Anyone know of a decent Actionscript HTTP library?

July 11, 2007

Four years with Gentoo

Filed under: gentoo, linux, work — Lisa Seelye @ 7:15 am

Just over four years ago I was given CVS commit access to the Gentoo portage tree. It's been quite a dramatic time and it's been very enlightening. The people I've met have been really nifty! Most of my the things that I maintain are stable. My main regret is that due to work completely sucking up my time I don't have much left over at the end of the day for Gentoo.

July 6, 2007

Rails JSON is broken

Filed under: json, rails, ruby, snippet, work — Lisa Seelye @ 11:12 pm

At work yesterday I found out that by default ActiveSupport::JSON.unquote_hash_key_identifiers is true. This means that a JSON string looks like:

{attributes: {name: "0-3VW8 I", coordinate_id: "6229", system_id: "30000995", id: "40063299", igbtype: "7"}}

Flash JSON decoders (and probably others) expect the above attributes word to be doublequoted. This is achieved (after much Googling) by setting ActiveSupport::JSON.unquote_hash_key_identifiers=false.

But it doesn't stop there. In the above JSON output the IDs are quoted, that means they're encoded as strings and not integers. This is against the spec.

This means that

RUBY:
  1. ActiveSupport::JSON.unquote_hash_key_identifiers = false
  2. planet = Planet.find :first
  3. print planet.to_json # => {"attributes": {name: "0-3VW8 I", coordinate_id: "6229", system_id: "30000995", id: "40063299", igbtype: "7"}}

The integers should not be double quoted.

With the help of lifo from #rails-contrib the problem seems to have been narrowed down to instance_values/instance_variable_get borking things up. From my poking around (n.b., I'm not all that experienced with the source for Rails itself! I may be wrong) the issue seems to lay within vendor/rails/activesupport/lib/active_support/json/encoders/object.rb. This bit of code seems to work for AR::Base derived models:

RUBY:
  1. class ActiveRecord::Base
  2. def to_json
  3. ret = ActiveSupport::JSON.unquote_hash_key_identifiers ? "{attributes: " : '{"attributes": '
  4. ret + self.attributes.to_json + '}'
  5. end
  6. end

Edit (10 July 2007): It seems that this isn't exactly a drop-in replacement fix: If a model has associations loaded (Foo.find :first, :include => :bars) the above JSON-fixing snippet will not work because it looks at attributes only and not the loaded associations. I'll have to work around that.

July 2, 2007

I did something evil today

Filed under: rails, work — Lisa Seelye @ 9:07 pm

I did something quite evil today at work.

A customer wants to be able to roll back changes made by its moderators. If there's a difference of opinion they want to roll back a change. Makes sense. (No, it isn't a wiki.)

I decided that a second-best effort (instead of doing:

RUBY:
  1. AdminEditLog.create(:user => @session_user, :description => "Set object=#{@object.inspect}")

I'd do:

RUBY:
  1. old = Marshal.dump(@object)
  2. @object.update_attributes(params[:object])
  3. AdminEditLog.create(:user => @session_user, :old => old, :current => Marshal.dump(@object.reload))

Why is it evil? It requires two BLOB fields in MySQL, stores the ENTIRE object, twice and can't be searched upon.

The next iteration will likely just be a diff. Something like:

RUBY:
  1. def object_diff(o,c)
  2. changes = {}
  3. c.attributes.each do |k,v|
  4. if o[k] != v
  5. changes[k.to_sym] = v
  6. end
  7. end
  8. changes
  9. end
  10. Marshal.dump(object_diff(old_obj,new_obj)) # Store this, works for the arbitrary models

It is just a little better. They want to revert back to it so there's no need to do the diff after, upon reverting.

It works and I was impressed how easy it was because the revert action is simply to Marshal.load the old object and to just .save it.

Ruby is love. Just don't abuse it by stuffing crap into the database!

June 27, 2007

Why CRUD? - Part 4

Filed under: crud, rails, work — Lisa Seelye @ 1:59 pm

This is probably going to be the final part.

Last time I stopped just getting ready to talk about designing a JSON based RESTful API.

My initial design had no consideration for an administration component. Every action had a block that looks, at the bare minimum, like this:

RUBY:
  1. respond_to do |format|
  2. format.js
  3. end

I cared about JSON more than HTML or XML (I included XML where it was easy enough on the first pass simply because .to_xml is just as easy as .to_json). Another developer's concern was an HTML-based administration interface for the end users to use. My JSON part was going to be used by "meat" of the project to interact with the database.

At the start of the project I had about two dozen or so models to represent the application. This is all going to sound the same:

ruby script/generate resource -c Person
ruby script/generate resource -c Avatar

And so forth.

Add the basic four (really five) CRUD operations to each controller:

RUBY:
  1. def index
  2. end
  3. def show
  4. end
  5. def create
  6. end
  7. def update
  8. end
  9. def destroy
  10. end

(Don't forget helpers!

RUBY:
  1. before_filter :get_thing, :only => [ :show, :update, :destroy ]
  2. protected
  3. def get_thing
  4. @thing = Thing.find params[:id]
  5. end

)

And then fill in the blanks. Edit config/routes.rb to do resource nesting so editing the first Person's Avatar(s) is baked in to the URL and the caller doesn't have to worry about polluting the URL with parameters that aren't needed.

The tests become simple, too. There isn't much complexity to most resources, thankfully, it's always testing five basic actions.

My coworker that was doing the HTML only had to add a format.html in the respond_to block as well as two new actions (new and edit) to handle peripheral HTML forms that my JSON-using clients would never use. Handy, that.

By using CRUD it meant that REST could be used in Rails automatically. The actions just worked. Resource nesting just worked. I got to spend time thinking about cool stuff, like the application, and not how to make "it" work, kind of like why I liked Rails in the first place! No glue code.

CRUD/REST makes me a happy coder. Using CRUD means, for example, that buying a Product in a E-Commerce store it isn't an operation on a Product per se, but rather it is the creation of a CartEntry in a Person's ShoppingCart (POST /people/1/shopping_cart/cart_entries) and to check out it's just a creation of an action on the ShoppingCartController called, for example, checkout attached to the DELETE HTTP verb. It's debatable whether or not DELETE should semantically mean "Okay, now let me purchase everything" but that's another post. ;)

In the checkout method there's then a concern of what do I do with all these CartEntries? What about a model called a ShippingQueue that the people in the shipping department at the store can use (with RESTful interfaces!) that has some state attached to it (ShippingStatus < AR::Base and then Shipped < ShippingStatus; Received < ShippingStatus, etc). Models are cool.

That's the end. CRUD helps me and that's all that matters in the end.

June 25, 2007

Why CRUD? - Part 3

Filed under: crud, rails, work — Lisa Seelye @ 9:45 pm

In the last part of what is probably going to obnoxiously long I realised that not using CRUD was making the application codebase ugly and making me not want to work with it. I rediscovered resources and discovered REST.

One of the projects at my new job was to migrate two Rails SVN repos into one (combining both Rails projects). I must admit that the codebase wasn't the best I had seen. It was clearly designed without REST in mind (especially considering there was no simply_restful and no rails 1.2) or CRUDdiness in mind. A lot of actions in controllers and a lot of scoping (addComment to some model in the model's controller, for example).

Tracing through the code was difficult: views had partials referencing partials that referenced other partials. If the project was started from scratch right now (the client would probably freak out) and it would definiately have a better structure.

After I had merged the two SVN repos into one work began on adding more features and bugfixing to the same application. The usual "Getting to know the codebase" time was applicable, but I can't help wonder what may have been if it had used REST concepts in the beginning adding to it and maintaining it would be much easier (but I may be biased).

Eventually I've become familiar with the codebase and understand how it works and its quirks. I'd love to talk the powers that be into letting us rewrite it RESTfully... but I doubt that will happen any time soon. ;-) Maybe Version 2.0.

The next project was one from scratch to expose an API to a database. In the beginning SOAP through ActionWebService was considered but ultimately discarded in favour of a RESTful design, much to my relief! ActionWebService::Struct derrived classes would have numbered in the hundreds in an effort to map the data combinations. Yick.

And so I got to design a RESTful design, this is where I really got to see the goodness of REST; prior to this project everything was just theory. Now I had to chose between scoping methods (PeopleController#addAvatar, which was actually creating an Avatar) or REST. I researched more into REST, played, and was thankful to see that it wasn't as hard as I had imagined. Rather, it was easy. Very easy, almost too easy.

The project in question is a large scale and involves a dozen or so people. There is a Java part, a Flash part, a Rails part and those crazy art-type people (who I believe practice dark arts to make such images appear on paper and computer screens!). The Java and Flash guys had the harder part. All I had to do was architect a bunch of models and controllers to do CRUD operations with an agreeable data envelope (we chose JSON). Everyone else had to knit everything together and make it all work. CRUD is easy, Working with Java and Flash is painful.

Part four will talk about the way I went about architecting the API. It's going to be very boring - CRUD is predictable (which is good!)

« Newer Posts

Powered by WordPress