Sunday, December 13, 2009

Storing single values in Rails with ActiveRecord

Sometimes you want to store a bunch of single values that are not related to anything, and of course they are not constant values, it is better to put it in a constant or in a yaml config file if they are constant values.

This is what i usually do, maybe there is a better solution out there, but this one is very simple.
It's kind of make an active record object to work like a Hash, or key-value store.

Create the migration(name it the way you like it):



class CreateSingleValues < ActiveRecord::Migration
def self.up
create_table :single_values do |t|
t.string :key
t.string :value
end
end

def self.down
drop_table :single_values
end
end



And this is the code:



class SingleValue < ActiveRecord::Base

def self.[](key)
val = self.find_by_key(key.to_s)
val.value if val
end

def self.[]=(key, value)
val = self.find_by_key(key.to_s)
if val
value.nil? ? val.delete : val.update_attribute(:value, value)
else
self.create(:key => key.to_s, :value => value) unless value.nil?
end
end

end




I add the [] operator methods to the active record class.

this way I can store values like this:
SingleValue[:foo] = "bar" or SingleValue["foo"] = "bar"

and then retrieve it with:
SingleValue[:foo] or SingleValue["foo"]

It returns nil if the key is not found, and delete the record if you assign a nil value.
For example SingleValue[:foo] = nil will delete SingleValue[:foo] if it exists.
Also you can do substitution on the key like SingleValue["foo_#{bar}"] if you are going to get the key at runtime.

You may find convenient to add an index on the key column for quicker searches if you are storing many values.

Note that you can only store single string values(or numbers that will be returned as strings).
If you want to store complete objects you can serialize them into the value column with ActiveRecord "serialize" method,
you may have to change the column type to 'text' , the implementation will be the same I think, try it!

Bye!

Friday, November 20, 2009

Accessing soap web services in Ruby using soap4r.

Recently I had to access a soap web service written in .NET with a Rails application, the most "common" way is using the soap4r gem. There are another alternatives, one of them is "handsoap" but i decided to go with soap4r, because it looked simpler and the project is more mature. The bad thing is that is difficult to find examples, that's why I am writing this article to help someone who does not have any prior experience with this library.

Standard ruby installation comes with soap4r but it's better to use the latest version, so install the gem.

sudo gem install soap4r

Then you should know the url of the wsdl of the service you want to consume, we are going to use this which is freely available:

http://www.webservicex.net/CurrencyConvertor.asmx?WSDL

once you have the url, there are 2 options:
1. create proxy classes with a utility called wsdl2ruby which resides on the bin directory of your library installation.
2. create the driver and call the methods directly passing a hash in the form of an XML as parameter.

The first one didn't work for me because the classes were not representing exactly the wsdl. There are possibly some wsdl directives which the utility doesn't recognizes, that depends on the service. So my only choice was the second one which ended to be more intuitive.

Hashes and XML
It's very easy to transform xml into hashes and viceversa, in Rails if you have a hash instance you can simply call my_hash.to_xml and it returns the xml string with the keys as nodes and the data associated inside.

these are a couple of examples

For example, suppose you have this hash:
 
{
"band" => {
"guitars" => [{"name" => "Allan Holdsworth"},{"name" => "Frank Gambale"}],
"drums" => "Vinnie Colaiuta",
"bass" => "Gary Willis"
}
}


it will turn into this XML:


<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<hash>
<band>
<bass>Gary Willis</bass>
<drums>Vinnie Colaiuta</drums>
<guitars type=\\\"array\\\">
<guitar>
<name>Allan Holdsworth</name>
</guitar>
<guitar>
<name>Frank Gambale</name>
</guitar>
</guitars>
</band>
</hash>


The idea is very simple, xml tags can be nested defining hashes of hashes, and a tag which expects several same tags on the inside has to be defined with an array.
You can go the other way with Hash.from_xml and passing the xml

So Hash.from_xml(band.to_xml) should return the original hash.



Now lets checkout the web service definition

you can ckeckout the web service methods in:
http://www.webservicex.net/CurrencyConvertor.asmx

the web service definition is in:
http://www.webservicex.net/CurrencyConvertor.asmx?WSDL

you can go to the only method available in this service on:
http://www.webservicex.net/CurrencyConvertor.asmx?op=ConversionRate
go to the section which describes the soap protocol and there is the exchanging xml messages format look in the soap protocol part, this is where it describes the method parameters:



...
<ConversionRate xmlns=\"http://www.webserviceX.NET/\">
<FromCurrency>AFA or ALL or DZD ....</FromCurrency>
<ToCurrency>AFA or ALL or DZD ...</ToCurrency>
</ConversionRate>
...



So the parameters will be the hash:

{"FromCurrency" => "...", "ToCurrency" => "..."}


and the response will be in:



...
<ConversionRateResponse xmlns=\"http://www.webserviceX.NET/\">
<ConversionRateResult>double</ConversionRateResult>
</ConversionRateResponse>
...



in this case the response will be only a "double"


Now the code:

In rails add this to your environment.rb to include the latest library version:

config.gem 'soap4r', :lib => 'soap/wsdlDriver', :version => '1.5.8'

you should change the version number to the latest you have install


If you are using just Ruby:


require "rubygems"
gem 'soap4r'
require "soap/wsdlDriver"


and this is the Ruby code:




require "rubygems"
gem "soap4r"
require "soap/wsdlDriver"
wsdl = "http://www.webservicex.net/CurrencyConvertor.asmx?WSDL"

driver = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver

#driver.wiredump_dev = STDOUT

params = {
"FromCurrency" => "USD",
"ToCurrency" => "UYU"
}

puts driver.conversionRate(params).conversionRateResult




with driver.wiredump_dev = STDOUT all the exchanging messages are going to STDOUT. Uncomment this if you want to know the exchanging xml messages.
The response is everything inside the "<conversionrateresult>" tag, in this case is a simple value which can be parsed easily. Normally it's an xml that can be parsed with any Ruby xml library.

So, the steps are:
-create the driver from the wsdl
-build a hash based on the web service method parameters, which can be taken from the asmx method description
-call the driver method with the hash as parameter, and call the xxxResult method, to get the string response

hope it helps somebody

Thursday, March 26, 2009

Send mails asynchronous from a Rails app



Dealing with mails is not an easy task, sending mails synchronously is not an option, because maybe a user wants to send invitations to lots of people and this takes time, or even if it is only one, maybe there is another user trying to send another invitation and the smtp will collapse.




I looked for a plugin or something that could handle this but I didn't like most of them.
The option that I liked most was "ar_mailer", the plugin basically saves the mails to the database and provide you with a process for send them later, programming this process with a cron job or running it as a daemon. But it does not manage prioritys!, so for example in a common web app suppose a user wants to send invitations and another user wants to reset his password. If the user sending invitations starts first, the user requesting his password will have to wait for all the invitations to be sent before he could receive the password reset. If you decide to send the urgent mails directly without going thru ar_mailer is the same, because maybe your smtp is busy sending invitations emails and it will refuse to send the urgent ones. So you must have prioritys!, there are some solutions there but they are a little bit complicated.




So I come up with a very easy solution that do what ar_mailer do but in a simpler way, and that could handle prioritys, I am not saying it is better than ar_mailer because is a LOT more "amateurish" but for this problem I could not use ar_mailer.




What I did was create a model "MailQueue", this is the migration for the model:






class CreateMailQueue < ActiveRecord::Migration
def self.up
create_table :mail_queue do |t|
t.text :mail
t.integer :priority, :default => 0
t.timestamps
end
end

def self.down
drop_table :mail_queue
end
end






and this is the MailQueue class:







class MailQueue < ActiveRecord::Base
set_table_name 'mail_queue'

serialize :mail

#/* the mail object generated with the 'create' method of the mailer
# * the priority of the email, the highest, the most
# */
def self.enqueue(mail, priority = 0)
MailQueue.create(:mail => mail, :priority => priority)
end

#/* max: max amount of mails to send per method call
# * time: the maximum amount of time the method should run(in minutes)
# * chunks: the amount of mail to send without checking
# * if there are new ones with higher priority
# */
def self.deliver(max, time, chunks = 5)
init_time = Time.now
delivered = 0
while true
queued_mails = MailQueue.find(:all, :order => "priority DESC, created_at", :limit => chunks)
return if queued_mails.empty?
queued_mails.each do |queued_mail|
return if (delivered == max) || (Time.now - init_time > time * 60)
begin
ActionMailer::Base.deliver(queued_mail.mail)
queued_mail.delete
delivered += 1
rescue
# wait for the smtp to react
sleep 10
end
end
end
end

end





notice I change the model default table name because "mail_queueS" was not very semantic...





The model class has 2 class methods:




The first one creates a MailQueue with a mail attribute, which is the saved mail generated by ActionMailer serialized to the database, and sets a priority for this mail. (Remember that with an ActionMailer class, is possible to use: "deliver_method_name" to send it instantly or "create_method_name" to save it for send it later.)
Serializing is done thru ActiveRecord's serialize method which is called at the beginning of the class. You can call this method in your controller.




The second is supposed to be call with script/runner and automate it with cron to run in a periodical basics. The way in which you decide to run it depends totally on the server environmet you have, for example if you use a shared hosting, most of them limits the amounts of email that can be send per hour. So I add some parameters to customize this behaviour, they are explained at the beginning of the method. Basically this method chechs the MailQueue and retrieves them in "chunks" to send them one by one. Each time it querys the database it orders the queue first by priority and then by the date it was created.




So in your controller, instead of doing:






Notifier.deliver_invite(...)





being Notifier the ActionMailer class, you will do something like this:






MailQueue.enqueue(Notifier.create_invite(...), 0)





and sets a priority in the last argument, the default is 0, so in this case you don't need to put it, but the higher the priority number is, first is going to be retrieved in the queue.
So for the non urgent mails I set a priority of 0, and for the urgent ones I set a higher priority, basically I have 0 and 1, but maybe you need extremely urgent mails, you can set a priority of 2 or higher. After the first "chunk"(I don't know if it is the appropriate word, but I like it in this context) of mails is sent, it queries again to see if another mail was created with higer priority. It's possible to set the chunk, but the default is 5.




So you have to add a cron task for example to run this every hour.
script/runner can be called this way:





>path_to_rails_app/script/runner 'MailQueue.deliver(200, 55)'




to deliver a max of 200 emails in a maximum period of 55 minutes.




Another detail is that changing this method a little bit, it could be set to run as a daemon(I mean never ending..), but first is an instance of rails that you must have up all the time so it's not very efficient, and second maybe it hungs up and you didn't notice, so I think the safest and efficient way is running it with a cron task, and tell the method to run for a maximum specific period of time.




I think the code speaks for itself, if you have any questions or remarks, please comment or send an email.




NOTE: Now there are other options like delayed_job which is great, anyway I think the code is interesting.

Saturday, February 21, 2009

Using will_paginate rails plugin with ajax.

will_paginate is great, but... it doesnt support ajax, here the developer explains why.

But actually, it is very common that we would like to use will_paginate with ajax.

On this great post, it explains the hack (READ THAT ARTICLE BEFORE). But how can we use it? for example if we want to update a partial using the collection that returns me the "paginate" method added by the plugin in any active record class.

This is what i did:

First i added the helper(in the mentioned article explains how):



class RemoteLinkRenderer < WillPaginate::LinkRenderer
def prepare(collection, options, template)
#@remote = options.delete(:remote) || {} #(1)
options.delete(:remote) #(2)
@remote = options || {} #(3)
super
end

protected def page_link(page, text, attributes = {})
@template.link_to_remote(text, {:url => url_for(page), :method =>:get}.merge(@remote))
end
end



The options parameter of the "prepare" method, is the hash that will be passed to the options parameter of the link_to_remote helper, but i had to change lines (2) and (3) instead of (1), because the ruby Hash delete method returns the associated value of the key instead of the resulting hash.

So, suppose i want to render a list of users:

This is my controller method:



def index
@users = User.paginate(...options..., :page => params[:page], :per_page => 5)
respond_to do |format|
format.html
format.js { render :action => 'show' } #(4)
end
end



and this is my view (index.html.erb)



<p>
<div id="paginate_refresh">
<%= will_paginate (@users, :renderer => 'RemoteLinkRenderer',
:loading => "some_js_function()", :complete => "some_other_js_function()") %>
</div>
</p>
<p>
<div id="user_refresh">
<%= render :partial => "user", :collection => @users %>
</div>
</p>



I have a _user.html.erb partial with the user information(i will not show it here because it doesn't matter).

My idea was to use 2 different divs for updating the information and the pagination links.

As you can see i also pass to the will_paginate helper method, the callbacks of my ajax request, this options will go directly to the couple of link_to_remote helpers that the will_paginate helper method renders, you can use here all the options provided by link_to_remote helpers.

In the controller method at (4), i render 'show', which is the rjs template(show.js.rjs) that will be rendered when the call is an ajax request.

And here is this template:



page.replace_html 'user_refresh', :partial => "user", :collection => @users
page.replace_html 'paginate_refresh', will_paginate (@users, :renderer =>'RemoteLinkRenderer',
:loading => "some_js_function()", :complete => "some_other_js_function()")



And that's it!

I just refresh the two divs with the new information and the new will_paginate links.

Any comments or questions, please leave a message.

Bye