Stored procedure OUT params in JRuby using Sequel - ruby

I'm trying to call a stored procedure in a DB2 database that has output params and also returns a cursor. I can get this done using JDBC through JRuby, but I'd like to extend Sequel to do it, because of the nicer interface. I've gotten this far:
Sequel::JDBC::Database.class_eval do
def call_test
sql = "{call ddd.mystoredproc(?)}"
result = {}
synchronize do |conn|
cps = conn.prepare_call(sql)
cps.register_out_parameter(1, Types::INTEGER)
result[:success] = cps.execute
result[:outparam_val] = cps.get_int(1)
if result[:success]
dataset.send(:process_result_set, cps.get_data_set) do |row|
yield row
end
end
# rescue block
end
end
end
This gets me a ResultSet that I have to work with in a very Java-ish way, though, not a nice Sequel::Dataset object. I know this code doesn't make sense - I'm just using it to experiment with values, so at one point I was returning the result hash and seeing what it contained. If I can get something that works, I will clean it up and make it more flexible. It looks like the log_yield method just logs the sql and yields to the block, so I don't know how anything else is getting converted to a Sequel::Dataset. Doing something like DB[:ddd__sometable] will return a dataset that I can loop through, but I can't figure out how and at what point the underlying Java ResultSet is getting changed over, or how to do it myself.
edit: Since Sequel::Database can create a dummy Dataset, and the Sequel::JDBC::Dataset has a private method that converts a result set and yields it to a block, the above is what I have now. This works, but I'm absolutely positive that there has to be a better way.
Sequel seems like the best database library for Ruby, which is why I'm trying to work with it, but if there are alternatives that are nicer than using straight JDBC, I'd like to know about them, too.

Sequel doesn't currently support OUT params in stored procedures on JDBC, so what you are currently doing is probably best.

Related

How should I merge hash while using 'first_or_create'

I have this hash, which is built dynamically:
additional_values = {"grouping_id"=>1}
I want to merge it with this record object after creation via first_or_create:
result = model.where(name: 'test').first_or_create do |record|
# I'm trying to merge any record attributes that exist in my hash:
record.attributes.merge(additional_values)
# This works, but it sucks:
# record.grouping_id = data['grouping_id'] if model.name == 'Grouping'
end
#Not working:
#result.attributes>>{"id"=>1, "name"=>"Test", "grouping_id"=>nil}
I understand that if the record already exists (returned via 'first'), it won't be updated...although that would be a nice option and any recommendations on that are welcome, but the table was just dropped and recreated, so that's not the issue.
What am I missing?
I also tried using to_sym, resulting with:
additional_values = {:grouping_id=>1}
...just in case there was some weirdness I didn't know about...didn't make a difference
The problem is Hash#merge returns a new hash and then you aren't doing anything with that hash, you're just throwing it away. I would also suggest sticking to using the ActiveRecord methods for updating attributes, instead of trying to manipulate the underlying hash, such as using assign_attributes or, if you want to save the record update. Though, you may find the create_with, which can be used with find_or_create_by, useful here:
model.create_with(additional_values).find_or_create_by(name: 'test')
I can't find any documentation that I like (if at all) for first_or_create in recent rails versions, but if you like that more than find_or_create_by, then if we look at the Rails 3 documentation for first_or_create, you should be able to do with out the create_with:
model.where(name: 'test').first_or_create(additional_attributes)

Split array into comma separated list of values

I'm working on a bit of metaprogramming using send methods quite a bit. I've been successful so far because the methods I'm sending to only take one argument.
Example:
client is an API client
#command is a method on client taken as an option to a CLI utility
#verb is a method on command taken as another option in the CLI
def command_keys
case #command
when "something"
self.command_options.slice(:some, :keys)
end
end
Then I call the API client like this:
client.send(#command).send(#verb, command_keys)
This works since the methods all take a Hash as their argument. The problem I've run into is when I need to send more than 1 parameter in command_keys. What I'm wondering is the best way to handle the command_keys method returning more than 1 value. Example:
def command_keys
case #command
when "something"
return self.command_options[:some], self.command_options[:keys]
end
end
In this case, command_keys returns an Array as expected, but when I try to pass that in the send(#verb, command_options) call, it passes it as an Array (which is obviously expected). So, to make a long story short, is there some easy way to make this condition be handled easily?
I know send(#verb, argument1, argument2) would get me the result I want, but I would like to be able to not have to give my script any more implementation logic than it needs, that is to say I would like it to remain as abstracted as possible.
Use splat. You might have to rethink the code a bit, but something like:
client.send(#command).send(#verb, *all_the_args)

Stored Procedure return codes in Sequel (Ruby ORM)

In MS SQL Server a stored procedure can return an integer value called a return code to indicate the execution status of a procedure.
Does Sequel support this?
The value returned from my_stored_proc.call() does not match the integer I am returning in SQL. Is there some other way to access this?
This is now supported from version 4.6 of Sequel using the call_mssql_proc method:
DB.call_mssql_sproc(:SequelTest, {:args => ['Input String', :output]})
> {:result => 0, :numrows => 1, :var1 => "1"}
The result will be in the :result element.
Read "Stored Procedures in MSSQL" for more.
The response from Jeremy Evans on the Sequel Talk Google Group was:
Unfortunately, Sequel's support for stored procedures is fairly limited. I don't believe it supports return codes or in/out variables. It's designed similarly to the prepared statement support, where delete/update returns the number of affected rows, and select yields rows.
If Sequel's support doesn't meet your needs, use Database#synchronize to get to the underlying database connection, and operate directly on that using the connection's API (which depends on the driver in use).
"Getting the Return Value from JDBC MSSQL" describes how to do it from a Java perspective.
And, here is a (working) first stab at a Ruby implementation:
return_code = #db.synchronize do |conn|
stmnt = conn.prepareCall('{ ? = call dbo.sp_sequel_test() }')
stmnt.registerOutParameter(1, java::sql::Types::INTEGER)
stmnt.execute
# output parameters have not yet been processed, must call getMoreResults() first.
stmnt.getMoreResults
stmnt.getInt(1)
end

How to fire raw MongoDB queries directly in Ruby

Is there any way that I can fire a raw mongo query directly in Ruby instead of converting them to the native Ruby objects?
I went through Ruby Mongo Tutorial, but I cannot find such a method anywhere.
If it were mysql, I would have fired a query something like this.
ActiveRecord::Base.connection.execute("Select * from foo")
My mongo query is a bit large and it is properly executing in the MongoDB console. What I want is to directly execute the same inside Ruby code.
Here's a (possibly) better mini-tutorial on how to get directly into the guts of your MongoDB. This might not solve your specific problem but it should get you as far as the MongoDB version of SELECT * FROM table.
First of all, you'll want a Mongo::Connection object. If
you're using MongoMapper then you can call the connection
class method on any of your MongoMapper models to get a connection
or ask MongoMapper for it directly:
connection = YourMongoModel.connection
connection = MongoMapper.connection
Otherwise I guess you'd use the from_uri constructor to build
your own connection.
Then you need to get your hands on a database, you can do this
using the array access notation, the db method, or get
the current one straight from MongoMapper:
db = connection['database_name'] # This does not support options.
db = connection.db('database_name') # This does support options.
db = MongoMapper.database # This should be configured like
# the rest of your app.
Now you have a nice shiny Mongo::DB instance in your hands.
But, you probably want a Collection to do anything interesting
and you can get that using either array access notation or the
collection method:
collection = db['collection_name']
collection = db.collection('collection_name')
Now you have something that behaves sort of like an SQL table so
you can count how many things it has or query it using find:
cursor = collection.find(:key => 'value')
cursor = collection.find({:key => 'value'}, :fields => ['just', 'these', 'fields'])
# etc.
And now you have what you're really after: a hot out of the oven Mongo::Cursor
that points at the data you're interested in. Mongo::Cursor is
an Enumerable so you have access to all your usual iterating
friends such as each, first, map, and one of my personal
favorites, each_with_object:
a = cursor.each_with_object([]) { |x, a| a.push(mangle(x)) }
There are also command and eval methods on Mongo::DB that might do what you want.
In case you are using mongoid you will find the answer to your question here.
If you're using Mongoid 3, it provides easy access to its MongoDB driver: Moped. Here's an example of accessing some raw data without using Models to access the data:
db = Mongoid::Sessions.default
# inserting a new document
collection = db[:collection_name]
collection.insert(name: 'my new document')
# finding a document
doc = collection.find(name: 'my new document').first
# "select * from collection"
collection.find.each do |document|
puts document.inspect
end

SOAP::RPC::Driver formatting problems. How can I change it?

I'm dealing with a SOAP webservice call from a server that is expecting to receive method calls with the paramaters in the format of:
<urn:offeringId> 354 </urn:offeringId>
But SOAP::RPC::Driver is generating messages in the form of:
<offeringId xsi:type = "xsd:int">354</offeringId>
The server keeps erroring when it gets these messages (especially since it's expecting offeringId to be a custom type internal to itself, not an int).
Is there anyway to configure the driver to format things the way the server is expecting it. Is the server even doing SOAP? I'm having trouble finding reference to that style of formating for SOAP (I know it DOES work though, because SOAPUI works just fine with that type of message).
-Jenny
Edit: I've got at least part of it solved. the RPC::Driver (obviously) uses the RPC standard, whereas apparently the server I'm trying to talk to is doing "document". Now, when I look at RPC::Driver's API, I'm seeing a method named "add_document_method". That SOUNDS to me like it might be what I want, but I can't figure out what paramaters to give it. The examples I've seen around the net don't make much sense to me, things like:
def GetNamePair(response)
response.account.each do |x|
class << x
attr :configuration, true
end
x.configuration = Hash[*x.a.map do |y|
[y.__xmlattr[XSD::QName.new(nil, 'n')], String.new(y)]
end.flatten]
end
end
mNS = 'urn:zimbraAdmin'
drv.add_document_method('GetAllAdminAccountsRequest', mNS, [XSD::QName.new(mNS, 'GetAllAdminAccountsRequest')],
[XSD::QName.new(mNS, 'GetAllAdminAccountsResponse')] )
puts YAML.dump(GetNamePair(drv.GetAllAdminAccountsRequest([]))
All I really know is that I have a method that takes in certain parameters.... I really don't get why, if this method does what I think it does, it has to be more complicated. Isn't this just a matter of taking the exact same data and formating it differently? I'm so confused....
Okay, what I ended up doing was using SOAP:RPC:Drivers add_document_method, which requires me to give it the wsdl, namespace, etc, and then give it the attributes later as a single input hash thingy (and gives me the output in a similar format). It worked, it just wasn't as clean as add_rpc_method (which is waht add_method defaults to)
-Jenny

Resources