ODBC on Mac Lion with Ruby Sequel - ruby

I'm having issues getting Sequel to connect to a MS SQL Database.
I have installed the following:
UnixODBC
FreeTDS
I have configured both software packages, and the following commands allow me to connect to my database without a problem:
isql
tsql
osql
However, when I try it from Ruby code using the Sequel.odbc command, I receive the following error:
ODBC::Error: IM003 (0) [iODBC][Driver Manager]Specified driver could not be loaded.
This is as far as I can get. I used to receive another error first, but managed to solve that by redoing the configuration part. Guess I missed something there.
EDIT
This is the code for my base talker class. It basically loads a YAML file like rails does, holding the database settings and establishes a connection to the database.
This seems to be working, trying it manually returns me a DB object from sequel:
module Talkers
require 'yaml'
require 'sequel'
class BaseTalker
# This function will load the desired settings as a hash from the database.yml file
# in the config folder. The data will be returned as a hash following the standard
# YAML structure.
def self.load_config(name)
cfg = YAML::load(File.open(File.join(ENV['API_ROOT'], 'config', 'database.yml')))
cfg.key?(name) ? cfg[name] : nil
end
# This function will establish a connection with the Florensia User database and return
# the Sequel database object, so that queries can be executed against the database.
def self.connect_to_user_db
settings = self.load_config("florensia_user_#{ENV['RACK_ENV']}")
Sequel.odbc settings['dsn'], :db_type => settings['adapter'], :user => settings['user'], :password => settings['password']
end
end
end
The class below inherits from the talker and performs certain actions for a User. It contains the DB logic specific to the game. When I call this logic, I receive the errors:
module Talkers
require 'yaml'
require 'sequel'
class LoginDbTalker < BaseTalker
#
# Bans the specified User from the game. The function requires the following information
# to be supplied in order for the ban to be properly set:
# - id : The identifier of the account.
# - gm_name : The name of the GM setting the ban.
# - punish_code : The punishment code being applied on the account.
# - days : The duration of the ban in days, starting from today.
#
# The function will return true if the ban has been properly set; otherwise the function
# will return false.
def self.ban_user(options = {})
return false if options.empty?
db = self.connect_to_user_db
ds = db[:tbl_User].filter(:id => options[:id])
ps = ds.prepare(:update, :apply_ban)
ps.call(
:punishcode => options[:punish_code],
:punishstory => "Banned by #{options[:gm_name]}",
:punishdate => Date.today,
:punishfreedate => (options[:days].to_i == -1) ? (Date.today + (30 * 265)) : (Date.today + options[:days].to_i))
true
rescue Exception => e
puts "Exception caught in ban_user: #{e.to_s}"
puts "Provided variables: id=#{options[:id]}, gm_name=#{options[:gm_name]}, punish_code=#{options[:punish_code]}, days=#{options[:days]}"
false
end
#
# Unbans the specified User from the game. The function requires the following information
# to be supplied in order for the ban to be properly lifted:
# - id : The identifier of the account.
# - gm_name : The name of the GM removing the ban.
#
# The function will return true if the ban has been properly lifted; otherwise the function
# will return false.
def self.unban_user(options = {})
db = self.connect_to_user_db
ds = db[:tbl_User].filter(:id => options[:id])
ps = ds.prepare(:update, :lift_ban)
ps.call(
:punishcode => '0',
:punishstory => "Ban removed by #{options[:gm_name]}",
:punishdate => Date.today,
:punishfreedate => Date.today
)
true
rescue Exception => e
puts "Exception caught in unban_user: #{e.to_s}"
puts "Provided variables: id=#{options[:id]}, gm_name=#{options[:gm_name]}"
false
end
#
# Kicks the specified User from the game, regardless on the server or character he currently is on.
# This requires a direct connection to the game servers so a specialized command can be sent that
# instruct the server to close the connection with the offending player.
# The function returns true if the kick succeeded; otherwise false.
def self.kick_player(id)
false
end
end
end
Calling any of the ban/unban functions results in the error message.
EDIT2
I've added the folder /Library/ODBC and linked all config files to there for iODBC. This removes the error I had before and now brings me this error:
ODBC::Error: 01000 (20002) [FreeTDS][SQL Server]Adaptive Server connection failed
So it seems I made some progress again

I recommend you use the tinytds adapter instead of the odbc adapter if you are connecting to Microsoft SQL Server from OS X. I know there are people who have got Sequel/ODBC running on non-Windows boxes, but I only have experience with Sequel/ODBC on Windows.

Related

Using ADAL with Logstash Custom Plugin const errors

I'm working to create a Logstash Input plugin to utilize ADAL for integration with the Office 365 Management Activity API's. I've written the individual components to get a token, use that token to subscribe, and to pull activity log data.
Now I'm working to integrate into the Logstash framework, and running into issues where Logstash is complaining that it's doesn't know what ADAL is, even though I have it required.
All the same code works independently outside of Logstash, just not within the plugin class.
This is my first foray into Ruby, so I'm pretty stumped. Any help?
Error message from Logstash:
[2018-09-16T00:51:32,816][INFO ][logstash.pipeline ] Starting pipeline {:pipeline_id=>"main", "pipeline.workers"=>8, "pipeline.batch.size"=>125, "pipeline.batch.delay"=>50}
[2018-09-16T00:51:33,921][INFO ][logstash.inputs.office365managementapi] Starting Office 365 Management API input...
[2018-09-16T00:51:34,246][ERROR][logstash.pipeline ] Error registering plugin {:pipeline_id=>"main", :plugin=>"<LogStash::Inputs::Office365ManagementApi client_id=>\"redacted\", tenant_id=>\"redacted\", tenant_domain=>\"redacted\", private_key=>\"/tmp/o365.pfx\", subscriptions=>[\"Audit.AzureActiveDirectory\", \"Audit.Exchange\", \"Audit.SharePoint\", \"Audit.General\", \"DLP.All\"], id=>\"fb61b83b76494f098a0a7e24391779ee1212f0d9adf8ef8dedae4424e8dedb57\", enable_metric=>true, codec=><LogStash::Codecs::Plain id=>\"plain_c7c9d514-5d23-459d-98ea-87d250e7a00c\", enable_metric=>true, charset=>\"UTF-8\">, resource=>\"https://manage.office.com\">", :error=>"uninitialized constant LogStash::Inputs::Office365ManagementApi::ADAL::Logging\nDid you mean? LogStash::Logging", :thread=>"#<Thread:0xca2e135 run>"}
[2018-09-16T00:51:34,367][ERROR][logstash.pipeline ] Pipeline aborted due to error {:pipeline_id=>"main", :exception=>#<NameError: uninitialized constant LogStash::Inputs::Office365ManagementApi::ADAL::Logging
Did you mean? LogStash::Logging>, :backtrace=>["org/jruby/RubyModule.java:3343:in `const_missing'", "/usr/local/Cellar/logstash/6.2.4/libexec/vendor/local_gems/82bdbf8d/logstash-input-office365_management_api-1.0.0/lib/logstash/inputs/office365_management_api.rb:70:in `register'", "/usr/local/Cellar/logstash/6.2.4/libexec/logstash-core/lib/logstash/pipeline.rb:342:in `register_plugin'", "/usr/local/Cellar/logstash/6.2.4/libexec/logstash-core/lib/logstash/pipeline.rb:353:in `block in register_plugins'", "org/jruby/RubyArray.java:1734:in `each'", "/usr/local/Cellar/logstash/6.2.4/libexec/logstash-core/lib/logstash/pipeline.rb:353:in `register_plugins'", "/usr/local/Cellar/logstash/6.2.4/libexec/logstash-core/lib/logstash/pipeline.rb:500:in `start_inputs'", "/usr/local/Cellar/logstash/6.2.4/libexec/logstash-core/lib/logstash/pipeline.rb:394:in `start_workers'", "/usr/local/Cellar/logstash/6.2.4/libexec/logstash-core/lib/logstash/pipeline.rb:290:in `run'", "/usr/local/Cellar/logstash/6.2.4/libexec/logstash-core/lib/logstash/pipeline.rb:250:in `block in start'"], :thread=>"#<Thread:0xca2e135 run>"}
[2018-09-16T00:51:34,418][ERROR][logstash.agent ] Failed to execute action {:id=>:main, :action_type=>LogStash::ConvergeResult::FailedAction, :message=>"Could not execute action: LogStash::PipelineAction::Create/pipeline_id:main, action_result: false", :backtrace=>nil}
Code is below:
# encoding: utf-8
require "logstash/inputs/base"
require "logstash/namespace"
require "stud/interval"
require "socket" # for Socket.gethostname
require "json"
require 'net/http'
require 'uri'
# Using this input you can receive activities from the Office 365 Management API
# ==== Security
# This plugin utilizes certificate authentication with the Office 365 Management API
# to generate an access token, which is then used for all subsequent API activities.
# If the token expires, the plugin will request a new token automatically.
# All communication for this plugin is encrypted by SSL/TLS communication.
class LogStash::Inputs::Office365ManagementApi < LogStash::Inputs::Base
config_name "office365_management_api"
# Codec used to decode the incoming data.
# This codec will be used as a fall-back if the content-type
# is not found in the "additional_codecs" hash
default :codec, "plain"
# Fix for broken ruby ADAL
module ADAL
class TokenRequest
module GrantType
JWT_BEARER = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
end
end
end
# Client ID generated through your custom application in Azure AD
# https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps
config :client_id, :validate => :string, :required => true
# Tenant ID/Directory ID of your Office 365 tenant
# https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Properties
config :tenant_id, :validate => :string, :required => true
# Your Office 365 tenant domain, ie. yourdomain.onmicrosoft.com
config :tenant_domain, :validate => :string, :required => true
# Resource you are requesting access to. This defaults to https://manage.office.com and shouldn't change unless necessary.
config :resource, :validate => :string, :default => 'https://manage.office.com'
# PFX Private key for your Application Certificate you created
config :private_key, :validate => :path
# Private key password if one was used
config :private_key_password, :validate => :string, :default => nil
# Activity subscriptions you want to monitor
# These can be one or many of:
# Audit.AzureActiveDirectory
# Audit.Exchange
# Audit.Sharepoint
# Audit.General
# DLP.All
config :subscriptions, :validate => :array, :default => ["Audit.AzureActiveDirectory", "Audit.Exchange", "Audit.SharePoint", "Audit.General", "DLP.All"]
public
def register
require "adal"
#logger.info("Starting Office 365 Management API input...")
#host = Socket.gethostname
# ADAL supports four logging options: VERBOSE, INFO, WARN and ERROR.
ADAL::Logging.log_level = ADAL::Logger::VERBOSE
end # def register
def get_token
#logger.info("Generating access token...")
if #private_key_password.nil?
pfx = OpenSSL::PKCS12.new(File.read(#private_key))
else
pfx = OpenSSL::PKCS12.new(File.read(#private_key), #private_key_password)
end
authority = ADAL::Authority.new("login.microsoftonline.com", #tenant_domain)
client_cred = ADAL::ClientAssertionCertificate.new(authority, #client_id, pfx)
result = ADAL::AuthenticationContext
.new("login.microsoftonline.com", #tenant_domain)
.acquire_token_for_client(#resource, client_cred)
case result
when ADAL::SuccessResponse
puts 'Successfully authenticated with client credentials. Received access ' "token: #{result.access_token}."
# Create class variable for reuse of Access Token
#access_token = result.access_token
#http_headers = {
'Authorization' => "Bearer #{#access_token}",
'Content-Type' => 'application/x-www-form-urlencoded'
}
when ADAL::FailureResponse
puts 'Failed to authenticate with client credentials. Received error: ' "#{result.error} and error description: #{result.error_description}."
exit 1
end
end #def get_token
def check_subscription
#logger.info("Checking for proper subscriptions...")
#subscriptions.each do |sub|
sub_uri = URI("https://manage.office.com/api/v1.0/#{#tenant_id}/activity/feed/subscriptions/start?contentType=#{sub}")
sub_http = Net::HTTP.new(sub_uri.host, sub_uri.port)
sub_http.use_ssl = true
sub_resp = http.post(sub_uri.request_uri, data = "", #http_headers)
case sub_resp
when Net::HTTPSuccess
puts "Created subscription to #{sub} in tenant #{#tenant_id}..."
when Net::HTTPUnauthorized
puts "Authentication Error Encountered: #{sub_resp.message}"
when Net::HTTPServerError
puts "Server Error Encountered: #{sub_resp.message}"
else
puts "Unknown Error Encountered: #{sub_resp.message}"
end
end
end #def check_subscription
def run(queue)
# we can abort the loop if stop? becomes true
while !stop?
#event = LogStash::Event.new("message" => #message, "host" => #host)
#decorate(event)
#queue << event
raise 'Error getting token' unless get_token().status == 0
# because the sleep interval can be big, when shutdown happens
# we want to be able to abort the sleep
# Stud.stoppable_sleep will frequently evaluate the given block
# and abort the sleep(#interval) if the return value is true
Stud.stoppable_sleep(#interval) { stop? }
end # loop
end # def run
def stop
# nothing to do in this case so it is not necessary to define stop
# examples of common "stop" tasks:
# * close sockets (unblocking blocking reads/accepts)
# * cleanup temporary files
# * terminate spawned threads
end
end # class LogStash::Inputs::Office365ManagementApi
There appears to be a bug when using empty passwords with logstash.
The code responsible for this comes from the calculate_property method in HttpClient , that treats empty strings as nil:
default = nil if default.is_a?(String) && default.empty? # Blanks are as good as nil
uri_value = nil if uri_value.is_a?(String) && uri_value.empty?
One way to fix this is by upgrading to the latest Logstash, if you are currently using an older version.

How to print postgres "raise notice" output from Sequel?

Raise notice is typically used to debug PSQL scripts in postgres (link).
The docs say that there's some kind of support for printing notices when using the pg gem, but there's no info on how to use this proc, what it yields, possible (probable?) caveats etc.
Does anyone have a working code example for production and/or development? Ideally, I'm looking for a solution that allows PG notices to be printed out in development when Sequel logging is enabled.
When I do:
DB = Sequel.connect(
ENV['DATABASE_URL'],
notice_receiver: lambda{ |x| binding.pry }
)
the notice_receiver lambda never gets called once I execute a function that raises a notice. I.e
[1] pry(#<Psql::CalculateMasterBalancesTest>)> DB.select{ |o| Sequel.function(:emit_notice) }.first
I, [2017-05-17T16:51:56.746003 #23139] INFO -- : (0.000335s) SELECT emit_notice() LIMIT 1
=> {:emit_notice=>""}
where emit notice is:
CREATE OR REPLACE FUNCTION emit_notice()
RETURNS VOID AS $$
BEGIN
RAISE NOTICE 'NOTICE ME!!!';
END;
$$ LANGUAGE plpgsql;
and it works from PgAdmin:
NOTICE: NOTICE ME!!!
Total query runtime: 21 ms.
1 row retrieved.
UPDATE
Alejandro C gave a good working example, and it seems that notices don't get distributed with the notice_receiver hook. For example:
Sequel.connect(DB.opts.merge(:notice_receiver=>proc{|r| puts r.result_error_message})){ |db|
db.do("BEGIN\nRAISE NOTICE 'foo';\nEND;")
}
prints nothing, and:
Sequel.connect(DB.opts.merge(:notice_receiver=>proc{|r| puts r.result_error_message})){ |db|
db.do("BEGIN\nRAISE WARNING 'foo';\nEND;")
}
Prints
WARNING: foo
Since Sequel just calls set_notice_receiver from PG, I guess I should file a bug report with PG.
EDIT 2
Yet when I try things just with the PG gem I get
conn = PG.connect( :dbname => 'db_test', user: 'test', password: 'test', host: '127.0.0.1' )
conn.set_notice_receiver{|r| puts r.result_error_message }
conn.exec("SELECT emit_notice()")
NOTICE: NOTICE ME!!!
=> #<PG::Result:0x0000000405ac18 status=PGRES_TUPLES_OK ntuples=1 nfields=1 cmd_tuples=1>
So at this point I'm a bit confused...
EDIT 3
Posted an issue GitHub...
EDIT 4
Ah, apparently there's another options you need to use, client_min_messages needs to be set to :notice as so:
DB = Sequel.connect(
ENV['DATABASE_URL'],
notice_receiver: proc{|r| puts r.result_error_message},
client_min_messages: :notice
)
and this works
You pass in your own proc which gets the notice as a string. To have it trigger on notices and not just warnings and above, use client_min_messages. For example:
a = nil
Sequel.connect(
DB.opts.merge(
notice_receiver: proc{|r| a = r.result_error_message},
client_min_messages: :notice)) { |db|
db.do("BEGIN\nRAISE WARNING 'foo';\nEND;")
}
a == "WARNING: foo\n" # true

Sequel Model is not mapping all columns by default

Sequel Model is not mapping all columns by default.
I'm using:
Sequel version 4.24.0
Ruby version 1.9.3p194
Windows Server 2008 Enterprise
SQLServer 2008 R2 Enterprise
Below is a brief script that shows the problem:
require "sequel"
DB = Sequel.ado(:conn_string=>CONNECTION_STRING)
print "Loading LnProfile... "
class LnProfile < Sequel::Model(:lnprofile)
# attr_accessor :crawl_start_time
end
puts "Done!"
id = "808d8421-6c05-471e-addc-6165de044ad3"
# crawl_start_time field shouldn't be empty
p1 = LnProfile.where(:id=>id).first
puts ".id:#{p1.id.to_s}:." # => .id:808d8421-6c05-471e-addc-6165de044ad3:.
puts ".crawl_start_time:#{p1.crawl_start_time.to_s}:." # => .crawl_start_time::.
# same record, different data
p2 = LnProfile.select_all.select_append(:crawl_start_time).where(:id=>id).first
puts ".id:#{p2.id.to_s}:." # => .id:808d8421-6c05-471e-addc-6165de044ad3:.
puts ".crawl_start_time:#{p2.crawl_start_time.to_s}:." # => .crawl_start_time:2016-01-10 12:02:29 -0300:.
Below is the output of the script above:
Loading LnProfile... Done!
.id:{808D8421-6C05-471E-ADDC-6165DE044AD3}:.
.crawl_start_time::.
.id:{808D8421-6C05-471E-ADDC-6165DE044AD3}:.
.crawl_start_time:2016-01-10 12:02:29 -0300:.
Please advice. This issue is dirtying the database when the code loads an object and saves it again few lines above.
Thanks in advance.
Try using the tinytds adapter, it is much more robust than the ado adapter.

Redmine_Backlogs fails to show successful migration in settings page

So, I am trying to get redmine_backlogs to work with SQL server. [We are using SQL Server rather than SQLite, to scale better]
I am NOT a Ruby programmer, but I really want this plugin for my team, as we actively use Redmine for several projects.
After reading through some Ruby tutorials, I've managed to get make some modifications and get the plugin installed and migrated correctly [it appears].
On the plugin settings screen [in administration] in Redmine, it shows the migration wasn't successful.
Even though all the list items are green, and the migrations appeared to work.
Any ideas?
The changes I made were to bypass suspected issues with the way Active records handles direct SQL queries.
Here are the changes I've made -
ERROR #1 –
C:\Projects\Redmine\redmine-2.3.2>rake redmine:plugins:migrate
Migrating redmine_backlogs (Redmine Backlogs)...
== AddStoryPositions: migrating ==============================================
-- execute("select max(position) from issues")
-> 0.0020s
-> -1 rows
rake aborted!
An error has occurred, this and all later migrations canceled:
undefined method `each' for -1:FixnumC:/Projects/Redmine/redmine-2.3.2/plugins/redmine_backlogs/db/migrate/026_add_story_positions.rb:10:in `up'
FIX #1 –
Direct queries are not working correctly with the sqladapter [TinyTds + Active Record]
026_add_story_positions.rb
class AddStoryPositions < ActiveRecord::Migration
def self.up
# Rails doesn't support temp tables, mysql doesn't support update
# from same-table subselect
unless RbStory.trackers.size == 0
max = 0
dbconfig = YAML.load_file(File.join(File.dirname(__FILE__), '../../../../config/database.yml'))#[Rails.env]['username']
if dbconfig[Rails.env]['adapter'] == 'sqlserver' then
database = dbconfig[Rails.env]['database']
dataserver = dbconfig[Rails.env]['dataserver']
mode = dbconfig[Rails.env]['mode']
port = dbconfig[Rails.env]['port']
username = dbconfig[Rails.env]['username']
password = dbconfig[Rails.env]['password']
client = TinyTds::Client.new(
:database => database,
:dataserver => dataserver,
:mode => mode,
:port => port,
:username => username,
:password => password)
client.execute("select max(position) from issues").each{|row| max = row[0]}
client.execute "update issues
set position = #{max} + id
where position is null and tracker_id in (#{RbStory.trackers(:type=>:string)})"
else
execute("select max(position) from issues").each{|row| max = row[0]}
execute "update issues
set position = #{max} + id
where position is null and tracker_id in (#{RbStory.trackers(:type=>:string)})"
end
end
end
def self.down
puts "Reverting irreversible migration"
end
end
ERROR #2
rake aborted!
An error has occurred, this and all later migrations canceled:
TinyTds::Error: ALTER TABLE ALTER COLUMN position failed because one or more objects access this column.: ALTER TABLE [issues] ALTER COLUMN [position]
integer NOT NULLC:/Projects/Redmine/redmine-2.3.2/plugins/redmine_backlogs/db/migrate/033_unique_positions.rb:30:in `up'
FIX #2
033_unique_positions.rb
#SQLServer cannot change the type of an indexes column, so it must be dropped first
remove_index :issues, :position
change_column :issues, :position, :integer, :null => false
add_index :issues, :position
ERROR #3
rake aborted!
undefined method each' for -1:Fixnum
C:/Projects/Redmine/redmine-2.3.2/plugins/redmine_backlogs/lib/backlogs_setup.rb:155:inmigrated?'
FIX #3
def migrated?
available = Dir[File.join(File.dirname(__FILE__), '../db/migrate/*.rb')].collect{|m| Integer(File.basename(m).split('_')[0].gsub(/^0+/, ''))}.sort
return true if available.size == 0
available = available[-1]
ran = []
dbconfig = YAML.load_file(File.join(File.dirname(__FILE__), '../../../config/database.yml'))#[Rails.env]['username']
if dbconfig[Rails.env]['adapter'] == 'sqlserver' then
database = dbconfig[Rails.env]['database']
dataserver = dbconfig[Rails.env]['dataserver']
mode = dbconfig[Rails.env]['mode']
port = dbconfig[Rails.env]['port']
username = dbconfig[Rails.env]['username']
password = dbconfig[Rails.env]['password']
client = TinyTds::Client.new(
:database => database,
:dataserver => dataserver,
:mode => mode,
:port => port,
:username => username,
:password => password)
client.execute("select version from schema_migrations where version like '%-redmine_backlogs'").each{|m|
ran << Integer((m.is_a?(Hash) ? m.values : m)[0].split('-')[0])
}
else
Setting.connection.execute("select version from schema_migrations where version like '%-redmine_backlogs'").each{|m|
ran << Integer((m.is_a?(Hash) ? m.values : m)[0].split('-')[0])
}
end
return false if ran.size == 0
ran = ran.sort[-1]
return ran >= available
end
module_function :migrated?
I was using the wrong where clause -
This is the correct one, I must have overwritten when debugging.
'%-redmine_backlogs'
The above code works.
I could not answer my own question before, but now I can.
The above code was tested and works. I have been running backlogs on Windows with MS SQL successfully since.

Using Open-URI to fetch XML and the best practice in case of problems with a remote url not returning/timing out?

Current code works as long as there is no remote error:
def get_name_from_remote_url
cstr = "http://someurl.com"
getresult = open(cstr, "UserAgent" => "Ruby-OpenURI").read
doc = Nokogiri::XML(getresult)
my_data = doc.xpath("/session/name").text
# => 'Fred' or 'Sam' etc
return my_data
end
But, what if the remote URL times out or returns nothing? How I detect that and return nil, for example?
And, does Open-URI give a way to define how long to wait before giving up? This method is called while a user is waiting for a response, so how do we set a max timeoput time before we give up and tell the user "sorry the remote server we tried to access is not available right now"?
Open-URI is convenient, but that ease of use means they're removing the access to a lot of the configuration details the other HTTP clients like Net::HTTP allow.
It depends on what version of Ruby you're using. For 1.8.7 you can use the Timeout module. From the docs:
require 'timeout'
begin
status = Timeout::timeout(5) {
getresult = open(cstr, "UserAgent" => "Ruby-OpenURI").read
}
rescue Timeout::Error => e
puts e.to_s
end
Then check the length of getresult to see if you got any content:
if (getresult.empty?)
puts "got nothing from url"
end
If you are using Ruby 1.9.2 you can add a :read_timeout => 10 option to the open() method.
Also, your code could be tightened up and made a bit more flexible. This will let you pass in a URL or default to the currently used URL. Also read Nokogiri's NodeSet docs to understand the difference between xpath, /, css and at, %, at_css, at_xpath:
def get_name_from_remote_url(cstr = 'http://someurl.com')
doc = Nokogiri::XML(open(cstr, 'UserAgent' => 'Ruby-OpenURI'))
# xpath returns a nodeset which has to be iterated over
# my_data = doc.xpath('/session/name').text # => 'Fred' or 'Sam' etc
# at returns a single node
doc.at('/session/name').text
end

Resources