Archive for June 2008
Ever since Rails 2.0 came out, my REST in Place Plugins stopped working because of the request forgery protecting introduced into ActiveController. The AJAX request didn’t submit the rails authenticity token and the requests weren’t answered.
I fixed that problem yesterday in both the jQuery and the Prototype version (both included in the plugin.)
Check it out on the REST in Place project page.
I had a problem with Adium: After 3 minutes of inactivity, my monitor goes to sleep. With the laptops volume turned low I would not notice any incoming messages whenever I was playing a game or anything that made some level of noise.
Back when I used windows, Miranda woke up my display when it received a message. That was an option I missed from Adium. The solution to this was pretty simple though. Adium provides the possibility to execute an Applescript on events. So I wrote a litte script to wake up my display and bound that to the “Message received (initial)” event. The script? Not very impressive:
tell application "System Events" to key code 59
But it works! :)
(Key Code 59 is the CTRL key, so you don’t accidentally trigger anything by firing it)
Today a post by Reg Braithwaite brought to my attention several blogposts about ActiveRecords alledged shortcomings.
Most of their writers are posting rants rants about some ideas they have about ActiveRecord without ever having really used it, it seems. The main issues seem to be
- Domain-code in your controllers instead of your models
- Rails choice of inheritance (AR as a base class for models) for implementing AR
Putting domain code inside your controllers is a programmer problem, not a framework problem.
I won’t even elaborate this any further.
The other issue is trickier. Not because of technical issues but because there’s a problem with the perception of Rails ActiveRecord. I first didn’t even want to waste my time to write this rant but then I started a comment on one of the postings that turned longer and longer until I decided to make a post of it.
Bill Karwin wrote some observations that I can mostly agree with.
There’s one exception though: I don’t see any problem with AR being implemented as a base class for your models.
One perceived problem is a lack of separation of concerns, the idea that you should separate model logic and database logic and that ActiveRecords fails here.
But one must look beyond the separation-of-concerns dogma here and ask what motivates this principle. Mixed concerns lead to confusion, confusions leads to errors and redundance (and thus to more errors). So, the motivation is to maintain a clear view over your methods and to reduce redundance.
Both goals are maintained by Rails although the models contain a mix of model- and database logic. Because most things can be expressed very concisely in Ruby, one line or even no code at all (using the powers of reflection) is enough to achieve what you want. If all your model-functionality and ORM-functionality fit within one page on your screen, the benefit of splitting both into separate modules and/or files becomes too low to justify the cost.
The second problem with inheriting your models from AR::Base is supposedly the tight coupling between your model and your DB-access layer, requiring you to have a database ready for unit testing. If you’ve ever written tests for Rails, you know this is bullshit. Testing is a non-issue and the support for the testing database so seamless that I can’t imagine it being any more simple. One rake task sets up your testing database after a migration, the rest is handled automatically.
Pretending that relying on AR so much for your models is still bad somehow because you might want to change your record-store a some point in the lifetime of your application is nothing but a bunch of bullshit. NO solution EVER will enable you to do THAT in a way that’s so seamless that you’d want to. We’re talking about software here, not lightbulbs that you just screw in. Abstractions are always leaky and talking as if they were not is foolish.
The whole point is that Rails set out to to one thing well, and that is to write web apps in a certain way that has proven to be quite productive and covering the needs of most projects pretty well. You get a lot of comfort following that way but you pay a price if you don’t, namely losing that comfort. People are demanding stuff that Rails never promised to deliver. About their motivations I can only speculate. Maybe they’re just clueless, maybe they just want to make some noise. In any way, they’re hell of a reason to stay away from Digg and Reddit and stick to some bloggers you trust with not wasting your time.
05.06.08
Common sense, above all else, is usually the essential ingredient in being good at anything.
– Tim Rogers
Rails association collections know two ways to determine their size:
- Through a dumb count as soon as you query the collection for its size.
This doesn’t require any additional work but puts quite a load on the database as soon as you have a large list of objects and want to know the size of an associated collection for each object. For 100 posts with comments, this approach would query the database 100 times with SELECT count(*) AS count_all FROMcommentsWHERE (comments.post_id = 1234). This might be okay for single objects which are queried from time to time but not for collections that need to be displayed frequently.
- Through counter caches that are a bit of a pain in the ass to set up correctly and gave me the fishy impression that they are easily corrupted.
Now, I admit that I didn’t spend too long investigating the implementation of the counter cache but after fiddling around with it for an hour before I finally found out how to properly initialize the cache in my migration and after discovering that the cache can only be changed relative to its current content, I left with a bad feeling.
All this hassle is absolutely unavoidable if you need maximum performance. In this article I’ll use blogposts and comments as an example, though only because this is familiar to many people. A real blog with a bunch of posts on its index page, receiving any considerable amount of hits per day is a bad candidate for the trick I describe here.
In raw SQL, if you want to find out the amount of associated rows in a different table, you use a JOIN, combined with COUNT() and GROUP BY, like this:
SELECT posts.*, COUNT(comments.id) AS comments_count
FROM posts
LEFT OUTER JOIN comments ON posts.id = comments.post_id
GROUP BY posts.id
Admittedly, this is slower than a counter cache but is not as difficult to set up, doesn’t risk errors due to a corrupt cache and if you chose your database keys wisely it is a pretty nice compromise.
When you ask an association collection for its size(), rails checks for the presence of a counter cache attribute in the current ActiveRecord object. If it finds one, it uses its content to return the collection size, otherwise the database is queried.
The catch is now, that in the above example, we inserted a perfectly valid counter cache column into our posts without specifying caching in the association declaration in the class. ActiveRecord inserts that columns content into a comments_count attribute in our Posts but since it doesn’t know exactly what to do with it, it doesn’t cast it into an integer but leaves it in there as a String. That makes size(), or to be more precise, the count_records() method trip with a “String can’t be coerced into Fixnum” error.
To fix this, I wrote an extension for the has_many Association:
module OptionalJoinCounter
def count_records
count = if has_cached_counter?
@owner.send(:read_attribute, cached_counter_attribute_name).to_i
elsif @reflection.options[:counter_sql]
@reflection.klass.count_by_sql(@counter_sql)
else
@reflection.klass.count(:conditions => @counter_sql, :include => @reflection.options[:include])
end
@target = [] and loaded if count == 0
if @reflection.options[:limit]
count = [ @reflection.options[:limit], count ].min
end
return count
end
end
Uh, well, yeah, the only change here is the .to_i at the end of line 4 but hey, what did you expect?
Save that in lib/optional_join_counter.rb and extend your association with has_many :whatevers, :extend => OptionalJoinCounter
Now, to get back to the example, imagine we want to use this trick to count our comments.
The above SQL can either be written by hand, or with Rails finder options:
Post.find :all,
:select => ‘posts.*, count(comments.id) as comments_count’,
:joins => ‘LEFT JOIN comments ON posts.id = comments.post_id’,
:group => ‘posts.id’