For JRuby on Rails project I use warbler to create the war file. To know the details see my previous blog JRuby on Rails building with warbler. Warbler doesn't have support to create ear file but it does a lot to create the war file. Now I will show you how can you create a ear file when you have the war file. I am assuming that you have warbler properly configured so that it can create the war file for you.
Create a new rake file (ear.rake) in RAILS_APP/lib/tasks folder. In this rake file we will define rake tasks to create the ear from the war file. Note that if you define a task in this file you will be able to execute that task from your capistrano capfile. To create the ear we need an application.xml file. We can create the application.xml file externally or we can create the file of our own. I am creating the content of this file using a method create_application_xml. Now in this rake file add the following task that will create the ear file for us:
task 'ear:create' => 'war' do
name = "myapp"
ear_file = "#{name}.ear"
staging = File.join(RAILS_ROOT,'tmp','ear')
File.makedirs(staging) if !File.exists?(staging)
puts "Copying war to #{staging}"
#add the war file to the tmp directory
File.copy("#{name}.war", staging)
puts "Making application.xml"
#create and add application.xml
File.makedirs(File.join(staging, 'META-INF'))
ear_file_dir = File.join(staging, 'META-INF')
application_xml = File.join(staging, 'META-INF', 'application.xml')
# always create a new override file, regardless of whether one exists
File.open(application_xml, 'w') { |out| out << create_application_xml(name) }
puts "Creating the ear ...."
sh "jar cf #{ear_file} -C #{staging} ."
end
The method that is called by the above task to create the ear file content:
def create_application_xml(name)
require 'erb'
template=<<EOFAPPXML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application PUBLIC "-//Sun Microsystems, Inc.//DTD J2EE Application 1.2//EN" "http://java.sun.com/j2ee/dtds/application_1_2.dtd">
<application id="application_myap">
<display-name>myapp</display-name>
<module id="webmodule_myapp">
<web>
<web-uri>#{name}.war</web-uri>
<context-root>/#{name}</context-root>
</web>
</module>
</application>
EOFAPPXML
erb = ERB.new(template)
erb.result(binding)
end
To execute the jar command to create the ear file you need to make sure that JAVA_HOME property is properly set.
Monday, October 19, 2009
Thursday, October 15, 2009
Some good links to learn more about Webservice (SOAP & REST)
This is a little effort for those who wants to know about Webservice, WS-*, SOAP, REST. Here I am mentioning some link from where you will be able to know about all these keywords:
- http://oracled.wordpress.com/2009/03/11/when-to-use-rest-based-web-services/
- http://oracled.wordpress.com/2008/09/12/rpc-style-vs-document-style-web-service/
- http://oracled.wordpress.com/2009/03/07/some-web-services-best-practices/
- http://www.ibm.com/developerworks/webservices/library/ws-whichwsdl/
- http://www.ajaxonomy.com/2008/xml/web-services-part-1-soap-vs-rest
- http://www.infoq.com/articles/sanjiva-rest-myths
- http://en.wikipedia.org/wiki/Representational_State_Transfer
- http://java.sun.com/developer/technicalArticles/WebServices/restful/
- http://www.informit.com/guides/content.aspx?g=xml&seqNum=160
- http://www.ibm.com/developerworks/webservices/library/ws-policy.html
- http://www.ibm.com/developerworks/webservices/library/ws-security.html
- http://www.ibm.com/developerworks/library/specification/ws-tx/
- http://www.ibm.com/developerworks/library/specification/ws-fed/
- http://www.ibm.com/developerworks/library/specification/ws-rm/
- http://oracled.wordpress.com/2009/03/11/when-to-use-rest-based-web-services/
- http://oracled.wordpress.com/2008/09/12/rpc-style-vs-document-style-web-service/
- http://oracled.wordpress.com/2009/03/07/some-web-services-best-practices/
- http://www.ibm.com/developerworks/webservices/library/ws-whichwsdl/
- http://www.ajaxonomy.com/2008/xml/web-services-part-1-soap-vs-rest
- http://www.infoq.com/articles/sanjiva-rest-myths
- http://en.wikipedia.org/wiki/Representational_State_Transfer
- http://java.sun.com/developer/technicalArticles/WebServices/restful/
- http://www.informit.com/guides/content.aspx?g=xml&seqNum=160
- http://www.ibm.com/developerworks/webservices/library/ws-policy.html
- http://www.ibm.com/developerworks/webservices/library/ws-security.html
- http://www.ibm.com/developerworks/library/specification/ws-tx/
- http://www.ibm.com/developerworks/library/specification/ws-fed/
- http://www.ibm.com/developerworks/library/specification/ws-rm/
Friday, October 9, 2009
Capistrano task to confirm stop of JBoss
This is very common in the domain of automatic deployment to restart the application server and obviously the application server takes sometimes to stop. If we need to make sure the application server is completely stopped before starting it again we need to write some shell script to check that. Capistrano users can use following script to ensure that the application server (in this case JBoss) is completely stopped. The following script will execute the stop script and then check if the server is still running. If it is running then it will sleep for 6 seconds and then check it again. It will do it for 15 times and even then if it is running it will issue kill -9 to kill the process. You can change any of these values. Note that I have assumed the server is running at port 8080.
task :ensure_jboss_stopped do
set :use_sudo, true
set :user, prompt("User to run stop.sh ")
set :is_jboss_running, true
counter = 0
run "/opt/jboss-4.2.3.GA/bin/stop.sh"
while is_jboss_running
counter = counter + 1
run "netstat -an | grep -i listen | grep 8080|wc -l" do |ch, stream, data|
if data.to_i > 0
set :is_jboss_running, true
else
set :is_jboss_running, false
end
end
if is_jboss_running and counter > 15
system "echo JBoss is still running, finding the process id"
run "ps -eaf |grep frengine |grep -v grep |awk '{print $2}'|xargs -I{} echo kill -9 {};"
system "echo Now KILLING JBOSS"
run "ps -eaf |grep frengine |grep -v grep |awk '{print $2}'|xargs -I{} kill -9 {} ;"
sleep 6
elsif is_jboss_running
system "echo JBoss is still running, waiting for 6 seconds"
sleep 6
end
end
system "echo JBoss stopped successfully"
end
How to get the output of Capistrano executed command
Sometimes it is required to get the the output of some executed command in external/deployment server and based on that output you may need to take some decision. Lets say for some reason you are planning for hot deployment to a JBoss application server. So before deployment you want to make sure if the JBoss is running or not. You can do that by following script. Here I am assuming that the JBoss is running at 8080
task :do_something_if_jboss_is_running do
set :use_sudo, true
set :user, prompt("User to check jboss running status: ")
set :is_jboss_running, true
run "netstat -an | grep -i listen | grep 8080|wc -l" do |ch, stream, data|
if data.to_i > 0
set :is_jboss_running, true
else
set :is_jboss_running, false
end
end
if is_jboss_running
#do something
else
#do some other thing
end
end
Run JBoss in background using Capistrano
I had a very hard time to start JBoss as background process using Capistrano. At the I end I found the solution which is so simple. The important point is in you capfile don't execute the commmad with both nohup and ampersand (&). At first create start.sh file that will call run.sh to start the jboss.
The content of start.sh:
/opt/jboss-4.2.3.GA/bin/run.sh &
And in your capfile:
desc "start jboss server"
task :start_jboss do
set :use_sudo, true
set :user, prompt("User to run 'service jboss-frengine start' command: ")
run "nohup /opt/jboss-4.2.3.GA/bin/start.sh" # ONLY NOHUP IS USED, DON'T USE AMPERSAND HERE
end
Make sure the user has permission to write into nohup.out
Tuesday, October 6, 2009
Using JNDI in JRuby on Rails for JBoss
Its very easy to user JNDI with JBoss in your JRuby on Rails application. At first you will need to install all the required gems/plugins and for that please see my previous blog JRuby on Rails: using JDBC driver. Now if you have everything okay that is specified in that post you will have to configure followings.
I am assuming you don't have any JNDI datasource created in your JBoss.
To create a JNDI datasource in your JBoss create a file my-oracle-ds.xml with following content and save it in JBOSS_HOME/server/default/deploy folder:
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
<jndi-name>MyDS</jndi-name>
<connection-url>jdbc:oracle:thin:@172.189.171.149:1521:MYDB</connection-url>
<driver-class>oracle.jdbc.driver.OracleDriver</driver-class>
<user-name>dbuser</user-name>
<password>dbpassword</password>
<use-java-context>false</use-java-context>
<exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter</exception-sorter-class-name>
<metadata>
<type-mapping>Oracle10g</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>
In your jboss-web.xml file:
<jboss-web>
.......
<resource-ref>
<res-ref-name>jdbc/MyDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<jndi-name>MyDS</jndi-name>
</resource-ref>
</jboss-web>
In your web.xml file:
<resource-ref>
<res-ref-name>jdbc/MyDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
Now to access the database in your database.yml use following configuration:
development:
adapter: jdbc
jndi: java:comp/env/jdbc/MyDS
driver: oracle.jdbc.driver.OracleDriver
Monday, September 28, 2009
Problem with Warbler
While I was building the war/ear file I was facing problem at the building time as well as after deployment. Here I tried to mention all those problems:
1) Warbler doesn't copy .specifications file: When we execute gems:unpack:dependencies it creates .specifications file for each gem in your vendor/gems folder. Then when we execute 'war' task it doesn't copy this file into the war file. I solved this problem by adding following line in the config/warble.rb file
config.includes = FileList["vendor/gems/*/.specification"]
2) I was getting Don't know how to build task '' when log folder was there: This is something wired I got when I added the gem 'composite_primary_keys'. There are two solutions for this
- rename the log folder to some other name and then execute the war task
- in your warble.rb file use config.gems = []
3) After deployment I was getting undefined method `new' for "Rack::Lock":String: After googling I came to following solution
4) Cannot load gem at [SYSTEM_JRUBY_CACHE_PATH] in MY_APP_FOLDER: I was getting this error when I started using 'composite_primary_keys' gem and trying to execute the war task by jruby rake. The version of this jruby rake is 0.8.7. After some analysis and testing with different version of rake I have identified that if I use older rake (version less then 0.8.4) then the problem is gone. Other thing that you can do is in your warble.rb file use config.gems = []
Some helpful links:
http://kenai.com/jira/browse/JRUBY_RACK-18
http://stufftohelpyouout.blogspot.com/2009/06/how-to-get-warbler-to-include-custom.html
http://stufftohelpyouout.blogspot.com/2009/07/fix-ioerrorio-errorbroken-pipestrange.html
http://wiki.oracle.com/thread/3104945/Error+during+jruby-rack+initialization
Friday, September 25, 2009
JRuby on Rails building with warbler
Here I will mention the steps to create a JRuby (v1.3.1) on Rails (v2.3.3) application and build the war file using Warbler (v0.9.13) gem and then deploy in the JBoss (4.2.3.GA).
See my previous blog Oracle with JRuby using activerecord-jdbc-adapter to install JRuby, Rails and activerecord-jdbc-adapter to
In real life we need to work with gems & plugins for that reason I will now install some gems and plugins. jruby-openssl is required because if you don't install it then you will get JRuby limited openssl loaded. gem install jruby-openssl for full support
- JRUBY_HOME\bin\jruby -S gem install jruby-openssl
- JRUBY_HOME\bin\jruby -S gem install mislav-will_paginate
- install wee_date_picker plugin. To see the installation instruction see http://github.com/pyrat/wee_date_picker
Now create a rails project and in the environment.rb file add followings:
#config all the gems used with this project
config.gem "activerecord-jdbc-adapter", :version => '0.8', :lib => 'jdbc_adapter'
config.gem "jruby-openssl", :version => "0.5.1", :lib => 'openssl'
config.gem 'mislav-will_paginate', :version => '~> 2.3.11', :lib => 'will_paginate', :source => 'http://gems.github.com'
I like to configure my rails app to use gems and rails that are in the vendor folder. To make all the used gems copied to vendor/gems folder execute following rake task:
RAILS_APP_HOME>JRUBY_HOME\bin\rake gems:unpack:dependencies
To freeze rails with my application execute following rake task:
RAILS_APP_HOME>JRUBY_HOME\bin\rake rails:freeze:gems
Now its time to think about building the war/ear file to deploy in JBoss. As I have already said to build the war file I use Warbler. To do that install the gem:
JRUBY_HOME\bin\jruby -S gem install warbler
Its convenient to have warbler as plugins in your application and to make the plugin use following command and then you will have the warbler plugin in your vendor/plugins folder:
JRUBY_HOME\bin\warble pluginize
To customize files, libraries, and gems included in the.war file, you'll need a config/warble.rb file. As you have already created the warble plugin you can execute following to create warble.rb file for you:
RAILS_APP_HOME>JRUBY_HOME\bin\jruby script/generate warble
Now its time to create the war file of your project. To do that execute following rake task:
RAILS_APP_HOME>JRUBY_HOME\bin\rake war
At this point everything should be done but in my case I faced couple of problems. If you face problem please see my next blog 'Problem with Warbler'
Labels:
JRuby warble,
JRuby warbler,
Rails warble,
Rails warbler
Thursday, September 24, 2009
Configure Log4j with JRuby on Rails for JBoss
Its very easy to use any Java library from you JRuby code. In one of my JRuby on Rails project I had to use two different logger for logging purpose. One is the Rails default logger for all jruby code and other is log4j logger that is for my java codes and libraries. To use both of the logger I made following changes.
At first I added mylog4j.properties and log4j-1.2.15.jar in my project's lib folder. By default the ruby log file goes to log folder. If we want to keep this log file in this default folder then we don't need to change anything. A sample mylog4j.properties file content is given here:
log4j.rootLogger=DEBUG, R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Pattern to output the caller's file name and line number.
log4j.appender.stdout.layout.ConversionPattern=[%d{ISO8601}] %-5p (%F:%L) - %m%n
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=/opt/jboss/server/default/log/myapp_rails.log
log4j.appender.R.MaxFileSize=10000KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=5
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=[%d{ISO8601}] %-5p (%F:%L) - %m%n
log4j.logger.org.apache.commons.digester=WARN
log4j.logger.org.apache.commons.beanutils=WARN
log4j.logger.org.apache.commons.validator=WARN
log4j.logger.org.apache.commons.httpclient=DEBUG
log4j.logger.org.springframework=DEBUG
In my case I developed a JRuby on Rails application that was deployed in JBoss server. Thats why I decided not to use the default log folder of Rails application for log files rather use the JBoss log folder to store the log files.
For that reason I need to know the log folder path of JBoss in my environment.rb file and then change the log file path that will be used by Rails. You can get the JBoss log folder path by:
java.lang.System.getProperty('jboss.server.log.dir')
So the corresponding changes in environment.rb file that I made are:
jboss_server_log_dir = java.lang.System.getProperty('jboss.server.log.dir')
if jboss_server_log_dir
config.log_path = File.join(jboss_server_log_dir,'frengine_admin.log') if (jboss_server_log_dir && !jboss_server_log_dir.strip.empty?)
end
config.logger = Logger.new(config.log_path, 50, 1048576)
And to specify the same log folder in the mylog4j.properties file I used following line:
log4j.appender.R.File=${jboss.server.log.dir}/myapp_java.log
Now you will be able to see two different log file in the JBoss log folder. If you want to use same log file for all kind of logging use single file name.
Wednesday, September 23, 2009
JRuby on Rails: using JDBC driver
In JRuby world the bridge between Rails activerecord and JDBC driver is activerecord-jdbc-adapter. activerecord-jdbc-adapter allows you to use virtually any JDBC-compliant database with your JRuby on Rails application. You can verify the supported JDBC-compliant database by going to JRUBY_HOME\lib\ruby\gems\1.8\gems\activerecord-jdbc-adapter-0.8\lib\active_record\connection_adapters. For every supported database you will have databasename_adapter.rb file. Please see my blog 'Oracle with JRuby using activerecord-jdbc-adapter' to install JRuby, Rails and activerecord-jdbc-adapter and test your installation is correct.
Now its time to configure the database.yml file. Since version 2.0 Rails automatically look for and load an adapter gem based on the name of the adapter you specify in database.yml. Example:
development:
adapter: jdbc
...
With this database configuration, Rails will attempt to load the activerecord-jdbc-adapter gem. Here is an example for oracle database:
development:
adapter: oracle
driver: oracle.jdbc.driver.OracleDriver
url: jdbc:oracle:thin:@localhost:1521:XE
username: username
password: password
If you want to work with the connection itself then require the active_record/connection_adapters/jdbc_adapter library and call the method ActiveRecord::Base.jdbc_connection in order to obtain a connection to the database:
require 'rubygems'
require 'active_record'
require 'active_record/connection_adapters/oracle_adapter'
require 'active_record'
require 'active_record/connection_adapters/oracle_adapter'
ActiveRecord::Base.jdbc_connection
In upcoming blog I am going to write on using JNDI in your JRuby on Rails application and deploying in JBoss.
Monday, September 21, 2009
Oracle with JRuby using activerecord-jdbc-adapter
- Download jruby from http://dist.codehaus.org/jruby/1.3.1/ and install it. For convenience you can set an environment variable JRUBY_HOME = C:\jruby-bin-1.3.1\ (/opt/jruby-1.3.1 for linux/OSX) and optionally you can add this environment variable to the PATH variable.
set PATH=%PATH%;JRUBY_HOME (windows)
export PATH=$PATH:JRUBY_HOME (linux or OSX) - install rails using jruby -S gem install -y rails (in my time the latest one was 2.3.3)
If you didn't set the environment varilable in the path variable then you will have to execute following command:
JRUBY_HOME\jruby -S gem install -y rails
If you want to specify version then
jruby -S gem install -y rails --version 2.3.3 - install activerecord-jdbc-adapter (version - 0.8) using
jruby -S gem install -y activerecord-jdbc-adapter --version 0.8 or
JRUBY_HOME\jruby -S gem install -y activerecord-jdbc-adapter --version 0.8
At first I installed the newer version 0.9.1 but with that version I was getting following error for timestamp type column: "undefined method 'new_date' for JdbcSpec::Oracle::Column:Module". I have a plan to solve this problem later - Now its time to test activerecord, activerecord-jdbc-adapter, jdbc-driver from jirb. To test we need couple of tables in the database. Lets say we have two tables:
- authors
- articles [an author can have multiple articles] - Now copy the oracle-jdbc-driver jar file (ojdbc14-10.2.0.1.0.jar) in the JRUBY_HOME\lib folder then run 'jirb'. To run "jirb" in command prompt go to JRUBY_HOME\bin and type 'jirb'. Now in the 'irb' console type followings:
require 'rubygems'
require 'active_record'
require 'active_record/connection_adapters/oracle_adapter'
class Author < adapter =""> 'jdbc',
:driver => 'oracle.jdbc.driver.OracleDriver',
:url => 'jdbc:oracle:thin:@localhost:1521:XE',
:username => 'username',
:password => 'password')
Author.all()
Some useful links:
Tuesday, November 18, 2008
Extending Capistrano (1.x) task
You can extend the default behavior of Capistrano by different ways. Here I am mentioning the ways that I used to extend the default Capistrano behavior.
Writing Custom Task
We used Capistrano for the first time in a project [www.scrumpad.com] to deploy our application. We used Rails 1.2.5 and the default support from Capistrano was fine for us. But in next project when we were trying to use Capistrano to deploy our Rails application we felt the necessity of extending the default behavior of Capistrano.
In the second case we had different servers (6 servers) to deploy the application in development (2 servers), testing (2 servers) and production (2 servers) environment. We were planning to use the same capfile for deployment in all the servers. There were different configurations for different types of environments and we needed something that will do the configuration based on the user command.
Capistrano has nice support to extend its default behavior to write custom tasks. For our purpose we made three tasks (development, testing, production) and each task set the environment of each environment.
desc "this task sets the development environment"
task :development do
role :app, "my.dev1.server.ip"
role :app, "my.dev2.server.ip"
set :environment, "development"
end
Now as we want to set the environment before any standard Capistrano task start running we run the capfile in this way:
cap [environment] [standard_capistrano_task]
i.e. cap development setup
Using the Event Framework
In Capistrano 1.x we can extend a default task my creating new tasks whose names are prefixed with 'before_' and 'after_'. For example depending on the environment we need to change the value of 'deploy_to' in our application. In that case we can write another task 'before_deploy':
desc "this task set the value for deploy_to based on the value of 'environment'"
task :before_deploy do
if environment == 'development'
set :deploy_to = "deploy_folder_path_in_development_server"
elsif environment == 'testing'
set :deploy_to = "deploy_folder_path_in_testing_server"
elsif environment == 'production'
set :deploy_to = "deploy_folder_path_in_production_server"
end
end
So this task will be called before the calling of 'deploy' task. Similarly if you want to do some extra work after 'deploy' task is called in that case you could write a new task 'after_deploy'
Overriding default task
As I have already said, I was using Capistrano for Rails application. Later for a Ruby application I need to use Capistrano to deploy the application in server and here I found a problem. In Rails application there is a folder 'Public' and the default 'update_code' task makes a link to that folder. But as I was trying with Ruby application where there is no 'Public' folder I was getting an error. To solve the problem I override the standard implementation by creating the same task with custom implementation. Here is the code:
desc <<-DESC
Update all servers with the latest release of the source code. All this does is do a checkout (as defined by the selected scm module). This task is modified for Ruby application
DESC
task :update_code, :except => { :no_release => true } do
on_rollback { delete release_path, :recursive => true }
source.checkout(self)
set_permissions
run <<-CMD rm -rf #{release_path}/log #{release_path}/public/system &&
ln -nfs #{shared_path}/log #{release_path}/log
CMD
@releases = nil
end
In future I will post more on how to extend the default Capistrano tasks with other ways.
Writing Custom Task
We used Capistrano for the first time in a project [www.scrumpad.com] to deploy our application. We used Rails 1.2.5 and the default support from Capistrano was fine for us. But in next project when we were trying to use Capistrano to deploy our Rails application we felt the necessity of extending the default behavior of Capistrano.
In the second case we had different servers (6 servers) to deploy the application in development (2 servers), testing (2 servers) and production (2 servers) environment. We were planning to use the same capfile for deployment in all the servers. There were different configurations for different types of environments and we needed something that will do the configuration based on the user command.
Capistrano has nice support to extend its default behavior to write custom tasks. For our purpose we made three tasks (development, testing, production) and each task set the environment of each environment.
desc "this task sets the development environment"
task :development do
role :app, "my.dev1.server.ip"
role :app, "my.dev2.server.ip"
set :environment, "development"
end
Now as we want to set the environment before any standard Capistrano task start running we run the capfile in this way:
cap [environment] [standard_capistrano_task]
i.e. cap development setup
Using the Event Framework
In Capistrano 1.x we can extend a default task my creating new tasks whose names are prefixed with 'before_' and 'after_'. For example depending on the environment we need to change the value of 'deploy_to' in our application. In that case we can write another task 'before_deploy':
desc "this task set the value for deploy_to based on the value of 'environment'"
task :before_deploy do
if environment == 'development'
set :deploy_to = "deploy_folder_path_in_development_server"
elsif environment == 'testing'
set :deploy_to = "deploy_folder_path_in_testing_server"
elsif environment == 'production'
set :deploy_to = "deploy_folder_path_in_production_server"
end
end
So this task will be called before the calling of 'deploy' task. Similarly if you want to do some extra work after 'deploy' task is called in that case you could write a new task 'after_deploy'
Overriding default task
As I have already said, I was using Capistrano for Rails application. Later for a Ruby application I need to use Capistrano to deploy the application in server and here I found a problem. In Rails application there is a folder 'Public' and the default 'update_code' task makes a link to that folder. But as I was trying with Ruby application where there is no 'Public' folder I was getting an error. To solve the problem I override the standard implementation by creating the same task with custom implementation. Here is the code:
desc <<-DESC
Update all servers with the latest release of the source code. All this does is do a checkout (as defined by the selected scm module). This task is modified for Ruby application
DESC
task :update_code, :except => { :no_release => true } do
on_rollback { delete release_path, :recursive => true }
source.checkout(self)
set_permissions
run <<-CMD rm -rf #{release_path}/log #{release_path}/public/system &&
ln -nfs #{shared_path}/log #{release_path}/log
CMD
@releases = nil
end
In future I will post more on how to extend the default Capistrano tasks with other ways.
Thursday, November 6, 2008
Connecting with Oracle from you ruby/rails applications on an "Intel Mac OS X" using DBI with OCI8
Prerequisite:
- Mac OS X (10.5.5)
- ruby --version => 1.8.6 (2008-03-03 patchlevel 114) [universal-darwin9.0]
NOTE: Here the machine where i am working is 'SBKislamzD1' and the user name is 'islamz'. In your case just replace this user name with your user name
Installing DBI
The easiest way of installing a gem is using gem command [gem install dbi] But here I am going to use OCI8-1.0.3. With this version of OCI8 library not all the version of DBI works. In my case DBI version 0.2.0 worked. You can't install this version (0.2.0) of DBI using gem install. You need to downloaded the tar file and unzip it.
Lets say the unzip folder is /Users/islamz/Downloads/dbi-0.2.2
Now open a terminal/console and goto /Users/islamz/Downloads/dbi-0.2.2 and run following commands:
> ruby setup.rb config --with=dbi
> ruby setup.rb setup
Now you should run the following command with 'sudo'
> sudo ruby setup.rb install
If you have any other version of DBI gem you might need to uninstall that gem
Installing Oracle Instant Client
At first from http://www.oracle.com/technology/software/tech/oci/instantclient/htdocs/intel_macsoft.html download following packages:
Now unzip all of packages in /Library/Oracle/instantclient_10_2. After unzipping you should have followings in this folder:
Its now time to add some environment variables. For this purpose open /etc/profile in edit mode and add the environment variables as stated below:
Now you need to create symlinks. To do this follow these steps:
To compile ruby-oci8 follow the steps as stated below:
You can easily test by following these steps:
If there is no error then you installation is SUCCESSFUL
SIDE NOTES
1) How to check which version of DBI is supported:
- goto /Library/Ruby/Site/1.8/DBD/OCI8/
- open the file OCI8.rb
- check the value of USED_DBD_VERSION. [In my case it was "0.2"]
2) Problems with NetBeans:
If you are using NetBeans for developing your project in that case you might face some problems:
a) You might get some permission related problems. So run Netbeans with sudo
b) When running your application if it throws an error that 'libclntsh.dylib.10.1' is not found in '/scratch/plebld/208/rdbms/lib/' then put the file in the specified path
c) When running your application you are getting this error:
OCIError (Error while trying to retrieve text for error ORA-24812):
lob.c:194:in oci8lib.so
/Library/Ruby/Site/1.8/oci8.rb:1005:in `write'
It means when you are running NetBeans as sudo the environment variables are missing. Follow these steps:
- Mac OS X (10.5.5)
- ruby --version => 1.8.6 (2008-03-03 patchlevel 114) [universal-darwin9.0]
NOTE: Here the machine where i am working is 'SBKislamzD1' and the user name is 'islamz'. In your case just replace this user name with your user name
Installing DBI
The easiest way of installing a gem is using gem command [gem install dbi] But here I am going to use OCI8-1.0.3. With this version of OCI8 library not all the version of DBI works. In my case DBI version 0.2.0 worked. You can't install this version (0.2.0) of DBI using gem install. You need to downloaded the tar file and unzip it.
Lets say the unzip folder is /Users/islamz/Downloads/dbi-0.2.2
Now open a terminal/console and goto /Users/islamz/Downloads/dbi-0.2.2 and run following commands:
> ruby setup.rb config --with=dbi
> ruby setup.rb setup
Now you should run the following command with 'sudo'
> sudo ruby setup.rb install
If you have any other version of DBI gem you might need to uninstall that gem
Installing Oracle Instant Client
At first from http://www.oracle.com/technology/software/tech/oci/instantclient/htdocs/intel_macsoft.html download following packages:
- - Instant Client Package - Basic (10.2.0.4)
- - Instant Client Package - SQL*Plus (10.2.0.4)
- - Instant Client Package - SDK (10.2.0.4)
Now unzip all of packages in /Library/Oracle/instantclient_10_2. After unzipping you should have followings in this folder:
- - BASIC_README
- - classes12.jar
- - genezi
- - glogin.sql
- - libclntsh.dylib.10.1
- - libnnz10.dylib
- - libocci.dylib.10.1
- - libociei.dylib
- - libocijdbc10.dylib
- - libocijdbc10.jnilib
- - libsqlplus.dylib
- - libsqlplusic.dylib
- - ojdbc14.jar
- - sdk
- - sqlplus
- - SQLPLUS_README
Its now time to add some environment variables. For this purpose open /etc/profile in edit mode and add the environment variables as stated below:
- Open the file in edit mode using: sudo vi /etc/profile
- Add following in this file:
ORACLE_HOME=/Library/Oracle/instantclient_10_2
export TNS_ADMIN=$ORACLE_HOME
export LD_LIBRARY_PATH=$ORACLE_HOME
export DYLD_LIBRARY_PATH=$ORACLE_HOME
export PATH=$PATH:$ORACLE_HOME - Save the file and logoff then login
- To test whether the environment variables are set or not type 'env' in terminal(console)
Now you need to create symlinks. To do this follow these steps:
- open a terminal/console and goto /Library/Oracle/instantclient_10_2
- sudo ln -s libclntsh.dylib.10.1 libclntsh.dylib
- sudo ln -s libocci.dylib.10.1 libocci.dylib
- in /Library/Oracle/instantclient_10_2 folder you will find two new symlink
To compile ruby-oci8 follow the steps as stated below:
- download the ruby-oci8-1.0.3.tar If you are using Safari then it will be downloaded in /Users/islamz/Downloads/
- unzip it to ruby-oci8-1.0.3 (/Users/islamz/Downloads/ruby-oci8-1.0.3)
- run terminal/console and goto /Users/islamz/Downloads/ruby-oci8-1.0.3
- export SQLPATH=$ORACLE_HOME
- export RC_ARCHS=i386
- ruby setup.rb config
- make
- sudo make install [Don't forget to run this command as 'sudo']
You can easily test by following these steps:
- run terminal/console
- irb
- require 'oci8' #it should return 'true'
If there is no error then you installation is SUCCESSFUL
SIDE NOTES
1) How to check which version of DBI is supported:
- goto /Library/Ruby/Site/1.8/DBD/OCI8/
- open the file OCI8.rb
- check the value of USED_DBD_VERSION. [In my case it was "0.2"]
2) Problems with NetBeans:
If you are using NetBeans for developing your project in that case you might face some problems:
a) You might get some permission related problems. So run Netbeans with sudo
b) When running your application if it throws an error that 'libclntsh.dylib.10.1' is not found in '/scratch/plebld/208/rdbms/lib/' then put the file in the specified path
c) When running your application you are getting this error:
OCIError (Error while trying to retrieve text for error ORA-24812):
lob.c:194:in oci8lib.so
/Library/Ruby/Site/1.8/oci8.rb:1005:in `write'
It means when you are running NetBeans as sudo the environment variables are missing. Follow these steps:
- SBKislamzD1:~ islamz# sudo su
- sh-3.2# ORACLE_HOME=/Library/Oracle/instantclient_10_2
- sh-3.2# export ORACLE_HOME=$ORACLE_HOME
- sh-3.2# export TNS_ADMIN=$ORACLE_HOME
- sh-3.2# export LD_LIBRARY_PATH=$ORACLE_HOME
- sh-3.2# export DYLD_LIBRARY_PATH=$ORACLE_HOME
- sh-3.2# export NLS_LANG=AMERICAN_AMERICA.AL32UTF8
- sh-3.2# /Applications/NetBeans/NetBeans\ 6.1.app/Contents/MacOS/netbeans
Wednesday, October 22, 2008
Data Consistency by Locking: Rails Way
In Databased based application data consistency is an important issue. Data inconsistency might occur when more than one users are updating the same model at the sametime. For example you have a model Book. The model has this properties:
- id
- title
- writer
There is one Book with this properties:
- id => 1
- title => "My book title"
- writer => "Mr. Y"
User1 selects a Book (id=1) in to edit and then User2 selects the same Book (id=1) to edit. Now the User1 changed the value of writer to 'Zahid' and saved it. In the database following update query will be executed:
UPDATE books SET title = 'My book title', writer = 'Zahid' WHERE id = 1
And the User2 changed the value of title to 'Updated Title' and saved it. In the database following update query will be executed:
UPDATE books SET title = 'Updated Title', writer = 'Mr. Y' WHERE id = 1
As a result the update done by User1 will get lost.
This is a common problem and there are two different solutions of this problem:
1) Pessimistic Locking
2) Optimistic Locking
Pessimistic Locking:
Pessimistic locking functionality is provided by the database. This locking happens at the database level. In this locking you have to lock the record while selecting the record to update. You can lock a record while updating by using "FOR UPDATE" with the "SELECT" query [NOTE: There are some other locking clause except "FOR UPDATE" and for that read the database documentation].
And the rails ways to do this locking are:
1) while selecting use :lock => true
2) use lock! method of a model that is already selected
Example:
Book.transaction do
book = Book.find(1, :lock=> true)
book.writer = 'Zahid'
book.save!
end
This will lock the book record (id = 1) through out the specified transaction and prevent updates of this record during this time. But if the model is already selected and then you want to lock it for update in that case you can lock the model with lock! method:
book = Book.find(1)
book.lock!
The problem with Passimistic Locking is, if a user select a book to update it and then forget about it or the locking time gets extended for any reason then if there is any other call to update this model then the second call will just HANG, no error will be raised.
Optimistic Locking:
This is a tricky way of confirming data consistency but not locking the record. As here no record is locked so there is no chance of HANG. In this method a new column is added to a table that column contains a version of the record. Unlike Pessimistic Locking the protection is provided at the application level instead of database level.
Lets say the column name is lock_version and the default value is 0. So when the User1 select a Book to update then
- the lock_version for this model is 0
User2 selects the same Book to update and this again
- the lock_version for this model is 0
If any of the user update the Book model then:
- save the udpated model
- the lock_version is incremented by one
Then if the other user wants to save his data the updated record will have lock_version = 0 and in the table the record has lock_version = 1 that means there is dirty data. In that case we can write code so that the second user will not be able to save the dirty data. He will need to refetch the data and update it.
In rails this support is already given with ActiveRecord. What you will need is only to add an extra column lock_version [aka. 'magic column'] with default value = 0 in the 'books' table. When updating the framework will confirm that the updating record has the same value as when the model was first read from database. If the values are equal then it will allow to update the record and then increment the value of lock_version column. Otherwise it will raise ActiveRecord::StableError.
You can deactivate this behavior by setting ActiveRecord::Base.lock_optimistically = false. If you want to override the name of the lock_version column you will need to invoke the set_locking_column method.
- id
- title
- writer
There is one Book with this properties:
- id => 1
- title => "My book title"
- writer => "Mr. Y"
User1 selects a Book (id=1) in to edit and then User2 selects the same Book (id=1) to edit. Now the User1 changed the value of writer to 'Zahid' and saved it. In the database following update query will be executed:
UPDATE books SET title = 'My book title', writer = 'Zahid' WHERE id = 1
And the User2 changed the value of title to 'Updated Title' and saved it. In the database following update query will be executed:
UPDATE books SET title = 'Updated Title', writer = 'Mr. Y' WHERE id = 1
As a result the update done by User1 will get lost.
This is a common problem and there are two different solutions of this problem:
1) Pessimistic Locking
2) Optimistic Locking
Pessimistic Locking:
Pessimistic locking functionality is provided by the database. This locking happens at the database level. In this locking you have to lock the record while selecting the record to update. You can lock a record while updating by using "FOR UPDATE" with the "SELECT" query [NOTE: There are some other locking clause except "FOR UPDATE" and for that read the database documentation].
And the rails ways to do this locking are:
1) while selecting use :lock => true
2) use lock! method of a model that is already selected
Example:
Book.transaction do
book = Book.find(1, :lock=> true)
book.writer = 'Zahid'
book.save!
end
This will lock the book record (id = 1) through out the specified transaction and prevent updates of this record during this time. But if the model is already selected and then you want to lock it for update in that case you can lock the model with lock! method:
book = Book.find(1)
book.lock!
The problem with Passimistic Locking is, if a user select a book to update it and then forget about it or the locking time gets extended for any reason then if there is any other call to update this model then the second call will just HANG, no error will be raised.
Optimistic Locking:
This is a tricky way of confirming data consistency but not locking the record. As here no record is locked so there is no chance of HANG. In this method a new column is added to a table that column contains a version of the record. Unlike Pessimistic Locking the protection is provided at the application level instead of database level.
Lets say the column name is lock_version and the default value is 0. So when the User1 select a Book to update then
- the lock_version for this model is 0
User2 selects the same Book to update and this again
- the lock_version for this model is 0
If any of the user update the Book model then:
- save the udpated model
- the lock_version is incremented by one
Then if the other user wants to save his data the updated record will have lock_version = 0 and in the table the record has lock_version = 1 that means there is dirty data. In that case we can write code so that the second user will not be able to save the dirty data. He will need to refetch the data and update it.
In rails this support is already given with ActiveRecord. What you will need is only to add an extra column lock_version [aka. 'magic column'] with default value = 0 in the 'books' table. When updating the framework will confirm that the updating record has the same value as when the model was first read from database. If the values are equal then it will allow to update the record and then increment the value of lock_version column. Otherwise it will raise ActiveRecord::StableError.
You can deactivate this behavior by setting ActiveRecord::Base.lock_optimistically = false. If you want to override the name of the lock_version column you will need to invoke the set_locking_column method.
Labels:
Data Consistency,
Lock,
optimistic locking,
pessimistic locking,
Rails
Subscribe to:
Posts (Atom)