Ok, so this post is a bit earlier than advertised, but it’s better than being late! Following up from Rails on Oracle: Part 1 – Installing the oracle instant client on Mac OS X, now we are going to get rails to use our freshly installed oracle client…

Step 1: Download ruby-oci8

Download the latest tarball from the ruby-oci8 rubyforge page to your ~/Downloads directory. At the time of writing the latest is version 1.0.5.

Step 2: Make and install ruby-oci8

If you’ve followed the instructions from part 1 then you should have your oracle instant client installed in /opt/oracle/instantclient. If you’ve not installed the instant client yet then scoot off to part 1 and perform those steps before going any further. If you’ve installed it somewhere different then you will need to amend the following commands where appropriate:

1
2
3
4
5
6
7
cd ~/Downloads
tar xvzf ruby-oci8-1.0.5.tar.gz

cd ruby-oci8-1.0.5
ruby setup.rb config -- --with-instant-client=/opt/oracle/instantclient
make
sudo make install

Step 3: Install the oracle_enhanced adapter

1
sudo gem install activerecord-oracle_enhanced-adapter

Step 4: Configure you rails project to use an oracle database

Lets say with have the same oracle database as we had in part 1 which looked like so:

  • Host : myserver
  • domain : mydomain.com
  • Port : 1521
  • SID : mydatabase
  • Service : mydatabase.mydomain.com

We’d express this in our database.yml file with the following entry:

1
2
3
4
5
development:
  adapter: oracle_enhanced
  database: myserver:1521/mydatabase.mydomain.com
  username: myusername
  password: mypassword

Step 5: Profit!

Now (fingers crossed!) you should be ready. It’s been tricky for me to test this thoroughly because I don’t have access to a clean mac to start from scratch with. My mac is already set up and ready to go so it’s possible I could have missed something. If you have problems then leave a comment and I’ll try my best to sort you out and and update the instructions.

Looking for part 1?

Part 1 : Instructions on how to install the Oracle instant client on OS X and test it with sqlplus.

These instructions were performed using version 10.5.6 of OS X and the 10.1.03 Oracle instant client.

Update: I’ve just performed these steps with version 11.2.0.3.0 (64-bit) of the instant client and it worked without a hitch.

Step 1: Download the instant client from Oracle

We’re going to use the instant client because this is by far the easiest way to get up and running. Traditionally oracle clients have been quite heavy weight but the instant client is quick to download and easy to install.

Go to the appropriate download page for your CPU:

There are several download options. Download the following to your ~/Downloads directory:

  • Instant Client Package – Basic
  • Instant Client Package – SQL*Plus
  • Instant Client Package – SDK

Step 2: Install the files on your machine

I usually install these files in the /opt/oracle/instantclient directory. Assuming you’ve downloaded the zip files to your ~/Downloads directory we need to make a new home for our instant client and unzip the files there….

1
2
3
4
5
6
7
8
9
mkdir -p /opt/oracle/
cd /opt/oracle

mv ~/Downloads/instantclient*.zip .
unzip instantclient-basic-macosx-10.2.0.4.0.zip
unzip instantclient-sdk-macosx-10.2.0.4.0.zip
unzip instantclient-sqlplus-macosx-10.2.0.4.0.zip

mv instantclient_10_2 instantclient

Obviously if you have a slightly different version of the client you are going to need to amend those paths slightly.

Step 3: Create some symbolic links for the libraries

1
2
3
cd instantclient
ln -s libclntsh.dylib.10.1 libclntsh.dylib
ln -s libocci.dylib.10.1 libocci.dylib

Step 4: Set your ORACLE_HOME, NLS_LANG and DYLD_LIBRARY_PATH variables

You could do this in a couple of ways. Because I’m the only user of my machine I like to do this in my personal ~/.bash_profile. If you share the machine with someone who also wants to use the client you may want to put this in /etc/profile or/etc/bashrc. The choice is yours. I find ~/.bash_profile is cleaner:

1
2
3
export NLS_LANG="AMERICAN_AMERICA.UTF8"
export ORACLE_HOME=/opt/oracle/instantclient
export DYLD_LIBRARY_PATH=$ORACLE_HOME

Now you could close your terminal session and start a new one to force a reload or try this instead:

1
source ~/.bash_profile

Do a quick echo on $ORACLE_HOME and $DYLD_LIBRARY_PATH to make sure these environment variables are set correctly.

Step 5: Put sqlplus in your $PATH

There are couple of ways to do this. You could add /opt/oracle/bin to your $PATH or you could add a sym link to the sqlplus binary in a directory which is already in your $PATH. I’m going to do the latter as I don’t want to make my $PATH variable longer just for one executable.

I’ve already got /usr/local/bin in my $PATH so I’m going to make the sym link there:

1
ln -s /opt/oracle/instantclient/sqlplus /usr/local/bin/sqlplus

Just give that a test to make sure it’s getting found :

1
which sqlplus

This should return /usr/local/bin/sqlplus. Check your $PATH variable if it’s not working.

Step 6: Try it out!

Lets assume we’ve got the following Oracle database:

  • Host : myserver
  • domain : mydomain.com
  • Port : 1521
  • SID : mydatabase
  • Service : mydatabase.mydomain.com

And a username and password of :

  • Username : myuser
  • Password : mypassword

We can connect to this database using the following connect string:

1
sqlplus myuser/mypassword@//myserver:1521/mydatabase.mydomain.com

It’s a bit of a mouthful, but if that worked then we’re all up and running. In this instance we’re connecting over the default port of 1521. Because of this we can actually leave the port out and Oracle will assume the default:

1
sqlplus myuser/mypassword@//myserver/mydatabase.mydomain.com

Possible errors

ORA-12154: TNS:could not resolve the connect identifier specified

It couldn’t find your host. Try the following:

  • Instead of just putting myserver, try myserver.mydomain.com
  • Are you sure you got the port right? Oracle defaults to listening on 1521. Is the listener running on your server for sure? Try running lsnrctl status on the server just to double check it’s running and which port it’s on.

ORA-12514: TNS:listener does not currently know of service requested in connect descriptor

It’s found the host and the listener but it’s can’t find the database you’ve specified at that listener. The last part of the connect string where it says mydatabase.mydomain.com is where it’s falling over now. Again, the quickest way to check this is to ssh onto your oracle database server and query the listener with lsnrctl status which will also tell you which services the listener is listening on. Copy and paste the correct service into the sqlplus connect string and it should work.

ORA-01017: invalid username/password; logon denied

I think this one speaks for itself! If you’re sure of the username and password then maybe it contains characters which may need escaping? Try surrounding the username and password in double quotes:

1
sqlplus "myuser"/"mypassword"@//myserver:1521/mydatabase.mydomain.com

Coming in Part 2

Now that we can connect our machine to Oracle, in part 2 we’re going to get rails in on the fun as well. Check back in a week or so and I should have that post finished.

Update: Part 2 has now been published and shows you how to install the ruby-oci8 library, how to install the oracle_enhanced adapter and how to configure your database.yml.


Update 15/05/09: Added NLS_LANG to the environment variables
Update : Apparently this problem is specific to has_one relationships. If you’re updating a nested model through a has_many relationship then you may not have this problem.

I’ve just solved a little gotcha with the new nested models feature which shipped with rails 2.3. If you’ve been having the following error when you try to render your model/new forms then read on…

1
2
3
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.new_record?

What is causing the nil which knocks on to the NoMethodError on nil.new_record?

By following the instructions from rails guides I’d hit a brick wall and kept getting the error above whenever I’d try and render my new form. My code looked like so :

First the two models….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# app/models/student.rb

# == Schema Information
# Schema version: 20090317153604
#
# Table name: students
#
#  id         :integer         not null, primary key
#  name       :string(255)
#  age        :integer
#  created_at :datetime
#  updated_at :datetime
#

class Student < ActiveRecord::Base
  has_one :address
  
  accepts_nested_attributes_for :address
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# app/models/address.rb

# == Schema Information
# Schema version: 20090317153604
#
# Table name: addresses
#
#  id           :integer         not null, primary key
#  house_number :integer
#  road         :string(255)
#  student_id   :integer
#  created_at   :datetime
#  updated_at   :datetime
#

class Address < ActiveRecord::Base
  belongs_to :student
end

And the view….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# app/views/students/new.html.erb

<h1>New student</h1>

<% form_for(@student) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.label :age %><br />
    <%= f.text_field :age %>
  </p>

  <% f.fields_for :address do |a| %>
    <p>
      <%= a.label :house_number %>
      <%= a.text_field :house_number %>
    </p>
    <p>
      <%= a.label :road %>
      <%= a.text_field :road %>
    </p>
  <% end %>

  <p>
    <%= f.submit 'Create' %>
  </p>
<% end %>

<%= link_to 'Back', students_path %>

The controller was left untouched.

As far as I can tell from the rails guide, unless I’m missing something really obvious, this is all that is needed. However I still get nil.new_record? errors when I try and hit that view.

The solution!

Thanks to the complex forms examples I was able to see where I was going wrong and put things right. It’s such a good idea to have working code examples of new features, thanks for that guys.

It turns out I needed to call build_address on my new student object. Makes sense I suppose. Otherwise we’re returning a nil from @student.address so we need to put a new object in @student.address so that its attributes can eventually be set.

And here’s how it works…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# app/views/students/new.html.erb

# Code from before and after the nested model has been removed for brevity

<% @student.build_address unless @student.address %>
<% f.fields_for :address do |a| %>
  <p>
    <%= a.label :house_number %>
    <%= a.text_field :house_number %>
  </p>
  <p>
    <%= a.label :road %>
    <%= a.text_field :road %>
  </p>
<% end %>

I assume that @student.build_address is the same as @student.address = Address.new. So it gives you a new blank address object, ready for the attributes to be pushed into. And now when rails calls new_record? on this object we don’t get a NoMethodError any more.

References

Sprinkle and passenger-stack v Deprec

Anton Jenkins | March 05, 2009

I decided to give passenger-stack a quick spin and I’ve got so say I’m impressed. I love the whole idea of being able to write recipes to build a server. It’s definitely the way forward as it’s so much easier having a DSL to spec a server.

However I don’t think I’ll be moving away from deprec just yet. The aim of sprinkle and passenger-stack is to be technology agnostic and it’s a great idea. Deprec is purely aimed at getting your rails app up and running as quickly as possible and it scratches this itch perfectly. And once you are running it’s the little touches which you start to appreciate. I’ve fallen in love with the way you can do something along the lines of :

1
2
3
4
5
6
7
8
cap deprec:passenger:config_gen

# Edit the newly created config files

cap deprec:passenger:config

# The config files are now pushed on to the server 
# and the relevant service is bounced!

This way of generating and pushing configuration changes to the components that deprec has installed is a fantastic time saver and I’m not sure how I could live without it now that I’ve been so spoilt.

In summary

So I guess what I’m getting at is passenger-stack and sprinkle is a really elegant solution, but if you are purely using this for rails then I’d personally be inclined to stick with deprec if it still works for you. However….. if there was a way of abstracting out the configuration goodness from deprec and mixing it with the actual building steps which passenger-stack and sprinkle performs then I think we’d have a perfect solution! Taking the best parts from both and mixing them together. Sounds great!

Adding a reCAPTCHA to enki

Anton Jenkins | March 04, 2009

There are several techniques to help prevent spam in your blog comments. I’ve opted for a captcha using the popular reCAPTCHA system. This post will show you how to apply reCAPTCHA to the enki blogging engine.

The idea with enki is that you clone the basic master tree and then merge in features you want from other people’s trees. So the following steps will show you how to take a basic enki blog from github and merge in the recaptcha branch to keep spam away.

Step 1 : Register at recaptcha.net and install the recaptcha gem.

When you sign up and register your domain at recaptcha.net they will give you two keys – a public key and private key. Keep these keys handy as you’ll need them later on.

Install the recaptcha gem with :

1
sudo gem install ruby-recaptcha

Step 2 : Clone enki and make a branch for your own blog

Assuming your blog is going to be called “myblogname” :

1
2
3
git clone git://github.com/xaviershay/enki.git myblogname
cd myblogname
git checkout -b myblogname

Step 4 : Merge in the reCAPTCHA branch

1
2
git remote add recaptcha git://github.com/antonjenkins/enki.git
git pull recaptcha recaptcha

Now we should have a branch call myblogname with a clean install of enki merged with the recaptcha branch.

Step 5 : Configure the blog to use your reCAPTCHA keys

If you look in the environment.rb file you should see a little section right at the top for you to paste your public and private keys :

1
2
3
4
5
# config/environment.rb

# reCAPTCHA keys. Obtain these for your domain from http://recaptcha.net/
RCC_PUB = "Put your reCAPTCHA public key in here"
RCC_PRIV = "Put your reCAPTCHA private key in here"

Step 6 : Configure enki as usual

That concludes the specific steps needed for the reCAPTCHA support. All that remains is to follow Xavier Shay’s instructions for configuring enki as detailed on the enki github page :

1
2
3
4
5
6
  cp config/database.example.yml config/database.yml
  # Edit config/enki.yml and config/database.yml to taste
  rake db:migrate
  rake spec
  ./script/server
  # Load http://localhost:3000/admin in your browser

Now when you upload your blog to the web your comments form should be protected with reCAPTCHA. You probably wont see the reCAPTCHA on your development box because usually the public and private reCAPTCHA keys only work on the domain you’ve specified when you requested them (unless you checked the global box).

Resources