How to use websockets in Rails - ruby

I'm trying to integrate web sockets into my Rails application as per this link: https://github.com/websocket-rails/websocket-rails/wiki/Installation-and-Setup
I use Ruby 2.0 and Rails3.
The contents of the relevant files:
Gemfile:
gem 'websocket-rails
events.rb
WebsocketRails::EventMap.describe do
subscribe :awesomeness_approval, :to => ChatController, :with_method => :awesomeness_approval
end
development.rb
...
config.middleware.delete Rack::Lock
...
chat_controller.rb
class ChatController < WebsocketRails::BaseController
def initialize_session
# perform application setup here
controller_store[:message_count] = 0
end
def awesomeness_approval
if message[:awesomeness] > 5
trigger_success {:m => 'awesome level is sufficient'}
else
trigger_failure {:m => 'awesome level is insufficient'}
end
end
end
chat.js
var dispatcher = new WebSocketRails('localhost:3000/websocket');
dispatcher.on_open = function(data) {
console.log('Connection has been established: ', data);
}
var success = function(response) {
console.log("You are awesome because: "+response.message);
}
var failure = function(response) {
console.log("You are not very awesome because: "+response.message);
}
var message = { awesomeness: 4 }
dispatcher.trigger('awesomeness_approval', message, success, failure);
When I try to start rails server, I get the following error:
w/app/controllers/chat_controller.rb:9: syntax error, unexpected =>, expecting '}' (SyntaxError)
trigger_success {:m => 'awesome level is sufficient'}
...
/app/controllers/chat_controller.rb:11: syntax error, unexpected =>, expecting '}'
trigger_failure {:m => 'awesome level is insufficient'}
However, if I comment out the above lines, I'm able to start the server, and the browser is able to connect to the web socket. The following output is seen on browser console:
"Connection has been established: " Object { connection_id: "f66c4298eb103312e181" } chat.js:3
Please help.

Your error comes from a syntax ambiguity in your method call.
When you write
trigger_success {:m => 'awesome level is sufficient'}
the opening curly brace is interpreted to be the start of a block argument to the method. However, you intend it to be a hash. Interpreted as a block, it would indeed be wrong syntax as a block doesn't allow a simple hash rocket.
You can resolve this ambiguity, you can use parenthesis:
trigger_success({:m => 'awesome level is sufficient'})
That way, it is clear for the ruby parser that you actually mean to pass a single hash argument to the method and is currently parsed.

Related

In Ruby/Sinatra, how to halt with an ERB template and error message

In my Sinatra project, I'd like to be able to halt with both an error code and an error message:
halt 403, "Message!"
I want this, in turn, to be rendered in an error page template (using ERB). For example:
error 403 do
erb :"errors/error", :locals => {:message => env['sinatra.error'].message}
end
However, apparently env['sinatra.error'].message (aka the readme and every single website says I should do it) does not expose the message I've provided. (This code, when run, returns the undefined method `message' for nil:NilClass error.)
I've searched for 4-5 hours and experimented with everything and I can't figure out where the message is exposed for me to render via ERB! Does anyone know where it is?
(It seems like the only alternative I can think of is writing this instead of the halt code above, every time I would like to halt:
halt 403, erb(:"errors/error", :locals => {m: "Message!"})
This code works. But this is a messy solution since it involves hardcoding the location of the error ERB file.)
(If you were wondering, this problem is not related to the show_exceptions configuration flag because both set :show_exceptions, false and set :show_exceptions, :after_handler make no difference.)
Why doesn't it work − use the source!
Lets look at the Sinatra source code to see why this problem doesn't work. The main Sinatra file (lib/sinatra/base.rb) is just 2043 lines long, and pretty readable code!
All halt does is:
def halt(*response)
response = response.first if response.length == 1
throw :halt, response
end
And exceptions are caught with:
# Dispatch a request with error handling.
def dispatch!
invoke do
static! if settings.static? && (request.get? || request.head?)
filter! :before
route!
end
rescue ::Exception => boom
invoke { handle_exception!(boom) }
[..]
end
def handle_exception!(boom)
#env['sinatra.error'] = boom
[..]
end
But for some reason this code is never run (as tested with basic "printf-debugging"). This is because in invoke the block is run like:
# Run the block with 'throw :halt' support and apply result to the response.
def invoke
res = catch(:halt) { yield }
res = [res] if Fixnum === res or String === res
if Array === res and Fixnum === res.first
res = res.dup
status(res.shift)
body(res.pop)
headers(*res)
elsif res.respond_to? :each
body res
end
nil # avoid double setting the same response tuple twice
end
Notice the catch(:halt) here. The if Array === res and Fixnum === res.first part is what halt sets and how the response body and status code are set.
The error 403 { .. } block is run in call!:
invoke { error_block!(response.status) } unless #env['sinatra.error']
So now we understand why this doesn't work, we can look for solutions ;-)
So can I use halt some way?
Not as far as I can see. If you look at the body of the invoke method, you'll see that the body is always set when using halt. You don't want this, since you want to override the response body.
Solution
Use a "real" exception and not the halt "pseudo-exception". Sinatra doesn't seem to come with pre-defined exceptions, but the handle_exception! does look at http_status to set the correct HTTP status:
if boom.respond_to? :http_status
status(boom.http_status)
elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
status(boom.code)
else
status(500)
end
So you could use something like this:
require 'sinatra'
class PermissionDenied < StandardError
def http_status; 403 end
end
get '/error' do
#halt 403, 'My special message to you!'
raise PermissionDenied, 'My special message to you!'
end
error 403 do
'Error message -> ' + #env['sinatra.error'].message
end
Which works as expected (the output is Error message -> My special message to you!). You can return an ERB template here.
In Sinatra v2.0.7+, messages passed to halt are stored in the body of the response. So a halt with an error code and an error message (eg: halt 403, "Message!") can be caught and rendered in an error page template with:
error 403 do
erb :"errors/error", locals: { message: body[0] }
end

Exit code from with Ruby + rest client

I learning ruby and playing it with restsclient I have following test the code and I'm expecting that to return 1/false. I can't seem to make it work.
n#lap-jta102:~/tsamcode$ ./get.rb
n#lap-jta102:~/tsamcode$ echo $?
0
#!/usr/bin/env ruby
require 'rest_client'
require 'json'
begin
response = RestClient.get("https://admin:admin#172.16.210.10/isam/host_records/187.0.0.1/hostnames", :content_type => :json, :accept => :json)
return true if response.code == 200
rescue => e
return false unless response != 200
end
$? is not set by return, but by exit. In fact, your return doesn't even do what you think. Try just this:
# one-returner.rb
return 1
$ ruby one-returner.rb
one-returner.rb:1:in `<main>': unexpected return (LocalJumpError)
The reason you're not getting an error in your program is the fact that you blanket-rescue this error when raised by return true (since you have an unrestricted rescue, which is a bad practice for exactly this reason, it can catch a wrong thing and leave you puzzled), and return false never executes (and thus never raises an error) due to unless.

Syntax error in for loop (Ruby/RSpec)

While running the RSPEC test as shown below im getting this error:
Using Accessor#strict_set for specs
SyntaxError: /home/sam/projects/logstash.king-foo.dev/ansible/roles/logstash/spec/syslog.rb:6: syntax error, unexpected kEND
end
^
load at org/jruby/RubyKernel.java:1101
(root) at /opt/logstash/vendor/bundle/jruby/1.9/gems/rspec-core-2.14.7/lib/rspec/core/configuration.rb:1
each at org/jruby/RubyArray.java:1613
load_spec_files at /opt/logstash/vendor/bundle/jruby/1.9/gems/rspec-core-2.14.7/lib/rspec/core/configuration.rb:896
load_spec_files at /opt/logstash/vendor/bundle/jruby/1.9/gems/rspec-core-2.14.7/lib/rspec/core/configuration.rb:896
run at /opt/logstash/vendor/bundle/jruby/1.9/gems/rspec-core-2.14.7/lib/rspec/core/command_line.rb:22
I tried messing around with the syntax but without success.
files = Dir['../configs/filter*.conf']
##configuration = String.new
files.sort.each.do |file|
##configuration << File.read(file)
end
describe "my first logstash rspec test", :if => RUBY_ENGINE == "jruby" do
extend LogStash::RSpec
config(##configuration)
... some code here ...
end
Does anybody know what i'm doing wrong?
Why do i get a syntax error for the end statement ander the ##configuration variable?
The error means there was an unexpected end in your code. Just simply replace the 3rd line with
files.sort.each do |file|
I optionally recommend you use { and } instead of do and end. The { and } are space-insensitive and you are less likely to receive an error than do and end.

Why Am I getting a NoMethodError when using the google API to run a query against google Big Query?

I'm trying to run a Query against Google Big Query, using the Ruby API.
This is my first project with Ruby and I'm still learning the language.
This is also my first project using the Google API.
ENVIORNMENT:
Windows 7
Ruby 1.9
Faraday 0.90
Goolge API - Service Account Authentication
My Code runs without giving any warnings or error messages through:
#client.authorization.fetch_access_token!
doc = File.read('bigQueryAPI.json')
#bigQuery = #client.register_discovery_document('bigquery', 'v2', doc)
NOTE: #bigQuery is loaded from a file because when I try to load #bigquery with
#bigquery = #client.discovered_api('bigquery', 'v2')
I get Google::APIClient::ClientError: Not Found and inspect only prints
#<Google::APIClient::API:0x17c94cc ID:bigquery:v2>
However If I save the Big Query API as a text file from
https://www.googleapis.com/discovery/v1/apis/bigquery/v2/rest
and then load it as a text file with
doc = File.read('bigQueryAPI.json')
#bigQuery = #client.register_discovery_document('bigquery', 'v2', doc)
then #bigQuery.inspect actually returns something useful.
#bigQuery.inspect output.
However, When I try to actually run a query, like so:
result = #client.execute!(
:api_method => #bigQuery.batch_path.query,
:body_object => { "query" => "SELECT count(DISTINCT repository_name) as repository_total, " +
"count(payload_commit) as commits_total, " +
"count(DISTINCT repository_name) / count(payload_commit) as average, " +
"FROM [githubarchive:github.timeline]" }, #,
:parameters => { "projectId" => #project_id })
I get the following error:
NoMethodError: undefined method `query_values' for nil:NilClass
Here's the Full Stacktrace of the error:
1) Error:
test_averages(Test_GitHub_Archive):
NoMethodError: undefined method `query_values' for nil:NilClass
C:/Ruby193/lib/ruby/gems/1.9.1/gems/google-api-client-0.7.1/lib/google/api_client/request.rb:145:in `uri='
C:/Ruby193/lib/ruby/gems/1.9.1/gems/google-api-client-0.7.1/lib/google/api_client/request.rb:101:in `initialize'
C:/Ruby193/lib/ruby/gems/1.9.1/gems/google-api-client-0.7.1/lib/google/api_client.rb:518:in `new'
C:/Ruby193/lib/ruby/gems/1.9.1/gems/google-api-client-0.7.1/lib/google/api_client.rb:518:in `generate_request'
C:/Ruby193/lib/ruby/gems/1.9.1/gems/google-api-client-0.7.1/lib/google/api_client.rb:583:in `execute!'
C:/Users/tfburton/Documents/private/ProjectSuggestor/RubyStats/GitHub_Archive.rb:39:in `get_averages'
C:/Users/tfburton/Documents/private/ProjectSuggestor/RubyStats/TestSpec/test_GitHub_Archive.rb:26:in `test_averages'
Here is the results for #client.inspect
NOTE: I would have pasted here, but my post ended over the length limit.
After doing some digging. It looks like I'm not passing the proper #bigQuery prameter to get the query function.
Looking at the dump for #bigQuery.inspect I need to pass the method at line 751.
However I can't seem to figure out how to pass that method.
If you strip out the rest of the inspect output the "path" looks like this:
{ "resources => { "jobs" => { "methods" => { "query"
I've tried #bigQuery.Jobs.query and that results in an error stating that #bigQuery.Jobs doesn't exist.
So am I creating #bigQuery correctly?
Why doesn't #bigQuery.Jobs.query work?
Here's how I got it to work with the bigquery.jobs.query method, which is probably what you need.
I had to set OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE because otherwise the authorization process would fail miserably, but that might be specific to min Win7/MgitSys environment. In any case, this specific line is not safe in prod.
require 'google/api_client'
require 'google/api_client/client_secrets'
require 'google/api_client/auth/installed_app'
require 'google/api_client/auth/file_storage'
require 'openssl'
require 'json'
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
# Initialize the client.
client = Google::APIClient.new(
:application_name => 'Example Ruby application',
:application_version => '1.0.0'
)
CREDENTIAL_STORE_FILE = "#{$0}-oauth2.json"
file_storage = Google::APIClient::FileStorage.new(CREDENTIAL_STORE_FILE)
# Initialize Google+ API. Note this will make a request to the
# discovery service every time, so be sure to use serialization
# in your production code. Check the samples for more details.
#bigQuery = client.discovered_api('bigquery', 'v2')
# Load client secrets from your client_secrets.json.
client_secrets = Google::APIClient::ClientSecrets.load
file_storage = Google::APIClient::FileStorage.new(CREDENTIAL_STORE_FILE)
if file_storage.authorization.nil?
client_secrets = Google::APIClient::ClientSecrets.load
# The InstalledAppFlow is a helper class to handle the OAuth 2.0 installed
# application flow, which ties in with FileStorage to store credentials
# between runs.
flow = Google::APIClient::InstalledAppFlow.new(
:client_id => client_secrets.client_id,
:client_secret => client_secrets.client_secret,
:scope => ['https://www.googleapis.com/auth/cloud-platform','https://www.googleapis.com/auth/bigquery']
)
client.authorization = flow.authorize(file_storage)
else
client.authorization = file_storage.authorization
end
puts "authorized, requesting"
# Make an API call.
result = client.execute!(
:api_method => #bigQuery.jobs.query,
:body_object => { "query" => "SELECT count(DISTINCT repository_name) as repository_total, " +
"count(payload_commit) as commits_total, " +
"count(DISTINCT repository_name) / count(payload_commit) as average, " +
"FROM [githubarchive:github.timeline]" }, #,
:parameters => { "projectId" => "845227657643" })
puts JSON.dump result.data

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