How to access parameters of method being watched in ActiveSupport::Notification instrument? - ruby

I have been playing around with ActiveSupport::Notifications recently and I can't figure out how to get to the parameters of a method run in a block. Let me use an example.
You set up the following instrument.
ActiveSupport::Notifications.instrument('render', extra: :information) do
render text: 'Foo'
end
You can access the information by subscribing to the event.
ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
name # => String, name of the event (such as 'render' from above)
start # => Time, when the instrumented block started execution
finish # => Time, when the instrumented block ended execution
id # => String, unique ID for this notification
payload # => Hash, the payload
end
Looking at the documentation, I can't find anything that would give me the parameters of the render method - {text: 'foo'}
What am I missing? Is this possible? Thanks.

render text: 'Foo' is not a parameter; that's a Rails method that renders content back to the client.
ActiveSupport::Notificaitons.instrument("render", text: "foo")
is how you would "submit" the values to the notifier.
ActiveSupport::Notifications.subscribe("render") do |name, start, finish, id, payload|
text = payload[:text]
end
is how you would reference it in the subscriber.

Related

selecting elements using variables in ruby /watir

The real issue I am encountering is the delay on population of drop down lists in a web page.
The call i am using in the script is :-
clickOndropDown(":id","dropDownAutocomplete","ABC",#b)
where
1. clickOndropDown is the method invoked
2. id is the element selector (which can be :id,xpath, or :css [which are different selectors in my html code for different dropdown boxes] to handle all cases within the code)
3. ABC is the element to be selected from the drop down list
4. #b is the browser object
the function definition is as :
def clickOndropDown(identifier,selector,targetVal,br)
begin
puts "starting off" # gets printed
br.element("#{identifier}" => "#{selector}").fire_event :click #--- does not work---
# br.element(:id => "#{selector}").fire_event :click #--- works
puts "inside selector pass 1"
br.element(:link, targetVal).fire_event :click
...
...
# rest of the code
It does not throw any exception..just does the push statement before the selection statement.
is there any wild card handler for # (--"#{identifier}" ) so that I can write a single code for handling id,xpath,or css
I am surprised that there is no exception. I would expect a MissingWayOfFindingObjectException.
The problem is that the identifier is a String instead of a Symbol. The line that is not working is equivalent to:
br.element(":id" => "dropDownAutocomplete")
Which is not the same as:
br.element(:id => "dropDownAutocomplete")
Notice the difference between the String, ":id", and the Symbol, :id.
You should change the method call to send a Symbol and change the method to no longer change it to a String:
def clickOndropDown(identifier,selector,targetVal,br)
br.element(identifier => selector).fire_event :click
end
clickOndropDown(:id,"dropDownAutocomplete","ABC",#b)
If you want to have a more versatile method, you would be better off to have the clickOndropDown accept a Hash for the locator. This would allow you to accept multiple locator criteria.
def click_on_dropdown(br, target_val, locator)
br.element(locator).fire_event :click
end
# Call for the original example
click_on_dropdown(#b, "ABC", id: "dropDownAutocomplete")
# Call with multiple selectors
click_on_dropdown(#b, "ABC", id: "dropDownAutocomplete", name: "name_value")

Passing an object to another class

Using Clockwork, I periodically pull objects from my database and pass them to Sidekiq workers for processing. If I inspect the objects prior to passing them to the Sidekiq worker, I see all the fields that they have. However, inspecting the object after I pass it gives me what I assume is a reference to the object.
Clockwork function
every(30.seconds, 'check_modems.job'){
modem_list = Modem.all
modem_list.each do |modem|
TestWorker.perform_async(modem)
puts(modem.inspect)
end
}
The output from the puts above:
<Object id: 1, value: "15", ipaddress: nil>
And my worker function that receives the object:
def perform(test)
puts(test.inspect)
end
and the output:
#<Object:0x00563977f06f48>"
From what I have been able to find, Ruby is pass-by-value, and that value in this case is a reference, but how do I go backwards from here and get the object that I need?
You code would be more robust when you only pass simple identifiers (e.g. numbers or strings that are easy to serialize) instead of complex objects.
Change your clockwork function to something like this:
modem_ids = Modem.pluck(:id)
modem_ids.each { |id| TestWorker.perform_async(id) }
And your worker to:
def perform(modem_id)
modem = Modem.find_by(id: modem_id)
if modem
# ...
end
end

Creating a Ruby API

I have been tasked with creating a Ruby API that retrieves youtube URL's. However, I am not sure of the proper way to create an 'API'... I did the following code below as a Sinatra server that serves up JSON, but what exactly would be the definition of an API and would this qualify as one? If this is not an API, how can I make in an API? Thanks in advance.
require 'open-uri'
require 'json'
require 'sinatra'
# get user input
puts "Please enter a search (seperate words by commas):"
search_input = gets.chomp
puts
puts "Performing search on YOUTUBE ... go to '/videos' API endpoint to see the results and use the output"
puts
# define query parameters
api_key = 'my_key_here'
search_url = 'https://www.googleapis.com/youtube/v3/search'
params = {
part: 'snippet',
q: search_input,
type: 'video',
videoCaption: 'closedCaption',
key: api_key
}
# use search_url and query parameters to construct a url, then open and parse the result
uri = URI.parse(search_url)
uri.query = URI.encode_www_form(params)
result = JSON.parse(open(uri).read)
# class to define attributes of each video and format into eventual json
class Video
attr_accessor :title, :description, :url
def initialize
#title = nil
#description = nil
#url = nil
end
def to_hash
{
'title' => #title,
'description' => #description,
'url' => #url
}
end
def to_json
self.to_hash.to_json
end
end
# create an array with top 3 search results
results_array = []
result["items"].take(3).each do |video|
#video = Video.new
#video.title = video["snippet"]["title"]
#video.description = video["snippet"]["description"]
#video.url = video["snippet"]["thumbnails"]["default"]["url"]
results_array << #video.to_json.gsub!(/\"/, '\'')
end
# define the API endpoint
get '/videos' do
results_array.to_json
end
An "API = Application Program Interface" is, simply, something that another program can reliably use to get a job done, without having to busy its little head about exactly how the job is done.
Perhaps the simplest thing to do now, if possible, is to go back to the person who "tasked" you with this task, and to ask him/her, "well, what do you have in mind?" The best API that you can design, in this case, will be the one that is most convenient for the people (who are writing the programs which ...) will actually have to use it. "Don't guess. Ask!"
A very common strategy for an API, in a language like Ruby, is to define a class which represents "this application's connection to this service." Anyone who wants to use the API does so by calling some function which will return a new instance of this class. Thereafter, the program uses this object to issue and handle requests.
The requests, also, are objects. To issue a request, you first ask the API-connection object to give you a new request-object. You then fill-out the request with whatever particulars, then tell the request object to "go!" At some point in the future, and by some appropriate means (such as a callback ...) the request-object informs you that it succeeded or that it failed.
"A whole lot of voodoo-magic might have taken place," between the request object and the connection object which spawned it, but the client does not have to care. And that, most of all, is the objective of any API. "It Just Works.™"
I think they want you to create a third-party library. Imagine you are schizophrenic for a while.
Joe wants to build a Sinatra application to list some YouTube videos, but he is lazy and he does not want to do the dirty work, he just wants to drop something in, give it some credentials, ask for urls and use them, finito.
Joe asks Bob to implement it for him and he gives him his requirements: "Bob, I need YouTube library. I need it to do:"
# Please note that I don't know how YouTube API works, just guessing.
client = YouTube.new(api_key: 'hola')
video_urls = client.videos # => ['https://...', 'https://...', ...]
And Bob says "OK." end spends a day in his interactive console.
So first, you should figure out how you are going to use your not-yet-existing lib, if you can – sometimes you just don't know yet.
Next, build that library based on the requirements, then drop it in your Sinatra app and you're done. Does that help?

Not all methods are getting called Sinatra

I am building a Ruby app using Sinatra and the Twilio api.
Over a phone call to my assigned Twilio number, the user gets prompted to record an audio message. Once that message is recored the user gets redirected to the following route where if the user dials 1 (or anything else), they should get redirected to their feed of msgs, but if the user dials 2, then the user's message gets deleted and should get redirected to a route where they can record a new message.
Here is my route:
get '/playback/handle-recording/:recordingSID' do
if params['Digits'] = '2'
delete(params['recordingSID'])
deletedMsg = "Audio deleted."
getRecord(deletedMsg)
else
getFeed()
end
end
helper methods:
helpers do
def delete(recording)
recording = client().account.recordings.get(recording)
recording.delete
end
def getFeed()
redirect '/feed'
end
def getRecord(appendMsg)
Twilio::TwiML::Response.new do |response|
if appendMsg
response.Say appendMsg
end
response.Say "Record your message."
response.Record :maxLength => '5', :trim => "trim-silence", :playBeep => "true", :action => '/playback', :method => 'get'
end.text
end
end
My issue is that whether the user lands in the "if" or the "else" the first method, getRecord(deletedMsg) is the one that gets called, and not the getFeed().
How can I fix my route so that if the user lands in the else he does get redirected to his feed page and not to the getRecords method.
Are you sure they're actually making it into the else? Ruby won't just randomly not execute what's in there for no good reason.
One thing you may want to look at is you are assigning, not comparing the params value:
if params['Digits'] = '2'
You'll want to do:
if params['Digits'] == '2'
That could definitely lead to some strange behavior in an if statement, like, for instance always executing one path.

Ruby callback function to continue execution of another function

I'm using Pubnub to publish live messages from a Server to a client (browser page). When using Pubnub, one must abide by their message size constraints, sometimes resulting in the need to chunk the message, send it in pieces, and reconstruct on the client side. Following Pubnub's advice, one can ensure delivery of each chunk of a message if the Pubnub.publish() function is not called too quickly (i.e. if the message pieces are simply being pumped through a for loop).
The Pubnub Ruby API specifies 3 required arguments in a Pubnub.publish(), a channel, a message, and a callback function. The callback function shown below is from Pubnub's Ruby examples:
#my_callback = lambda { |message| puts(message) }
pn.publish(:channel => :hello_world,
:message => "hi",
:callback => #my_callback)
The message in the callback (not the "hi" message in the publish), contains the status information of the publish() call with values like "sent" and "message to large", both of which would be accompanied by a unique identifier.
So somewhere under the Pubnub hood, this callback is getting a .call() - I'm wondering if there is a way for me to get inbetween this process. More detailed, say I have a message that needs to be broken up into three chunks, I would like for chunk 0 to be sent, and upon receipt of a "sent" status in the callback, I would like to then send chunk 1, etc...
I'm not very familiar with lambda functions and their scopes, and this was my first attempt:
#my_callback = lambda { |message|
puts(message)
Rails.logger.debug("Setting pubnub_do_send to true from callback")
pubnub_do_send = true
}
pubnub_do_send = true
while !pubnub_message.nil?
if pubnub_do_send
#Send pubnub message
#Cut off first chunk of message (this is why I'm testing for nil)
#Set pubnub_do_send to false
Rails.logger.debug("Message #{message_id} chunk #{chunk_id} sent")
pubnub_do_send = false
end
end
This resulted in an utter failure - getting the server completely locked into an infinite while loop because (if I had to guess) pubnub_do_send was never set to true again. Looking at the debug log, I see the first message print ("Message 1 chunk 0 sent") but never the output from the callback function. (Probably because of the infinite while loop it's found itself in)
There must be a clean way to do this, and I'm ok with refactoring code to some extent, chunking up the messages and storing into an array first and then simply looping through the array to send, but I feel like the solution can't be far off, I'm just not too handy with lambda functions and callbacks.
I feel like the solution should look like:
#my_callback = lambda { |message|
puts(message)
send_pubnub_message(message_id, chunk_id, chunk)
}
def send_pubnub_message(message_id, chunk_id, chunk)
#Send pubnub message
#Send message_id, next chunk_id, and next chunk to my_callback
end
But the problem is my_callback is called by Pubnub when the Pubnub.publish() gets some status back about the message rather than me directly calling it! (Is there a way for me to insert message_id, chunk_id, and chunk into the callback while still letting pubnub attach its message to the mix? That sounds way wrong, but maybe with Ruby...)
Thanks in advance for all help.
You shouldn't be trying to handle message chunk state in your callback. It has only a single responsibility, notifying about the status of the publish. However, you can insert things into the message. I might create a message wrapper that knows it's current state, and send that in the lambda, so you don't have to keep track of it. I haven't tested the following code, but here's an example of what I'm talking about:
class Message
attr_accessor :id, :message, :chunked_message, :chunk_id
def initialize(id, message)
#id, #message = id, message
chunk_message
end
def current_chunk
#chunked_message[#chunk_id]
end
def next_chunk
#chunk_id += 1
self
end
def more?
#chunked_message.length > chunk_id
end
private
def chunk_message
implement splitting message here
end
end
def send_pubnub_message(message)
pn.publish(:channel => :hello_world,
:message => message.current_chunk
:callback => lambda { |status|
puts(status)
case status[0] // 1 = success, 0 = fail
when 1
send_pubnub_message(message.next_chunk) if message.more?
when 0
handle_pubnub_error(status[1], message)
end
}
end

Resources