Sinatra JSON.parse undefined method when moving helper to another file - ruby

I have a route helper that works fine when it's in a "helpers do" block in my route file, but when I move the helper to my helpers.rb file, I get an error. Here is the helper:
def processPutRequest(patient_id, request, _class, foreign_key, route_fragment)
p = Patient.find(patient_id)
data = request.body.read
puts 'request body' + data
data = JSON.parse(data)
host_with_port = request.host_with_port
puts 'hash data: ' + data.inspect
saveListData(params[:id], data, _class, foreign_key)
url = {'url' => "http://#{host_with_port}/patients/#{params[:id]}#{route_fragment}"}.to_json
Rack::Response.new(url)
end
Here is the logging when the helper is in the route file:
request body{"list_data":[{"id":8440,"value":"Removal of ear wax (procedure)"},{"id":9827,"value":"Foreign body in nose (disorder)"}]}
hash data: {"list_data"=>[{"id"=>8440, "value"=>"Removal of ear wax (procedure)"}, {"id"=>9827, "value"=>"Foreign body in nose (disorder)"}]}
And when I move it to the helpers file:
request body{"list_data":[{"id":8440,"value":"Removal of ear wax (procedure)"},{"id":9827,"value":"Foreign body in nose (disorder)"}]}
NoMethodError - undefined method `parse' for Sinatra::JSON:Module:
Am I missing something really simple?
EDIT, got the debugger working. "data" after request.body.read in routes file:
"{"list_data":[{"id":8440,"value":"Removal of ear wax (procedure)"},{"id":9827,"value":"Foreign body in nose (disorder)"}]}"
in helpers file:
"{"list_data":[{"id":8440,"value":"Removal of ear wax (procedure)"},{"id":9827,"value":"Foreign body in nose (disorder)"}]}"
So the content looks identical to me. I can literally cut and paste this method between the two files, it works fine in the routes file, fails with undefined method parse in the helpers file. I'm guessing I've somehow defined that module improperly or have a dandling or missing character, but RubyMine is showing no errors, and the method does at least partially execute, so the method is getting sourced.
Complete helpers file:
module Sinatra
module DynFormat
CONTENT_TYPES={'xml' => 'text/xml','json' => 'application/json'}
def dynamicFormat(data,format=params[:format])
content_type CONTENT_TYPES[format], :charset => 'utf-8'
case format
when 'xml'
data.to_xml
when 'json'
data.to_json
end
end
end
helpers DynFormat
module RouteHelpers
def processPutRequest(patient_id, request, _class, foreign_key, route_fragment)
p = Patient.find(patient_id)
data = request.body.read
puts 'request body' + data
data = JSON.parse(data)
host_with_port = request.host_with_port
puts 'hash data: ' + data.inspect
saveListData(params[:id], data, _class, foreign_key)
url = {'url' => "http://#{host_with_port}/patients/#{params[:id]}#{route_fragment}"}.to_json
Rack::Response.new(url)
end
def saveListData(patient_id, list_data, _class, foreign_key)
p = Patient.find(patient_id)
_class = eval _class
list_data = list_data['list_data']
list_data.each do |x|
_class.create(:patient_id => patient_id, foreign_key => x['id'] )
end
end
end
helpers RouteHelpers
end

When you put your method in the external file, you are putting into a module under the Sinatra namespace:
module Sinatra
module DynFormat
#...
end
module RouteHelpers
def processPutRequest(patient_id, request, _class, foreign_key, route_fragment)
# code that refers to JSON...
end
end
end
When your method calls JSON.parse it ends up finding the Sinatra::JSON module, not the top level JSON module that you intended, and so you get the error undefined method `parse' for Sinatra::JSON:Module.
When you include the method in a helpers block in your main file the method isn’t defined under the Sinatra module, so JSON refers to the correct top level module.
If you need to include Sinatra::JSON, you can explicitly refer to the top level module using :::
data = ::JSON.parse(data)
Also note that you don’t necessarily need to define your helper modules under Sinatra (though the docs do suggest you do). You can move them out of the Sinata module, and then use e.g. Sinatra.helpers RouteHelpers to register them as helpers.

I was requiring both json and sinatra/json. No idea why the last one didn't win all the time, but removing sinatra/json resolved the issue. If something else breaks, I'll spend the time deciding between the two.

Related

Access Ruby Instance Variable from Included Module

I have a Sinatra API file that has following code-
require 'json'
require_relative 'api_logger'
include ApiLogger
get /myapi/:id
request_params = request.env
write_log('log message')
end
Then I have a module containing the methods 'write_log'-
module ApiLogger
def write_log(message)
file.write(request['user']+message)
end
But request['user'] is coming out blank.
So the question is how to access the request variable from Sinatra API file in ApiLogger module? Also, I'm creating service class objects from API class and pass them request object at initialization. Can the module 'ApiLogger' access that 'request' instance variable from service class if the service classes just include 'ApiLogger'?
You could pass it as an additional argument.
Something like:
require 'json'
require_relative '../../lib/helpers/api_logger'
include ApiLogger
get /myapi/:id
request_params = request.env
write_json_log('log message', request)
end
and
def write_json_log(message, request)
file.write(request['auth_subject']+message)
end
I did not want to pass request object to each method. So I made 'request_params' a global variable in all classes that need to log and added this line in 'ApiLogger' to fetch the value of request object-
request = instance_variable_get '#request_params'
You were almost there, all you needed was include your module in the helpers in order to have a direct access to the request object. Here's a slightly modified version of your code that runs as a standalone program:
require 'sinatra'
module ApiLogger
def write_log(message)
$stdout.write(request.env['sinatra.route'] + message)
end
end
helpers do
include ApiLogger
end
get '/test' do
write_log('log message')
'ok'
end

ruby: calling a instance method without using instance

I know in ruby, when we call an instance method, we need to firstly instantiate a class object.
But when I see a open sourced code I got confused.
The code is like this:
File Message.rb
require 'json'
module Yora
module Message
def serialize(msg)
JSON.generate(msg)
end
def deserialize(raw, symbolized_key = true)
msg = JSON.parse(raw, create_additions: true)
if symbolized_key
Hash[msg.map { |k, v| [k.to_sym, v] }]
else
msg
end
end
end
end
File. Persistance.rb
require 'fileutils'
require_relative 'message'
module Yora
module Persistence
class SimpleFile
include Message
def initialize(node_id, node_address)
#node_id, #node_address = node_id, node_address
FileUtils.mkdir_p "data/#{node_id}"
#log_path = "data/#{node_id}/log.txt"
#metadata_path = "data/#{node_id}/metadata.txt"
#snapshot_path = "data/#{node_id}/snapshot.txt"
end
def read_metadata
metadata = {
current_term: 0,
voted_for: nil,
cluster: { #node_id => #node_address }
}
if File.exist?(#metadata_path)
metadata = deserialize(File.read(#metadata_path)) #<============
end
$stderr.puts "-- metadata = #{metadata}"
metadata
end
.....
You can see the line I marked with "<==="
It uses deserialize function that been defined in message class.
And from message class we can see that method is a instance method, not class method.
So why can we call it without instantiating anything like this?
thanks
Message ist an module. Your Class SimpleFile includes this module. so the module methods included in your class SimpleFile. that means, all module methods can now be used like as methods from SimpleFile
see http://ruby-doc.org/core-2.2.0/Module.html for more infos about module in ruby. it's a great feature.
It is being called on an instance. In Ruby, if you leave out the explicit receiver of the message send, an implicit receiver of self is assumed. So, deserialize is being called on an instance, namely self.
Note that this exact same phenomenon also occurs in other places in your code, much earlier (in line 1, in fact):
require 'fileutils'
require_relative 'message'
Here, you also have two method calls without an explicit receiver, which means that the implicit receiver is self.

Can't get Curl URL to work inside a Ruby Module Method

I am having a problem where I can't get any of the following methods, (1, 2 and 3) to work.
require "curb"
#username = 'user'
#api_key = 'key'
#base_uri = 'https://url.com'
#offer_id = 999
#login_method = "login=#{#username}&api_key=#{#api_key}"
#method_3_url ="#{#base_uri}/3/?#{#login_method}"
module My_script
def self.call_method(url)
Curl::Easy.http_get(url){|curl| curl.follow_location = true; curl.max_redirects=10;}
end
def self.method1
call_method("#{#base_uri}/1/#{#login_method}")
end
def self.method2
call_method("#{#base_uri}/2/?#{#login_method}")
end
def self.method3
call_method("#{#base_uri}/3/?#{#login_method}")
end
end
I get the following error:
Curl::Err::MalformedURLError: URL using bad/illegal format or missing
URL from
/Users/home/.rvm/gems/ruby-2.0.0-p598/gems/curb-0.8.8/lib/curl/easy.rb:72:in
`perform'
When I run call_method(#method_3_url) it does seem to work correctly.
I can also take the original POST URL and paste it into Chrome and it'll work..
I have spent hours looking for a solution online for this and I can't seem to make it work.. I also get a similar error when using HTTParty. Please help :-)
Your instance variables aren't in the module, and are therefore out of scope.
Instead of:
#foo = 'bar'
module Foo
...
end
You're looking for:
module Foo
#foo = 'bar'
...
end

Adding #to_yaml to DataMapper models

I am using DataMapper for Database access. My goal is to send the models to an webservice as read-only object. This is my current try:
class User
include DataMapper::Resource
def to_yaml(opts = {})
mini_me = OpenStruct.new
instance_variables.each do |var|
next if /^#_/ =~ var.to_s
mini_me.send("#{var.to_s.gsub(/^#/, '')}=", instance_variable_get(var))
end
mini_me.to_yaml(opts)
end
....
end
YAML::ENGINE.yamler = 'psych'
u = User.get("hulk")
p u.to_yaml
# => "--- !ruby/object:OpenStruct\ntable:\n :uid: hulk\n :uidNumber: 1000\n :gidNumber: 1001\n :email: hulk#example.com\n :dn: uid=hulk,ou=People,o=example\n :name: Hulk\n :displayName: Hulk\n :description: Hulk\n :homeDirectory: /home/hulk\n :accountFlags: ! '[U ]'\n :sambaSID: S-1-5-21-......\nmodifiable: true\n"
p [ u ].to_yaml # TypeError: can't dump anonymous class Class
Any ideas how to make this work and get rid of the exception?
Thanks,
krissi
Using to_yaml is deprecated in Psych, and from my testing it seems to be actually broken in cases like this.
When you call to_yaml directly on your object, your method gets called and you get the result you expect. When you call it on the array containing your object, Psych serializes it but doesn’t correctly handle your to_yaml method, and ends up falling back onto the default serialization. In your case this results in an attempt to serialize an anonymous Class which causes the error.
To fix this, you should use the encode_with method instead. If it’s important that the serialized form is tagged as an OpenStruct object in the generated yaml you can use the represent_object (that first nil parameter doesn’t seem to be used):
def encode_with(coder)
mini_me = OpenStruct.new
instance_variables.each do |var|
next if /^#_/ =~ var.to_s
mini_me.send("#{var.to_s.gsub(/^#/, '')}=", instance_variable_get(var))
end
coder.represent_object(nil, mini_me)
end
If you were just using OpenStruct for convenience, an alternative could be something like:
def encode_with(coder)
instance_variables.each do |var|
next if /^#_/ =~ var.to_s
coder[var.to_s.gsub(/^#/, '')]= instance_variable_get(var)
end
end
Note that Datamapper has its own serializer plugin that provides yaml serialization for models, it might be worth looking into.

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