Friday, June 26, 2009

Tracking value changes in ActiveRecord objects

Info about getting to old / new values on activerecord objects:

http://ryandaigle.com/articles/2008/3/31/what-s-new-in-edge-rails-dirty-objects

This can be useful if validation varies based upon the prior state of the the object, etc.

Rails Time Zone Support

A good summary of how to support users in different time zones:

http://mad.ly/2008/04/09/rails-21-time-zone-support-an-overview/

Thursday, June 11, 2009

Ext JS with Rails

Trying to get ExtJS working with Rails...

One of the first steps described in the Ext tutorials is just getting a page set up referencing the javascript libraries: http://extjs.com/learn/Tutorial:HTML_Page_Setup

Unfortunately, that example didn't work in rails. However, taking some suggestions from here, putting this in the head of a rails layout seems to work:

<%= stylesheet_link_tag '../javascripts/ext/resources/css/ext-all' %>
<%= javascript_include_tag 'ext/adapter/prototype/prototype.js' %>
<%= javascript_include_tag 'ext/adapter/prototype/effects.js' %>
<%= javascript_include_tag 'ext/adapter/prototype/ext-prototype-adapter.js' %>
<%= javascript_include_tag 'ext/adapter/ext/ext-base.js' %>
<%= javascript_include_tag 'ext/ext-core.js' %>
<%= javascript_include_tag 'ext/ext-all.js' %>

script type="text/javascript" (took off the <>'s from this line for posting)
Ext.BLANK_IMAGE_URL = "../javascripts/ext/resources/images/default/s.gif";
Ext.onReady(function() {
console.info('finally!');
});
/script (took off the <>'s from this line too)

This is basically what's in their tutorial html page, but with extra include tags... It assumes that, as they suggest, you unzip extjs under the rails public/javascripts directory. For the example above, the directory was just named 'ext'.

With that in place (the onReady can be removed), a page using that layout can do something like this:

<%= link_to_function 'Ext Test',
"Ext.MessageBox.show({title: 'Ext Box',
msg: 'Ok?',
buttons: Ext.MessageBox.OK})" %>


(Note: this was Rails 2.2.2 with ExtJS 2.2.1)

Monday, May 4, 2009

More on Saving Dates to Oracle

I did some digging into rails after my last post (but before I saw the comment from Raimonds, so I still need to check that out -- thanks Raimonds!), and found that the setter method generated by rails is doing the timezone adjustment, turning the Date into a TimeWithZone (see the define_write_method_for_time_zone_conversion method in active_record/attribute_methods.rb).

However, in the process of determining which setter method to use, it looks at the skip_time_zone_conversion_for_attributes list, which controls whether to do the timezone conversion or just generate the standard setter method for the attribute. You can just add a line to your active record class, like this:

skip_time_zone_conversion_for_attributes << :my_date_attribute_name

With that line added, the date remains a date, and everything in the persistence layer is happy.

As I mentioned, I haven't tried it yet, but the new oracle enhanced adapter looks like the way to go. I thought I'd post this anyway, in case it's of use to anyone else -- there doesn't seem to be much written about "skip time zone conversion", and it might be useful in other situations...

Friday, May 1, 2009

Saving Dates to Oracle with JRuby / Rails

When you create date or datetime attributes in rails, both types get mapped to date columns in Oracle. The problem I was running into was trying to save date values (without time values).

Date attributes that were set in a view worked fine, but those that were set programmatically were getting saved with the time -- the time being offset from midnight based upon my timezone. I messed with the timezone setting ("config.timezone =" in environment.rb), but everything seemed to be working fine except this. It works if you set the attribute to a string value (i.e., '2009-05-01'), like the view / controller code does, but that's a pain.

So, digging through the activerecord extensions in jdbc_oracle.rb, I found that it tries to guess the type based upon whether there are values for hours, minutes, or seconds in the ActiveSupport::TimeWithZone objects that get sent through. The ones coming from the view had no hours, minutes, nor seconds, but the ones that had been set in the code (originally set to Date.today, for example) were getting converted into TimeWithZone objects that were midnight on the specified date, then adjusted for the timezone, turning the date part into the prior date. (I didn't dig through the rails code to find out where/how/why that conversion is taking place, but that might yield a better solution -- if any body knows, please share!) Since rails uses TimeWithZone objects, that seemed like a reasonable place to look.

Looking at the doc for TimeWithZone, it says that you shouldn't ever create instances directly, you should use TimeZone methods instead, via the Time.zone instance. The TimeZone methods include "today", "local", "parse" etc -- nice! So I tried each of those:
  • a = Time.zone.today
  • b = Time.zone.local(2009, 5, 1)
  • c = Time.zone.parse('2009-05-01')
Well, "b" and "c" worked -- they got saved to the database as dates -- but "a" acted the same as Date.today. It turns out that's exactly what Time.zone.today returns: a Date instance!?! I'm not sure why it works that way, but...

So, until I have time to dig through rails to see if there's a way to avoid the time zone conversion (besides setting the attribute to a string), I'll start using the TimeZone methods, with this little patch stuck in the config/initializers/ directory:

class ActiveSupport::TimeZone
def today
today = Date.today
self.local(today.year, today.month, today.day)
end
end

Again, if anyone has another suggestion, please share. Also, I'm not sure if this is JRuby-specific, but I don't have time to test that right now either...

(Note: running rails 2.2.2 w/ jruby 1.1.5 and activerecord-jdbc-adapter 0.9)

Tuesday, April 21, 2009

Rails 2 Functional Tests with HTTP Basic Authentication


I set up http basic authentication, as a quick way to show user-specific data for a demo I'm going to be doing.  Of course this broke my functional tests.

I found a solution here (thanks Flip!) -- manually adding an entry to session.env.  However, before I found that, I was digging through some of the rails code, and found a way to create the encoded credentials, which eliminates the string concatenation of Flip's solution (and may be a bit more robust going forward, in case the encoding changes in the future):

    @request.env['HTTP_AUTHORIZATION'] = 
         ActionController::HttpAuthentication::
             Basic.encode_credentials('username', 'password')

     (sorry about the line breaks -- tried to make it readable 
      without wrapping)

If anyone comes up with a more elegant solution, please let me know!

Wednesday, March 25, 2009

Warbler deployment to development environment


Warbler supports passing the environment on the command line, so if you do:

   warble war RAILS_ENV=development

and deploy the .war, it will use the development database connection, etc.

Wednesday, March 11, 2009

Multiple Gem Repositories

Found an easier way:  the -i option for gem install creates a new repository.  Just install a gem into an empty directory, and it creates a new repository (for example:  gem install rake -i new_repository) .  Just point GEM_HOME and/or GEM_PATH to the new repository, add the bin subdirectory to your PATH, and you're good to go.

Thursday, February 26, 2009

HowToUseMultipleGemRepositories -- Rescued!


Note:  My intent here is not to plagarize, only to save lost content.  This information was posted on the rails wiki, but it's gone!  It was here: 


It looks like the whole wiki has been redone, so if anyone knows where this info has been reposted, please let me know.  I did a quick search, but didn't find it, and don't have time right now to hunt...    This is great information, and it shouldn't be lost, so I'm grabbing a cached copy, and putting it here.  So, with thanks to those who created the content, here it is:

-------------------------------------------------------------------------------

Ruby on Rails
HowToUseMultipleGemRepositories

The gem utility is a handy way to manage software components in Ruby. When using Rails, though, you can sometimes find yourself in a position where gems might start to look like a handicap!

  • Perhaps you are installing a Rails application on a managed host account and it needs a gem which the system administrator does not support.
  • Perhaps you’re a system administrator and you want to allow individual users to add their own gems, without polluting the global collection you provide centrally.
  • Perhaps you just want individual Rails applications to have their individual requirements kept separate from each other to avoid Ruby’s own equivalent of DLL Hell…!

As it happens, it’s entirely possible to support more than one physical gem repository. The key to this is the GEM_PATH environment variable. While the GEM_HOME environment variable is relatively well known, not so many people have heard of GEM_PATH. Below I’ll describe how to use this to configure and initialise a new gem repository, then logically, but not physically combine that new collection with other existing repositories which Ruby code will treat as one large, combined set of available gems.

1. Decide on locations

1.1. A new gem installation

You’ll need to install a new copy of the gem utility, at least temporarily. Create an empty ‘geminstall’ directory to hold the gem installation itself – e.g.:

mkdir /home/username/ruby/geminstall

1.2. Location of the new repository

Create an empty directory to hold the gem repository (that is, the location where any gems you install will actually live) and set GEM_HOME to this location – e.g. assuming a bash shell:

mkdir /home/username/ruby/gems export GEM_HOME=/home/username/ruby/gems

2. Initialise the new repository

2.1. Get the gem installation package

Download the gem installation package from:

http://rubyforge.org/frs/?group_id=126

At the time of writing 0.9.0 is the latest version; using wget you might do this:

wget http://rubyforge.org/frs/download.php/11289/rubygems-0.9.0.tgz

2.2. Unpack into a temporary directory

Create a directory for temporary use and unpack the archive into it. For example:

mkdir /home/username/temp mv rubygems-0.9.0.tgz /home/username/temp cd /home/username/temp tar -z -x -f rubygems-0.9.0.tgz

2.3. Look at the unpacked contents

Change into the unpacked directory so you can see the CHANGESREADME etc. files. E.g.:

cd rubygems-0.9.0 ls

2.4. Ask the setup program to install everything, make sureGEM_HOME is set to the gems directory from step 1.2

Issue the following command:

ruby setup.rb all --prefix=/home/username/ruby/geminstall

…replacing /home/username/ruby/geminstall with the path to the ‘geminstall’ directory that you created in step 1.1 (and not the one you created in step 1.2!). The command should finish up by saying something like Successfully built Rubygem, with a bit more information on what it built afterwards. If not, look for error messages and see if you can figure out what went wrong (e.g. perhaps there were no write permission in the gem installation or repository directories, or maybe you have an incorrectly set GEM_HOME – double-check it with echo $GEM_HOME).

NOTE: If you already have rubygems installed, make sure the version you are installing matches the version you have installed (or upgrade your existing rubygems first). Otherwise you may run into strange errors due to mismatched files.

2.5. Make sure it worked!

You should find new directories along the lines of bin and lib in the directory you created in step 1.1. You should also find new directories along the lines of cachedocgems andspecifications in the directory you created in step 1.2. This is your new gem repository, now initialised and ready for further use.

NOTE: If you receive the message “/gem:9:in `require’: no such file to load — rubygems”, you will have to set the RUBYLIB environment variable to point to the ‘lib’ directory inside your gem install directory, like so: export RUBYLIB=/home/username/ruby/geminstall/lib

2.6. Don’t already have gem installed?

If you don’t already have gem installed on your system you will want to add the path to the directory specified in step 1.1 with /bin on the end to your PATH environment variable. E.g.:

export PATH=$PATH:/home/username/ruby/geminstall/bin

2.7. Do already have gem installed?

If you do already have a copy of gem on the system, now would be a good time to delete the ‘geminstall’ directory you created in step 1.1, along with its contents. You don’t need the second copy of the gem package, you only need the ‘gems’ repository directory. So:

rm -rf /home/username/ruby/geminstall

…taking care to provide the full path to the location of the unwanted new gem installation (geminstall) from step 1.1, not the path to the new gems repository from step 1.2.

3. Examining the repository contents

3.1. What do the GEM_... variables do?

While environment variable GEM_HOME controls where gem will write things, variable GEM_PATHcontrols from where gem and requirements in Ruby scripts will read.

3.2. Querying the default repository

If you issue the command gem query with GEM_PATH unset you’ll see the list of gems installed in your system default repository, if you have one, not your new repository.

3.3. Querying your new repository

Set GEM_PATH to the ‘gems’ directory you created in step 1.2 then query the repository again:

export GEM_PATH=/home/username/ruby/gems gem query

This time you should just now see one gem – the sources installed as part of repository initialisation in step 2.4.

3.4. GEM_PATH is the key when reading

So, GEM_PATH is the key to supporting multiple repositories. To treat multiple physical repositories as one big logical gem collection, set the variable to a colon-separated (semi-colon on windows) list of repository locations. For example, if you have a system-wide default repository at/lib/ruby/gems/1.8 you can get Ruby to search your new repository first, then the system one, using:

export GEM_PATH=/home/username/ruby/gems:/lib/ruby/gems/1.8

If you have two or more of the same gems with the same version, the one in whichever repository appears first in the path will be used. It shouldn’t ever matter, though, as if they’ve the same version number they ought to be identical anyway.

3.5. PATH to gem binaries

You need to add the new gem repositories bin sub-folder to the PATH environment variable in front of the default gems repository bin path otherwise any new gems installed in this repository will not have their bin wrappers in the path and you will not be able to run them as you normally would from the command line. For example, if you installed a gem like piston in this new repository a small shell script or batch file will be put in the repositories bin folder so that you can launch the piston script by simply typing ‘piston’, but the shell script or batch file will not be found if the bin folder is not in the path.

4. Modifying the new repository’s contents

4.1. GEM_HOME for writing, GEM_PATH for reading!

Always ensure that environment variable GEM_HOME points to the
repository you are interested in before running the gem command to modify a repository, or any other commands that modify the contents of a repository. This is very important! If the variable is unset, you’ll operate on the system’s default gem repository.

4.2. Business as usual

Once you’ve set GEM_HOME to point to the right place, just use gem in the usual fashion to delete or add new gems – see the RubyGems Manuals for more information.

If you want to make a quick repository change and just temporarily set the location, gem supports various command line switches to help. For example:

gem install [gem-name] --install-dir=/path/to/repository

…will do what the command implies. Depending on how you work this might be a better method than setting the environment variable. However, if you take the environment variable approach, there’s no danger of forgetting to specify the repository location on the command line and ending up accidentally attempting an operation in the wrong place. Again, see the RubyGems Manuals for more information on command line parameters.

5. Using the repositories in future

You will need to make sure GEM_PATH is set up wherever you want Ruby programs, including Rails applications, to use multiple gem repositories. The best place to do this will vary depending on your system configuration. Use, say, your .bashrc file or the Web server configuration file for Rails applications if it supports launching them with a configured private set of environment variables.

That’s it – all done. Remember to set GEM_HOME when adding or removing items; remember to setGEM_PATH when reading from one or more repositories. Now you can keep your gem collections compartmentalised if you wish, provide individual repositories for individual users, or if you’re on a rather limited managed hosting service, have a good chance of being able to add your own gems to a private collection without needing to go to the system administrator.

Note

If you are attempting to do a local-only install of multiple dependent gems, you need to cd to the directory containing the gems.



Tuesday, February 24, 2009

Linux Search


For example, to find "search_string" in all ruby files in your project:

   find . -iname *.rb -exec grep -l search_string {} \;

Anyone know of an easier way to do this?

Friday, February 6, 2009

Jruby Ubuntu TimeZone Problem

I'm moving to a new linux box, and ran into a problem. Unit tests on a JRuby project were failing. I traced the problem to the wrong time being returned on the new box. DateTime.now was returning GMT in jruby. Ruby returned the correct time, as did "new Date()" in java. On the old linux box, all three of those returned consistent results, reporting the local time.

Both boxes are running Ubuntu Intrepid Ibex (8.10). On both machines, the jruby app was set up in its own account, with its own copy of java and jruby. The same versions of java and jruby were used; in fact, the downloads (jdk-6u10-linux-x64.bin and jruby-bin-1.1.5.tar) were copied from the old machine to the new.

The only difference I found was that the new machine (which I inherited, but is faster) has java 1.6.0_0 installed in /usr/bin. However, the account set up for the jruby app has JAVA_HOME and the PATH set so that java 1.6.0_10 is being used on both machines.

Thanks to Vladimir Sizikov (and others), I found a solution. This bug report started me on the right path, and this one almost gave me the fix. On the second one, Vladimir said "Once I replace /etc/localtime with proper America/New_York file, everything gets back to normal", so I copied the /etc/localtime file from the old box to the new. Still the same problem...

In another one of Vladimir's posts, he suggested this code, which shows the problem:
  • jruby -rjava -e "p org.joda.time.DateTimeZone.getDefault.getID; p java.util.TimeZone.getDefault.getID"
Running that gave me "UTC" "SystemV/CST6CDT" on the new machine, and "America/Chicago" "US/Central" on the old machine.

After more hunting (using the SystemV timezone clue), I found this post, which suggested "change /etc/localtime to a symbolic link again", so I got rid of the localtime file and created a link to /usr/share/zoneinfo/CST6CDT, and that worked! However, it really wasn't the link that made the difference -- I copied the CST6CDT file from zoneinfo instead of linking to it, and that also worked.

So, since the localtime file from the old machine works there, but doesn't work on the new machine, and using the localtime file from zoneinfo does work on the new machine, there's still something else going on that I don't understand. But since it's working...

Tuesday, January 27, 2009

Vim Settings for Ruby

syntax on " Enable syntax highlighting
filetype on " Enable filetype detection
filetype indent on " Enable filetype-specific indenting
filetype plugin on " Enable filetype-specific plugins
set ic " Case insensitive search
set hls " Highlight search
set showmatch " Show matching brackets
set expandtab
set autoindent
set tabstop=2
set shiftwidth=2
set softtabstop=2

Sunday, January 25, 2009

Rails on Ubuntu Intrepid Ibex


Got Rails working with a local copy of ruby, gems, and rails, so I can have independent accounts with different versions.  I haven't gone back to a clean ubuntu install to verify, and I'm not sure that all these are needed, but I think these are the required packages:
  • zlib1g-dev
  • libzlib-ruby
  • libssl0.9.8
  • libssl-dev
  • libopenssl-ruby
After building ruby, from the top directory in the source:
  • cd ext/openssl
  • ruby extconf.rb
  • make
  • make install
Without that last set of steps, I was getting a "no such file to load -- openssl" error when trying to start rails.

After that, install rubygems (download and extract the source, then "ruby setup.rb") and install the rails gem.

References (with thanks to the contributors):

Wednesday, January 14, 2009

Ubuntu Package List: Available vs. Installed

apt-cache pkgnames : lists all available packages
dpkg -l : lists all installed packages (Can apt-cache do it?)

Also useful:

apt-get update : updates the local list of available packages
apt-cache search : lists all available packages matching


Reference: http://www.debian.org/doc/manuals/apt-howto/index.en.html

Tuesday, January 13, 2009

Ruby Gem Repository Setup

To have a linux account with an independent ruby gem repository, follow this basic process.  (Note the part about setting the RUBYLIB environment variable.)

MD5 Checksums (Ubuntu)

Create a new file containing the expected checksum, followed by 2 spaces, then the name of the file to be checked. The checksum file can contain multiple lines (multiple md5/filename pairs).

Then the md5 command is:

md5sum -c checksum_file

Monday, January 12, 2009

Building Ruby

To build ruby (an old version, for example) for a specific linux account:
  • download the source
  • tar xvzf ruby-1.8.5.tar.gz
  • cd ruby-1.8.5
  • ./configure --prefix=$HOME/ruby
  • make
  • make install

Thursday, January 1, 2009

Synergy: One keyboard to rule them all...


Moved my linux box / monitor next to my windows box, and set up Synergy (http://synergy2.sourceforge.net) so I only need a single keyboard and mouse.

The install went fine on both machines, including the quicksynergy install on the linux box, but ran into a problem connecting.

With the windows machine in running a Synergy test in server mode, with Logging Level set to Debug1, these messages appear when the mouse got to the right side of the screen (where it should have gone to the linux box):

DEBUG1: try to leave "" on right
DEBUG1: no neighbor right

With the windows machine in running a Synergy test in client mode, this was the error:

ERROR: failed to connect to server: The attempt to connect was forcefully rejected

On the "Using Synergy" page (http://synergy2.sourceforge.net/running.html), under the "Step 5 - Test" section, it has a list of common errors, including the "forcefully rejected" error, which correctly pointed to a firewall issue.  With the firewall off, it worked fine.

To take care of it, added a firewall exception:  
  • Settings > Windows Firewall > Advanced (tab)
  • Select connection, hit Settings button
  • On that window, hit Add...
  • Give it a name, add the IP for the linux box, and put in the synergy port (24800)