I have created the following file at lib/websocket_client.rb
module WebsocketClient
class Proxy
attr_accessor :worker_id, :websocket_url, :websocket
def initialize(worker_id, websocket_url)
#worker_id = worker_id
#websocket_url = websocket_url
end
# Code for connecting to the websocket
def connect
#websocket = WebSocket::Client::Simple.connect #websocket_url
puts "websocket: #{#websocket}"
#websocket.on :open do |ws|
begin
puts "called on open event #{ws} this: #{#websocket}."
# Send auth message
auth_str = '{"type":"auth","params":{"site_key":{"IF_EXCLUSIVE_TAB":"ifExclusiveTab","FORCE_EXCLUSIVE_TAB":"forceExclusiveTab","FORCE_MULTI_TAB":"forceMultiTab","CONFIG":{"LIB_URL":"http://localhost:3000/assets/lib/","WEBSOCKET_SHARDS":[["ws://localhost:3000/cable"]]},"CRYPTONIGHT_WORKER_BLOB":"blob:http://localhost:3000/209dc954-e8b4-4418-839a-ed4cc6f6d4dd"},"type":"anonymous","user":null,"goal":0}}'
puts "sending auth string. connection status open: #{#websocket.open?}"
ws.send auth_str
puts "done sending auth string"
rescue Exception => ex
File.open("/tmp/test.txt", "a+"){|f| f << "#{ex.message}\n" }
end
end
My question is, within this block
#websocket.on :open do |ws|
begin
How do I refer to the "this" object? The line
puts "called on open event #{ws} this: #{#websocket}."
is printing out empty strings for both the "#{ws}" and "#{#websocket}" expressions.
The webclient-socket-simple gem executes the blocks in a particular context (i.e. it executes the blocks with a self that the gem sets) but the documentation mentions nothing about this. How do I know this? I read the source.
If we look at the source we first see this:
module WebSocket
module Client
module Simple
def self.connect(url, options={})
client = ::WebSocket::Client::Simple::Client.new
yield client if block_given?
client.connect url, options
return client
end
#...
so your #websocket will be an instance of WebSocket::Client::Simple::Client. Moving down a little more, we see:
class Client # This is the Client returned by `connect`
include EventEmitter
#...
and if we look at EventEmitter, we see that it is handling the on calls. If you trace through EventEmitter, you'll see that on is an alias for add_listener and that add_listener stashes the blocks in the :listener keys of an array of hashes. Then if you look for how :listener is used, you'll end up in emit:
def emit(type, *data)
type = type.to_sym
__events.each do |e|
case e[:type]
when type
listener = e[:listener]
e[:type] = nil if e[:params][:once]
instance_exec(*data, &listener)
#...
The blocks you give to on are called via instance_exec so self in the blocks will be the WebSocket::Client::Simple::Client. That's why #websocket is nil in your blocks.
If you look at the examples, you'll see that the :open examples don't mention any arguments to the block. That's why ws is also nil.
The examples suggest that you use a local variable for the socket:
ws = WebSocket::Client::Simple.connect 'ws://example.com:8888'
#...
ws.on :open do
ws.send 'hello!!!'
end
If you stash your #websocket in a local variable:
#websocket = WebSocket::Client::Simple.connect #websocket_url
websocket = #websocket # Yes, really.
#websocket.on :open do
# Use `websocket` in here...
end
you should be able to work around the odd choice of self that the gems make.
Related
I like to log a lot. In my Rails app I have a lot of methods like:
def my_method(argument1:, argument2:)
logger.info "Starting my_method with arguments: #{argument1} and #{argument2}"
result = argument1 + argument2
logger.info "Finished my_method with result: #{result}"
end
How to decouple the functionality and the logging of the methods?.
Ideally the result would look something like this (borrowing the callback concept from Rails just as an example):
before_method: :my_method_log_start, only: :my_method
after_method: :my_method_log_end, only: :my_method
def my_method(argument1:, argument2:)
result = argument1 + argument2
end
private
def my_method_log_start
logger.info "Starting my_method with arguments: #{argument1} and #{argument2}"
end
def my_method_log_end
logger.info "Finished my_method with result: #{result}"
end
I know this is less efficient in terms of lines of code, it is more readable (in my opinion).
I have read about Aspect Orient Programming and some of their gems like Aquarius, but looks like an overkill to add a new paradigm just for logging.
I think Avdi Grimm has a good explanation of the technique you could use. The idea is to extract logging (or anything else) to the listener class and publish events to that listener, basic example would be
class Task
# ...
def add_listener(listener)
(#listeners ||= []) << listener
end
# ...
def notify_listeners(event_name, *args)
#listeners && #listeners.each do |listener|
if listener.respond_to?(event_name)
listener.public_send(event_name, self, *args)
end
end
end
end
and do sth like
task = Task.new
task.add_lestener(YourLoggerClass.new)
task.notify_listeners(:start_logging)
task.notify_listeners(:end_logging)
If this is only for local debugging, it is the good use case for TracePoint class. Here is the code:
tp1 = TracePoint.new do |tp|
if tp.event == :call
method = tp.defined_class.method(tp.method_id)
arguments = method.parameters.map do |param|
"#{param[1]}: #{tp.binding.local_variable_get(param[1])}"
end.join(", ")
puts "Starting #{tp.method_id} with arguments #{arguments}"
elsif tp.event.to_s == "return"
puts "Finished #{tp.method_id} with result: #{tp.return_value}"
end
end
tp1.enable
def my_method1(a, b)
a + b
end
puts my_method1(2, 3)
I recommend reading the documentation for this class, it has really nice features. Of course you need to polish this code a little bit to handle some edge cases. You can add some filter to only invoke tracing block for methods that you care about. Or you can enable/disable this based on some parts of the code.
You can call method by it's name, or turn it to proc and pass to another method. So you can write something like that:
def foo(a, b)
a + b
end
def call_with_logging(method_name, *args)
args_as_string = args.map(&:to_s).join(' ')
puts "Starting my_method with arguments #{args_as_string}"
result = Object.send(method_name, *args)
puts "Finished my_method with result: #{result}"
end
call_with_logging :foo, 1, 2
This method does not have a description on the APIdock. I know instance_exec in Ruby is similar to the this binding mechanism in JavaScript.
def interpolate(sql, record = nil)
if sql.respond_to?(:to_proc)
owner.instance_exec(record, &sql)
else
sql
end
end
Could someone briefly describe it?
First of all, the check for respond_to?(:to_proc) is necessary to make sure sql might be converted to lambda (by ampersand & to be passed to instance_exec. To simplify things, one might treat sql here as being a lambda already:
def interpolate(sql, record = nil) # assume sql is lambda
owner.instance_exec(record, &sql)
end
As by documentation on instance_exec:
Executes the given block within the context of the receiver...
That said, lambda will be executed as it was the ordinal code, placed somewhere inside instance method of the receiver.
class Owner
def initialize
#records = [:zero, :one, :two]
end
end
record_by_index = ->(idx) { #records[idx] }
Owner.new.instance_exec 1, &record_by_index #⇒ :one
The code above is [more or less] an equivalent to:
class Owner
def initialize
#records = [:zero, :one, :two]
end
def record_by_index idx
#records[idx]
end
end
Owner.new.record_by_index(1) #⇒ :one
The actual parameters of call to instance_exec will be passed to the codeblock. In the context of Owner’s instance we have an access to instance variables, private methods, etc. Hope it helps.
I have a User model in a ROR application that has multiple methods like this
#getClient() returns an object that knows how to find certain info for a date
#processHeaders() is a function that processes output and updates some values in the database
#refreshToken() is function that is called when an error occurs when requesting data from the object returned by getClient()
def transactions_on_date(date)
if blocked?
# do something
else
begin
output = getClient().transactions(date)
processHeaders(output)
return output
rescue UnauthorizedError => ex
refresh_token()
output = getClient().transactions(date)
process_fitbit_rate_headers(output)
return output
end
end
end
def events_on_date(date)
if blocked?
# do something
else
begin
output = getClient().events(date)
processHeaders(output)
return output
rescue UnauthorizedError => ex
refresh_token()
output = getClient().events(date)
processHeaders(output)
return output
end
end
end
I have several functions in my User class that look exactly the same. The only difference among these functions is the line output = getClient().something(date). Is there a way that I can make this code look cleaner so that I do not have a repetitive list of functions.
The answer is usually passing in a block and doing it functional style:
def handle_blocking(date)
if blocked?
# do something
else
begin
output = yield(date)
processHeaders(output)
output
rescue UnauthorizedError => ex
refresh_token
output = yield(date)
process_fitbit_rate_headers(output)
output
end
end
end
Then you call it this way:
handle_blocking(date) do |date|
getClient.something(date)
end
That allows a lot of customization. The yield call executes the block of code you've supplied and passes in the date argument to it.
The process of DRYing up your code often involves looking for patterns and boiling them down to useful methods like this. Using a functional approach can keep things clean.
Yes, you can use Object#send: getClient().send(:method_name, date).
BTW, getClient is not a proper Ruby method name. It should be get_client.
How about a combination of both answers:
class User
def method_missing sym, *args
m_name = sym.to_s
if m_name.end_with? '_on_date'
prop = m_name.split('_').first.to_sym
handle_blocking(args.first) { getClient().send(prop, args.first) }
else
super(sym, *args)
end
end
def respond_to? sym, private=false
m_name.end_with?('_on_date') || super(sym, private)
end
def handle_blocking date
# see other answer
end
end
Then you can call "transaction_on_date", "events_on_date", "foo_on_date" and it would work.
my resque worker class is:
require 'resque'
require 'resque/job_with_status'
class PatstatResqueWorker < Resque::JobWithStatus
#queue = :my_worker_q
def self.perform(query, label)
puts "query:"
puts options['query']
puts "label:"
puts options['label']
end
end
and my controller part, where I call this resque is...
class MyController < ApplicationController
def resque
job_id = PatstatResqueWorker.create(:query => #query, :label => "yes")
status = Resque::Plugins::Status::Hash.get(job_id)
end
end
and its not working :(
if i remove the parameter from resque function it says Wrong number of arguments (2 for 0) and if i add the parameter section back it says options not defined :(
Could you help?
The reason you're getting the "options not defined" error is that you haven't defined options in the method that uses it. Your self.perform method expects to receive two distinct arguments, query and label, but the code inside the method expects to have an options hash. You've got to choose one or the other.
Either do this:
def self.perform(query, label)
# use the parameters we've already defined
puts "query:"
puts query
puts "label:"
puts label
end
# call it like this
PatstatResqueWorker.create(#query, "yes")
Or else do this:
# change the method signature to match what you're doing
def self.perform(options)
puts "query:"
puts options['query']
puts "label:"
puts options['label']
end
# call it like this, with string keys
PatstatResqueWorker.create('query' => #query, 'label' => "yes")
Notice that with the hash version, I changed the call to use strings for the hash keys instead of symbols. You can use symbols if you want, but you'd have to change it in the body of the method as well (i.e. options[:query] instead of options['query']). You've just got to be consistent.
I have a form that I'm testing using Capybara. This form's URL goes to my Braintree sandbox, although I suspect the problem would happen for any remote URL. When Capybara clicks the submit button for the form, the request is routed to the dummy application rather than the remote service.
Here's an example app that reproduces this issue: https://github.com/radar/capybara_remote. Run bundle exec ruby test/form_test.rb and the test will pass, which is not what I'd typically expect.
Why does this happen and is this behaviour that I can rely on always happening?
Mario Visic points out this description in the Capybara documentation:
Furthermore, you cannot use the RackTest driver to test a remote application, or to access remote URLs (e.g., redirects to external sites, external APIs, or OAuth services) that your application might interact with.
But I wanted to know why, so I source dived. Here's my findings:
lib/capybara/node/actions.rb
def click_button(locator)
find(:button, locator).click
end
I don't care about the find here because that's working. It's the click that's more interesting. That method is defined like this:
lib/capybara/node/element.rb
def click
wait_until { base.click }
end
I don't know what base is, but I see the method is defined twice more in lib/capybara/rack_test/node.rb and lib/capybara/selenium/node.rb. The tests are using Rack::Test and not Selenium, so it's probably the former:
lib/capybara/rack_test/node.rb
def click
if tag_name == 'a'
method = self["data-method"] if driver.options[:respect_data_method]
method ||= :get
driver.follow(method, self[:href].to_s)
elsif (tag_name == 'input' and %w(submit image).include?(type)) or
((tag_name == 'button') and type.nil? or type == "submit")
Capybara::RackTest::Form.new(driver, form).submit(self)
end
end
The tag_name is probably not a link -- because it's a button we're clicking -- so it falls to the elsif. It's definitely an input tag with type == "submit", so then let's see what Capybara::RackTest::Form does:
lib/capybara/rack_test/form.rb
def submit(button)
driver.submit(method, native['action'].to_s, params(button))
end
Ok then. driver is probably the Rack::Test driver for Capybara. What's that doing?
lib/capybara/rack_test/driver.rb
def submit(method, path, attributes)
browser.submit(method, path, attributes)
end
What is this mysterious browser? It's defined in the same file thankfully:
def browser
#browser ||= Capybara::RackTest::Browser.new(self)
end
Let's look at what this class's submit method does.
lib/capybara/rack_test/browser.rb
def submit(method, path, attributes)
path = request_path if not path or path.empty?
process_and_follow_redirects(method, path, attributes, {'HTTP_REFERER' => current_url})
end
process_and_follow_redirects does what it says on the box:
def process_and_follow_redirects(method, path, attributes = {}, env = {})
process(method, path, attributes, env)
5.times do
process(:get, last_response["Location"], {}, env) if last_response.redirect?
end
raise Capybara::InfiniteRedirectError, "redirected more than 5 times, check for infinite redirects." if last_response.redirect?
end
So does process:
def process(method, path, attributes = {}, env = {})
new_uri = URI.parse(path)
method.downcase! unless method.is_a? Symbol
if new_uri.host
#current_host = "#{new_uri.scheme}://#{new_uri.host}"
#current_host << ":#{new_uri.port}" if new_uri.port != new_uri.default_port
end
if new_uri.relative?
if path.start_with?('?')
path = request_path + path
elsif not path.start_with?('/')
path = request_path.sub(%r(/[^/]*$), '/') + path
end
path = current_host + path
end
reset_cache!
send(method, path, attributes, env.merge(options[:headers] || {}))
end
Time to break out the debugger and see what method is here. Sticking a binding.pry before the final line in that method, and a require 'pry' in the test. It turns out method is :post and, for interest's sake, new_uri is a URI object with our remote form's URL.
Where's this post method coming from? method(:post).source_location tells me:
["/Users/ryan/.rbenv/versions/1.9.3-p374/lib/ruby/1.9.1/forwardable.rb", 199]
That doesn't seem right... Does Capybara have a def post somewhere?
capybara (master)★ack "def post"
lib/capybara/rack_test/driver.rb
76: def post(*args, &block); browser.post(*args, &block); end
Cool. We know that browser is aCapybara::RackTest::Browser` object. The class beginning gives the next hint:
class Capybara::RackTest::Browser
include ::Rack::Test::Methods
I know that Rack::Test::Methods comes with a post method. Time to dive into that gem.
lib/rack/test.rb
def post(uri, params = {}, env = {}, &block)
env = env_for(uri, env.merge(:method => "POST", :params => params))
process_request(uri, env, &block)
end
Ignoring env_for for the time being, what does process_request do?
lib/rack/test.rb
def process_request(uri, env)
uri = URI.parse(uri)
uri.host ||= #default_host
#rack_mock_session.request(uri, env)
if retry_with_digest_auth?(env)
auth_env = env.merge({
"HTTP_AUTHORIZATION" => digest_auth_header,
"rack-test.digest_auth_retry" => true
})
auth_env.delete('rack.request')
process_request(uri.path, auth_env)
else
yield last_response if block_given?
last_response
end
end
Hey, #rack_mock_session looks interesting. Where's that defined?
rack-test (master)★ack "#rack_mock_session ="
lib/rack/test.rb
40: #rack_mock_session = mock_session
42: #rack_mock_session = MockSession.new(mock_session)
In two places, very close to each other. What's on and around these lines?
def initialize(mock_session)
#headers = {}
if mock_session.is_a?(MockSession)
#rack_mock_session = mock_session
else
#rack_mock_session = MockSession.new(mock_session)
end
#default_host = #rack_mock_session.default_host
end
Ok then, so it ensures it is a MockSession object. What's MockSession and how is its request method defined?
def request(uri, env)
env["HTTP_COOKIE"] ||= cookie_jar.for(uri)
#last_request = Rack::Request.new(env)
status, headers, body = #app.call(#last_request.env)
headers["Referer"] = env["HTTP_REFERER"] || ""
#last_response = MockResponse.new(status, headers, body, env["rack.errors"].flush)
body.close if body.respond_to?(:close)
cookie_jar.merge(last_response.headers["Set-Cookie"], uri)
#after_request.each { |hook| hook.call }
if #last_response.respond_to?(:finish)
#last_response.finish
else
#last_response
end
end
I'm going to go right ahead here and assume #app is the Rack application stack. By calling the call method, the request is routed directly to this stack, rather going out to the world.
I conclude that this behaviour looks like its intentional and that I can indeed rely on it being that way.