Overriding third-party method in Ruby - ruby

I'm developing some tool using pure ruby and RestClient and I'd like to override default log_request method of Request class.
lib/restclient/request.rb
module RestClient
class Request
def log_request
RestClient.log << "SECRET"
end
end
end
But now, if I try to test this, it is not working:
$ irb
irb(main):001:0> require 'restclient'
=> true
irb(main):002:0> RestClient.log = "stdout"
=> "stdout"
irb(main):003:0> RestClient.get("http://localhost")
RestClient.get "http://localhost", "Accept"=>"*/*; q=0.5, application/xml", "Accept-Encoding"=>"gzip, deflate"
Expected to see only SECRET as output.
I'm probably missing, how to "inject" my code in default RestClient library ?
How can I do this from another file in lib/mytool/somefile.rb ?

Since you place it in your lib folder, this file is never being loaded as RestClient::Request constant is already defined. Place this code inside your config/initializers folder

This file need to be exactly required after require 'restclient'
Adding load 'lib/restclient/request.rb' did the trick

Related

Can't access custom helper functions made within the file - Sinatra

I have this helper within my 'app.rb' file, which is used to get the current user object.
helpers do
def get_current_user(column, value)
user = User.where(column => value).first
return user
end
end
get '/' do
user = get_current_user(:id, params[:id])
end
This is what i did in irb:
irb(main):001:0> require './app'
=> true
irb(main):007:0> user = get_current_user(:id, 2)
NoMethodError: undefined method `get_current_user' for main:Object
from (irb):7
from /usr/bin/irb:12:in `<main>'
I don't understand why i can't access the helper methods from irb. Should i explicitly include the helpers or something? If so, why? Because i put them under the same file, under the same class.
get_current_users is metaprogrammed through the helpers method to be an instance method of App. So, if app.rb looks something like this:
require 'sinatra/base'
class App < Sinatra::Base
helpers do
def get_current_user
puts "here!"
end
end
end
...then from irb you can invoke get_current_user on an instance of App like this:
>> require './app'
>> App.new!.get_current_user
here!
=> nil
>>
(If you're wondering why that's new! and not new like most sane ruby code, read this answer.)

Ruby 1.9.3 JSON Parsing

I'm encountering a weird problem when parsing JSON with Ruby 1.9.3-p392 under RVM on CentOS 6.4. Instead of decoding embedded objects into their appropriate Ruby classes, it's just loading the object as a hash. In Ruby 1.9.3-p194 it works correctly.
Take the following sample:
require 'json'
class TestMe
attr_accessor :me
def initialize(option_hash = nil)
if option_hash
#me = option_hash['me']
end
#me ||= "Hello"
end
def to_json(*a)
{
JSON.create_id => self.class.name,
'data' => {
"me" => #me
}
}.to_json(*a)
end
def self.json_create(o)
new(o['data'])
end
end
t = TestMe.new
t.me = "foo"
t2 = JSON.parse(t.to_json)
puts t2
If I run this on Ruby 1.9.3-p194, it outputs the following:
#<TestMe:0x00000001c877f0>
If I run the same snippet on Ruby 1.9.3-p392, it outputs the following:
{"json_class"=>"TestMe", "data"=>{"me"=>"foo"}}
The behavior in p194 is what I expect and what the documentation implies. Why isn't p392 parsing the JSON data correctly?
Still not sure why/what changed, but I found a work-around. Basically, you need to construct a Parser object and pass in the :create_additions option, instead of just calling JSON.parse.
Example:
p = JSON::Parser.new(json_string, {:create_additions => true})
result = p.parse
As others have stated, it sounds like the recent change to the way JSON objects are unmarshelled. I ran into a very similar issue here and got a great answer.
JSON by itself will return a hash. There is an extension to it that gives it added capability. Try using:
require 'json'
require 'json/add/core'
I think at one point in past Rubies JSON automatically loaded the extensions but that was dropped for comparability with the JSON spec.
"add/core" includes some to_json methods to base objects and might add the capability to recover custom objects. I ran into a similar situation passing regular expressions via JSON and that was the fix.
I'm not near my computer so that isn't confirmed, but it might help.

Tracing Ruby Gmail Gem Methods Back To Their Origins

My goal is to find the place where save_attachments_to is called in this gmail gem readme example:
folder = "/where/ever"
gmail.mailbox("Faxes").emails do |email|
if !email.message.attachments.empty?
email.message.save_attachments_to(folder)
end
end
I run a "puts email.message.attachments.methods and a "email.message.attachments.class" in the loop:
Mail::AttachmentsList
guess_encoding
set_mime_type
inspect
Then I run a "puts email.message.methods and a "puts email.message.class" for good measure. The example method call is not in the list.
So I go diving into https://github.com/nu7hatch/gmail/blob/master/lib/gmail/message.rb.
No methods are defined there either, but I notice that mime/message is defined, so I go over there to look at its methods: http://rubydoc.info/gems/mime/0.1/MIME/Message
There is no save_attachments_to method here either.
Where the deuce is this method? The gmail gem does not define attachment methods, so the whole thing must be inherited from somewhere. Where? And where's the call that inherits it?
The reason you can't find it is because it doesn't exist. I'm not sure why. I downloaded the gem and played with it for a while in irb:
1.9.3-p194 :066 > x.message.attachments
=> [#<Mail::Part:70234804200840, Multipart: false, Headers: <Content-Type: application/vnd.ms-excel; name="MVBINGO.xls">, <Content-Transfer-Encoding: base64>, <Content-Disposition: attachment; filename="MVBINGO.xls">, <Content-Description: MVBINGO.xls>>]
1.9.3-p194 :063 > x.message.save_attachments_to(folder)
NoMethodError: undefined method `save_attachments_to' for #<Mail::Message:0x007fc1a3875818>
from /Users/Qsario/.rvm/gems/ruby-1.9.3-p194/gems/mail-2.4.4/lib/mail/message.rb:1289:in `method_missing'
from (irb):63
from /Users/Qsario/.rvm/rubies/ruby-1.9.3-p194/bin/irb:16:in `<main>'
Not very helpful. Ordinarily, you can do something like
puts my_obj.method(:some_method_name).source_location
But when the method in question does not exist, that won't help you very much. EDIT: Now that I look, this exact bug is already on their issue tracker. A few people have posted code to implement the non-existent function, such as this code by a-b:
folder = Dir.pwd # for example
email.message.attachments.each do |f|
File.write(File.join(folder, f.filename), f.body.decoded)
end
Thanks for the sanity check Qsario. :-)
Here is code that works in Ruby 1.9.3 (1.9.3-p194):
gmail = Gmail.connect('username#gmail.com', 'pass')
gmail.inbox.emails.each do |email|
email.message.attachments.each do |f|
File.write(File.join(local_path, f.filename), f.body.decoded)
end
end
Here is code that works in 1.9.2 (1.9.2-p320) and 1.9.3 (1.9.3-p194):
gmail = Gmail.connect('username#gmail.com', 'pass')
gmail.inbox.emails.each do |email|
email.message.attachments.each do |file|
File.open(File.join(local_path, "name-of-file.doc or use file.filename"), "w+b", 0644 ) { |f| f.write file.body.decoded }
end
end

Ruby namespacing issues

I'm attempting to build a gem for interacting w/ the Yahoo Placemaker API but I'm running into an issue. When I attempt to run the following code I get:
NameError: uninitialized constant Yahoo::Placemaker::Net
from /Users/Kyle/.rvm/gems/ruby-1.9.2-p290/gems/yahoo-placemaker-0.0.1/lib/yahoo-placemaker.rb:17:in `extract'
from (irb):4
from /Users/Kyle/.rvm/rubies/ruby-1.9.2-p290/bin/irb:16:in `<main>'
yahoo-placemaker.rb
require "yahoo-placemaker/version"
require 'json'
require 'ostruct'
require 'net/http'
module Yahoo
module Placemaker
def self.extract (text = '')
host = 'wherein.yahooapis.com'
payload = {
'documentContent' => text,
'appid' => APP_ID,
'outputType' => 'json',
'documentType' => 'text/plain'
}
req = Net::HTTP::Post.new('/v1/document')
req.body = to_url_params(payload)
response = Net::HTTP.new(host).start do |http|
http.request(req)
end
json = JSON.parse(response.body)
Yahoo::Placemaker::Result.new(json)
end
end
end
I have yet to figure out how exactly constant name resolution works in Ruby (I think the rules are a bit messy here), but from my experience it could well be that Net is looked up in the current namespace instead of the global one. Try using the fully qualified name:
::Net::HTTP::Post.new
A similar problem could occur in this line:
Yahoo::Placemaker::Result
You should replace it with either ::Yahoo::Placemaker::Result or better Result (as it lives in the current namespace).
Try requiring net/http before. Ruby is falling back to find it in the module if it isn't defined.
require 'net/http'

Calling Sinatra from within Sinatra

I have a Sinatra based REST service app and I would like to call one of the resources from within one of the routes, effectively composing one resource from another. E.g.
get '/someresource' do
otherresource = get '/otherresource'
# do something with otherresource, return a new resource
end
get '/otherresource' do
# etc.
end
A redirect will not work since I need to do some processing on the second resource and create a new one from it. Obviously I could a) use RestClient or some other client framework or b) structure my code so all of the logic for otherresource is in a method and just call that, however, it feels like it would be much cleaner if I could just re-use my resources from within Sinatra using their DSL.
Another option (I know this isn't answering your actual question) is to put your common code (even the template render) within a helper method, for example:
helpers do
def common_code( layout = true )
#title = 'common'
erb :common, :layout => layout
end
end
get '/foo' do
#subtitle = 'foo'
common_code
end
get '/bar' do
#subtitle = 'bar'
common_code
end
get '/baz' do
#subtitle = 'baz'
#common_snippet = common_code( false )
erb :large_page_with_common_snippet_injected
end
Sinatra's documentation covers this - essentially you use the underlying rack interface's call method:
http://www.sinatrarb.com/intro.html#Triggering%20Another%20Route
Triggering Another Route
Sometimes pass is not what you want, instead
you would like to get the result of calling another route. Simply use
call to achieve this:
get '/foo' do
status, headers, body = call env.merge("PATH_INFO" => '/bar')
[status, headers, body.map(&:upcase)]
end
get '/bar' do
"bar"
end
I was able to hack something up by making a quick and dirty rack request and calling the Sinatra (a rack app) application directly. It's not pretty, but it works. Note that it would probably be better to extract the code that generates this resource into a helper method instead of doing something like this. But it is possible, and there might be better, cleaner ways of doing it than this.
#!/usr/bin/env ruby
require 'rubygems'
require 'stringio'
require 'sinatra'
get '/someresource' do
resource = self.call(
'REQUEST_METHOD' => 'GET',
'PATH_INFO' => '/otherresource',
'rack.input' => StringIO.new
)[2].join('')
resource.upcase
end
get '/otherresource' do
"test"
end
If you want to know more about what's going on behind the scenes, I've written a few articles on the basics of Rack you can read. There is What is Rack? and Using Rack.
This may or may not apply in your case, but when I’ve needed to create routes like this, I usually try something along these lines:
%w(main other).each do |uri|
get "/#{uri}" do
#res = "hello"
#res.upcase! if uri == "other"
#res
end
end
Building on AboutRuby's answer, I needed to support fetching static files in lib/public as well as query paramters and cookies (for maintaining authenticated sessions.) I also chose to raise exceptions on non-200 responses (and handle them in the calling functions).
If you trace Sinatra's self.call method in sinatra/base.rb, it takes an env parameter and builds a Rack::Request with it, so you can dig in there to see what parameters are supported.
I don't recall all the conditions of the return statements (I think there were some Ruby 2 changes), so feel free to tune to your requirements.
Here's the function I'm using:
def get_route url
fn = File.join(File.dirname(__FILE__), 'public'+url)
return File.read(fn) if (File.exist?fn)
base_url, query = url.split('?')
begin
result = self.call('REQUEST_METHOD' => 'GET',
'PATH_INFO' => base_url,
'QUERY_STRING' => query,
'rack.input' => StringIO.new,
'HTTP_COOKIE' => #env['HTTP_COOKIE'] # Pass auth credentials
)
rescue Exception=>e
puts "Exception when fetching self route: #{url}"
raise e
end
raise "Error when fetching self route: #{url}" unless result[0]==200 # status
return File.read(result[2].path) if result[2].is_a? Rack::File
return result[2].join('') rescue result[2].to_json
end

Resources