Sub directories for STI models in Rails
I recently started using Single Table Interitance in my Rails application and immediately wanted to group all the child models in a sub directory inside the models directory.
All over the web, the advice was just create a directory, stick your models in there and then add on of the following into environment.rb:
config.load_paths += Dir["#{RAILS_ROOT}/app/models/**/**]
# or
config.load_paths += Dir["#{RAILS_ROOT}/app/models/mysubdir"]
Well, this doesn’t seem to work, as of Rails 2.3.2 anyway (no idea about earlier versions).
It seems that Rails scans the models directory, and when it finds subdirectories in it, it loads the classes into a namespace that is the subdirectory name. If you have a subdirectory called Actions, your classes will end up loaded as Actions::SubClass, which is not want I wanted.
To get this working, I had to put the extra load path I wanted at the front of the load path array (at least before it loads the models directory):
config.load_paths.unshift "#{RAILS_ROOT}/app/models/**/**"
# or
config.load_paths.unshift "#{RAILS_ROOT}/app/models/mysubdir"
I am not really happy with this approach, but I have already wasted hours trying to get it working at all, so the hack will have to do for now!
Add comment April 10, 2009
Code Comments are not for tracking changes
Recently, I have been reviewing and correcting a lot of broken code, and in doing so, I have concluded that the number of developers who know how to properly use a bug tracker and version control is not nearly high enough (disclaimer – this is in a big faceless corporation).
Commenting Anti-Pattern
So what leads me to this bold conclusion? Have you ever seen code that looks like this:
begin
-- start change for #3245
v_myvar varchar2(100)
-- end change for #3245
for r in (select c1,
/* start change for #9870 */
c2,
c3
/* end change for #9870 */
from foo_table
where c1 in (1, 4, 6)
-- commented out for #9870
-- and c2 = 'val1'
-- start change for #5678
and c3 = 'val2'
-- end change for #5678
) loop
null;
end loop;
end;
Ignore the fact that this code does nothing useful, and is PLSQL – its just there to illustrate a point.
The amount of code I have seen recently that has its changes tracked through inline comments that attempt to encapsulate every single line changed to fix a bug or add a feature is unbelievable.
Some may ask, what is the big deal? Well firstly, when I see code like this, it says to me, the developer who did this, doesn’t have any idea about version control – they are commenting every change to attempt to provide traceability and rollback ability to their change. Often, when I see this anti-pattern, I subsequently find the code hasn’t seen the inside of Subversion or anything like it.
Show me the code
If I ever dare to ask to see the changes that were made to fix a bug in code like this, I get handed a source code file and told to grep through it for the bug number – what scares me, is how do I know he remember to comment every single change? For a big change, it takes a lot of patience to do something like that! And then the Delivery Manager (DM) arrives at the developers desk and the conversation goes like this:
DM: Remember those 10 feature requests we agreed to deliver tomorrow, well how are they going?
Dev: No problems, we have them all finished and tested, ready to roll!
DM: Well, we have a problem – turns out the front end developers have not been able to complete feature #4567 and that impacts us – we need to deliver the other 9 changes, but not that one.
Dev: Huum, thats going to be a problem, #4567 was a big change – I don’t think we can get it out of there without a lot of effort!
DM: Don’t you have all this tracked in your version control system … back in my day we committed individual changes into SCCS and things like this were never a problem!
Dev: Gulp … guess its going to be a long night for me then sir …
Finally, don’t even get me started on how much harder the code is to read with all these annoying ’starting/ending’ change comments.
A simple recipe
If you come across this rant, and have found yourself doing this, then stop a moment to think about how to use version control to track these changes for you – thats what is was designed for!
Here is a tip – when you have to fix a bug or add a feature to existing code:
1. Checkout your code
2. Make changes to whatever files are necessary and forget about pointless start/end change comments.
3. Get your test suite to pass your bug/feature (you have a test suite, right?)
4. Check in you code when its all done, quoting the bug number in the commit comment
Simple, clean code changes, traceable in a much more robust way and you can safely roll back the changes to any bug at any time.
The biggest mistake I see developers making with version control, aside from not using it at all, is to fix about 10 bugs, and then do a commit – please don’t do this, as it removes the ability to examime the changes made to fix a single bug, not to mention removing the changes if the need arises – which happens more than you may think, as our poor Dev found out above!
2 comments September 16, 2008
sqlite3-ruby-1.2.2 is broken on OS X
So I just spent ages trying to figure out why the latest version of sqlite3-ruby wouldn’t run on OS X. Turns out there is a bug …
If you are getting an error that looks like:
dyld: NSLinkModule() error
dyld: Symbol not found: _RSTRING_PTR
Referenced from: /usr/local/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.2.2/lib/sqlite3_api.bundle
Expected in: flat namespace
Trace/BPT trap
Its best to revert back to 1.2.1 which seems to work fine. This behavior occurs on Tiger and Ruby 1.8.4.
Add comment August 14, 2008
Four things hotel 1.0 and web app 1.0 have in common
For the past week I have been staying in a very nice hotel, which I just learned opened only a month ago. Today it dawned on me, that opening a new hotel is in many ways a lot like getting version 1.0 of a web application launched.
Hotel 1.0 does not need all the features
Both a hotel and a software product require a large amount of investment to get to the launch date. When developing version 1.0, there is a real temptation to keep adding features that you think are essential, continuously delaying the launch. Guess what – its perfectly fine to get the place opened with only the indoor pool done while the outdoor one is not finished, or when the first restaurant is ready but the second one is not. I can imagine the hotel owner stressing out about all the things he thinks are essential, but to be honest, the guests are not going to care so long as the rough edges are kept out of sight and what is there does its job well.
Just like with software, getting the doors open at the earliest opportunity allows feedback to be gathered from guests, and more importantly start at least a small amount of money coming in!
Hotel 1.0 does not need every to be feature perfect
OK, the rooms need to be perfect as does the food and restaurant – that is what the guests came for (to sleep and probably to eat) – in other words the core features need to be perfect.
However it doesn’t matter that much if the heating doesn’t work in the indoor pool yet, so long as its still usable. Same goes for software – the user experience of the main part of the application had better be spot on, but I can live with some discomfort in the data exporter or preferences pane … for now!
Hotel 1.0 can really impress the early guests
When the hotel opens, it will probably not be booked out on week 1, 2 or even 3. This is a special chance to give exceptional service to the first guests. If the hotel treats the guests really well, responds to any problems quickly and explains the reasons behind the unfinished pool and restaurant they will probably leave happy and evangelise the place to friends and colleagues.
The same goes for new software products. The first customers are the most important – so treat them like kings and they will tell others.
Hotel 1.0 will have many of the best rooms empty
Related to point 3 – when the hotel first opens, many of the more expensive rooms will remain empty. Why not upgrade some guests into these rooms? In doing this, the guests will probably be less annoyed by the unfinished features and they will have a positive story to tell colleagues and friends.
In new web application the same thing goes – bandwidth and servers are not going to be taxed for the first while (unless you hit the Facebook jackpot) so why not give early users the top package for the first few months in the hope they there when the trial period ends?
Honesty is the best Policy
One final important thing – don’t advertise the hotel as having a heated indoor and outdoor pool if only the indoor pool works, and it’s cold – guests will just feel insulted when they find out and that will be what sticks in their mind. Honest usually pays dividends if everything else is good.
If, for any reason you happen to be travelling to Hyderabad on business, version 1.0 of the Ella Compass Suites hotel is highly recommended!
Add comment November 18, 2007
Setting up a Sun Enterprise 250 (E250)
Recently I needed to install Solaris 10 on an old creaky Sun Enterprise 250 (E250) and 450 (E450) box. This quick how-to documents what I did to get the machine up and running, and answers a few of the questions it took me a while to figure out along the way! Most of it is pretty applicable to a modern Sun too.
Getting to ok
The first thing you have to do is get to the OK prompt. On most boxes I have used in the past, getting to ok is easy, just power on the machine and it stops at the ok prompt, waiting for someone to type boot, however this machine was different – it just booted straight away, without an ok in sight!
To get the ok prompt, wait for the system to initialise the memory and devices and either press STOP-A (if you are using a VVT220, it will probably have a STOP key) or ctrl-break repeatedly.
Now that you have got the OK prompt, you probably want to stop the machine auto-booting until you have it working the way you want it. To stop auto-booting, enter the following commands at the ok prompt:
ok setenv auto-boot-on-error? false
ok setenv auto-boot? false
Now power off the machine and let it restart (apparently you need to do this, as STOP-a or ctrl-break stops the machine initialising some things, so its best to restart to be sure you don’t hit problems later).
Installing Solaris 10
Installing Solaris 10 is pretty simple at this point – pop the DVD in the drive and enter:
ok boot cdrom
The installer will kick off, asking you a bunch of questions along the way – make sure and have your hostname, domain, network and DNS settings to hand.
Before the OS actually installs, you need to layout your disk. How you set this up is totally up to you – I usually create a parition for each of /, /usr, /export/home, /opt, /tmp and /var/crash on the primary disk. Sizes will very much depend on your setup and requirements.
Getting Rid of the Desktop and Login Server
By default, Solaris 10 attempts to run the desktop environment on boot. If, like me, you want to use this machine as a server and have no need for a desktop, then save yourself some RAM and disable it:
# svcadm disable svc:/application/graphical-login/cde-login:default
# /usr/dt/bin/dtconfig -d
(Thanks to this site for that tip!)
ZFS Rocks
I knew in the back of my mind that Solaris 10 had a fancy new filesystem called ZFS, but I didn’t know what that meant until I came across this article. If you have a sever with more than one disk, or you are not sure what sizes each of your mountpoints should be, or you need striping, mirroring or RAID, then go read up on ZFS. It just might make disk management as pleasurable as writing Ruby!
Add comment October 8, 2007
ActiveRecord, Oracle and stored procedures
After much painful searching for a built in way to get ActiveRecord to execute a stored procedure on Oracle complete with in and out bind variables I have concluded that its just not possible.
The Oracle connection adapter uses OCI8 under the covers to connect to Oracle and many of its methods are exposed to you. To execute a stored procedure with OCI 8, one method is like this:
require 'oci8'
conn = OCI8.new(user, password, db.world)
res = conn.exec('begin my_proc(:in,
ut); end;', 'invalue', 'outvalue')
That’s pretty simple, I guess. To do such magical things with ActiveRecord, a method ‘execute’ is exposed, this looks like this:
def execute(sql, name = nil) #:nodoc:
log(sql, name) { @connection.exec sql }
end
The method spec for OCI8#exec is however
def exec(sql, *args, &blk)
...
end
So ActiveRecord gives us a wrapper to exec, but sadly removes the essential bind variables.
In my application I added the following code:
class ActiveRecord::ConnectionAdapters::OracleAdapter
def _exec(stm, *args)
@connection.exec(stm, *args)
end
def _cursor(stm)
@connection.parse(stm)
end
end
Which lets me execute Oracle stored procedures using my ActiveRecord connection at last!
My application is not a Rails application as such, more an application that uses ActiveRecord, so I put the above code in my boot script after including ActiveRecord – not sure exactly where you would put it in a Rails app.
If there is a better built in way to do this, please comment and let me know as I have not been able to find it!
UPDATE – it seems there are a few other struggling to get this stuff to work – I have added a chunk of code below that makes this stuff work:
require "rubygems"
require_gem "activerecord"
ActiveRecord::Base.establish_connection(:adapter => "oracle",
:database => "avdev7",
:username => "sodonnel2",
:password => "sodonnel2")
class ActiveRecord::ConnectionAdapters::OracleAdapter
def _exec(stm, *args)
@connection.exec(stm, *args)
end
def _cursor(stm)
@connection.parse(stm)
end
end
# create or replace procedure testproc(inxml in varchar2, outxml out varchar2)
# is
# begin
# outxml := inxml;
# end;
# /
class PLSQL < ActiveRecord::Base
attr_accessor
utxml
def initialize
execute
end
def execute
cursor = connection._cursor("BEGIN testproc(:inxml,
utxml); end;");
xmltext = 'some text to print'
cursor.bind_param("inxml", xmltext)
cursor.bind_param("outxml", nil, String, 1000)
cursor.exec
@outxml = cursor['outxml']
cursor.close
end
end
obj = PLSQL.new
puts "The returned value is : #{obj.outxml}"
4 comments August 29, 2007
Of course it’s the Database, I don’t need a Profiler
We all know that when any database backed application starts to behave slowly, its always the databases fault, right? After all, if the application code hasn’t change so what else could it be?
If you have spent much time around Oracle, you are sure to have seen complex queries when Oracle decides to changes its query plan for “no good reason” and application performance changes drastically. Perhaps this is what leads to all this finger pointing at the database, mostly by people who don’t understand how it works.
Last week I was contacted to investigate a performance problem in an application that had everyone baffled. The system works off 15 – 20 queues of requests, and a batch job is responsible for processing each queue, so there are up to 20 batch jobs running at once. Normal throughput for a batch job is several requests per second, but this had dropped to several a minute and things were backing up.
Lost Time
The DBAs had provided me with a level 1 trace file captured during this slowdown that looked very normal – no bad queries in sight. Only strange thing was a lot of walk clock time in the trace compared to CPU seconds (5 or 6 times more elapsed time). My initial reaction was certain there was a locking issue somewhere in the database, so I asked the DBAs to create a level 8 trace file that would surely should show me where the all this “lost time” had gone.
No such luck, Oracle was not waiting on anything, so where had all this time gone?
Perhaps the database server was under heavy load you say? Not so, this was a 24 core server 50% idle through the test, humm.
Luckily, I had read Optimising Oracle Performance and actually understand much of the gibberish inside an SQL Trace file. Still mystified where all the time had gone, I fired up Emacs and used Ruby to tell me the biggest time between two database calls, which stood at an impressive 30 seconds between a fetch on cursor 18 and exec on 19 – ie the “lost time” was in the application somewhere between those two queries, not the database calls.
In this case its a bit greyer, as the application is written in PLSQL, but the problem turned out to be the OS struggling to open log files in a directory containing over 2 million files!
Power to the Profiler
The morale of this story, apart from the need to housekeep your log files, is that its important to have the ability to profile your application code as well as the database calls.
Instead of writing scripts to parse Oracle trace files, I should have ran the code through DBMS_Profiler which would have solved this mystery in about two minutes and saved much head scratching and other people making ridiculous suggestions to change irrelevant database parameters!
Add comment July 28, 2007
Emacs and Oracle
After learning so much about Emacs to get my Rails setup working the way I wanted it, I though it was about time I figured out how to use SQLPLUS in Emacs too. Turns out it was really easy, as sql-mode is built right in, no .emacs changes required or extra files to download.
To connect to SQLPLUS in an emacs buffer, fire up emacs and type ALT-x sql-oracle. Emacs will prompt you for a username and password and a database to connect to. The database name will need to be in your local tnsnames.ora – in other words, if you cannot sqlplus username/password@database, emacs will not be able to connect either.
You can now enter commands into SQLPLUS just like normal, only inside Emacs. The thing that always frustrated me about SQLPLUS is that there is no command history recall, so I always found myself writing a query in an editor, and copying and pasting into SQLPLUS. Not anymore … thanks to Emacs.
When you enter sql-oracle mode and login, Emacs splits your window in two. You can then edit your SQL in the original window and send the query to SQLPLUS in the other buffer in (at least) one of two ways:
- Send the entire buffer by typing ALT-x sql-send-buffer
- Select a region to send by typing ALT-x sql-send-region, or if you buffer is in sql-mode, type CTRL-C CTRL-r
You can of course bind a key sequence to each of these commands to save on the typing if you use them a lot!
If you have an SQLPLUS buffer open for a while, it could get very large. To truncate it, use the command ALT-x comint-truncate-buffer, or add the following to your .emacs:
(add-hook 'sql-interactive-mode-hook
(function (lambda ()
(setq comint-output-filter-functions 'comint-truncate-buffer
comint-buffer-maximum-size 5000
comint-scroll-show-maximum-output t
comint-input-ring-size 500))))
Which will keep your buffer under 5000 lines.
2 comments July 10, 2007