Saturday, May 10, 2008

Actionwebservice and rails 2.0

Recently the management of a project of i was working on decided that we should upgrade our application to Rails 2.0. I instantly started to salivate, my mind listing all the fine new features I could finally make use of ( the application had been frozen to 1.2.3 ). So I sallied forth and jumped head first into making the upgrade.

For the most part the upgrade was easy, a handful of deprecated method calls had to be re-factored. No big deal.. The hard part of this upgrade is: The application communicates with mobile devices through XMLRPC. So, we need to maintain actionwebservice.. Sure I’d love to re-factor the whole application to REST but rewriting the Windows Mobile application in the client devices is not an option.



Before i lose you I’ll stop telling the story and jump into the how-to:



First. If you haven’t read the ‘Freezing and Autoloading Gems’ aritcle click ‘back’ and do that first as these steps build on that practice.



Don’t.. that is DO NOT try to unpack a version of actionwebservice gem into your vendor/gems directory I tried several different versions and none of them worked.. complaints about missing methods and such..



Do however checkout actionwebservice from it’s svn trunk




cd vendor/gems/
svn co http://svn.rubyonrails.org/rails/ousted/actionwebservice/ actionwebservice


once actionwebservice is installed add to your environment or an initializer.




require 'actionwebservice'


At first glance you might think you’re done. but i seems to have to add one more tweak to get things working for me. I needed to add my web service API definition to my load path. so i went back to environment.rb and added the following.




config.load_paths += %W( #{RAILS_ROOT}/app/apis )


Finally, to ensure that your tests will run (you are testing your api, right?) open your test_helper.rb or your spec_helper.rb and add the following line




require 'action_web_service/test_invoke'


And that allows your legacy web service API to ride modern rails..

Monday, May 5, 2008

Freezing and Autoloading Gems - Not just for Rails

Now this practice in part is counterintuitive to the beauty of ruby gems. After all, the DRY concept of gems means removing code from your application. Especially that code that is shared between multiple applications on the same server. Gems are easy to intstall, easy to share, and easy to maintain.



But sometimes you want to reduce the number of steps required to deploy your application to a new server or perhaps your application required a one time customization to a gem library or maybe for whatever reason you want to keep all code dependencies for your application in one tree.



There is a straightforward way to make this happen.



Create a place in your application tree to store the gems your app uses. I chose to create a vendor/gems/ directory. So create the directory and issue the commands below




cd vendor/gems/
unpack gemName


Now these gems are part of our application source tree but aren’t yet visible to our application environment. We need to add these gems load paths to our application load path. Open config/environment.rb and add the following code (place it inside the Rails::Initializer.run do |config| code block):




config.load_paths += Dir["#{RAILS_ROOT}/vendor/gems/**"].map do |dir|
File.directory?(lib = "#{dir}/lib") ? lib : dir
end


With this done.. you can go about your business and use gem ‘genName’ as you typically do, however, I like the sledgehammer approach.. it’s fun.. and i want to be lazy and have all the gems in my vendor/gems directory loaded auto-magically. Add the following code to your environment.rb or one of your initializers if you’re running Rails 2..




Dir[File.dirname(__FILE__) + "/../vendor/*"].each do |path|
gem_name = File.basename(path.gsub(/-\d+.\d+.\d+$/, ''))
gem_path = path + "/lib/" + gem_name + ".rb"
require gem_path if File.exists? gem_path
end


With this you can freeze any gem into your application and have that gem automatically load.

Saturday, May 3, 2008

Rails Migrations - Directly Import SQL

Recently a project i was working on required to integration of triggers and stored procedures. To my knowledge rails migrations do not have to ability to create and manage stored procedures. And why would it really? Stored Procedures and Triggers are database specific and not all Database engines support them.

Now, you can execute database specific queries directly through the connection like this:




ActiveRecord::Base.connection().execute( "call #{some custom query};" )


and in many instances this may be all you need.



However an issue (bug?) in ActiveRecord means that ActiveRecord does not understand the syntax used to set the delimiter.. a requirement for creating stored procedures and triggers.



In an effort to work around ActiveRecord limitations and still stay as DRY as possible by managing the application schema through migrations I created a monkey patch to the ActiveRecord MySQL connection adapter to load the sql commands directly to MySQL



It’s super simple.. here’s how..



Add the following code to your environment, initializers, or separately in the lib folder. If you take the lib folder approach don’t forget to require the file..




class ActiveRecord::ConnectionAdapters::MysqlAdapter
def import_sql(file)
conf = ActiveRecord::Base.configurations[RAILS_ENV]
sql_file = File.join(File.join(File.dirname(__FILE__), "sql" ), "#{file}.sql")
cmd_line = "mysql -h "+conf["host"]+" -D "+conf["database"]+ " --user="+conf["username"]+" --password="+conf["password"]+" < "+sql_file raise Exception, "Error executing " + cmd_line unless system(cmd_line)
end
end


Then.. create your sql commands.. i chose to store these commands in the db/ directory in a new folder called sql/


Create one file per command.. That is, one file to create your trigger and one file to remove your trigger.


I leave the practice of creating the actual sql syntax to you..


Then.. create your sql commands.. i chose to store these commands in the db/ directory in a new folder called sql/


Create one file per command.. That is, one file to create your trigger and one file to remove your trigger.


I leave the practice of creating the actual sql syntax to you..And this is a sample migration that used our new sql importing mechanism.




class AddTriggers < ActiveRecord::Migration
def self.triggers
%w( insert update )
end
def self.up
triggers.each do |trigger|
import_sql("t_#{trigger}_up.sql")
end
end
def self.down
triggers.each do |trigger|
import_sql("t_#{trigger}_down.sql")
end
end
end