... mostly about Ruby and Rails...

Donnerstag, 30. August 2007

Quickie: find a route with ./script/console

... so I was playing around with hobo and wanted to add another method to a controller. The controller was made by hobo scaffolding and looked like that one:


class InstancesController < ApplicationController
hobo_model_controller
end


In hobo, you add another method by calling it a 'web_method':


class InstancesController < ApplicationController
hobo_model_controller
web_method :chart

def chart
end
end


According to the documentation, this should be all, but when I tried to use it:


$ curl -v http://localhost:3000/instances/1/chart
> GET /instances/1/chart HTTP/1.1
> Host: localhost:3000
> Accept: */*
>
< HTTP/1.1 404 Not Found


and the server says:

Processing InstancesController#1 (for 127.0.0.1 at 2007-08-30 17:12:51) [GET]
Session ID: da18a6b4ef8b5d17773f4f4b3c117895
Parameters: {"action"=>"1", "id"=>"chart", "controller"=>"instances"}

Rendering /var/lib/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/templates/rescues/layout.rhtml (404 Page Not Found)


wait a second... "action"=>"1", "id"=>"chart"... this should be the other way around! Hobo did not seem to have added the correct route. Let's have a look at that with the rails console:


$ ./script/console
Loading development environment.
>> # List all routes
?> ActionController::Routing::Routes.named_routes.routes.each do |name, route|
?> puts "%20s: %s" % [name, route] if name.to_s.include? "chart"
>> end; nil
instance_chart: POST /instances/:id/chart/ {:action=> "chart", :controller=>"instances"}
=> nil
>>


Okay, here is the problem. The route is only added for POSTing it, not GETting :( So, let's use curl with a POST:


$ curl -v -d "some_data" http://localhost:3000/instances/1/chart
* Connected to localhost (127.0.0.1) port 3000 (#0)
> POST /instances/1/chart HTTP/1.1
> Host: localhost:3000
>
< HTTP/1.1 403 Forbidden
< Connection: close
< Date: Thu, 30 Aug 2007 15:26:59 GMT
< Set-Cookie: _supervisor_session_id=ee7182c092c5222820bf458316c19deb; path=/
< Status: 403 Forbidden
< Pragma: no-cache
< Cache-Control: no-store
< Server: Mongrel 1.0.1
< Content-Type: text/html; charset=utf-8
< Content-Length: 17
< Expires: 0
<
* Closing connection #0


Aha, a change. So, that's how hobo wants to do it. But I need to use GET. So, config/routes.rb needs an additinal route:


map.named_route("instance_chart_get",
"instances/:id/chart",
:controller => 'instances',
:action => 'chart',
:conditions => { :method => :get } )


And now, what does curl, now:


$ curl -v http://localhost:3000/instances/1/chart
* Connected to localhost (127.0.0.1) port 3000 (#0)
> POST /instances/1/chart HTTP/1.1
> Host: localhost:3000
>
< HTTP/1.1 403 Forbidden
< Connection: close
< Date: Thu, 30 Aug 2007 15:26:59 GMT
< Set-Cookie: _supervisor_session_id=ee7182c092c5222820bf458316c19deb; path=/
< Status: 403 Forbidden
< Pragma: no-cache
< Cache-Control: no-store
< Server: Mongrel 1.0.1
< Content-Type: text/html; charset=utf-8
< Content-Length: 17
< Expires: 0
<
* Closing connection #0


and in the console:


$ ./script/console
Loading development environment.
>> ActionController::Routing::Routes.named_routes.routes.each do |name, route|
?> puts "%20s: %s" % [name, route] if name.to_s.include? "chart"
>> end; nil
instance_chart: POST /instances/:id/chart/ {:action=>"chart", :controller=>"instances"}
instance_chart_get: GET /instances/:id/chart/ {:action=>"chart", :controller=>"instances"}
=> nil
>>


Fine for me!!

Mittwoch, 20. Juni 2007

Quickie: using alias_method_chain

... while doing some development on my ddcplugin, I had to use the feature alias_method_chain to extend some of ActionController's functionality, but I wasn't able to find a good description on the net on how to use that. So, here is a quicky about that.

You have:

class MyClass
def aMethod
puts "Hi"
end
end

you want to add a feature to aMethod

class MyClass
def aMethod_with_feature
puts "feature"
aMethod_without_feature
end
alias_method_chain :aMethod, :feature
end

Ok, simply put, you have to name the new method 'aMethod_with_feature'. If you want to call the old method, you use 'aMethod_without_feature'. The rest is done by alias_method_chain and your new functionality will be in. Remember that you have to specify the names of the methods as symbols. *thumbs_up*

-alex


References:

“alias_method_bling
New in Rails: Module#alias_method_chain
Interesting technique using #alias_method_chain

Freitag, 15. Juni 2007

Rails Database Loadbalancing with DynamicDatabaseChanger Example

Okay, as requested by DrNic, I will give a quick example on how to loadbalance a Rails application to a clustered database with my DynamicDatabaseChanger plugin.

The assumptions for this to work is that you can connect to whatever database server you want to for reading and for writing. All instances have the same database.

... and of course you should have installed the plugin from svn://rubyforge.org/var/svn/ddcplugin

So, here we go. First, we need to define all the additional DB instances that we could connect to in database.yml:


shared: &shared
adapter: mysql
encoding: utf8
username: rails
password: rails_password
database: app_production

production:
<<: *shared
host: db
production0:
<<: *shared
host: db1
production1:
<<: *shared
host: db2
production2:
<<: *shared
host: db3


As you can see, the standard production DB is located on host db, the additional ones on db[1-3]. The rest of the definitions is the same for all.

Next, we have to tell the plugin when to use what DB. We do this in environment.rb:


ActionController::Base.dynamic_database_change_guard=
Proc.new{ |request|
prodDBs = ActiveRecord::Base.configurations.keys.select
{|k| k.include? "production" }
prodDBs[rand(prodDBs.size)]
}

What happens here? This is the central decision point of the plugin. The implementation here simply fetches all affected DB definitions from database.yml and returns a random one.

That's all! It's just that simple.


What happens in the background? The plugin adds an around filter to the beginning of the ActionController processing. In this hook, the above function is called and depending on the result, the connection of ActiveRecord::Base is changed. From then on, the action is processed using the changed database connection. After finishing the request, the connection is reseted to the standard one.

I implemented this in my lab at home( the hard part was the database set up ;-) and it worked quite well.

More examples can be found in the README.

So, try it out for your self and tell me your results.

-alex

P.S. instead of using 4 different DB servers for testing this, it's possible to use 1 DB server with 4 different users and see in the DB log the different users connecting:


shared: &shared
adapter: mysql
encoding: utf8
database: app_production
host: db

production:
<<: *shared
username: rails
password: rails_password
production0:
<<: *shared
username: rails1
password: rails1_password
production1:
<<: *shared
username: rails2
password: rails2_password
production2:
<<: *shared
username: rails3
password: rails3_password

Donnerstag, 14. Juni 2007

Uploaded the DynamicDatabaseChanger Plugin to RubyForge

Yesterday, I moved the first attempt of my DynamicDatabaseChanger Plugin for RoR onto RubyForge. It can be viewed at

http://rubyforge.org/projects/ddcplugin/

and the sources can be installed with: svn checkout svn://rubyforge.org/var/svn/ddcplugin/trunk

This plugin can be used to change the database connection of the RoR application dynamically for each request.

What is this good for?

2 real world usage come to my mind at the moment( and have been tested)
- RubyOnRails database loadbalancing ( similar to Dr. Nic's Magic Multi-Connections: A “facility in Rails to talk to more than one database at a time” or ActsAsReadonlyable from Revolution On Rails)
- using a different database for a set of IPs

In my next posts I will show you how to use the plugin and give examples for the cases above...

I hope that somebody will find it to be useful and give me some feedback.

-Alex